diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp --- a/clang-tools-extra/clang-doc/BitcodeReader.cpp +++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp @@ -350,6 +350,8 @@ return decodeRecord(R, I->USR, Blob); case REFERENCE_NAME: return decodeRecord(R, I->Name, Blob); + case REFERENCE_QUAL_NAME: + return decodeRecord(R, I->QualName, Blob); case REFERENCE_TYPE: return decodeRecord(R, I->RefType, Blob); case REFERENCE_PATH: @@ -362,6 +364,29 @@ } } +llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, + TemplateInfo *I) { + // Currently there are no child records of TemplateInfo (only child blocks). + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for TemplateParamInfo"); +} + +llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, + TemplateSpecializationInfo *I) { + if (ID == TEMPLATE_SPECIALIZATION_OF) + return decodeRecord(R, I->SpecializationOf, Blob); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for TemplateParamInfo"); +} + +llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, + TemplateParamInfo *I) { + if (ID == TEMPLATE_PARAM_CONTENTS) + return decodeRecord(R, I->Contents, Blob); + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for TemplateParamInfo"); +} + template llvm::Expected getCommentInfo(T I) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid type cannot contain CommentInfo"); @@ -594,6 +619,45 @@ I->Children.Functions.emplace_back(std::move(R)); } +// TemplateParam children. These go into either a TemplateInfo (for template +// parameters) or TemplateSpecializationInfo (for the specialization's +// parameters). +template void addTemplateParam(T I, TemplateParamInfo &&P) { + llvm::errs() << "invalid container for template parameter"; + exit(1); +} +template <> void addTemplateParam(TemplateInfo *I, TemplateParamInfo &&P) { + I->Params.emplace_back(std::move(P)); +} +template <> +void addTemplateParam(TemplateSpecializationInfo *I, TemplateParamInfo &&P) { + I->Params.emplace_back(std::move(P)); +} + +// Template info. These apply to either records or functions. +template void addTemplate(T I, TemplateInfo &&P) { + llvm::errs() << "invalid container for template info"; + exit(1); +} +template <> void addTemplate(RecordInfo *I, TemplateInfo &&P) { + I->Template.emplace(std::move(P)); +} +template <> void addTemplate(FunctionInfo *I, TemplateInfo &&P) { + I->Template.emplace(std::move(P)); +} + +// Template specializations go only into template records. +template +void addTemplateSpecialization(T I, TemplateSpecializationInfo &&TSI) { + llvm::errs() << "invalid container for template specialization info"; + exit(1); +} +template <> +void addTemplateSpecialization(TemplateInfo *I, + TemplateSpecializationInfo &&TSI) { + I->Specialization.emplace(std::move(TSI)); +} + // Read records from bitcode into a given info. template llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) { @@ -718,6 +782,27 @@ addChild(I, std::move(EV)); return llvm::Error::success(); } + case BI_TEMPLATE_BLOCK_ID: { + TemplateInfo TI; + if (auto Err = readBlock(ID, &TI)) + return Err; + addTemplate(I, std::move(TI)); + return llvm::Error::success(); + } + case BI_TEMPLATE_SPECIALIZATION_BLOCK_ID: { + TemplateSpecializationInfo TSI; + if (auto Err = readBlock(ID, &TSI)) + return Err; + addTemplateSpecialization(I, std::move(TSI)); + return llvm::Error::success(); + } + case BI_TEMPLATE_PARAM_BLOCK_ID: { + TemplateParamInfo TPI; + if (auto Err = readBlock(ID, &TPI)) + return Err; + addTemplateParam(I, std::move(TPI)); + return llvm::Error::success(); + } case BI_TYPEDEF_BLOCK_ID: { TypedefInfo TI; if (auto Err = readBlock(ID, &TI)) diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h --- a/clang-tools-extra/clang-doc/BitcodeWriter.h +++ b/clang-tools-extra/clang-doc/BitcodeWriter.h @@ -17,7 +17,6 @@ #include "Representation.h" #include "clang/AST/AST.h" -#include "llvm/ADT/APSInt.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" @@ -64,6 +63,9 @@ BI_FUNCTION_BLOCK_ID, BI_COMMENT_BLOCK_ID, BI_REFERENCE_BLOCK_ID, + BI_TEMPLATE_BLOCK_ID, + BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, + BI_TEMPLATE_PARAM_BLOCK_ID, BI_TYPEDEF_BLOCK_ID, BI_LAST, BI_FIRST = BI_VERSION_BLOCK_ID @@ -121,9 +123,12 @@ BASE_RECORD_IS_PARENT, REFERENCE_USR, REFERENCE_NAME, + REFERENCE_QUAL_NAME, REFERENCE_TYPE, REFERENCE_PATH, REFERENCE_FIELD, + TEMPLATE_PARAM_CONTENTS, + TEMPLATE_SPECIALIZATION_OF, TYPEDEF_USR, TYPEDEF_NAME, TYPEDEF_DEFLOCATION, @@ -169,6 +174,9 @@ void emitBlock(const FieldTypeInfo &B); void emitBlock(const MemberTypeInfo &T); void emitBlock(const CommentInfo &B); + void emitBlock(const TemplateInfo &T); + void emitBlock(const TemplateSpecializationInfo &T); + void emitBlock(const TemplateParamInfo &T); void emitBlock(const Reference &B, FieldId F); private: @@ -215,7 +223,7 @@ void emitRecord(bool Value, RecordId ID); void emitRecord(int Value, RecordId ID); void emitRecord(unsigned Value, RecordId ID); - void emitRecord(llvm::APSInt Value, RecordId ID); + void emitRecord(const TemplateInfo &Templ); bool prepRecordData(RecordId ID, bool ShouldEmit = true); // Emission of appropriate abbreviation type. diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp --- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp +++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp @@ -121,7 +121,10 @@ {BI_BASE_RECORD_BLOCK_ID, "BaseRecordBlock"}, {BI_FUNCTION_BLOCK_ID, "FunctionBlock"}, {BI_COMMENT_BLOCK_ID, "CommentBlock"}, - {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"}}; + {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"}, + {BI_TEMPLATE_BLOCK_ID, "TemplateBlock"}, + {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, "TemplateSpecializationBlock"}, + {BI_TEMPLATE_PARAM_BLOCK_ID, "TemplateParamBlock"}}; assert(Inits.size() == BlockIdCount); for (const auto &Init : Inits) BlockIdNameMap[Init.first] = Init.second; @@ -186,9 +189,12 @@ {FUNCTION_IS_METHOD, {"IsMethod", &BoolAbbrev}}, {REFERENCE_USR, {"USR", &SymbolIDAbbrev}}, {REFERENCE_NAME, {"Name", &StringAbbrev}}, + {REFERENCE_QUAL_NAME, {"QualName", &StringAbbrev}}, {REFERENCE_TYPE, {"RefType", &IntAbbrev}}, {REFERENCE_PATH, {"Path", &StringAbbrev}}, {REFERENCE_FIELD, {"Field", &IntAbbrev}}, + {TEMPLATE_PARAM_CONTENTS, {"Contents", &StringAbbrev}}, + {TEMPLATE_SPECIALIZATION_OF, {"SpecializationOf", &SymbolIDAbbrev}}, {TYPEDEF_USR, {"USR", &SymbolIDAbbrev}}, {TYPEDEF_NAME, {"Name", &StringAbbrev}}, {TYPEDEF_DEFLOCATION, {"DefLocation", &LocationAbbrev}}, @@ -244,8 +250,12 @@ FUNCTION_ACCESS, FUNCTION_IS_METHOD}}, // Reference Block {BI_REFERENCE_BLOCK_ID, - {REFERENCE_USR, REFERENCE_NAME, REFERENCE_TYPE, REFERENCE_PATH, - REFERENCE_FIELD}}}; + {REFERENCE_USR, REFERENCE_NAME, REFERENCE_QUAL_NAME, REFERENCE_TYPE, + REFERENCE_PATH, REFERENCE_FIELD}}, + // Template Blocks. + {BI_TEMPLATE_BLOCK_ID, {}}, + {BI_TEMPLATE_PARAM_BLOCK_ID, {TEMPLATE_PARAM_CONTENTS}}, + {BI_TEMPLATE_SPECIALIZATION_BLOCK_ID, {TEMPLATE_SPECIALIZATION_OF}}}; // AbbreviationMap @@ -378,6 +388,8 @@ Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); } +void ClangDocBitcodeWriter::emitRecord(const TemplateInfo &Templ) {} + bool ClangDocBitcodeWriter::prepRecordData(RecordId ID, bool ShouldEmit) { assert(RecordIdNameMap[ID] && "Unknown RecordId."); if (!ShouldEmit) @@ -416,6 +428,7 @@ StreamSubBlockGuard Block(Stream, BI_REFERENCE_BLOCK_ID); emitRecord(R.USR, REFERENCE_USR); emitRecord(R.Name, REFERENCE_NAME); + emitRecord(R.QualName, REFERENCE_QUAL_NAME); emitRecord((unsigned)R.RefType, REFERENCE_TYPE); emitRecord(R.Path, REFERENCE_PATH); emitRecord((unsigned)Field, REFERENCE_FIELD); @@ -556,6 +569,8 @@ emitBlock(C); for (const auto &C : I.Children.Typedefs) emitBlock(C); + if (I.Template) + emitBlock(*I.Template); } void ClangDocBitcodeWriter::emitBlock(const BaseRecordInfo &I) { @@ -591,6 +606,28 @@ emitBlock(I.ReturnType); for (const auto &N : I.Params) emitBlock(N); + if (I.Template) + emitBlock(*I.Template); +} + +void ClangDocBitcodeWriter::emitBlock(const TemplateInfo &T) { + StreamSubBlockGuard Block(Stream, BI_TEMPLATE_BLOCK_ID); + for (const auto &P : T.Params) + emitBlock(P); + if (T.Specialization) + emitBlock(*T.Specialization); +} + +void ClangDocBitcodeWriter::emitBlock(const TemplateSpecializationInfo &T) { + StreamSubBlockGuard Block(Stream, BI_TEMPLATE_SPECIALIZATION_BLOCK_ID); + emitRecord(T.SpecializationOf, TEMPLATE_SPECIALIZATION_OF); + for (const auto &P : T.Params) + emitBlock(P); +} + +void ClangDocBitcodeWriter::emitBlock(const TemplateParamInfo &T) { + StreamSubBlockGuard Block(Stream, BI_TEMPLATE_PARAM_BLOCK_ID); + emitRecord(T.Contents, TEMPLATE_PARAM_CONTENTS); } bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) { diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -18,7 +18,6 @@ #include "clang/Basic/Specifiers.h" #include "clang/Tooling/StandaloneExecution.h" #include "llvm/ADT/APSInt.h" -#include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include @@ -117,13 +116,21 @@ }; struct Reference { + // This variant (that takes no qualified name parameter) uses the Name as the + // QualName (very useful in unit tests to reduce verbosity). This can't use an + // empty string to indicate the default because we need to accept the empty + // string as a valid input for the global namespace (it will have + // "GlobalNamespace" as the name, but an empty QualName). Reference(SymbolID USR = SymbolID(), StringRef Name = StringRef(), - InfoType IT = InfoType::IT_default, StringRef Path = StringRef()) - : USR(USR), Name(Name), RefType(IT), Path(Path) {} + InfoType IT = InfoType::IT_default) + : USR(USR), Name(Name), QualName(Name), RefType(IT) {} + Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName, + StringRef Path = StringRef()) + : USR(USR), Name(Name), QualName(QualName), RefType(IT), Path(Path) {} bool operator==(const Reference &Other) const { - return std::tie(USR, Name, RefType) == - std::tie(Other.USR, Other.Name, Other.RefType); + return std::tie(USR, Name, QualName, RefType) == + std::tie(Other.USR, Other.Name, QualName, Other.RefType); } bool mergeable(const Reference &Other); @@ -136,7 +143,17 @@ llvm::SmallString<16> getFileBaseName() const; SymbolID USR = SymbolID(); // Unique identifier for referenced decl - SmallString<16> Name; // Name of type (possibly unresolved). + + // Name of type (possibly unresolved). Not including namespaces or template + // parameters (so for a std::vector this would be "vector"). See also + // QualName. + SmallString<16> Name; + + // Full qualified name of this type, including namespaces and template + // parameter (for example this could be "std::vector"). Contrast to + // Name. + SmallString<16> QualName; + InfoType RefType = InfoType::IT_default; // Indicates the type of this // Reference (namespace, record, // function, enum, default). @@ -169,13 +186,46 @@ // Convenience constructor for when there is no symbol ID or info type // (normally used for built-in types in tests). TypeInfo(StringRef Name, StringRef Path = StringRef()) - : Type(SymbolID(), Name, InfoType::IT_default, Path) {} + : Type(SymbolID(), Name, InfoType::IT_default, Name, Path) {} bool operator==(const TypeInfo &Other) const { return Type == Other.Type; } Reference Type; // Referenced type in this info. }; +// Represents one template parameter. +// +// This is a very simple serialization of the text of the source code of the +// template parameter. It is saved in a struct so there is a place to add the +// name and default values in the future if needed. +struct TemplateParamInfo { + TemplateParamInfo() = default; + explicit TemplateParamInfo(StringRef Contents) : Contents(Contents) {} + + // The literal contents of the code for that specifies this template parameter + // for this declaration. Typical values will be "class T" and + // "typename T = int". + SmallString<16> Contents; +}; + +struct TemplateSpecializationInfo { + // Indicates the declaration that this specializes. + SymbolID SpecializationOf; + + // Template parameters applying to the specialized record/function. + std::vector Params; +}; + +// Records the template information for a struct or function that is a template +// or an explicit template specialization. +struct TemplateInfo { + // May be empty for non-partial specializations. + std::vector Params; + + // Set when this is a specialization of another record/function. + std::optional Specialization; +}; + // Info for field types. struct FieldTypeInfo : public TypeInfo { FieldTypeInfo() = default; @@ -317,6 +367,13 @@ // with value 0 to be used as the default. // (AS_public = 0, AS_protected = 1, AS_private = 2, AS_none = 3) AccessSpecifier Access = AccessSpecifier::AS_public; + + // Full qualified name of this function, including namespaces and template + // specializations. + SmallString<16> FullName; + + // When present, this function is a template or specialization. + std::optional Template; }; // TODO: Expand to allow for documenting templating, inheritance access, @@ -332,6 +389,13 @@ // Type of this record (struct, class, union, interface). TagTypeKind TagType = TagTypeKind::TTK_Struct; + // Full qualified name of this record, including namespaces and template + // specializations. + SmallString<16> FullName; + + // When present, this record is a template or specialization. + std::optional Template; + // Indicates if the record was declared using a typedef. Things like anonymous // structs in a typedef: // typedef struct { ... } foo_t; @@ -433,12 +497,12 @@ Index(StringRef Name, StringRef JumpToSection) : Reference(SymbolID(), Name), JumpToSection(JumpToSection) {} Index(SymbolID USR, StringRef Name, InfoType IT, StringRef Path) - : Reference(USR, Name, IT, Path) {} + : Reference(USR, Name, IT, Name, Path) {} // This is used to look for a USR in a vector of Indexes using std::find bool operator==(const SymbolID &Other) const { return USR == Other; } bool operator<(const Index &Other) const; - llvm::Optional> JumpToSection; + std::optional> JumpToSection; std::vector Children; void sort(); @@ -467,7 +531,7 @@ // to definition locations will only be generated if // the file is in this dir. // URL of repository that hosts code used for links to definition locations. - llvm::Optional RepositoryUrl; + std::optional RepositoryUrl; // Path of CSS stylesheets that will be copied to OutDirectory and used to // style all HTML files. std::vector UserStylesheets; diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -250,6 +250,8 @@ reduceChildren(Children.Enums, std::move(Other.Children.Enums)); reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs)); SymbolInfo::merge(std::move(Other)); + if (!Template) + Template = Other.Template; } void EnumInfo::merge(EnumInfo &&Other) { @@ -274,6 +276,8 @@ if (Params.empty()) Params = std::move(Other.Params); SymbolInfo::merge(std::move(Other)); + if (!Template) + Template = Other.Template; } void TypedefInfo::merge(TypedefInfo &&Other) { diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -250,7 +250,7 @@ IT = InfoType::IT_default; } return TypeInfo(Reference(getUSRForDecl(TD), TD->getNameAsString(), IT, - getInfoRelativePath(TD))); + T.getAsString(), getInfoRelativePath(TD))); } static bool isPublic(const clang::AccessSpecifier AS, @@ -281,12 +281,12 @@ // See MakeAndInsertIntoParent(). static void InsertChild(ScopeChildren &Scope, const NamespaceInfo &Info) { Scope.Namespaces.emplace_back(Info.USR, Info.Name, InfoType::IT_namespace, - getInfoRelativePath(Info.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, - getInfoRelativePath(Info.Namespace)); + Info.Name, getInfoRelativePath(Info.Namespace)); } static void InsertChild(ScopeChildren &Scope, EnumInfo Info) { @@ -405,10 +405,7 @@ for (const ParmVarDecl *P : D->parameters()) { FieldTypeInfo &FieldInfo = I.Params.emplace_back( getTypeInfoForType(P->getOriginalType()), P->getNameAsString()); - - if (const Expr *DefaultArg = P->getDefaultArg()) { - FieldInfo.DefaultValue = getSourceCode(D, DefaultArg->getSourceRange()); - } + FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange()); } } @@ -424,18 +421,19 @@ 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); + 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, getInfoRelativePath(P)); + 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, - getInfoRelativePath(P)); + I.VirtualParents.emplace_back( + getUSRForDecl(P), P->getNameAsString(), InfoType::IT_record, + P->getQualifiedNameAsString(), getInfoRelativePath(P)); else I.VirtualParents.emplace_back(SymbolID(), B.getType().getAsString()); } @@ -455,16 +453,19 @@ } else Namespace = N->getNameAsString(); Namespaces.emplace_back(getUSRForDecl(N), Namespace, - InfoType::IT_namespace); + InfoType::IT_namespace, + N->getQualifiedNameAsString()); } else if (const auto *N = dyn_cast(DC)) Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), - InfoType::IT_record); + InfoType::IT_record, + N->getQualifiedNameAsString()); else if (const auto *N = dyn_cast(DC)) Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), - InfoType::IT_function); + InfoType::IT_function, + N->getQualifiedNameAsString()); else if (const auto *N = dyn_cast(DC)) Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), - InfoType::IT_enum); + 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 @@ -476,6 +477,30 @@ 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) { @@ -508,6 +533,26 @@ 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) { @@ -627,6 +672,46 @@ } 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); @@ -669,7 +754,8 @@ SymbolID ParentUSR = getUSRForDecl(Parent); Func.Parent = - Reference{ParentUSR, Parent->getNameAsString(), InfoType::IT_record}; + 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. @@ -731,8 +817,10 @@ return {}; Enum.Scoped = D->isScoped(); - if (D->isFixed()) - Enum.BaseType = TypeInfo(D->getIntegerType().getAsString()); + 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. diff --git a/clang-tools-extra/clang-doc/YAMLGenerator.cpp b/clang-tools-extra/clang-doc/YAMLGenerator.cpp --- a/clang-tools-extra/clang-doc/YAMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/YAMLGenerator.cpp @@ -9,6 +9,7 @@ //===----------------------------------------------------------------------===// #include "Generators.h" +#include "Representation.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include @@ -24,6 +25,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(EnumValueInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(TemplateParamInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(TypedefInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(BaseRecordInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr) @@ -143,6 +145,7 @@ IO.mapOptional("ChildFunctions", I.Children.Functions); IO.mapOptional("ChildEnums", I.Children.Enums); IO.mapOptional("ChildTypedefs", I.Children.Typedefs); + IO.mapOptional("Template", I.Template); } static void CommentInfoMapping(IO &IO, CommentInfo &I) { @@ -175,6 +178,7 @@ static void mapping(IO &IO, Reference &Ref) { IO.mapOptional("Type", Ref.RefType, InfoType::IT_default); IO.mapOptional("Name", Ref.Name, SmallString<16>()); + IO.mapOptional("QualName", Ref.QualName, SmallString<16>()); IO.mapOptional("USR", Ref.USR, SymbolID()); IO.mapOptional("Path", Ref.Path, SmallString<128>()); } @@ -268,6 +272,28 @@ // the AS that shouldn't be part of the output. Even though AS_public is the // default in the struct, it should be displayed in the YAML output. IO.mapOptional("Access", I.Access, clang::AccessSpecifier::AS_none); + IO.mapOptional("Template", I.Template); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TemplateParamInfo &I) { + IO.mapOptional("Contents", I.Contents); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TemplateSpecializationInfo &I) { + IO.mapOptional("SpecializationOf", I.SpecializationOf); + IO.mapOptional("Params", I.Params); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TemplateInfo &I) { + IO.mapOptional("Params", I.Params); + IO.mapOptional("Specialization", I.Specialization, + std::optional()); } }; diff --git a/clang-tools-extra/test/clang-doc/single-file-public.cpp b/clang-tools-extra/test/clang-doc/single-file-public.cpp --- a/clang-tools-extra/test/clang-doc/single-file-public.cpp +++ b/clang-tools-extra/test/clang-doc/single-file-public.cpp @@ -28,8 +28,9 @@ // CHECK-NEXT: Namespace: // CHECK-NEXT: - Type: Namespace // CHECK-NEXT: Name: 'GlobalNamespace' +// CHECK-NEXT: QualName: 'GlobalNamespace' // CHECK-NEXT: DefLocation: -// CHECK-NEXT: LineNumber: [[@LINE-20]] +// CHECK-NEXT: LineNumber: 12 // CHECK-NEXT: Filename: '{{.*}}' // CHECK-NEXT: TagType: Class // CHECK-NEXT: ChildFunctions: @@ -38,22 +39,26 @@ // CHECK-NEXT: Namespace: // CHECK-NEXT: - Type: Record // CHECK-NEXT: Name: 'Record' +// CHECK-NEXT: QualName: 'Record' // CHECK-NEXT: USR: '{{([0-9A-F]{40})}}' // CHECK-NEXT: - Type: Namespace // CHECK-NEXT: Name: 'GlobalNamespace' +// CHECK-NEXT: QualName: 'GlobalNamespace' // CHECK-NEXT: DefLocation: -// CHECK-NEXT: LineNumber: [[@LINE-23]] +// CHECK-NEXT: LineNumber: 22 // CHECK-NEXT: Filename: '{{.*}}' // CHECK-NEXT: Location: -// CHECK-NEXT: - LineNumber: [[@LINE-31]] +// CHECK-NEXT: - LineNumber: 17 // CHECK-NEXT: Filename: '{{.*}}' // CHECK-NEXT: IsMethod: true // CHECK-NEXT: Parent: // CHECK-NEXT: Type: Record // CHECK-NEXT: Name: 'Record' +// CHECK-NEXT: QualName: 'Record' // CHECK-NEXT: USR: '{{([0-9A-F]{40})}}' // CHECK-NEXT: ReturnType: // CHECK-NEXT: Type: // CHECK-NEXT: Name: 'void' +// CHECK-NEXT: QualName: 'void' // CHECK-NEXT: Access: Public // CHECK-NEXT: ... diff --git a/clang-tools-extra/test/clang-doc/single-file.cpp b/clang-tools-extra/test/clang-doc/single-file.cpp --- a/clang-tools-extra/test/clang-doc/single-file.cpp +++ b/clang-tools-extra/test/clang-doc/single-file.cpp @@ -11,21 +11,23 @@ void function(int x) {} // CHECK: --- -// CHECK-NEXT: USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}' +// CHECK-NEXT: USR: '{{([0-9A-F]{40})}}' // CHECK-NEXT: ChildFunctions: -// CHECK-NEXT: - USR: '{{[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]}}' +// CHECK-NEXT: - USR: '{{([0-9A-F]{40})}}' // CHECK-NEXT: Name: 'function' // CHECK-NEXT: DefLocation: -// CHECK-NEXT: LineNumber: [[@LINE-8]] +// CHECK-NEXT: LineNumber: 11 // CHECK-NEXT: Filename: '{{.*}} // CHECK-NEXT: Location: -// CHECK-NEXT: - LineNumber: [[@LINE-13]] +// CHECK-NEXT: - LineNumber: 9 // CHECK-NEXT: Filename: '{{.*}}' // CHECK-NEXT: Params: // CHECK-NEXT: - Type: // CHECK-NEXT: Name: 'int' +// CHECK-NEXT: QualName: 'int' // CHECK-NEXT: Name: 'x' // CHECK-NEXT: ReturnType: // CHECK-NEXT: Type: // CHECK-NEXT: Name: 'void' +// CHECK-NEXT: QualName: 'void' // CHECK-NEXT:... diff --git a/clang-tools-extra/test/clang-doc/templates.cpp b/clang-tools-extra/test/clang-doc/templates.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/templates.cpp @@ -0,0 +1,76 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: echo "" > %t/compile_flags.txt +// RUN: cp "%s" "%t/test.cpp" +// RUN: clang-doc --doxygen --executor=standalone -p %t %t/test.cpp -output=%t/docs +// RUN: cat %t/docs/index.yaml | FileCheck %s --check-prefix=CHECK +// RUN: rm -rf %t + +template +void function(T x) {} + +template<> +void function(bool x) {} + +template +void ParamPackFunction(T... args); + +// CHECK: --- +// CHECK-NEXT: USR: '{{([0-9A-F]{40})}}' +// CHECK-NEXT: ChildFunctions: +// CHECK-NEXT: - USR: '{{([0-9A-F]{40})}}' +// CHECK-NEXT: Name: 'function' +// CHECK-NEXT: DefLocation: +// CHECK-NEXT: LineNumber: 10 +// CHECK-NEXT: Filename: '{{.*}}' +// CHECK-NEXT: Params: +// CHECK-NEXT: - Type: +// CHECK-NEXT: Name: 'T' +// CHECK-NEXT: QualName: 'T' +// CHECK-NEXT: Name: 'x' +// CHECK-NEXT: ReturnType: +// CHECK-NEXT: Type: +// CHECK-NEXT: Name: 'void' +// CHECK-NEXT: QualName: 'void' +// CHECK-NEXT: Template: +// CHECK-NEXT: Params: +// CHECK-NEXT: - Contents: 'typename T' +// CHECK-NEXT: - Contents: 'int U = 1' +// CHECK-NEXT: - USR: '{{([0-9A-F]{40})}}' +// CHECK-NEXT: Name: 'function' +// CHECK-NEXT: DefLocation: +// CHECK-NEXT: LineNumber: 12 +// CHECK-NEXT: Filename: '{{.*}}' +// CHECK-NEXT: Params: +// CHECK-NEXT: - Type: +// CHECK-NEXT: Name: '_Bool' +// CHECK-NEXT: QualName: '_Bool' +// CHECK-NEXT: Name: 'x' +// CHECK-NEXT: ReturnType: +// CHECK-NEXT: Type: +// CHECK-NEXT: Name: 'void' +// CHECK-NEXT: QualName: 'void' +// CHECK-NEXT: Template: +// CHECK-NEXT: Specialization: +// CHECK-NEXT: SpecializationOf: '{{([0-9A-F]{40})}}' +// CHECK-NEXT: Params: +// CHECK-NEXT: - Contents: 'bool' +// CHECK-NEXT: - Contents: '0' +// CHECK-NEXT: - USR: '{{([0-9A-F]{40})}}' +// CHECK-NEXT: Name: 'ParamPackFunction' +// CHECK-NEXT: Location: +// CHECK-NEXT: - LineNumber: 16 +// CHECK-NEXT: Filename: '{{.*}}' +// CHECK-NEXT: Params: +// CHECK-NEXT: - Type: +// CHECK-NEXT: Name: 'T...' +// CHECK-NEXT: QualName: 'T...' +// CHECK-NEXT: Name: 'args' +// CHECK-NEXT: ReturnType: +// CHECK-NEXT: Type: +// CHECK-NEXT: Name: 'void' +// CHECK-NEXT: QualName: 'void' +// CHECK-NEXT: Template: +// CHECK-NEXT: Params: +// CHECK-NEXT: - Contents: 'class... T' +// CHECK-NEXT: ... diff --git a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp --- a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp @@ -44,9 +44,10 @@ I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); I.Children.Namespaces.emplace_back(EmptySID, "ChildNamespace", - InfoType::IT_namespace, "Namespace"); + InfoType::IT_namespace, + "Namespace::ChildNamespace", "Namespace"); I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record, - "Namespace"); + "Namespace::ChildStruct", "Namespace"); I.Children.Functions.emplace_back(); I.Children.Functions.back().Access = AccessSpecifier::AS_none; I.Children.Functions.back().Name = "OneFunction"; @@ -152,14 +153,13 @@ SmallString<16> PathTo; llvm::sys::path::native("path/to", PathTo); - I.Members.emplace_back(TypeInfo("int", "X/Y"), "X", - AccessSpecifier::AS_private); + I.Members.emplace_back(TypeInfo("int"), "X", AccessSpecifier::AS_private); I.TagType = TagTypeKind::TTK_Class; - I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, PathTo); + I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, "F", PathTo); I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record, - "X/Y/Z/r"); + "X::Y::Z::r::ChildStruct", "X/Y/Z/r"); I.Children.Functions.emplace_back(); I.Children.Functions.back().Name = "OneFunction"; I.Children.Enums.emplace_back(); @@ -195,11 +195,7 @@

Members

    -
  • - private - int - X -
  • +
  • private int X

Records

    @@ -277,8 +273,8 @@ SmallString<16> PathTo; llvm::sys::path::native("path/to", PathTo); - I.ReturnType = - TypeInfo(Reference(EmptySID, "float", InfoType::IT_default, PathTo)); + I.ReturnType = TypeInfo( + Reference(EmptySID, "float", InfoType::IT_default, "float", PathTo)); I.Params.emplace_back(TypeInfo("int", PathTo), "P"); I.IsMethod = true; I.Parent = Reference(EmptySID, "Parent", InfoType::IT_record); diff --git a/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp b/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp --- a/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp @@ -390,7 +390,7 @@ RecordInfo *F = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedF(EmptySID, /*Name=*/"F", /*Path=*/"GlobalNamespace"); ExpectedF.Namespace.emplace_back(EmptySID, "GlobalNamespace", - InfoType::IT_namespace); + InfoType::IT_namespace, ""); ExpectedF.TagType = TagTypeKind::TTK_Class; ExpectedF.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedF, F); @@ -410,9 +410,10 @@ ExpectedE.Namespace.emplace_back(EmptySID, "GlobalNamespace", InfoType::IT_namespace); ExpectedE.Parents.emplace_back(EmptySID, /*Name=*/"F", InfoType::IT_record, - /*Path*=*/"GlobalNamespace"); - ExpectedE.VirtualParents.emplace_back( - EmptySID, /*Name=*/"G", InfoType::IT_record, /*Path*=*/"GlobalNamespace"); + /*QualName=*/"", /*Path*=*/"GlobalNamespace"); + ExpectedE.VirtualParents.emplace_back(EmptySID, /*Name=*/"G", + InfoType::IT_record, /*QualName=*/"G", + /*Path*=*/"GlobalNamespace"); ExpectedE.Bases.emplace_back(EmptySID, /*Name=*/"F", /*Path=*/"GlobalNamespace", false, AccessSpecifier::AS_public, true); @@ -455,9 +456,10 @@ ExpectedH.TagType = TagTypeKind::TTK_Class; ExpectedH.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); ExpectedH.Parents.emplace_back(EmptySID, /*Name=*/"E", InfoType::IT_record, - /*Path=*/"GlobalNamespace"); - ExpectedH.VirtualParents.emplace_back( - EmptySID, /*Name=*/"G", InfoType::IT_record, /*Path=*/"GlobalNamespace"); + /*QualName=*/"E", /*Path=*/"GlobalNamespace"); + ExpectedH.VirtualParents.emplace_back(EmptySID, /*Name=*/"G", + InfoType::IT_record, /*QualName=*/"G", + /*Path=*/"GlobalNamespace"); ExpectedH.Bases.emplace_back(EmptySID, /*Name=*/"E", /*Path=*/"GlobalNamespace", false, AccessSpecifier::AS_private, true); @@ -562,7 +564,7 @@ NamespaceInfo *ParentA = InfoAsNamespace(Infos[1].get()); NamespaceInfo ExpectedParentA(EmptySID); ExpectedParentA.Children.Records.emplace_back( - EmptySID, "A", InfoType::IT_record, "GlobalNamespace"); + EmptySID, "A", InfoType::IT_record, "A", "GlobalNamespace"); CheckNamespaceInfo(&ExpectedParentA, ParentA); RecordInfo *ParentB = InfoAsRecord(Infos[3].get()); @@ -570,13 +572,13 @@ llvm::SmallString<128> ExpectedParentBPath("GlobalNamespace/A"); llvm::sys::path::native(ExpectedParentBPath); ExpectedParentB.Children.Records.emplace_back( - EmptySID, "B", InfoType::IT_record, ExpectedParentBPath); + EmptySID, "B", InfoType::IT_record, "A::B", ExpectedParentBPath); CheckRecordInfo(&ExpectedParentB, ParentB); NamespaceInfo *ParentC = InfoAsNamespace(Infos[7].get()); NamespaceInfo ExpectedParentC(EmptySID); ExpectedParentC.Children.Records.emplace_back( - EmptySID, "C", InfoType::IT_record, "@nonymous_namespace"); + EmptySID, "C", InfoType::IT_record, "C", "@nonymous_namespace"); CheckNamespaceInfo(&ExpectedParentC, ParentC); } @@ -594,8 +596,8 @@ NamespaceInfo *ParentB = InfoAsNamespace(Infos[3].get()); NamespaceInfo ExpectedParentB(EmptySID); - ExpectedParentB.Children.Namespaces.emplace_back(EmptySID, "B", - InfoType::IT_namespace, "A"); + ExpectedParentB.Children.Namespaces.emplace_back( + EmptySID, "B", InfoType::IT_namespace, "A::B", "A"); CheckNamespaceInfo(&ExpectedParentB, ParentB); } @@ -626,5 +628,105 @@ EXPECT_EQ("double", SecondTD.Underlying.Type.Name); } +TEST(SerializeTests, emitFunctionTemplate) { + EmittedInfoList Infos; + // A template and a specialization. + ExtractInfosFromCode("template void GetFoo(T);\n" + "template<> void GetFoo(bool);", + 2, + /*Public=*/false, Infos); + + // First info will be the global namespace. + NamespaceInfo *GlobalNS1 = InfoAsNamespace(Infos[0].get()); + ASSERT_EQ(1u, GlobalNS1->Children.Functions.size()); + + const FunctionInfo &Func1 = GlobalNS1->Children.Functions[0]; + EXPECT_EQ("GetFoo", Func1.Name); + ASSERT_TRUE(Func1.Template); + EXPECT_FALSE(Func1.Template->Specialization); // Not a specialization. + + // Template parameter. + ASSERT_EQ(1u, Func1.Template->Params.size()); + EXPECT_EQ("typename T = int", Func1.Template->Params[0].Contents); + + // The second will be another global namespace with the function in it (the + // global namespace is duplicated because the items haven't been merged at the + // serialization phase of processing). + NamespaceInfo *GlobalNS2 = InfoAsNamespace(Infos[1].get()); + ASSERT_EQ(1u, GlobalNS2->Children.Functions.size()); + + // This one is a template specialization. + const FunctionInfo &Func2 = GlobalNS2->Children.Functions[0]; + EXPECT_EQ("GetFoo", Func2.Name); + ASSERT_TRUE(Func2.Template); + EXPECT_TRUE(Func2.Template->Params.empty()); // No template params. + ASSERT_TRUE(Func2.Template->Specialization); + + // Specialization values. + ASSERT_EQ(1u, Func2.Template->Specialization->Params.size()); + EXPECT_EQ("bool", Func2.Template->Specialization->Params[0].Contents); + EXPECT_EQ(Func1.USR, Func2.Template->Specialization->SpecializationOf); +} + +TEST(SerializeTests, emitClassTemplate) { + EmittedInfoList Infos; + // This will generate 2x the number of infos: each Record will be followed by + // a copy of the global namespace containing it (this test checks the data + // pre-merge). + ExtractInfosFromCode( + "template class MyTemplate { int i[I]; };\n" + "template<> class MyTemplate<0> {};\n" + "template class OtherTemplate {};\n" + "template class OtherTemplate, U> {};", + 8, + /*Public=*/false, Infos); + + // First record. + const RecordInfo *Rec1 = InfoAsRecord(Infos[0].get()); + EXPECT_EQ("MyTemplate", Rec1->Name); + ASSERT_TRUE(Rec1->Template); + EXPECT_FALSE(Rec1->Template->Specialization); // Not a specialization. + + // First record template parameter. + ASSERT_EQ(1u, Rec1->Template->Params.size()); + EXPECT_EQ("int I", Rec1->Template->Params[0].Contents); + + // Second record. + const RecordInfo *Rec2 = InfoAsRecord(Infos[2].get()); + EXPECT_EQ("MyTemplate", Rec2->Name); + ASSERT_TRUE(Rec2->Template); + EXPECT_TRUE(Rec2->Template->Params.empty()); // No template params. + ASSERT_TRUE(Rec2->Template->Specialization); + + // Second record specialization values. + ASSERT_EQ(1u, Rec2->Template->Specialization->Params.size()); + EXPECT_EQ("0", Rec2->Template->Specialization->Params[0].Contents); + EXPECT_EQ(Rec1->USR, Rec2->Template->Specialization->SpecializationOf); + + // Third record. + const RecordInfo *Rec3 = InfoAsRecord(Infos[4].get()); + EXPECT_EQ("OtherTemplate", Rec3->Name); + ASSERT_TRUE(Rec3->Template); + + // Third record template parameters. + ASSERT_EQ(2u, Rec3->Template->Params.size()); + EXPECT_EQ("typename T", Rec3->Template->Params[0].Contents); + EXPECT_EQ("int U = 1", Rec3->Template->Params[1].Contents); + + // Fourth record. + const RecordInfo *Rec4 = InfoAsRecord(Infos[6].get()); + EXPECT_EQ("OtherTemplate", Rec3->Name); + ASSERT_TRUE(Rec4->Template); + ASSERT_TRUE(Rec4->Template->Specialization); + + // Fourth record template + specialization parameters. + ASSERT_EQ(1u, Rec4->Template->Params.size()); + EXPECT_EQ("int U", Rec4->Template->Params[0].Contents); + ASSERT_EQ(2u, Rec4->Template->Specialization->Params.size()); + EXPECT_EQ("MyTemplate<0>", + Rec4->Template->Specialization->Params[0].Contents); + EXPECT_EQ("U", Rec4->Template->Specialization->Params[1].Contents); +} + } // namespace doc } // end namespace clang diff --git a/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp --- a/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp @@ -28,10 +28,11 @@ I.Path = "path/to/A"; I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); - I.Children.Namespaces.emplace_back(EmptySID, "ChildNamespace", - InfoType::IT_namespace, - "path/to/A/Namespace"); + I.Children.Namespaces.emplace_back( + EmptySID, "ChildNamespace", InfoType::IT_namespace, + "path::to::A::Namespace::ChildNamespace", "path/to/A/Namespace"); I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record, + "path::to::A::Namespace::ChildStruct", "path/to/A/Namespace"); I.Children.Functions.emplace_back(); I.Children.Functions.back().Name = "OneFunction"; @@ -53,13 +54,16 @@ Namespace: - Type: Namespace Name: 'A' + QualName: 'A' ChildNamespaces: - Type: Namespace Name: 'ChildNamespace' + QualName: 'path::to::A::Namespace::ChildNamespace' Path: 'path/to/A/Namespace' ChildRecords: - Type: Record Name: 'ChildStruct' + QualName: 'path::to::A::Namespace::ChildStruct' Path: 'path/to/A/Namespace' ChildFunctions: - USR: '0000000000000000000000000000000000000000' @@ -83,8 +87,7 @@ I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"}); I.Loc.emplace_back(12, llvm::SmallString<16>{"test.cpp"}); - I.Members.emplace_back(TypeInfo("int", "path/to/int"), "X", - AccessSpecifier::AS_private); + I.Members.emplace_back(TypeInfo("int"), "X", AccessSpecifier::AS_private); // Member documentation. CommentInfo TopComment; @@ -103,15 +106,15 @@ AccessSpecifier::AS_public, true); I.Bases.back().Children.Functions.emplace_back(); I.Bases.back().Children.Functions.back().Name = "InheritedFunctionOne"; - I.Bases.back().Members.emplace_back(TypeInfo("int", "path/to/int"), "N", + I.Bases.back().Members.emplace_back(TypeInfo("int"), "N", AccessSpecifier::AS_private); // F is in the global namespace I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, ""); I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record, - "path/to/G"); + "path::to::G::G", "path/to/G"); I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record, - "path/to/A/r"); + "path::to::A::r::ChildStruct", "path/to/A/r"); I.Children.Functions.emplace_back(); I.Children.Functions.back().Name = "OneFunction"; I.Children.Enums.emplace_back(); @@ -131,6 +134,7 @@ Namespace: - Type: Namespace Name: 'A' + QualName: 'A' DefLocation: LineNumber: 10 Filename: 'test.cpp' @@ -142,7 +146,7 @@ Members: - Type: Name: 'int' - Path: 'path/to/int' + QualName: 'int' Name: 'X' Access: Private Description: @@ -161,7 +165,7 @@ Members: - Type: Name: 'int' - Path: 'path/to/int' + QualName: 'int' Name: 'N' Access: Private ChildFunctions: @@ -178,10 +182,12 @@ VirtualParents: - Type: Record Name: 'G' + QualName: 'path::to::G::G' Path: 'path/to/G' ChildRecords: - Type: Record Name: 'ChildStruct' + QualName: 'path::to::A::r::ChildStruct' Path: 'path/to/A/r' ChildFunctions: - USR: '0000000000000000000000000000000000000000' @@ -206,10 +212,9 @@ I.Access = AccessSpecifier::AS_none; - I.ReturnType = TypeInfo( - Reference(EmptySID, "void", InfoType::IT_default, "path/to/void")); - I.Params.emplace_back(TypeInfo("int", "path/to/int"), "P"); - I.Params.emplace_back(TypeInfo("double", "path/to/double"), "D"); + I.ReturnType = TypeInfo(Reference(EmptySID, "void", InfoType::IT_default)); + I.Params.emplace_back(TypeInfo("int"), "P"); + I.Params.emplace_back(TypeInfo("double"), "D"); I.Params.back().DefaultValue = "2.0 * M_PI"; I.IsMethod = true; I.Parent = Reference(EmptySID, "Parent", InfoType::IT_record); @@ -227,6 +232,7 @@ Namespace: - Type: Namespace Name: 'A' + QualName: 'A' DefLocation: LineNumber: 10 Filename: 'test.cpp' @@ -237,20 +243,21 @@ Parent: Type: Record Name: 'Parent' + QualName: 'Parent' Params: - Type: Name: 'int' - Path: 'path/to/int' + QualName: 'int' Name: 'P' - Type: Name: 'double' - Path: 'path/to/double' + QualName: 'double' Name: 'D' DefaultValue: '2.0 * M_PI' ReturnType: Type: Name: 'void' - Path: 'path/to/void' + QualName: 'void' ... )raw"; EXPECT_EQ(Expected, Actual.str()); @@ -284,6 +291,7 @@ Namespace: - Type: Namespace Name: 'A' + QualName: 'A' DefLocation: LineNumber: 10 Filename: 'test.cpp' @@ -322,6 +330,7 @@ BaseType: Type: Name: 'short' + QualName: 'short' Members: - Name: 'X' Value: '-9876' @@ -349,6 +358,7 @@ Name: 'MyUsing' Underlying: Name: 'int' + QualName: 'int' IsUsing: true ... )raw"; @@ -548,13 +558,16 @@ Params: - Type: Name: 'int' + QualName: 'int' Name: 'I' - Type: Name: 'int' + QualName: 'int' Name: 'J' ReturnType: Type: Name: 'void' + QualName: 'void' ... )raw";