//===-- Serialize.cpp - ClangDoc Serializer ---------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "Serialize.h" #include "BitcodeWriter.h" #include "clang/AST/Comment.h" #include "clang/Index/USRGeneration.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/SHA1.h" using clang::comments::FullComment; namespace clang { namespace doc { namespace serialize { SymbolID hashUSR(llvm::StringRef USR) { return llvm::SHA1::hash(arrayRefFromStringRef(USR)); } template static void populateParentNamespaces(llvm::SmallVector &Namespaces, const T *D, bool &IsAnonymousNamespace); static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D); // A function to extract the appropriate relative path for a given info's // documentation. The path returned is a composite of the parent namespaces. // // Example: Given the below, the directory path for class C info will be // /A/B // // namespace A { // namespace B { // // class C {}; // // } // } llvm::SmallString<128> getInfoRelativePath(const llvm::SmallVectorImpl &Namespaces) { llvm::SmallString<128> Path; for (auto R = Namespaces.rbegin(), E = Namespaces.rend(); R != E; ++R) llvm::sys::path::append(Path, R->Name); return Path; } llvm::SmallString<128> getInfoRelativePath(const Decl *D) { llvm::SmallVector Namespaces; // The third arg in populateParentNamespaces is a boolean passed by reference, // its value is not relevant in here so it's not used anywhere besides the // function call bool B = true; populateParentNamespaces(Namespaces, D, B); return getInfoRelativePath(Namespaces); } class ClangDocCommentVisitor : public ConstCommentVisitor { public: ClangDocCommentVisitor(CommentInfo &CI) : CurrentCI(CI) {} void parseComment(const comments::Comment *C); void visitTextComment(const TextComment *C); void visitInlineCommandComment(const InlineCommandComment *C); void visitHTMLStartTagComment(const HTMLStartTagComment *C); void visitHTMLEndTagComment(const HTMLEndTagComment *C); void visitBlockCommandComment(const BlockCommandComment *C); void visitParamCommandComment(const ParamCommandComment *C); void visitTParamCommandComment(const TParamCommandComment *C); void visitVerbatimBlockComment(const VerbatimBlockComment *C); void visitVerbatimBlockLineComment(const VerbatimBlockLineComment *C); void visitVerbatimLineComment(const VerbatimLineComment *C); private: std::string getCommandName(unsigned CommandID) const; bool isWhitespaceOnly(StringRef S) const; CommentInfo &CurrentCI; }; void ClangDocCommentVisitor::parseComment(const comments::Comment *C) { CurrentCI.Kind = C->getCommentKindName(); ConstCommentVisitor::visit(C); for (comments::Comment *Child : llvm::make_range(C->child_begin(), C->child_end())) { CurrentCI.Children.emplace_back(std::make_unique()); ClangDocCommentVisitor Visitor(*CurrentCI.Children.back()); Visitor.parseComment(Child); } } void ClangDocCommentVisitor::visitTextComment(const TextComment *C) { if (!isWhitespaceOnly(C->getText())) CurrentCI.Text = C->getText(); } void ClangDocCommentVisitor::visitInlineCommandComment( const InlineCommandComment *C) { CurrentCI.Name = getCommandName(C->getCommandID()); for (unsigned I = 0, E = C->getNumArgs(); I != E; ++I) CurrentCI.Args.push_back(C->getArgText(I)); } void ClangDocCommentVisitor::visitHTMLStartTagComment( const HTMLStartTagComment *C) { CurrentCI.Name = C->getTagName(); CurrentCI.SelfClosing = C->isSelfClosing(); for (unsigned I = 0, E = C->getNumAttrs(); I < E; ++I) { const HTMLStartTagComment::Attribute &Attr = C->getAttr(I); CurrentCI.AttrKeys.push_back(Attr.Name); CurrentCI.AttrValues.push_back(Attr.Value); } } void ClangDocCommentVisitor::visitHTMLEndTagComment( const HTMLEndTagComment *C) { CurrentCI.Name = C->getTagName(); CurrentCI.SelfClosing = true; } void ClangDocCommentVisitor::visitBlockCommandComment( const BlockCommandComment *C) { CurrentCI.Name = getCommandName(C->getCommandID()); for (unsigned I = 0, E = C->getNumArgs(); I < E; ++I) CurrentCI.Args.push_back(C->getArgText(I)); } void ClangDocCommentVisitor::visitParamCommandComment( const ParamCommandComment *C) { CurrentCI.Direction = ParamCommandComment::getDirectionAsString(C->getDirection()); CurrentCI.Explicit = C->isDirectionExplicit(); if (C->hasParamName()) CurrentCI.ParamName = C->getParamNameAsWritten(); } void ClangDocCommentVisitor::visitTParamCommandComment( const TParamCommandComment *C) { if (C->hasParamName()) CurrentCI.ParamName = C->getParamNameAsWritten(); } void ClangDocCommentVisitor::visitVerbatimBlockComment( const VerbatimBlockComment *C) { CurrentCI.Name = getCommandName(C->getCommandID()); CurrentCI.CloseName = C->getCloseName(); } void ClangDocCommentVisitor::visitVerbatimBlockLineComment( const VerbatimBlockLineComment *C) { if (!isWhitespaceOnly(C->getText())) CurrentCI.Text = C->getText(); } void ClangDocCommentVisitor::visitVerbatimLineComment( const VerbatimLineComment *C) { if (!isWhitespaceOnly(C->getText())) CurrentCI.Text = C->getText(); } bool ClangDocCommentVisitor::isWhitespaceOnly(llvm::StringRef S) const { return llvm::all_of(S, isspace); } std::string ClangDocCommentVisitor::getCommandName(unsigned CommandID) const { const CommandInfo *Info = CommandTraits::getBuiltinCommandInfo(CommandID); if (Info) return Info->Name; // TODO: Add parsing for \file command. return ""; } // Serializing functions. std::string getSourceCode(const Decl *D, const SourceRange &R) { return Lexer::getSourceText(CharSourceRange::getTokenRange(R), D->getASTContext().getSourceManager(), D->getASTContext().getLangOpts()) .str(); } template static std::string serialize(T &I) { SmallString<2048> Buffer; llvm::BitstreamWriter Stream(Buffer); ClangDocBitcodeWriter Writer(Stream); Writer.emitBlock(I); return Buffer.str().str(); } std::string serialize(std::unique_ptr &I) { switch (I->IT) { case InfoType::IT_namespace: return serialize(*static_cast(I.get())); case InfoType::IT_record: return serialize(*static_cast(I.get())); case InfoType::IT_enum: return serialize(*static_cast(I.get())); case InfoType::IT_function: return serialize(*static_cast(I.get())); default: return ""; } } static void parseFullComment(const FullComment *C, CommentInfo &CI) { ClangDocCommentVisitor Visitor(CI); Visitor.parseComment(C); } static SymbolID getUSRForDecl(const Decl *D) { llvm::SmallString<128> USR; if (index::generateUSRForDecl(D, USR)) return SymbolID(); return hashUSR(USR); } static TagDecl *getTagDeclForType(const QualType &T) { if (const TagDecl *D = T->getAsTagDecl()) return D->getDefinition(); return nullptr; } static RecordDecl *getRecordDeclForType(const QualType &T) { if (const RecordDecl *D = T->getAsRecordDecl()) return D->getDefinition(); return nullptr; } TypeInfo getTypeInfoForType(const QualType &T) { const TagDecl *TD = getTagDeclForType(T); if (!TD) return TypeInfo(Reference(SymbolID(), T.getAsString())); InfoType IT; if (dyn_cast(TD)) { IT = InfoType::IT_enum; } else if (dyn_cast(TD)) { IT = InfoType::IT_record; } else { IT = InfoType::IT_default; } return TypeInfo(Reference(getUSRForDecl(TD), TD->getNameAsString(), IT, T.getAsString(), getInfoRelativePath(TD))); } static bool isPublic(const clang::AccessSpecifier AS, const clang::Linkage Link) { if (AS == clang::AccessSpecifier::AS_private) return false; else if ((Link == clang::Linkage::ModuleLinkage) || (Link == clang::Linkage::ExternalLinkage)) return true; return false; // otherwise, linkage is some form of internal linkage } static bool shouldSerializeInfo(bool PublicOnly, bool IsInAnonymousNamespace, const NamedDecl *D) { bool IsAnonymousNamespace = false; if (const auto *N = dyn_cast(D)) IsAnonymousNamespace = N->isAnonymousNamespace(); return !PublicOnly || (!IsInAnonymousNamespace && !IsAnonymousNamespace && isPublic(D->getAccessUnsafe(), D->getLinkageInternal())); } // The InsertChild functions insert the given info into the given scope using // the method appropriate for that type. Some types are moved into the // appropriate vector, while other types have Reference objects generated to // refer to them. // // See MakeAndInsertIntoParent(). static void InsertChild(ScopeChildren &Scope, const NamespaceInfo &Info) { Scope.Namespaces.emplace_back(Info.USR, Info.Name, InfoType::IT_namespace, Info.Name, getInfoRelativePath(Info.Namespace)); } static void InsertChild(ScopeChildren &Scope, const RecordInfo &Info) { Scope.Records.emplace_back(Info.USR, Info.Name, InfoType::IT_record, Info.Name, getInfoRelativePath(Info.Namespace)); } static void InsertChild(ScopeChildren &Scope, EnumInfo Info) { Scope.Enums.push_back(std::move(Info)); } static void InsertChild(ScopeChildren &Scope, FunctionInfo Info) { Scope.Functions.push_back(std::move(Info)); } static void InsertChild(ScopeChildren &Scope, TypedefInfo Info) { Scope.Typedefs.push_back(std::move(Info)); } // Creates a parent of the correct type for the given child and inserts it into // that parent. // // This is complicated by the fact that namespaces and records are inserted by // reference (constructing a "Reference" object with that namespace/record's // info), while everything else is inserted by moving it directly into the child // vectors. // // For namespaces and records, explicitly specify a const& template parameter // when invoking this function: // MakeAndInsertIntoParent(...); // Otherwise, specify an rvalue reference and move into the // parameter. Since each variant is used once, it's not worth having a more // elaborate system to automatically deduce this information. template std::unique_ptr MakeAndInsertIntoParent(ChildType Child) { if (Child.Namespace.empty()) { // Insert into unnamed parent namespace. auto ParentNS = std::make_unique(); InsertChild(ParentNS->Children, std::forward(Child)); return ParentNS; } switch (Child.Namespace[0].RefType) { case InfoType::IT_namespace: { auto ParentNS = std::make_unique(); ParentNS->USR = Child.Namespace[0].USR; InsertChild(ParentNS->Children, std::forward(Child)); return ParentNS; } case InfoType::IT_record: { auto ParentRec = std::make_unique(); ParentRec->USR = Child.Namespace[0].USR; InsertChild(ParentRec->Children, std::forward(Child)); return ParentRec; } default: llvm_unreachable("Invalid reference type for parent namespace"); } } // There are two uses for this function. // 1) Getting the resulting mode of inheritance of a record. // Example: class A {}; class B : private A {}; class C : public B {}; // It's explicit that C is publicly inherited from C and B is privately // inherited from A. It's not explicit but C is also privately inherited from // A. This is the AS that this function calculates. FirstAS is the // inheritance mode of `class C : B` and SecondAS is the inheritance mode of // `class B : A`. // 2) Getting the inheritance mode of an inherited attribute / method. // Example : class A { public: int M; }; class B : private A {}; // Class B is inherited from class A, which has a public attribute. This // attribute is now part of the derived class B but it's not public. This // will be private because the inheritance is private. This is the AS that // this function calculates. FirstAS is the inheritance mode and SecondAS is // the AS of the attribute / method. static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS, AccessSpecifier SecondAS) { if (FirstAS == AccessSpecifier::AS_none || SecondAS == AccessSpecifier::AS_none) return AccessSpecifier::AS_none; if (FirstAS == AccessSpecifier::AS_private || SecondAS == AccessSpecifier::AS_private) return AccessSpecifier::AS_private; if (FirstAS == AccessSpecifier::AS_protected || SecondAS == AccessSpecifier::AS_protected) return AccessSpecifier::AS_protected; return AccessSpecifier::AS_public; } // The Access parameter is only provided when parsing the field of an inherited // record, the access specification of the field depends on the inheritance mode static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly, AccessSpecifier Access = AccessSpecifier::AS_public) { for (const FieldDecl *F : D->fields()) { if (!shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, F)) continue; // Use getAccessUnsafe so that we just get the default AS_none if it's not // valid, as opposed to an assert. MemberTypeInfo &NewMember = I.Members.emplace_back( getTypeInfoForType(F->getTypeSourceInfo()->getType()), F->getNameAsString(), getFinalAccessSpecifier(Access, F->getAccessUnsafe())); populateMemberTypeInfo(NewMember, F); } } static void parseEnumerators(EnumInfo &I, const EnumDecl *D) { for (const EnumConstantDecl *E : D->enumerators()) { std::string ValueExpr; if (const Expr *InitExpr = E->getInitExpr()) ValueExpr = getSourceCode(D, InitExpr->getSourceRange()); SmallString<16> ValueStr; E->getInitVal().toString(ValueStr); I.Members.emplace_back(E->getNameAsString(), ValueStr, ValueExpr); } } static void parseParameters(FunctionInfo &I, const FunctionDecl *D) { for (const ParmVarDecl *P : D->parameters()) { FieldTypeInfo &FieldInfo = I.Params.emplace_back( getTypeInfoForType(P->getOriginalType()), P->getNameAsString()); FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange()); } } // TODO: Remove the serialization of Parents and VirtualParents, this // information is also extracted in the other definition of parseBases. static void parseBases(RecordInfo &I, const CXXRecordDecl *D) { // Don't parse bases if this isn't a definition. if (!D->isThisDeclarationADefinition()) return; for (const CXXBaseSpecifier &B : D->bases()) { if (B.isVirtual()) continue; if (const auto *Ty = B.getType()->getAs()) { const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl(); I.Parents.emplace_back(getUSRForDecl(D), B.getType().getAsString(), InfoType::IT_record, B.getType().getAsString()); } else if (const RecordDecl *P = getRecordDeclForType(B.getType())) I.Parents.emplace_back(getUSRForDecl(P), P->getNameAsString(), InfoType::IT_record, P->getQualifiedNameAsString(), getInfoRelativePath(P)); else I.Parents.emplace_back(SymbolID(), B.getType().getAsString()); } for (const CXXBaseSpecifier &B : D->vbases()) { if (const RecordDecl *P = getRecordDeclForType(B.getType())) I.VirtualParents.emplace_back( getUSRForDecl(P), P->getNameAsString(), InfoType::IT_record, P->getQualifiedNameAsString(), getInfoRelativePath(P)); else I.VirtualParents.emplace_back(SymbolID(), B.getType().getAsString()); } } template static void populateParentNamespaces(llvm::SmallVector &Namespaces, const T *D, bool &IsInAnonymousNamespace) { const DeclContext *DC = D->getDeclContext(); do { if (const auto *N = dyn_cast(DC)) { std::string Namespace; if (N->isAnonymousNamespace()) { Namespace = "@nonymous_namespace"; IsInAnonymousNamespace = true; } else Namespace = N->getNameAsString(); Namespaces.emplace_back(getUSRForDecl(N), Namespace, InfoType::IT_namespace, N->getQualifiedNameAsString()); } else if (const auto *N = dyn_cast(DC)) Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), InfoType::IT_record, N->getQualifiedNameAsString()); else if (const auto *N = dyn_cast(DC)) Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), InfoType::IT_function, N->getQualifiedNameAsString()); else if (const auto *N = dyn_cast(DC)) Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), InfoType::IT_enum, N->getQualifiedNameAsString()); } while ((DC = DC->getParent())); // The global namespace should be added to the list of namespaces if the decl // corresponds to a Record and if it doesn't have any namespace (because this // means it's in the global namespace). Also if its outermost namespace is a // record because that record matches the previous condition mentioned. if ((Namespaces.empty() && isa(D)) || (!Namespaces.empty() && Namespaces.back().RefType == InfoType::IT_record)) Namespaces.emplace_back(SymbolID(), "GlobalNamespace", InfoType::IT_namespace); } void PopulateTemplateParameters(std::optional &TemplateInfo, const clang::Decl *D) { if (const TemplateParameterList *ParamList = D->getDescribedTemplateParams()) { if (!TemplateInfo) { TemplateInfo.emplace(); } for (const NamedDecl *ND : *ParamList) { TemplateInfo->Params.emplace_back( getSourceCode(ND, ND->getSourceRange())); } } } TemplateParamInfo TemplateArgumentToInfo(const clang::Decl *D, const TemplateArgument &Arg) { // The TemplateArgument's pretty printing handles all the normal cases // well enough for our requirements. std::string Str; llvm::raw_string_ostream Stream(Str); Arg.print(PrintingPolicy(D->getLangOpts()), Stream, false); return TemplateParamInfo(Str); } template static void populateInfo(Info &I, const T *D, const FullComment *C, bool &IsInAnonymousNamespace) { I.USR = getUSRForDecl(D); I.Name = D->getNameAsString(); populateParentNamespaces(I.Namespace, D, IsInAnonymousNamespace); if (C) { I.Description.emplace_back(); parseFullComment(C, I.Description.back()); } } template static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C, int LineNumber, StringRef Filename, bool IsFileInRootDir, bool &IsInAnonymousNamespace) { populateInfo(I, D, C, IsInAnonymousNamespace); if (D->isThisDeclarationADefinition()) I.DefLoc.emplace(LineNumber, Filename, IsFileInRootDir); else I.Loc.emplace_back(LineNumber, Filename, IsFileInRootDir); } static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, const FullComment *FC, int LineNumber, StringRef Filename, bool IsFileInRootDir, bool &IsInAnonymousNamespace) { populateSymbolInfo(I, D, FC, LineNumber, Filename, IsFileInRootDir, IsInAnonymousNamespace); I.ReturnType = getTypeInfoForType(D->getReturnType()); parseParameters(I, D); PopulateTemplateParameters(I.Template, D); // Handle function template specializations. if (const FunctionTemplateSpecializationInfo *FTSI = D->getTemplateSpecializationInfo()) { if (!I.Template) I.Template.emplace(); I.Template->Specialization.emplace(); auto &Specialization = *I.Template->Specialization; Specialization.SpecializationOf = getUSRForDecl(FTSI->getTemplate()); // Template parameters to the specialization. if (FTSI->TemplateArguments) { for (const TemplateArgument &Arg : FTSI->TemplateArguments->asArray()) { Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg)); } } } } static void populateMemberTypeInfo(MemberTypeInfo &I, const FieldDecl *D) { assert(D && "Expect non-null FieldDecl in populateMemberTypeInfo"); ASTContext& Context = D->getASTContext(); // TODO investigate whether we can use ASTContext::getCommentForDecl instead // of this logic. See also similar code in Mapper.cpp. RawComment *Comment = Context.getRawCommentForDeclNoCache(D); if (!Comment) return; Comment->setAttached(); if (comments::FullComment* fc = Comment->parse(Context, nullptr, D)) { I.Description.emplace_back(); parseFullComment(fc, I.Description.back()); } } static void parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir, bool PublicOnly, bool IsParent, AccessSpecifier ParentAccess = AccessSpecifier::AS_public) { // Don't parse bases if this isn't a definition. if (!D->isThisDeclarationADefinition()) return; for (const CXXBaseSpecifier &B : D->bases()) { if (const RecordType *Ty = B.getType()->getAs()) { if (const CXXRecordDecl *Base = cast_or_null(Ty->getDecl()->getDefinition())) { // Initialized without USR and name, this will be set in the following // if-else stmt. BaseRecordInfo BI( {}, "", getInfoRelativePath(Base), B.isVirtual(), getFinalAccessSpecifier(ParentAccess, B.getAccessSpecifier()), IsParent); if (const auto *Ty = B.getType()->getAs()) { const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl(); BI.USR = getUSRForDecl(D); BI.Name = B.getType().getAsString(); } else { BI.USR = getUSRForDecl(Base); BI.Name = Base->getNameAsString(); } parseFields(BI, Base, PublicOnly, BI.Access); for (const auto &Decl : Base->decls()) if (const auto *MD = dyn_cast(Decl)) { // Don't serialize private methods if (MD->getAccessUnsafe() == AccessSpecifier::AS_private || !MD->isUserProvided()) continue; FunctionInfo FI; FI.IsMethod = true; // The seventh arg in populateFunctionInfo is a boolean passed by // reference, its value is not relevant in here so it's not used // anywhere besides the function call. bool IsInAnonymousNamespace; populateFunctionInfo(FI, MD, /*FullComment=*/{}, /*LineNumber=*/{}, /*FileName=*/{}, IsFileInRootDir, IsInAnonymousNamespace); FI.Access = getFinalAccessSpecifier(BI.Access, MD->getAccessUnsafe()); BI.Children.Functions.emplace_back(std::move(FI)); } I.Bases.emplace_back(std::move(BI)); // Call this function recursively to get the inherited classes of // this base; these new bases will also get stored in the original // RecordInfo: I. parseBases(I, Base, IsFileInRootDir, PublicOnly, false, I.Bases.back().Access); } } } } std::pair, std::unique_ptr> emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { auto I = std::make_unique(); bool IsInAnonymousNamespace = false; populateInfo(*I, D, FC, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; I->Name = D->isAnonymousNamespace() ? llvm::SmallString<16>("@nonymous_namespace") : I->Name; I->Path = getInfoRelativePath(I->Namespace); if (I->Namespace.empty() && I->USR == SymbolID()) return {std::unique_ptr{std::move(I)}, nullptr}; // Namespaces are inserted into the parent by reference, so we need to return // both the parent and the record itself. return {std::move(I), MakeAndInsertIntoParent(*I)}; } std::pair, std::unique_ptr> emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { auto I = std::make_unique(); bool IsInAnonymousNamespace = false; populateSymbolInfo(*I, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; I->TagType = D->getTagKind(); parseFields(*I, D, PublicOnly); if (const auto *C = dyn_cast(D)) { if (const TypedefNameDecl *TD = C->getTypedefNameForAnonDecl()) { I->Name = TD->getNameAsString(); I->IsTypeDef = true; } // TODO: remove first call to parseBases, that function should be deleted parseBases(*I, C); parseBases(*I, C, IsFileInRootDir, PublicOnly, true); } I->Path = getInfoRelativePath(I->Namespace); PopulateTemplateParameters(I->Template, D); // Full and partial specializations. if (auto *CTSD = dyn_cast(D)) { if (!I->Template) I->Template.emplace(); I->Template->Specialization.emplace(); auto &Specialization = *I->Template->Specialization; // What this is a specialization of. auto SpecOf = CTSD->getSpecializedTemplateOrPartial(); if (SpecOf.is()) { Specialization.SpecializationOf = getUSRForDecl(SpecOf.get()); } else if (SpecOf.is()) { Specialization.SpecializationOf = getUSRForDecl(SpecOf.get()); } // Parameters to the specilization. For partial specializations, get the // parameters "as written" from the ClassTemplatePartialSpecializationDecl // because the non-explicit template parameters will have generated internal // placeholder names rather than the names the user typed that match the // template parameters. if (const ClassTemplatePartialSpecializationDecl *CTPSD = dyn_cast(D)) { if (const ASTTemplateArgumentListInfo *AsWritten = CTPSD->getTemplateArgsAsWritten()) { for (unsigned i = 0; i < AsWritten->getNumTemplateArgs(); i++) { Specialization.Params.emplace_back( getSourceCode(D, (*AsWritten)[i].getSourceRange())); } } } else { for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) { Specialization.Params.push_back(TemplateArgumentToInfo(D, Arg)); } } } // Records are inserted into the parent by reference, so we need to return // both the parent and the record itself. auto Parent = MakeAndInsertIntoParent(*I); return {std::move(I), std::move(Parent)}; } std::pair, std::unique_ptr> emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { FunctionInfo Func; bool IsInAnonymousNamespace = false; populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); Func.Access = clang::AccessSpecifier::AS_none; if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; // Info is wrapped in its parent scope so is returned in the second position. return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; } std::pair, std::unique_ptr> emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { FunctionInfo Func; bool IsInAnonymousNamespace = false; populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; Func.IsMethod = true; const NamedDecl *Parent = nullptr; if (const auto *SD = dyn_cast(D->getParent())) Parent = SD->getSpecializedTemplate(); else Parent = D->getParent(); SymbolID ParentUSR = getUSRForDecl(Parent); Func.Parent = Reference{ParentUSR, Parent->getNameAsString(), InfoType::IT_record, Parent->getQualifiedNameAsString()}; Func.Access = D->getAccess(); // Info is wrapped in its parent scope so is returned in the second position. return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; } std::pair, std::unique_ptr> emitInfo(const TypedefDecl *D, const FullComment *FC, int LineNumber, StringRef File, bool IsFileInRootDir, bool PublicOnly) { TypedefInfo Info; bool IsInAnonymousNamespace = false; populateInfo(Info, D, FC, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; Info.DefLoc.emplace(LineNumber, File, IsFileInRootDir); Info.Underlying = getTypeInfoForType(D->getUnderlyingType()); if (Info.Underlying.Type.Name.empty()) { // Typedef for an unnamed type. This is like "typedef struct { } Foo;" // The record serializer explicitly checks for this syntax and constructs // a record with that name, so we don't want to emit a duplicate here. return {}; } Info.IsUsing = false; // Info is wrapped in its parent scope so is returned in the second position. return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; } // A type alias is a C++ "using" declaration for a type. It gets mapped to a // TypedefInfo with the IsUsing flag set. std::pair, std::unique_ptr> emitInfo(const TypeAliasDecl *D, const FullComment *FC, int LineNumber, StringRef File, bool IsFileInRootDir, bool PublicOnly) { TypedefInfo Info; bool IsInAnonymousNamespace = false; populateInfo(Info, D, FC, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; Info.DefLoc.emplace(LineNumber, File, IsFileInRootDir); Info.Underlying = getTypeInfoForType(D->getUnderlyingType()); Info.IsUsing = true; // Info is wrapped in its parent scope so is returned in the second position. return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; } std::pair, std::unique_ptr> emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { EnumInfo Enum; bool IsInAnonymousNamespace = false; populateSymbolInfo(Enum, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; Enum.Scoped = D->isScoped(); if (D->isFixed()) { auto Name = D->getIntegerType().getAsString(); Enum.BaseType = TypeInfo(Name, Name); } parseEnumerators(Enum, D); // Info is wrapped in its parent scope so is returned in the second position. return {nullptr, MakeAndInsertIntoParent(std::move(Enum))}; } } // namespace serialize } // namespace doc } // namespace clang