[llvm-branch-commits] [clang-tools-extra] [clang-doc] serialize friends (PR #146165)

Erick Velez via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Jun 27 14:58:05 PDT 2025


https://github.com/evelez7 created https://github.com/llvm/llvm-project/pull/146165

None

>From a373ecb94c6137fb8de62226b9cfbfe8c8840564 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Thu, 26 Jun 2025 20:54:03 -0700
Subject: [PATCH] [clang-doc] serialize friends

---
 clang-tools-extra/clang-doc/BitcodeReader.cpp | 46 +++++++++++
 clang-tools-extra/clang-doc/BitcodeWriter.cpp | 27 ++++++-
 clang-tools-extra/clang-doc/BitcodeWriter.h   |  6 +-
 clang-tools-extra/clang-doc/HTMLGenerator.cpp |  3 +
 .../clang-doc/HTMLMustacheGenerator.cpp       |  1 +
 clang-tools-extra/clang-doc/JSONGenerator.cpp | 23 +++++-
 clang-tools-extra/clang-doc/MDGenerator.cpp   |  4 +
 .../clang-doc/Representation.cpp              | 16 ++++
 clang-tools-extra/clang-doc/Representation.h  | 21 ++++-
 clang-tools-extra/clang-doc/Serialize.cpp     | 53 +++++++++++++
 clang-tools-extra/clang-doc/YAMLGenerator.cpp |  1 +
 .../test/clang-doc/json/class.cpp             | 76 +++++++++----------
 .../unittests/clang-doc/BitcodeTest.cpp       |  2 +
 13 files changed, 234 insertions(+), 45 deletions(-)

diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp
index fd6f40cff1a4e..2cbf8bf6b2879 100644
--- a/clang-tools-extra/clang-doc/BitcodeReader.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp
@@ -94,6 +94,7 @@ static llvm::Error decodeRecord(const Record &R, InfoType &Field,
   case InfoType::IT_typedef:
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     Field = IT;
     return llvm::Error::success();
   }
@@ -111,6 +112,7 @@ static llvm::Error decodeRecord(const Record &R, FieldId &Field,
   case FieldId::F_child_namespace:
   case FieldId::F_child_record:
   case FieldId::F_concept:
+  case FieldId::F_friend:
   case FieldId::F_default:
     Field = F;
     return llvm::Error::success();
@@ -450,6 +452,15 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
   }
 }
 
+static llvm::Error parseRecord(const Record &R, unsigned ID, StringRef Blob,
+                               FriendInfo *F) {
+  if (ID == FRIEND_IS_CLASS) {
+    return decodeRecord(R, F->IsClass, Blob);
+  }
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "invalid field for Friend");
+}
+
 template <typename T> static llvm::Expected<CommentInfo *> getCommentInfo(T I) {
   return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                  "invalid type cannot contain CommentInfo");
@@ -525,6 +536,18 @@ template <> llvm::Error addTypeInfo(FunctionInfo *I, FieldTypeInfo &&T) {
   return llvm::Error::success();
 }
 
+template <> llvm::Error addTypeInfo(FriendInfo *I, FieldTypeInfo &&T) {
+  if (!I->Params)
+    I->Params.emplace();
+  I->Params->emplace_back(std::move(T));
+  return llvm::Error::success();
+}
+
+template <> llvm::Error addTypeInfo(FriendInfo *I, TypeInfo &&T) {
+  I->ReturnType.emplace(std::move(T));
+  return llvm::Error::success();
+}
+
 template <> llvm::Error addTypeInfo(EnumInfo *I, TypeInfo &&T) {
   I->BaseType = std::move(T);
   return llvm::Error::success();
@@ -667,6 +690,16 @@ llvm::Error addReference(ConstraintInfo *I, Reference &&R, FieldId F) {
       "ConstraintInfo cannot contain this Reference");
 }
 
