| //===-- 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/Attr.h" |
| #include "clang/AST/Comment.h" |
| #include "clang/AST/DeclFriend.h" |
| #include "clang/AST/Mangle.h" |
| #include "clang/Index/USRGeneration.h" |
| #include "clang/Lex/Lexer.h" |
| #include "llvm/ADT/StringExtras.h" |
| #include "llvm/Support/SHA1.h" |
| |
| using clang::comments::FullComment; |
| |
| namespace clang { |
| namespace doc { |
| namespace serialize { |
| |
| namespace { |
| static SmallString<16> exprToString(const clang::Expr *E) { |
| clang::LangOptions Opts; |
| clang::PrintingPolicy Policy(Opts); |
| SmallString<16> Result; |
| llvm::raw_svector_ostream OS(Result); |
| E->printPretty(OS, nullptr, Policy); |
| return Result; |
| } |
| } // namespace |
| |
| SymbolID hashUSR(llvm::StringRef USR) { |
| return llvm::SHA1::hash(arrayRefFromStringRef(USR)); |
| } |
| |
| template <typename T> |
| static void |
| populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces, |
| const T *D, bool &IsAnonymousNamespace); |
| |
| static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *D); |
| static void populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access, |
| const DeclaratorDecl *D, |
| bool IsStatic = false); |
| |
| static void getTemplateParameters(const TemplateParameterList *TemplateParams, |
| llvm::raw_ostream &Stream) { |
| Stream << "template <"; |
| |
| for (unsigned i = 0; i < TemplateParams->size(); ++i) { |
| if (i > 0) |
| Stream << ", "; |
| |
| const NamedDecl *Param = TemplateParams->getParam(i); |
| if (const auto *TTP = llvm::dyn_cast<TemplateTypeParmDecl>(Param)) { |
| if (TTP->wasDeclaredWithTypename()) |
| Stream << "typename"; |
| else |
| Stream << "class"; |
| if (TTP->isParameterPack()) |
| Stream << "..."; |
| Stream << " " << TTP->getNameAsString(); |
| |
| // We need to also handle type constraints for code like: |
| // template <class T = void> |
| // class C {}; |
| if (TTP->hasTypeConstraint()) { |
| Stream << " = "; |
| TTP->getTypeConstraint()->print( |
| Stream, TTP->getASTContext().getPrintingPolicy()); |
| } |
| } else if (const auto *NTTP = |
| llvm::dyn_cast<NonTypeTemplateParmDecl>(Param)) { |
| NTTP->getType().print(Stream, NTTP->getASTContext().getPrintingPolicy()); |
| if (NTTP->isParameterPack()) |
| Stream << "..."; |
| Stream << " " << NTTP->getNameAsString(); |
| } else if (const auto *TTPD = |
| llvm::dyn_cast<TemplateTemplateParmDecl>(Param)) { |
| Stream << "template <"; |
| getTemplateParameters(TTPD->getTemplateParameters(), Stream); |
| Stream << "> class " << TTPD->getNameAsString(); |
| } |
| } |
| |
| Stream << "> "; |
| } |
| |
| // Extract the full function prototype from a FunctionDecl including |
| // Full Decl |
| static llvm::SmallString<256> |
| getFunctionPrototype(const FunctionDecl *FuncDecl) { |
| llvm::SmallString<256> Result; |
| llvm::raw_svector_ostream Stream(Result); |
| const ASTContext &Ctx = FuncDecl->getASTContext(); |
| const auto *Method = llvm::dyn_cast<CXXMethodDecl>(FuncDecl); |
| // If it's a templated function, handle the template parameters |
| if (const auto *TmplDecl = FuncDecl->getDescribedTemplate()) |
| getTemplateParameters(TmplDecl->getTemplateParameters(), Stream); |
| |
| // If it's a virtual method |
| if (Method && Method->isVirtual()) |
| Stream << "virtual "; |
| |
| // Print return type |
| FuncDecl->getReturnType().print(Stream, Ctx.getPrintingPolicy()); |
| |
| // Print function name |
| Stream << " " << FuncDecl->getNameAsString() << "("; |
| |
| // Print parameter list with types, names, and default values |
| for (unsigned I = 0; I < FuncDecl->getNumParams(); ++I) { |
| if (I > 0) |
| Stream << ", "; |
| const ParmVarDecl *ParamDecl = FuncDecl->getParamDecl(I); |
| QualType ParamType = ParamDecl->getType(); |
| ParamType.print(Stream, Ctx.getPrintingPolicy()); |
| |
| // Print parameter name if it has one |
| if (!ParamDecl->getName().empty()) |
| Stream << " " << ParamDecl->getNameAsString(); |
| |
| // Print default argument if it exists |
| if (ParamDecl->hasDefaultArg() && |
| !ParamDecl->hasUninstantiatedDefaultArg()) { |
| if (const Expr *DefaultArg = ParamDecl->getDefaultArg()) { |
| Stream << " = "; |
| DefaultArg->printPretty(Stream, nullptr, Ctx.getPrintingPolicy()); |
| } |
| } |
| } |
| |
| // If it is a variadic function, add '...' |
| if (FuncDecl->isVariadic()) { |
| if (FuncDecl->getNumParams() > 0) |
| Stream << ", "; |
| Stream << "..."; |
| } |
| |
| Stream << ")"; |
| |
| // If it's a const method, add 'const' qualifier |
| if (Method) { |
| if (Method->isDeleted()) |
| Stream << " = delete"; |
| if (Method->size_overridden_methods()) |
| Stream << " override"; |
| if (Method->hasAttr<clang::FinalAttr>()) |
| Stream << " final"; |
| if (Method->isConst()) |
| Stream << " const"; |
| if (Method->isPureVirtual()) |
| Stream << " = 0"; |
| } |
| |
| if (auto ExceptionSpecType = FuncDecl->getExceptionSpecType()) |
| Stream << " " << ExceptionSpecType; |
| |
| return Result; // Convert SmallString to std::string for return |
| } |
| |
| static llvm::SmallString<16> getTypeAlias(const TypeAliasDecl *Alias) { |
| llvm::SmallString<16> Result; |
| llvm::raw_svector_ostream Stream(Result); |
| const ASTContext &Ctx = Alias->getASTContext(); |
| if (const auto *TmplDecl = Alias->getDescribedTemplate()) |
| getTemplateParameters(TmplDecl->getTemplateParameters(), Stream); |
| Stream << "using " << Alias->getNameAsString() << " = "; |
| QualType Q = Alias->getUnderlyingType(); |
| Q.print(Stream, Ctx.getPrintingPolicy()); |
| |
| return Result; |
| } |
| |
| // extract full syntax for record declaration |
| static llvm::SmallString<16> getRecordPrototype(const CXXRecordDecl *CXXRD) { |
| llvm::SmallString<16> Result; |
| LangOptions LangOpts; |
| PrintingPolicy Policy(LangOpts); |
| Policy.SuppressTagKeyword = false; |
| Policy.FullyQualifiedName = true; |
| Policy.IncludeNewlines = false; |
| llvm::raw_svector_ostream OS(Result); |
| if (const auto *TD = CXXRD->getDescribedClassTemplate()) { |
| OS << "template <"; |
| bool FirstParam = true; |
| for (const auto *Param : *TD->getTemplateParameters()) { |
| if (!FirstParam) |
| OS << ", "; |
| Param->print(OS, Policy); |
| FirstParam = false; |
| } |
| OS << ">\n"; |
| } |
| |
| if (CXXRD->isStruct()) |
| OS << "struct "; |
| else if (CXXRD->isClass()) |
| OS << "class "; |
| else if (CXXRD->isUnion()) |
| OS << "union "; |
| |
| OS << CXXRD->getNameAsString(); |
| |
| // We need to make sure we have a good enough declaration to check. In the |
| // case where the class is a forward declaration, we'll fail assertions in |
| // DeclCXX. |
| if (CXXRD->isCompleteDefinition() && CXXRD->getNumBases() > 0) { |
| OS << " : "; |
| bool FirstBase = true; |
| for (const auto &Base : CXXRD->bases()) { |
| if (!FirstBase) |
| OS << ", "; |
| if (Base.isVirtual()) |
| OS << "virtual "; |
| OS << getAccessSpelling(Base.getAccessSpecifier()) << " "; |
| OS << Base.getType().getAsString(Policy); |
| FirstBase = false; |
| } |
| } |
| return Result; |
| } |
| |
| // 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 |
| // <root>/A/B |
| // |
| // namespace A { |
| // namespace B { |
| // |
| // class C {}; |
| // |
| // } |
| // } |
| static llvm::SmallString<128> |
| getInfoRelativePath(const llvm::SmallVectorImpl<doc::Reference> &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; |
| } |
| |
| static llvm::SmallString<128> getInfoRelativePath(const Decl *D) { |
| llvm::SmallVector<Reference, 4> 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<ClangDocCommentVisitor> { |
| 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 = stringToCommentKind(C->getCommentKindName()); |
| ConstCommentVisitor<ClangDocCommentVisitor>::visit(C); |
| for (comments::Comment *Child : |
| llvm::make_range(C->child_begin(), C->child_end())) { |
| CurrentCI.Children.emplace_back(std::make_unique<CommentInfo>()); |
| 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 "<not a builtin command>"; |
| } |
| |
| // Serializing functions. |
| |
| static std::string getSourceCode(const Decl *D, const SourceRange &R) { |
| return Lexer::getSourceText(CharSourceRange::getTokenRange(R), |
| D->getASTContext().getSourceManager(), |
| D->getASTContext().getLangOpts()) |
| .str(); |
| } |
| |
| template <typename T> 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<Info> &I) { |
| switch (I->IT) { |
| case InfoType::IT_namespace: |
| return serialize(*static_cast<NamespaceInfo *>(I.get())); |
| case InfoType::IT_record: |
| return serialize(*static_cast<RecordInfo *>(I.get())); |
| case InfoType::IT_enum: |
| return serialize(*static_cast<EnumInfo *>(I.get())); |
| case InfoType::IT_function: |
| 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_friend: |
| case InfoType::IT_typedef: |
| case InfoType::IT_default: |
| return ""; |
| } |
| llvm_unreachable("unhandled enumerator"); |
| } |
| |
| 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; |
| } |
| |
| static TypeInfo getTypeInfoForType(const QualType &T, |
| const PrintingPolicy &Policy) { |
| const TagDecl *TD = getTagDeclForType(T); |
| if (!TD) { |
| TypeInfo TI = TypeInfo(Reference(SymbolID(), T.getAsString(Policy))); |
| TI.IsBuiltIn = T->isBuiltinType(); |
| TI.IsTemplate = T->isTemplateTypeParmType(); |
| return TI; |
| } |
| InfoType IT; |
| if (isa<EnumDecl>(TD)) { |
| IT = InfoType::IT_enum; |
| } else if (isa<RecordDecl>(TD)) { |
| IT = InfoType::IT_record; |
| } else { |
| IT = InfoType::IT_default; |
| } |
| Reference R = Reference(getUSRForDecl(TD), TD->getNameAsString(), IT, |
| T.getAsString(Policy), getInfoRelativePath(TD)); |
| TypeInfo TI = TypeInfo(R); |
| TI.IsBuiltIn = T->isBuiltinType(); |
| TI.IsTemplate = T->isTemplateTypeParmType(); |
| return TI; |
| } |
| |
| static bool isPublic(const clang::AccessSpecifier AS, |
| const clang::Linkage Link) { |
| if (AS == clang::AccessSpecifier::AS_private) |
| return false; |
| if ((Link == clang::Linkage::Module) || (Link == clang::Linkage::External)) |
| 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<NamespaceDecl>(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), |
| Info.MangledName); |
| } |
| |
| 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)); |
| } |
| |
| 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. |
| // |
| // 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<const Record&>(...); |
| // Otherwise, specify an rvalue reference <EnumInfo&&> 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 <typename ChildType> |
| static std::unique_ptr<Info> makeAndInsertIntoParent(ChildType Child) { |
| if (Child.Namespace.empty()) { |
| // Insert into unnamed parent namespace. |
| auto ParentNS = std::make_unique<NamespaceInfo>(); |
| InsertChild(ParentNS->Children, std::forward<ChildType>(Child)); |
| return ParentNS; |
| } |
| |
| switch (Child.Namespace[0].RefType) { |
| case InfoType::IT_namespace: { |
| auto ParentNS = std::make_unique<NamespaceInfo>(); |
| ParentNS->USR = Child.Namespace[0].USR; |
| InsertChild(ParentNS->Children, std::forward<ChildType>(Child)); |
| return ParentNS; |
| } |
| case InfoType::IT_record: { |
| auto ParentRec = std::make_unique<RecordInfo>(); |
| ParentRec->USR = Child.Namespace[0].USR; |
| InsertChild(ParentRec->Children, std::forward<ChildType>(Child)); |
| return ParentRec; |
| } |
| case InfoType::IT_default: |
| case InfoType::IT_enum: |
| case InfoType::IT_function: |
| 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"); |
| } |
| |
| // 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; |
| populateMemberTypeInfo(I, Access, F); |
| } |
| const auto *CxxRD = dyn_cast<CXXRecordDecl>(D); |
| if (!CxxRD) |
| return; |
| for (Decl *CxxDecl : CxxRD->decls()) { |
| auto *VD = dyn_cast<VarDecl>(CxxDecl); |
| if (!VD || |
| !shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, VD)) |
| continue; |
| |
| if (VD->isStaticDataMember()) |
| populateMemberTypeInfo(I, Access, VD, /*IsStatic=*/true); |
| } |
| } |
| |
| 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.str(), ValueExpr); |
| ASTContext &Context = E->getASTContext(); |
| if (RawComment *Comment = |
| E->getASTContext().getRawCommentForDeclNoCache(E)) { |
| Comment->setAttached(); |
| if (comments::FullComment *Fc = Comment->parse(Context, nullptr, E)) { |
| EnumValueInfo &Member = I.Members.back(); |
| Member.Description.emplace_back(); |
| parseFullComment(Fc, Member.Description.back()); |
| } |
| } |
| } |
| } |
| |
| static void parseParameters(FunctionInfo &I, const FunctionDecl *D) { |
| auto &LO = D->getLangOpts(); |
| for (const ParmVarDecl *P : D->parameters()) { |
| FieldTypeInfo &FieldInfo = I.Params.emplace_back( |
| getTypeInfoForType(P->getOriginalType(), LO), 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<TemplateSpecializationType>()) { |
| 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 <typename T> |
| static void |
| populateParentNamespaces(llvm::SmallVector<Reference, 4> &Namespaces, |
| const T *D, bool &IsInAnonymousNamespace) { |
| const DeclContext *DC = D->getDeclContext(); |
| do { |
| if (const auto *N = dyn_cast<NamespaceDecl>(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<RecordDecl>(DC)) |
| Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), |
| InfoType::IT_record, |
| N->getQualifiedNameAsString()); |
| else if (const auto *N = dyn_cast<FunctionDecl>(DC)) |
| Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), |
| InfoType::IT_function, |
| N->getQualifiedNameAsString()); |
| else if (const auto *N = dyn_cast<EnumDecl>(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<RecordDecl>(D)) || |
| (!Namespaces.empty() && Namespaces.back().RefType == InfoType::IT_record)) |
| Namespaces.emplace_back(SymbolID(), "GlobalNamespace", |
| InfoType::IT_namespace); |
| } |
| |
| static void |
| populateTemplateParameters(std::optional<TemplateInfo> &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())); |
| } |
| } |
| } |
| |
| static TemplateParamInfo convertTemplateArgToInfo(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 <typename T> |
| static void populateInfo(Info &I, const T *D, const FullComment *C, |
| bool &IsInAnonymousNamespace) { |
| I.USR = getUSRForDecl(D); |
| if (auto ConversionDecl = dyn_cast_or_null<CXXConversionDecl>(D); |
| ConversionDecl && ConversionDecl->getConversionType() |
| .getTypePtr() |
| ->isTemplateTypeParmType()) |
| I.Name = "operator " + ConversionDecl->getConversionType().getAsString(); |
| else |
| I.Name = D->getNameAsString(); |
| populateParentNamespaces(I.Namespace, D, IsInAnonymousNamespace); |
| if (C) { |
| I.Description.emplace_back(); |
| parseFullComment(C, I.Description.back()); |
| } |
| } |
| |
| template <typename T> |
| static void populateSymbolInfo(SymbolInfo &I, const T *D, const FullComment *C, |
| Location Loc, bool &IsInAnonymousNamespace) { |
| populateInfo(I, D, C, IsInAnonymousNamespace); |
| if (D->isThisDeclarationADefinition()) |
| I.DefLoc = Loc; |
| else |
| I.Loc.emplace_back(Loc); |
| |
| auto *Mangler = ItaniumMangleContext::create( |
| D->getASTContext(), D->getASTContext().getDiagnostics()); |
| std::string MangledName; |
| llvm::raw_string_ostream MangledStream(MangledName); |
| if (auto *CXXD = dyn_cast<CXXRecordDecl>(D)) |
| Mangler->mangleCXXVTable(CXXD, MangledStream); |
| else |
| MangledStream << D->getNameAsString(); |
| if (MangledName.size() > 255) |
| // File creation fails if the mangled name is too long, so default to the |
| // USR. We should look for a better check since filesystems differ in |
| // maximum filename length |
| I.MangledName = llvm::toStringRef(llvm::toHex(I.USR)); |
| else |
| I.MangledName = MangledName; |
| delete Mangler; |
| } |
| |
| static void |
| handleCompoundConstraints(const Expr *Constraint, |
| std::vector<ConstraintInfo> &ConstraintInfos) { |
| if (Constraint->getStmtClass() == Stmt::ParenExprClass) { |
| handleCompoundConstraints(dyn_cast<ParenExpr>(Constraint)->getSubExpr(), |
| ConstraintInfos); |
| } else if (Constraint->getStmtClass() == Stmt::BinaryOperatorClass) { |
| auto *BinaryOpExpr = dyn_cast<BinaryOperator>(Constraint); |
| handleCompoundConstraints(BinaryOpExpr->getLHS(), ConstraintInfos); |
| handleCompoundConstraints(BinaryOpExpr->getRHS(), ConstraintInfos); |
| } else if (Constraint->getStmtClass() == |
| Stmt::ConceptSpecializationExprClass) { |
| auto *Concept = dyn_cast<ConceptSpecializationExpr>(Constraint); |
| ConstraintInfo CI(getUSRForDecl(Concept->getNamedConcept()), |
| Concept->getNamedConcept()->getNameAsString()); |
| CI.ConstraintExpr = exprToString(Concept); |
| ConstraintInfos.push_back(CI); |
| } |
| } |
| |
| static void populateConstraints(TemplateInfo &I, const TemplateDecl *D) { |
| if (!D || !D->hasAssociatedConstraints()) |
| return; |
| |
| SmallVector<AssociatedConstraint> AssociatedConstraints; |
| D->getAssociatedConstraints(AssociatedConstraints); |
| for (const auto &Constraint : AssociatedConstraints) { |
| if (!Constraint) |
| continue; |
| |
| // TODO: Investigate if atomic constraints need to be handled specifically. |
| if (const auto *ConstraintExpr = |
| dyn_cast_or_null<ConceptSpecializationExpr>( |
| Constraint.ConstraintExpr)) { |
| ConstraintInfo CI(getUSRForDecl(ConstraintExpr->getNamedConcept()), |
| ConstraintExpr->getNamedConcept()->getNameAsString()); |
| CI.ConstraintExpr = exprToString(ConstraintExpr); |
| I.Constraints.push_back(std::move(CI)); |
| } else { |
| handleCompoundConstraints(Constraint.ConstraintExpr, I.Constraints); |
| } |
| } |
| } |
| |
| static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, |
| const FullComment *FC, Location Loc, |
| bool &IsInAnonymousNamespace) { |
| populateSymbolInfo(I, D, FC, Loc, IsInAnonymousNamespace); |
| auto &LO = D->getLangOpts(); |
| I.ReturnType = getTypeInfoForType(D->getReturnType(), LO); |
| I.Prototype = getFunctionPrototype(D); |
| parseParameters(I, D); |
| I.IsStatic = D->isStatic(); |
| |
| populateTemplateParameters(I.Template, D); |
| if (I.Template) |
| populateConstraints(I.Template.value(), D->getDescribedFunctionTemplate()); |
| |
| // 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(convertTemplateArgToInfo(D, Arg)); |
| } |
| } |
| } |
| } |
| |
| static void populateMemberTypeInfo(MemberTypeInfo &I, const Decl *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 populateMemberTypeInfo(RecordInfo &I, AccessSpecifier &Access, |
| const DeclaratorDecl *D, bool IsStatic) { |
| // 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(D->getTypeSourceInfo()->getType(), D->getLangOpts()), |
| D->getNameAsString(), |
| getFinalAccessSpecifier(Access, D->getAccessUnsafe()), IsStatic); |
| populateMemberTypeInfo(NewMember, D); |
| } |
| |
| 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<RecordType>()) { |
| if (const CXXRecordDecl *Base = cast_or_null<CXXRecordDecl>( |
| Ty->getOriginalDecl()->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<TemplateSpecializationType>()) { |
| 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<CXXMethodDecl>(Decl)) { |
| // Don't serialize private methods |
| if (MD->getAccessUnsafe() == AccessSpecifier::AS_private || |
| !MD->isUserProvided()) |
| continue; |
| FunctionInfo FI; |
| FI.IsMethod = true; |
| FI.IsStatic = MD->isStatic(); |
| // 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=*/{}, /*Location=*/{}, |
| 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<Info>, std::unique_ptr<Info>> |
| emitInfo(const NamespaceDecl *D, const FullComment *FC, Location Loc, |
| bool PublicOnly) { |
| auto NSI = std::make_unique<NamespaceInfo>(); |
| bool IsInAnonymousNamespace = false; |
| populateInfo(*NSI, D, FC, IsInAnonymousNamespace); |
| if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) |
| return {}; |
| |
| NSI->Name = D->isAnonymousNamespace() |
| ? llvm::SmallString<16>("@nonymous_namespace") |
| : NSI->Name; |
| NSI->Path = getInfoRelativePath(NSI->Namespace); |
| if (NSI->Namespace.empty() && NSI->USR == SymbolID()) |
| return {std::unique_ptr<Info>{std::move(NSI)}, nullptr}; |
| |
| // Namespaces are inserted into the parent by reference, so we need to return |
| // both the parent and the record itself. |
| return {std::move(NSI), makeAndInsertIntoParent<const NamespaceInfo &>(*NSI)}; |
| } |
| |
| static void parseFriends(RecordInfo &RI, const CXXRecordDecl *D) { |
| if (!D->hasDefinition() || !D->hasFriends()) |
| return; |
| |
| 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) { |
| |
| auto RI = std::make_unique<RecordInfo>(); |
| bool IsInAnonymousNamespace = false; |
| |
| populateSymbolInfo(*RI, D, FC, Loc, IsInAnonymousNamespace); |
| if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) |
| return {}; |
| |
| RI->TagType = D->getTagKind(); |
| parseFields(*RI, D, PublicOnly); |
| |
| if (const auto *C = dyn_cast<CXXRecordDecl>(D)) { |
| RI->FullName = getRecordPrototype(C); |
| if (const TypedefNameDecl *TD = C->getTypedefNameForAnonDecl()) { |
| RI->Name = TD->getNameAsString(); |
| RI->IsTypeDef = true; |
| } |
| // 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); |
| |
| populateTemplateParameters(RI->Template, D); |
| if (RI->Template) |
| populateConstraints(RI->Template.value(), D->getDescribedTemplate()); |
| |
| // Full and partial specializations. |
| if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D)) { |
| if (!RI->Template) |
| RI->Template.emplace(); |
| RI->Template->Specialization.emplace(); |
| auto &Specialization = *RI->Template->Specialization; |
| |
| // What this is a specialization of. |
| auto SpecOf = CTSD->getSpecializedTemplateOrPartial(); |
| if (auto *SpecTD = dyn_cast<ClassTemplateDecl *>(SpecOf)) |
| Specialization.SpecializationOf = getUSRForDecl(SpecTD); |
| else if (auto *SpecTD = |
| dyn_cast<ClassTemplatePartialSpecializationDecl *>(SpecOf)) |
| Specialization.SpecializationOf = getUSRForDecl(SpecTD); |
| |
| // Parameters to the specialization. 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<ClassTemplatePartialSpecializationDecl>(D)) { |
| if (const ASTTemplateArgumentListInfo *AsWritten = |
| CTPSD->getTemplateArgsAsWritten()) { |
| for (unsigned Idx = 0; Idx < AsWritten->getNumTemplateArgs(); Idx++) { |
| Specialization.Params.emplace_back( |
| getSourceCode(D, (*AsWritten)[Idx].getSourceRange())); |
| } |
| } |
| } else { |
| for (const TemplateArgument &Arg : CTSD->getTemplateArgs().asArray()) { |
| Specialization.Params.push_back(convertTemplateArgToInfo(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<const RecordInfo &>(*RI); |
| return {std::move(RI), std::move(Parent)}; |
| } |
| |
| std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>> |
| emitInfo(const FunctionDecl *D, const FullComment *FC, Location Loc, |
| bool PublicOnly) { |
| FunctionInfo Func; |
| bool IsInAnonymousNamespace = false; |
| populateFunctionInfo(Func, D, FC, Loc, 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<FunctionInfo &&>(std::move(Func))}; |
| } |
| |
| std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>> |
| emitInfo(const CXXMethodDecl *D, const FullComment *FC, Location Loc, |
| bool PublicOnly) { |
| FunctionInfo Func; |
| bool IsInAnonymousNamespace = false; |
| populateFunctionInfo(Func, D, FC, Loc, IsInAnonymousNamespace); |
| if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) |
| return {}; |
| |
| Func.IsMethod = true; |
| Func.IsStatic = D->isStatic(); |
| |
| const NamedDecl *Parent = nullptr; |
| if (const auto *SD = |
| dyn_cast<ClassTemplateSpecializationDecl>(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<FunctionInfo &&>(std::move(Func))}; |
| } |
| |
| static void extractCommentFromDecl(const Decl *D, TypedefInfo &Info) { |
| assert(D && "Invalid Decl when extracting comment"); |
| ASTContext &Context = D->getASTContext(); |
| RawComment *Comment = Context.getRawCommentForDeclNoCache(D); |
| if (!Comment) |
| return; |
| |
| Comment->setAttached(); |
| if (comments::FullComment *Fc = Comment->parse(Context, nullptr, D)) { |
| Info.Description.emplace_back(); |
| parseFullComment(Fc, Info.Description.back()); |
| } |
| } |
| |
| std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>> |
| emitInfo(const TypedefDecl *D, const FullComment *FC, Location Loc, |
| bool PublicOnly) { |
| TypedefInfo Info; |
| bool IsInAnonymousNamespace = false; |
| populateInfo(Info, D, FC, IsInAnonymousNamespace); |
| |
| if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) |
| return {}; |
| |
| Info.DefLoc = Loc; |
| auto &LO = D->getLangOpts(); |
| Info.Underlying = getTypeInfoForType(D->getUnderlyingType(), LO); |
| |
| 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; |
| extractCommentFromDecl(D, Info); |
| |
| // Info is wrapped in its parent scope so is returned in the second position. |
| return {nullptr, makeAndInsertIntoParent<TypedefInfo &&>(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<Info>, std::unique_ptr<Info>> |
| emitInfo(const TypeAliasDecl *D, const FullComment *FC, Location Loc, |
| bool PublicOnly) { |
| TypedefInfo Info; |
| bool IsInAnonymousNamespace = false; |
| populateInfo(Info, D, FC, IsInAnonymousNamespace); |
| if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) |
| return {}; |
| |
| Info.DefLoc = Loc; |
| const LangOptions &LO = D->getLangOpts(); |
| Info.Underlying = getTypeInfoForType(D->getUnderlyingType(), LO); |
| Info.TypeDeclaration = getTypeAlias(D); |
| Info.IsUsing = true; |
| |
| extractCommentFromDecl(D, Info); |
| |
| // Info is wrapped in its parent scope so is returned in the second position. |
| return {nullptr, makeAndInsertIntoParent<TypedefInfo &&>(std::move(Info))}; |
| } |
| |
| std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>> |
| emitInfo(const EnumDecl *D, const FullComment *FC, Location Loc, |
| bool PublicOnly) { |
| EnumInfo Enum; |
| bool IsInAnonymousNamespace = false; |
| populateSymbolInfo(Enum, D, FC, Loc, 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<EnumInfo &&>(std::move(Enum))}; |
| } |
| |
| std::pair<std::unique_ptr<Info>, std::unique_ptr<Info>> |
| emitInfo(const ConceptDecl *D, const FullComment *FC, const Location &Loc, |
| bool PublicOnly) { |
| ConceptInfo Concept; |
| |
| bool IsInAnonymousNamespace = false; |
| populateInfo(Concept, D, FC, IsInAnonymousNamespace); |
| Concept.IsType = D->isTypeConcept(); |
| Concept.DefLoc = Loc; |
| Concept.ConstraintExpression = exprToString(D->getConstraintExpr()); |
| |
| if (auto *ConceptParams = D->getTemplateParameters()) { |
| for (const auto *Param : ConceptParams->asArray()) { |
| Concept.Template.Params.emplace_back( |
| getSourceCode(Param, Param->getSourceRange())); |
| } |
| } |
| |
| if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) |
| return {}; |
| |
| 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 |