[clang-tools-extra] [clang-doc] document global variables (PR #145070)

Erick Velez via cfe-commits cfe-commits at lists.llvm.org
Sat Jun 21 11:58:13 PDT 2025


https://github.com/evelez7 updated https://github.com/llvm/llvm-project/pull/145070

>From f3e1d5e735b42bb8c123fe16fbe8317ded219331 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Wed, 18 Jun 2025 16:36:49 -0700
Subject: [PATCH] [clang-doc] document global variables

---
 clang-tools-extra/clang-doc/BitcodeReader.cpp | 40 ++++++++++++++++++
 clang-tools-extra/clang-doc/BitcodeWriter.cpp | 32 +++++++++++++--
 clang-tools-extra/clang-doc/BitcodeWriter.h   |  6 +++
 clang-tools-extra/clang-doc/HTMLGenerator.cpp |  3 ++
 .../clang-doc/HTMLMustacheGenerator.cpp       |  2 +
 clang-tools-extra/clang-doc/JSONGenerator.cpp | 14 +++++++
 clang-tools-extra/clang-doc/MDGenerator.cpp   |  4 ++
 clang-tools-extra/clang-doc/Mapper.cpp        |  6 +++
 clang-tools-extra/clang-doc/Mapper.h          |  1 +
 .../clang-doc/Representation.cpp              | 16 ++++++++
 clang-tools-extra/clang-doc/Representation.h  | 14 ++++++-
 clang-tools-extra/clang-doc/Serialize.cpp     | 27 ++++++++++++
 clang-tools-extra/clang-doc/Serialize.h       |  4 ++
 clang-tools-extra/clang-doc/YAMLGenerator.cpp |  1 +
 .../test/clang-doc/json/namespace.cpp         | 41 +++++++++----------
 .../unittests/clang-doc/BitcodeTest.cpp       |  2 +
 16 files changed, 188 insertions(+), 25 deletions(-)

diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index 66852931226bf..cbdd5d245b8de 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -93,6 +93,7 @@ static llvm::Error decodeRecord(const Record &R, InfoType &Field,
   case InfoType::IT_enum:
   case InfoType::IT_typedef:
   case InfoType::IT_concept:
+  case InfoType::IT_variable:
     Field = IT;
     return llvm::Error::success();
   }
@@ -416,6 +417,23 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
                                  "invalid field for ConstraintInfo");
 }
 
+static llvm::Error parseRecord(const Record &R, unsigned ID,
+                               llvm::StringRef Blob, VarInfo *I) {
+  switch (ID) {
+  case VAR_USR:
+    return decodeRecord(R, I->USR, Blob);
+  case VAR_NAME:
+    return decodeRecord(R, I->Name, Blob);
+  case VAR_DEFLOCATION:
+    return decodeRecord(R, I->DefLoc, Blob);
+  case VAR_IS_STATIC:
+    return decodeRecord(R, I->IsStatic, Blob);
+  default:
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "invalid field for VarInfo");
+  }
+}
+
 template <typename T> static llvm::Expected<CommentInfo *> getCommentInfo(T I) {
   return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                  "invalid type cannot contain CommentInfo");
@@ -458,6 +476,10 @@ template <> llvm::Expected<CommentInfo *> getCommentInfo(ConceptInfo *I) {
   return &I->Description.emplace_back();
 }
 
+template <> Expected<CommentInfo *> getCommentInfo(VarInfo *I) {
+  return &I->Description.emplace_back();
+}
+
 // When readSubBlock encounters a TypeInfo sub-block, it calls addTypeInfo on
 // the parent block to set it. The template specializations define what to do
 // for each supported parent block.
@@ -497,6 +519,11 @@ template <> llvm::Error addTypeInfo(TypedefInfo *I, TypeInfo &&T) {
   return llvm::Error::success();
 }
 