+template <>
+llvm::Error addReference(FriendInfo *Friend, Reference &&R, FieldId F) {
+  if (F == FieldId::F_friend) {
+    Friend->Ref = std::move(R);
+    return llvm::Error::success();
+  }
+  return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                 "Friend cannot contain this Reference");
+}
+
 template <typename T, typename ChildInfoType>
 static void addChild(T I, ChildInfoType &&R) {
   llvm::errs() << "invalid child type for info";
@@ -700,6 +733,9 @@ template <> void addChild(RecordInfo *I, EnumInfo &&R) {
 template <> void addChild(RecordInfo *I, TypedefInfo &&R) {
   I->Children.Typedefs.emplace_back(std::move(R));
 }
+template <> void addChild(RecordInfo *I, FriendInfo &&R) {
+  I->Friends.emplace_back(std::move(R));
+}
 
 // Other types of children:
 template <> void addChild(EnumInfo *I, EnumValueInfo &&R) {
@@ -741,6 +777,9 @@ template <> void addTemplate(FunctionInfo *I, TemplateInfo &&P) {
 template <> void addTemplate(ConceptInfo *I, TemplateInfo &&P) {
   I->Template = std::move(P);
 }
+template <> void addTemplate(FriendInfo *I, TemplateInfo &&P) {
+  I->Template.emplace(std::move(P));
+}
 
 // Template specializations go only into template records.
 template <typename T>
@@ -921,6 +960,10 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
   case BI_VAR_BLOCK_ID: {
     return handleSubBlock<VarInfo>(ID, I, CreateAddFunc(addChild<T, VarInfo>));
   }
+  case BI_FRIEND_BLOCK_ID: {
+    return handleSubBlock<FriendInfo>(ID, I,
+                                      CreateAddFunc(addChild<T, FriendInfo>));
+  }
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "invalid subblock type");
@@ -1032,6 +1075,8 @@ ClangDocBitcodeReader::readBlockToInfo(unsigned ID) {
     return createInfo<FunctionInfo>(ID);
   case BI_VAR_BLOCK_ID:
     return createInfo<VarInfo>(ID);
+  case BI_FRIEND_BLOCK_ID:
+    return createInfo<FriendInfo>(ID);
   default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "cannot create info");
@@ -1072,6 +1117,7 @@ ClangDocBitcodeReader::readBitcode() {
     case BI_TYPEDEF_BLOCK_ID:
     case BI_CONCEPT_BLOCK_ID:
     case BI_VAR_BLOCK_ID:
+    case BI_FRIEND_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 006ddda1b75e7..3cc0d4ad332f0 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp
@@ -131,7 +131,8 @@ static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
           {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"},
           {BI_CONSTRAINT_BLOCK_ID, "ConstraintBlock"},
           {BI_CONCEPT_BLOCK_ID, "ConceptBlock"},
-          {BI_VAR_BLOCK_ID, "VarBlock"}};
+          {BI_VAR_BLOCK_ID, "VarBlock"},
+          {BI_FRIEND_BLOCK_ID, "FriendBlock"}};
       assert(Inits.size() == BlockIdCount);
       for (const auto &Init : Inits)
         BlockIdNameMap[Init.first] = Init.second;
@@ -224,7 +225,8 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
           {VAR_USR, {"USR", &genSymbolIdAbbrev}},
           {VAR_NAME, {"Name", &genStringAbbrev}},
           {VAR_DEFLOCATION, {"DefLocation", &genLocationAbbrev}},
-          {VAR_IS_STATIC, {"IsStatic", &genBoolAbbrev}}};
+          {VAR_IS_STATIC, {"IsStatic", &genBoolAbbrev}},
+          {FRIEND_IS_CLASS, {"IsClass", &genBoolAbbrev}}};
 
       assert(Inits.size() == RecordIdCount);
       for (const auto &Init : Inits) {
@@ -293,7 +295,8 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
           CONCEPT_CONSTRAINT_EXPRESSION}},
         // Constraint Block
         {BI_CONSTRAINT_BLOCK_ID, {CONSTRAINT_EXPRESSION}},