+template <> llvm::Error addTypeInfo(VarInfo *I, TypeInfo &&T) {
+  I->Type = std::move(T);
+  return llvm::Error::success();
+}
+
 template <typename T>
 static llvm::Error addReference(T I, Reference &&R, FieldId F) {
   return llvm::createStringError(llvm::inconvertibleErrorCode(),
@@ -643,6 +670,9 @@ template <> void addChild(NamespaceInfo *I, TypedefInfo &&R) {
 template <> void addChild(NamespaceInfo *I, ConceptInfo &&R) {
   I->Children.Concepts.emplace_back(std::move(R));
 }
+template <> void addChild(NamespaceInfo *I, VarInfo &&R) {
+  I->Children.Variables.emplace_back(std::move(R));
+}
 
 // Record children:
 template <> void addChild(RecordInfo *I, FunctionInfo &&R) {
@@ -887,6 +917,13 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
     addChild(I, std::move(CI));
     return llvm::Error::success();
   }
+  case BI_VAR_BLOCK_ID: {
+    VarInfo VI;
+    if (auto Err = readBlock(ID, &VI))
+      return Err;
+    addChild(I, std::move(VI));
+    return llvm::Error::success();
+  }
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "invalid subblock type");
@@ -996,6 +1033,8 @@ ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
     return createInfo<ConceptInfo>(ID);
   case BI_FUNCTION_BLOCK_ID:
     return createInfo<FunctionInfo>(ID);
+  case BI_VAR_BLOCK_ID:
+    return createInfo<VarInfo>(ID);
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "cannot create info");
@@ -1035,6 +1074,7 @@ ClangDocBitcodeReader::readBitcode() {
     case BI_ENUM_BLOCK_ID:
     case BI_TYPEDEF_BLOCK_ID:
     case BI_CONCEPT_BLOCK_ID:
+    case BI_VAR_BLOCK_ID:
     case BI_FUNCTION_BLOCK_ID: {
       auto InfoOrErr = readBlockToInfo(ID);
       if (!InfoOrErr)
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
index b7308c012786f..c3351d1decbf5 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -130,7 +130,8 @@ static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
           {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, "TemplateSpecializationBlock"},
           {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"},
           {BI_CONSTRAINT_BLOCK_ID, "ConstraintBlock"},
-          {BI_CONCEPT_BLOCK_ID, "ConceptBlock"}};
+          {BI_CONCEPT_BLOCK_ID, "ConceptBlock"},
+          {BI_VAR_BLOCK_ID, "VarBlock"}};
       assert(Inits.size() == BlockIdCount);
       for (const auto &Init : Inits)
         BlockIdNameMap[Init.first] = Init.second;
@@ -213,7 +214,12 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {CONCEPT_IS_TYPE, {"IsType", &genBoolAbbrev}},
           {CONCEPT_CONSTRAINT_EXPRESSION,
            {"ConstraintExpression", &genStringAbbrev}},
-          {CONSTRAINT_EXPRESSION, {"Expression", &genStringAbbrev}}};
+          {CONSTRAINT_EXPRESSION, {"Expression", &genStringAbbrev}},
+          {VAR_USR, {"USR", &genSymbolIdAbbrev}},
+          {VAR_NAME, {"Name", &genStringAbbrev}},
+          {VAR_DEFLOCATION, {"DefLocation", &genLocationAbbrev}},
+          {VAR_IS_STATIC, {"IsStatic", &genBoolAbbrev}}};
+
       assert(Inits.size() == RecordIdCount);
       for (const auto &Init : Inits) {
         RecordIdNameMap[Init.first] = Init.second;
@@ -277,7 +283,8 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
          {CONCEPT_USR, CONCEPT_NAME, CONCEPT_IS_TYPE,
           CONCEPT_CONSTRAINT_EXPRESSION}},
         // Constraint Block
-        {BI_CONSTRAINT_BLOCK_ID, {CONSTRAINT_EXPRESSION}}};
+        {BI_CONSTRAINT_BLOCK_ID, {CONSTRAINT_EXPRESSION}},
+        {BI_VAR_BLOCK_ID, {VAR_NAME, VAR_USR, VAR_DEFLOCATION, VAR_IS_STATIC}}};
 
 // AbbreviationMap
 
@@ -540,6 +547,8 @@ void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) {
     emitBlock(C);
   for (const auto &C : I.Children.Concepts)
     emitBlock(C);
+  for (const auto &C : I.Children.Variables)
+    emitBlock(C);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) {
@@ -682,6 +691,20 @@ void ClangDocBitcodeWriter::emitBlock(const ConstraintInfo &C) {
   emitBlock(C.ConceptRef, FieldId::F_concept);
 }
 
+void ClangDocBitcodeWriter::emitBlock(const VarInfo &I) {
+  StreamSubBlockGuard Block(Stream, BI_VAR_BLOCK_ID);
+  emitRecord(I.USR, VAR_USR);
+  emitRecord(I.Name, VAR_NAME);
+  for (const auto &N : I.Namespace)
+    emitBlock(N, FieldId::F_namespace);
+  for (const auto &CI : I.Description)
+    emitBlock(CI);
+  if (I.DefLoc)
+    emitRecord(*I.DefLoc, VAR_DEFLOCATION);
+  emitRecord(I.IsStatic, VAR_IS_STATIC);
+  emitBlock(I.Type);
+}
+
 bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
   switch (I->IT) {
   case InfoType::IT_namespace:
@@ -702,6 +725,9 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
   case InfoType::IT_concept:
     emitBlock(*static_cast<clang::doc::ConceptInfo *>(I));
     break;
+  case InfoType::IT_variable:
+    emitBlock(*static_cast<VarInfo *>(I));
+    break;
   case InfoType::IT_default:
     llvm::errs() << "Unexpected info, unable to write.\n";
     return true;
diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h
index 4d0c0c07805e7..a70e50b53a61a 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.h
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.h
@@ -69,6 +69,7 @@ enum BlockId {
   BI_CONSTRAINT_BLOCK_ID,
   BI_TYPEDEF_BLOCK_ID,
   BI_CONCEPT_BLOCK_ID,
+  BI_VAR_BLOCK_ID,
   BI_LAST,
   BI_FIRST = BI_VERSION_BLOCK_ID
 };
@@ -142,6 +143,10 @@ enum RecordId {
   CONCEPT_IS_TYPE,
   CONCEPT_CONSTRAINT_EXPRESSION,
   CONSTRAINT_EXPRESSION,
+  VAR_USR,
+  VAR_NAME,
+  VAR_DEFLOCATION,
+  VAR_IS_STATIC,
   RI_LAST,
   RI_FIRST = VERSION
 };
@@ -190,6 +195,7 @@ class ClangDocBitcodeWriter {
   void emitBlock(const ConceptInfo &T);
   void emitBlock(const ConstraintInfo &T);
   void emitBlock(const Reference &B, FieldId F);
+  void emitBlock(const VarInfo &B);
 
 private:
   class AbbreviationMap {
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index 935bbfee7a9b1..c4303d287da9e 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -986,6 +986,7 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
         genHTML(*static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle);
     break;
   case InfoType::IT_concept:
+  case InfoType::IT_variable:
     break;
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
@@ -1015,6 +1016,8 @@ static std::string getRefType(InfoType IT) {
     return "typedef";
   case InfoType::IT_concept:
     return "concept";
+  case InfoType::IT_variable:
+    return "variable";
   }
   llvm_unreachable("Unknown InfoType");
 }
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 81ba99c21e374..c611c946b3937 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -587,6 +587,8 @@ Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
     break;
   case InfoType::IT_concept:
     break;
+  case InfoType::IT_variable:
+    break;
   case InfoType::IT_default:
     return createStringError(inconvertibleErrorCode(), "unexpected InfoType");
   }
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 8a37621597c6a..91f5ba4080619 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -482,6 +482,15 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
+static void serializeInfo(const VarInfo &I, json::Object &Obj,
+                          std::optional<StringRef> RepositoryUrl) {
+  serializeCommonAttributes(I, Obj, RepositoryUrl);
+  Obj["IsStatic"] = I.IsStatic;
+  auto TypeObj = Object();
+  serializeInfo(I.Type, TypeObj);
+  Obj["Type"] = std::move(TypeObj);
+}
+
 static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
                           std::optional<StringRef> RepositoryUrl) {
   serializeCommonAttributes(I, Obj, RepositoryUrl);
@@ -519,6 +528,10 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
   if (!I.Children.Concepts.empty())
     serializeArray(I.Children.Concepts, Obj, "Concepts", SerializeInfo);
 
+  if (!I.Children.Variables.empty()) {
+    serializeArray(I.Children.Variables, Obj, "Variables", SerializeInfo);
+  }
+
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
@@ -573,6 +586,7 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
   case InfoType::IT_enum:
   case InfoType::IT_function:
   case InfoType::IT_typedef:
+  case InfoType::IT_variable:
     break;
   case InfoType::IT_default:
     return createStringError(inconvertibleErrorCode(), "unexpected info type");
diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp
index 6e68e09cfa2a6..608a7f6d4a9d3 100644
--- a/clang-tools-extra/clang-doc/MDGenerator.cpp
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -375,6 +375,9 @@ static llvm::Error genIndex(ClangDocContext &CDCtx) {
       case InfoType::IT_concept:
         Type = "Concept";
         break;
+      case InfoType::IT_variable:
+        Type = "Variable";
+        break;
       case InfoType::IT_default:
         Type = "Other";
       }
@@ -468,6 +471,7 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
     break;
   case InfoType::IT_concept:
+  case InfoType::IT_variable:
     break;
   case InfoType::IT_default:
     return createStringError(llvm::inconvertibleErrorCode(),
diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp
index 6021e17b4696d..497b80cf97463 100644
--- a/clang-tools-extra/clang-doc/Mapper.cpp
+++ b/clang-tools-extra/clang-doc/Mapper.cpp
@@ -138,6 +138,12 @@ bool MapASTVisitor::VisitConceptDecl(const ConceptDecl *D) {
   return mapDecl(D, true);
 }
 
+bool MapASTVisitor::VisitVarDecl(const VarDecl *D) {
+  if (D->isCXXClassMember())
+    return true;
+  return mapDecl(D, D->isThisDeclarationADefinition());
+}
+
 comments::FullComment *
 MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const {
   RawComment *Comment = Context.getRawCommentForDeclNoCache(D);
diff --git a/clang-tools-extra/clang-doc/Mapper.h b/clang-tools-extra/clang-doc/Mapper.h
index 04dc5450c8ba3..322df6d594b3d 100644
--- a/clang-tools-extra/clang-doc/Mapper.h
+++ b/clang-tools-extra/clang-doc/Mapper.h
@@ -42,6 +42,7 @@ class MapASTVisitor : public clang::RecursiveASTVisitor<MapASTVisitor>,
   bool VisitTypedefDecl(const TypedefDecl *D);
   bool VisitTypeAliasDecl(const TypeAliasDecl *D);
   bool VisitConceptDecl(const ConceptDecl *D);
+  bool VisitVarDecl(const VarDecl *D);
 
 private:
   template <typename T> bool mapDecl(const T *D, bool IsDefinition);
diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 286aeeea1001b..5b94d37d868b4 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -145,6 +145,8 @@ mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
     return reduce<TypedefInfo>(Values);
   case InfoType::IT_concept:
     return reduce<ConceptInfo>(Values);
+  case InfoType::IT_variable:
+    return reduce<VarInfo>(Values);
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "unexpected info type");
@@ -291,6 +293,7 @@ void NamespaceInfo::merge(NamespaceInfo &&Other) {
   reduceChildren(Children.Enums, std::move(Other.Children.Enums));
   reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));
   reduceChildren(Children.Concepts, std::move(Other.Children.Concepts));
+  reduceChildren(Children.Variables, std::move(Other.Children.Variables));
   mergeBase(std::move(Other));
 }
 
@@ -368,6 +371,15 @@ void ConceptInfo::merge(ConceptInfo &&Other) {
   SymbolInfo::merge(std::move(Other));
 }
 
+void VarInfo::merge(VarInfo &&Other) {
+  assert(mergeable(Other));
+  if (!IsStatic)
+    IsStatic = Other.IsStatic;
+  if (Type.Type.USR == EmptySID && Type.Type.Name == "")
+    Type = std::move(Other.Type);
+  SymbolInfo::merge(std::move(Other));
+}
+
 BaseRecordInfo::BaseRecordInfo() : RecordInfo() {}
 
 BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,
@@ -407,6 +419,9 @@ llvm::SmallString<16> Info::extractName() const {
   case InfoType::IT_concept:
     return llvm::SmallString<16>("@nonymous_concept_" +
                                  toHex(llvm::toStringRef(USR)));
+  case InfoType::IT_variable:
+    return llvm::SmallString<16>("@nonymous_variable_" +
+                                 toHex(llvm::toStringRef(USR)));
   case InfoType::IT_default:
     return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));
   }