-        {BI_VAR_BLOCK_ID, {VAR_NAME, VAR_USR, VAR_DEFLOCATION, VAR_IS_STATIC}}};
+        {BI_VAR_BLOCK_ID, {VAR_NAME, VAR_USR, VAR_DEFLOCATION, VAR_IS_STATIC}},
+        {BI_FRIEND_BLOCK_ID, {FRIEND_IS_CLASS}}};
 
 // AbbreviationMap
 
@@ -476,6 +479,19 @@ void ClangDocBitcodeWriter::emitBlock(const Reference &R, FieldId Field) {
   emitRecord((unsigned)Field, REFERENCE_FIELD);
 }
 
+void ClangDocBitcodeWriter::emitBlock(const FriendInfo &R) {
+  StreamSubBlockGuard Block(Stream, BI_FRIEND_BLOCK_ID);
+  emitBlock(R.Ref, FieldId::F_friend);
+  emitRecord(R.IsClass, FRIEND_IS_CLASS);
+  if (R.Template)
+    emitBlock(*R.Template);
+  if (R.Params)
+    for (const auto &P : *R.Params)
+      emitBlock(P);
+  if (R.ReturnType)
+    emitBlock(*R.ReturnType);
+}
+
 void ClangDocBitcodeWriter::emitBlock(const TypeInfo &T) {
   StreamSubBlockGuard Block(Stream, BI_TYPE_BLOCK_ID);
   emitBlock(T.Type, FieldId::F_type);
@@ -628,6 +644,8 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
     emitBlock(C);
   if (I.Template)
     emitBlock(*I.Template);
+  for (const auto &C : I.Friends)
+    emitBlock(C);
 }
 
 void ClangDocBitcodeWriter::emitBlock(const BaseRecordInfo &I) {
@@ -744,6 +762,9 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) {
   case InfoType::IT_variable:
     emitBlock(*static_cast<VarInfo *>(I));
     break;
+  case InfoType::IT_friend:
+    emitBlock(*static_cast<FriendInfo *>(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 f1325094f957a..d09ec4ca34006 100644
--- a/clang-tools-extra/clang-doc/BitcodeWriter.h
+++ b/clang-tools-extra/clang-doc/BitcodeWriter.h
@@ -70,6 +70,7 @@ enum BlockId {
   BI_TYPEDEF_BLOCK_ID,
   BI_CONCEPT_BLOCK_ID,
   BI_VAR_BLOCK_ID,
+  BI_FRIEND_BLOCK_ID,
   BI_LAST,
   BI_FIRST = BI_VERSION_BLOCK_ID
 };
@@ -153,6 +154,7 @@ enum RecordId {
   VAR_NAME,
   VAR_DEFLOCATION,
   VAR_IS_STATIC,
+  FRIEND_IS_CLASS,
   RI_LAST,
   RI_FIRST = VERSION
 };
@@ -169,7 +171,8 @@ enum class FieldId {
   F_type,
   F_child_namespace,
   F_child_record,
-  F_concept
+  F_concept,
+  F_friend
 };
 
 class ClangDocBitcodeWriter {
@@ -201,6 +204,7 @@ class ClangDocBitcodeWriter {
   void emitBlock(const ConceptInfo &T);
   void emitBlock(const ConstraintInfo &T);
   void emitBlock(const Reference &B, FieldId F);
+  void emitBlock(const FriendInfo &R);
   void emitBlock(const VarInfo &B);
 
 private:
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
index c4303d287da9e..8294ff9118558 100644
--- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -987,6 +987,7 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     break;
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
@@ -1018,6 +1019,8 @@ static std::string getRefType(InfoType IT) {
     return "concept";
   case InfoType::IT_variable:
     return "variable";
+  case InfoType::IT_friend:
+    return "friend";
   }
   llvm_unreachable("Unknown InfoType");
 }
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index c611c946b3937..7aeaa1b7cf67d 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -588,6 +588,7 @@ Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
   case InfoType::IT_concept:
     break;
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     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 1f6167f7f9b8d..0e1a0cc347e45 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -39,8 +39,7 @@ static void serializeArray(const Container &Records, Object &Obj,
 static auto SerializeInfoLambda = [](const auto &Info, Object &Object) {
   serializeInfo(Info, Object);
 };
-static auto SerializeReferenceLambda = [](const Reference &Ref,
-                                          Object &Object) {
+static auto SerializeReferenceLambda = [](const auto &Ref, Object &Object) {
   serializeReference(Ref, Object);
 };
 
@@ -365,6 +364,22 @@ static void serializeInfo(const BaseRecordInfo &I, Object &Obj,
   Obj["IsParent"] = I.IsParent;
 }
 
+static void serializeInfo(const FriendInfo &I, Object &Obj) {
+  auto FriendRef = Object();
+  serializeReference(I.Ref, FriendRef);
+  Obj["Reference"] = std::move(FriendRef);
+  Obj["IsClass"] = I.IsClass;
+  if (I.Template)
+    serializeInfo(I.Template.value(), Obj);
+  if (I.Params)
+    serializeArray(I.Params.value(), Obj, "Params", SerializeInfoLambda);
+  if (I.ReturnType) {
+    auto ReturnTypeObj = Object();
+    serializeInfo(I.ReturnType.value(), ReturnTypeObj);
+    Obj["ReturnType"] = std::move(ReturnTypeObj);
+  }
+}
+
 static void serializeInfo(const RecordInfo &I, json::Object &Obj,
                           const std::optional<StringRef> &RepositoryUrl) {
   serializeCommonAttributes(I, Obj, RepositoryUrl);
@@ -436,6 +451,9 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
   if (I.Template)
     serializeInfo(I.Template.value(), Obj);
 
+  if (!I.Friends.empty())
+    serializeArray(I.Friends, Obj, "Friends", SerializeInfoLambda);
+
   serializeCommonChildren(I.Children, Obj, RepositoryUrl);
 }
 
@@ -525,6 +543,7 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
   case InfoType::IT_function:
   case InfoType::IT_typedef:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     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 608a7f6d4a9d3..6f16f5bd2f528 100644
--- a/clang-tools-extra/clang-doc/MDGenerator.cpp
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -378,6 +378,9 @@ static llvm::Error genIndex(ClangDocContext &CDCtx) {
       case InfoType::IT_variable:
         Type = "Variable";
         break;
+      case InfoType::IT_friend:
+        Type = "Friend";
+        break;
       case InfoType::IT_default:
         Type = "Other";
       }
@@ -472,6 +475,7 @@ llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     break;
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   case InfoType::IT_default:
     return createStringError(llvm::inconvertibleErrorCode(),
diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp
index 5b94d37d868b4..ba53329a41789 100644
--- a/clang-tools-extra/clang-doc/Representation.cpp
+++ b/clang-tools-extra/clang-doc/Representation.cpp
@@ -147,6 +147,8 @@ mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
     return reduce<ConceptInfo>(Values);
   case InfoType::IT_variable:
     return reduce<VarInfo>(Values);
+  case InfoType::IT_friend:
+    return reduce<FriendInfo>(Values);
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                    "unexpected info type");
@@ -247,6 +249,15 @@ void Reference::merge(Reference &&Other) {
     Path = Other.Path;
 }
 
+bool FriendInfo::mergeable(const FriendInfo &Other) {
+  return Ref.USR == Other.Ref.USR && Ref.Name == Other.Ref.Name;
+}
+
+void FriendInfo::merge(FriendInfo &&Other) {
+  assert(mergeable(Other));
+  Ref.merge(std::move(Other.Ref));
+}
+
 void Info::mergeBase(Info &&Other) {
   assert(mergeable(Other));
   if (USR == EmptySID)
@@ -313,6 +324,8 @@ void RecordInfo::merge(RecordInfo &&Other) {
     Parents = std::move(Other.Parents);
   if (VirtualParents.empty())
     VirtualParents = std::move(Other.VirtualParents);
+  if (Friends.empty())
+    Friends = std::move(Other.Friends);
   // Reduce children if necessary.
   reduceChildren(Children.Records, std::move(Other.Children.Records));
   reduceChildren(Children.Functions, std::move(Other.Children.Functions));
@@ -422,6 +435,9 @@ llvm::SmallString<16> Info::extractName() const {
   case InfoType::IT_variable:
     return llvm::SmallString<16>("@nonymous_variable_" +
                                  toHex(llvm::toStringRef(USR)));
+                                 case InfoType::IT_friend:
+    return llvm::SmallString<16>("@nonymous_friend_" +
+                                 toHex(llvm::toStringRef(USR)));
   case InfoType::IT_default:
     return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));
   }
diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h
index 59874f0cfcedf..fe5cc48069d58 100644
--- a/clang-tools-extra/clang-doc/Representation.h
+++ b/clang-tools-extra/clang-doc/Representation.h
@@ -46,7 +46,8 @@ enum class InfoType {
   IT_enum,
   IT_typedef,
   IT_concept,
-  IT_variable
+  IT_variable,
+  IT_friend
 };
 
 enum class CommentKind {
@@ -379,6 +380,22 @@ struct SymbolInfo : public Info {
   bool IsStatic = false;
 };
 
+struct FriendInfo : SymbolInfo {
+  FriendInfo() : SymbolInfo(InfoType::IT_friend) {}
+  FriendInfo(SymbolID USR) : SymbolInfo(InfoType::IT_friend, USR) {}
+  FriendInfo(const InfoType IT, const SymbolID &USR,
+             const StringRef Name = StringRef())
+      : SymbolInfo(IT, USR, Name) {}
+  bool mergeable(const FriendInfo &Other);
+  void merge(FriendInfo &&Other);
+
+  Reference Ref;
+  std::optional<TemplateInfo> Template;
+  std::optional<TypeInfo> ReturnType;
+  std::optional<SmallVector<FieldTypeInfo, 4>> Params;
+  bool IsClass = false;
+};
+
 struct VarInfo : SymbolInfo {
   VarInfo() : SymbolInfo(InfoType::IT_variable) {}
   explicit VarInfo(SymbolID USR) : SymbolInfo(InfoType::IT_variable, USR) {}
@@ -454,6 +471,8 @@ struct RecordInfo : public SymbolInfo {
       Bases; // List of base/parent records; this includes inherited methods and
              // attributes
 
+  std::vector<FriendInfo> Friends;
+
   ScopeChildren Children;
 };
 
diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index 7a9cb8a1eddb9..12ef8891c720e 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -7,9 +7,12 @@
 //===----------------------------------------------------------------------===//
 
 #include "Serialize.h"
+#include "../clangd/CodeCompletionStrings.h"
 #include "BitcodeWriter.h"
+
 #include "clang/AST/Attr.h"
 #include "clang/AST/Comment.h"
+#include "clang/AST/DeclFriend.h"
 #include "clang/Index/USRGeneration.h"
 #include "clang/Lex/Lexer.h"
 #include "llvm/ADT/StringExtras.h"
@@ -403,6 +406,7 @@ std::string serialize(std::unique_ptr<Info> &I) {
     return serialize(*static_cast<ConceptInfo *>(I.get()));
   case InfoType::IT_variable:
     return serialize(*static_cast<VarInfo *>(I.get()));
+  case InfoType::IT_friend:
   case InfoType::IT_typedef:
   case InfoType::IT_default:
     return "";
@@ -556,6 +560,7 @@ static std::unique_ptr<Info> makeAndInsertIntoParent(ChildType Child) {
   case InfoType::IT_typedef:
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   }
   llvm_unreachable("Invalid reference type for parent namespace");
@@ -947,6 +952,53 @@ emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc,
   return {std::move(NSI), makeAndInsertIntoParent<const NamespaceInfo &>(*NSI)};
 }
 
+static void parseFriends(RecordInfo &RI, const CXXRecordDecl *D) {
+  if (D->hasDefinition() && D->hasFriends())
+    for (const FriendDecl *FD : D->friends()) {
+      if (FD->isUnsupportedFriend())
+        continue;
+
+      FriendInfo F(InfoType::IT_friend, getUSRForDecl(FD));
+      const auto *ActualDecl = FD->getFriendDecl();
+      if (!ActualDecl) {
+        const auto *FriendTypeInfo = FD->getFriendType();
+        if (!FriendTypeInfo)
+          continue;
+        ActualDecl = FriendTypeInfo->getType()->getAsCXXRecordDecl();
+
+        if (!ActualDecl)
+          continue;
+        F.IsClass = true;
+      }
+
+      if (const auto *ActualTD = dyn_cast_or_null<TemplateDecl>(ActualDecl)) {
+        if (isa<RecordDecl>(ActualTD->getTemplatedDecl()))
+          F.IsClass = true;
+        F.Template.emplace();
+        for (const auto *Param : ActualTD->getTemplateParameters()->asArray())
+          F.Template->Params.emplace_back(
+              getSourceCode(Param, Param->getSourceRange()));
+        ActualDecl = ActualTD->getTemplatedDecl();
+      }
+
+      if (auto *FuncDecl = dyn_cast_or_null<FunctionDecl>(ActualDecl)) {
+        FunctionInfo TempInfo;
+        parseParameters(TempInfo, FuncDecl);
+        F.Params.emplace();
+        F.Params = std::move(TempInfo.Params);
+        F.ReturnType = getTypeInfoForType(FuncDecl->getReturnType(),
+                                          FuncDecl->getLangOpts());
+      }
+
+      F.Ref = Reference(getUSRForDecl(ActualDecl),
+                        ActualDecl->getNameAsString(), InfoType::IT_default,
+                        ActualDecl->getQualifiedNameAsString(),
+                        getInfoRelativePath(ActualDecl));
+
+      RI.Friends.push_back(std::move(F));
+    }
+}
+
 std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>>
 emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
          bool PublicOnly) {
@@ -970,6 +1022,7 @@ emitInfo(const RecordDecl *D, const FullComment *FC, Location Loc,
     // TODO: remove first call to parseBases, that function should be deleted
     parseBases(*RI, C);
     parseBases(*RI, C, /*IsFileInRootDir=*/true, PublicOnly, /*IsParent=*/true);
+    parseFriends(*RI, C);
   }
   RI->Path = getInfoRelativePath(RI->Namespace);
 
diff --git a/clang-tools-extra/clang-doc/YAMLGenerator.cpp b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
index 3ca4d4947fa97..eeccdd804b669 100644
--- a/clang-tools-extra/clang-doc/YAMLGenerator.cpp
+++ b/clang-tools-extra/clang-doc/YAMLGenerator.cpp
@@ -410,6 +410,7 @@ llvm::Error YAMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
     break;
   case InfoType::IT_concept:
   case InfoType::IT_variable:
+  case InfoType::IT_friend:
     break;
   case InfoType::IT_default:
     return llvm::createStringError(llvm::inconvertibleErrorCode(),
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp b/clang-tools-extra/test/clang-doc/json/class.cpp
index 0715fcefbb785..ae47da75edccb 100644
--- a/clang-tools-extra/test/clang-doc/json/class.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -89,44 +89,44 @@ struct MyClass {
 // CHECK-NEXT:        "USR": "{{[0-9A-F]*}}"
 // CHECK-NEXT:      }
 // CHECK-NEXT:    ],
-// CHECK-NOT:     "Friends": [
-// CHECK-NOT:       {
-// CHECK-NOT:         "IsClass": false,
-// CHECK-NOT:         "Params": [
-// CHECK-NOT:           {
-// CHECK-NOT:             "Name": "",
-// CHECK-NOT:             "Type": "int"
-// CHECK-NOT:           }
-// CHECK-NOT:         ],
-// CHECK-NOT:         "Reference": {
-// CHECK-NOT:           "Name": "friendFunction",
-// CHECK-NOT:           "Path": "",
-// CHECK-NOT:           "QualName": "friendFunction",
-// CHECK-NOT:           "USR": "{{[0-9A-F]*}}"
-// CHECK-NOT:         },
-// CHECK-NOT:         "ReturnType": {
-// CHECK-NOT:           "IsBuiltIn": true,
-// CHECK-NOT:           "IsTemplate": false,
-// CHECK-NOT:           "Name": "void",
-// CHECK-NOT:           "QualName": "void",
-// CHECK-NOT:           "USR": "0000000000000000000000000000000000000000"
-// CHECK-NOT:         },
-// CHECK-NOT:         "Template": {
-// CHECK-NOT:           "Parameters": [
-// CHECK-NOT:             "typename T"
-// CHECK-NOT:           ]
-// CHECK-NOT:         }
-// CHECK-NOT:       },
-// CHECK-NOT:       {
-// CHECK-NOT:         "IsClass": true,
-// CHECK-NOT:         "Reference": {
-// CHECK-NOT:           "Name": "Foo",
-// CHECK-NOT:           "Path": "GlobalNamespace",
-// CHECK-NOT:           "QualName": "Foo",
-// CHECK-NOT:           "USR": "{{[0-9A-F]*}}"
-// CHECK-NOT:         },
-// CHECK-NOT:       },
-// CHECK-NOT:    ],
+// CHECK-NEXT:    "Friends": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "IsClass": false,
+// CHECK-NEXT:        "Params": [
+// CHECK-NEXT:          {
+// CHECK-NEXT:            "Name": "",
+// CHECK-NEXT:            "Type": "int"
+// CHECK-NEXT:          }
+// CHECK-NEXT:        ],
+// CHECK-NEXT:        "Reference": {
+// CHECK-NEXT:          "Name": "friendFunction",
+// CHECK-NEXT:          "Path": "",
+// CHECK-NEXT:          "QualName": "friendFunction",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "ReturnType": {
+// CHECK-NEXT:          "IsBuiltIn": true,
+// CHECK-NEXT:          "IsTemplate": false,
+// CHECK-NEXT:          "Name": "void",
+// CHECK-NEXT:          "QualName": "void",
+// CHECK-NEXT:          "USR": "0000000000000000000000000000000000000000"
+// CHECK-NEXT:        },
+// CHECK-NEXT:        "Template": {
+// CHECK-NEXT:          "Parameters": [
+// CHECK-NEXT:            "typename T"
+// CHECK-NEXT:          ]
+// CHECK-NEXT:        }
+// CHECK-NEXT:      },
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "IsClass": true,
+// CHECK-NEXT:        "Reference": {
+// CHECK-NEXT:          "Name": "Foo",
+// CHECK-NEXT:          "Path": "GlobalNamespace",
+// CHECK-NEXT:          "QualName": "Foo",
+// CHECK-NEXT:          "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:        }
+// CHECK-NEXT:      }
+// CHECK-NEXT:    ],
 // COM:           FIXME: FullName is not emitted correctly.
 // CHECK-NEXT:    "FullName": "",
 // CHECK-NEXT:    "IsTypedef": false,
diff --git a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
index 323431c4fc9e0..d88b0c91bb9a9 100644
--- a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp
@@ -41,6 +41,8 @@ static std::string writeInfo(Info *I) {
     return writeInfo(*static_cast<ConceptInfo *>(I));
   case InfoType::IT_variable:
     return writeInfo(*static_cast<VarInfo *>(I));
+  case InfoType::IT_friend:
+    return writeInfo(*static_cast<FriendInfo *>(I));
   case InfoType::IT_default:
     return "";
   }



More information about the llvm-branch-commits mailing list