@@ -473,6 +488,7 @@ void ScopeChildren::sort() {
   llvm::sort(Enums.begin(), Enums.end());
   llvm::sort(Typedefs.begin(), Typedefs.end());
   llvm::sort(Concepts.begin(), Concepts.end());
+  llvm::sort(Variables.begin(), Variables.end());
 }
 } // namespace doc
 } // namespace clang
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index b23069f2bd324..59874f0cfcedf 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -36,6 +36,7 @@ struct FunctionInfo;
 struct Info;
 struct TypedefInfo;
 struct ConceptInfo;
+struct VarInfo;
 
 enum class InfoType {
   IT_default,
@@ -44,7 +45,8 @@ enum class InfoType {
   IT_function,
   IT_enum,
   IT_typedef,
-  IT_concept
+  IT_concept,
+  IT_variable
 };
 
 enum class CommentKind {
@@ -169,6 +171,7 @@ struct ScopeChildren {
   std::vector<EnumInfo> Enums;
   std::vector<TypedefInfo> Typedefs;
   std::vector<ConceptInfo> Concepts;
+  std::vector<VarInfo> Variables;
 
   void sort();
 };
@@ -376,6 +379,15 @@ struct SymbolInfo : public Info {
   bool IsStatic = false;
 };
 
+struct VarInfo : SymbolInfo {
+  VarInfo() : SymbolInfo(InfoType::IT_variable) {}
+  explicit VarInfo(SymbolID USR) : SymbolInfo(InfoType::IT_variable, USR) {}
+
+  void merge(VarInfo &&I);
+
+  TypeInfo Type;
+};
+
 // TODO: Expand to allow for documenting templating and default args.
 // Info for functions.
 struct FunctionInfo : public SymbolInfo {
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 5f3e5c37fa34d..7a9cb8a1eddb9 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -401,6 +401,8 @@ std::string serialize(std::unique_ptr<Info> &I) {
     return serialize(*static_cast<FunctionInfo *>(I.get()));
   case InfoType::IT_concept:
     return serialize(*static_cast<ConceptInfo *>(I.get()));
+  case InfoType::IT_variable:
+    return serialize(*static_cast<VarInfo *>(I.get()));
   case InfoType::IT_typedef:
   case InfoType::IT_default:
     return "";
@@ -508,6 +510,10 @@ static void InsertChild(ScopeChildren &Scope, ConceptInfo Info) {
   Scope.Concepts.push_back(std::move(Info));
 }
 
+static void InsertChild(ScopeChildren &Scope, VarInfo Info) {
+  Scope.Variables.push_back(std::move(Info));
+}
+
 // Creates a parent of the correct type for the given child and inserts it into
 // that parent.
 //
@@ -549,6 +555,7 @@ static std::unique_ptr<Info> makeAndInsertIntoParent(ChildType Child) {
   case InfoType::IT_function:
   case InfoType::IT_typedef:
   case InfoType::IT_concept:
+  case InfoType::IT_variable:
     break;
   }
   llvm_unreachable("Invalid reference type for parent namespace");
@@ -1164,6 +1171,26 @@ emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc,
   return {nullptr, makeAndInsertIntoParent<ConceptInfo &&>(std::move(Concept))};
 }
 
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const VarDecl *D, const FullComment *FC, const Location &Loc,
+         bool PublicOnly) {
+  VarInfo Var;
+  bool IsInAnonymousNamespace = false;
+  populateSymbolInfo(Var, D, FC, Loc, IsInAnonymousNamespace);
+  if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
+    return {};
+
+  if (D->getStorageClass() == StorageClass::SC_Static)
+    Var.IsStatic = true;
+  Var.Type =
+      getTypeInfoForType(D->getType(), D->getASTContext().getPrintingPolicy());
+
+  if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D))
+    return {};
+
+  return {nullptr, makeAndInsertIntoParent<VarInfo &&>(std::move(Var))};
+}
+
 } // namespace serialize
 } // namespace doc
 } // namespace clang
diff --git a/clang-tools-extra/clang-doc/Serialize.h b/clang-tools-extra/clang-doc/Serialize.h
index 497b09bb339f8..06c4d64c51494 100644
--- a/clang-tools-extra/clang-doc/Serialize.h
+++ b/clang-tools-extra/clang-doc/Serialize.h
@@ -72,6 +72,10 @@ std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc,
          bool PublicOnly);
 
+std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
+emitInfo(const VarDecl *D, const FullComment *FC, const Location &Loc,
+         bool PublicOnly);
+
 // Function to hash a given USR value for storage.
 // As USRs (Unified Symbol Resolution) could be large, especially for functions
 // with long type arguments, we use 160-bits SHA1(USR) values to
diff --git a/clang-tools-extra/clang-doc/YAMLGenerator.cpp b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
index f958871046981..3ca4d4947fa97 100644
--- a/clang-tools-extra/clang-doc/YAMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
@@ -409,6 +409,7 @@ llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     InfoYAML << *static_cast<clang::doc::TypedefInfo *>(I);
     break;
   case InfoType::IT_concept:
+  case InfoType::IT_variable:
     break;
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
diff --git a/clang-tools-extra/test/clang-doc/json/namespace.cpp b/clang-tools-extra/test/clang-doc/json/namespace.cpp
index 248d47351bd38..f1e094f6cab17 100644
--- a/clang-tools-extra/test/clang-doc/json/namespace.cpp
+++ b/clang-tools-extra/test/clang-doc/json/namespace.cpp
@@ -9,7 +9,6 @@ void myFunction(int Param);
 namespace NestedNamespace {
 } // namespace NestedNamespace
 
-// FIXME: Global variables are not mapped or serialized.
 static int Global;
 
 enum Color {
@@ -25,7 +24,7 @@ typedef int MyTypedef;
 // CHECK-NEXT:      {
 // CHECK-NEXT:        "Location": {
 // CHECK-NEXT:          "Filename": "{{.*}}namespace.cpp",
-// CHECK-NEXT:          "LineNumber": 15
+// CHECK-NEXT:          "LineNumber": 14
 // CHECK-NEXT:        },
 // CHECK-NEXT:        "Members": [
 // CHECK-NEXT:          {
@@ -88,7 +87,7 @@ typedef int MyTypedef;
 // CHECK-NEXT:      "IsUsing": false,
 // CHECK-NEXT:      "Location": {
 // CHECK-NEXT:        "Filename": "{{.*}}namespace.cpp",
-// CHECK-NEXT:        "LineNumber": 21
+// CHECK-NEXT:        "LineNumber": 20
 // CHECK-NEXT:      },
 // CHECK-NEXT:      "Name": "MyTypedef",
 // CHECK-NEXT:      "TypeDeclaration": "",
@@ -103,23 +102,23 @@ typedef int MyTypedef;
 // CHECK-NEXT:      }
 // CHECK-NEXT:    ],
 // CHECK-NEXT:    "USR": "0000000000000000000000000000000000000000"
-// CHECK-NOT:    "Variables": [
-// CHECK-NOT:      {
-// CHECK-NOT:        "IsStatic": true,
-// CHECK-NOT:        "Location": {
-// CHECK-NOT:          "Filename": "{{.*}}namespace.cpp",
-// CHECK-NOT:          "LineNumber": 13
-// CHECK-NOT:        },
-// CHECK-NOT:        "Name": "Global",
-// CHECK-NOT:        "Type": {
+// CHECK-NEXT:   "Variables": [
+// CHECK-NEXT:     {
+// CHECK-NEXT:       "IsStatic": true,
+// CHECK-NEXT:       "Location": {
+// CHECK-NEXT:         "Filename": "{{.*}}namespace.cpp",
+// CHECK-NEXT:         "LineNumber": 12
+// CHECK-NEXT:       },
+// CHECK-NEXT:       "Name": "Global",
+// CHECK-NEXT:       "Type": {
 // COM:                FIXME: IsBuiltIn emits as its default value
-// CHECK-NOT:          "IsBuiltIn": false,
-// CHECK-NOT:          "IsTemplate": false,
-// CHECK-NOT:          "Name": "int",
-// CHECK-NOT:          "QualName": "int",
-// CHECK-NOT:          "USR": "0000000000000000000000000000000000000000"
-// CHECK-NOT:        },
-// CHECK-NOT:        "USR": "{{[0-9A-F]*}}"
-// CHECK-NOT:      }
-// CHECK-NOT:    ]
+// CHECK-NEXT:         "IsBuiltIn": false,
+// CHECK-NEXT:         "IsTemplate": false,
+// CHECK-NEXT:         "Name": "int",
+// CHECK-NEXT:         "QualName": "int",
+// CHECK-NEXT:         "USR": "0000000000000000000000000000000000000000"
+// CHECK-NEXT:       },
+// CHECK-NEXT:       "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:     }
+// CHECK-NEXT:   ]
 // CHECK-NEXT:  }
diff --git a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
index a38dfd3036604..323431c4fc9e0 100644
--- a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
@@ -39,6 +39,8 @@ static std::string writeInfo(Info *I) {
     return writeInfo(*static_cast<TypedefInfo *>(I));
   case InfoType::IT_concept:
     return writeInfo(*static_cast<ConceptInfo *>(I));
+  case InfoType::IT_variable:
+    return writeInfo(*static_cast<VarInfo *>(I));
   case InfoType::IT_default:
     return "";
   }



More information about the cfe-commits mailing list