From 0f6dbb5f164662c3e6a167a89e7a89f07c60e32b Mon Sep 17 00:00:00 2001 From: Brett Wilson Date: Mon, 7 Nov 2022 15:07:56 -0800 Subject: [PATCH] [clang-doc] Add template support. Reads template information from the AST and adds template parameters and specialization information to the corresponding clang-doc structures. Add a "QualName" to the Reference struct which includes the full qualified type name. The Reference object represents a link in the HTML/MD generators so is based on the unqualified name. But this does not encode C-V qualifiers or template information that decorate the name. The new QualName member encodes all of this information and also makes it easier for the generators or downsteam YAML consumers to generate the full name (before they had to process the "Path"). In test code that was changed, remove made-up paths to built-in types like "int". In addition to slightnly cleaning up the code, these types do not have paths in real execution, and generating incorrect references to nonexistant data may complicate future changes in the generators. Differential Revision: https://reviews.llvm.org/D139154 --- clang-tools-extra/clang-doc/BitcodeReader.cpp | 85 ++++++++++++++ clang-tools-extra/clang-doc/BitcodeWriter.cpp | 43 ++++++- clang-tools-extra/clang-doc/BitcodeWriter.h | 12 +- clang-tools-extra/clang-doc/Representation.cpp | 4 + clang-tools-extra/clang-doc/Representation.h | 79 +++++++++++-- clang-tools-extra/clang-doc/Serialize.cpp | 126 +++++++++++++++++---- clang-tools-extra/clang-doc/YAMLGenerator.cpp | 26 +++++ .../test/clang-doc/single-file-public.cpp | 11 +- clang-tools-extra/test/clang-doc/single-file.cpp | 10 +- clang-tools-extra/test/clang-doc/templates.cpp | 76 +++++++++++++ .../unittests/clang-doc/HTMLGeneratorTest.cpp | 22 ++-- .../unittests/clang-doc/SerializeTest.cpp | 126 +++++++++++++++++++-- .../unittests/clang-doc/YAMLGeneratorTest.cpp | 47 +++++--- 13 files changed, 587 insertions(+), 80 deletions(-) create mode 100644 clang-tools-extra/test/clang-doc/templates.cpp diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp index 0720114..3fb8505 100644 --- a/clang-tools-extra/clang-doc/BitcodeReader.cpp +++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp @@ -351,6 +351,8 @@ llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, 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: @@ -363,6 +365,29 @@ llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, } } +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"); @@ -595,6 +620,45 @@ template <> void addChild(BaseRecordInfo *I, FunctionInfo &&R) { 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) { @@ -719,6 +783,27 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { 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.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp index bb0698a..8a5647c 100644 --- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp +++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp @@ -121,7 +121,10 @@ static const llvm::IndexedMap {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 @@ static const llvm::IndexedMap {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 @@ static const std::vector>> 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 @@ void ClangDocBitcodeWriter::emitRecord(unsigned Val, RecordId ID) { 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 @@ void ClangDocBitcodeWriter::emitBlock(const Reference &R, FieldId Field) { 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 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) { 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 @@ void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) { 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/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h index 5a2514a..9a572e4 100644 --- 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 @@ enum BlockId { 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 @@ enum RecordId { 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 @@ public: 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 @@ private: 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/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp index 27b83d6..c127999 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -250,6 +250,8 @@ void RecordInfo::merge(RecordInfo &&Other) { 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 @@ void FunctionInfo::merge(FunctionInfo &&Other) { 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/Representation.h b/clang-tools-extra/clang-doc/Representation.h index 2a69088..32272cd 100644 --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -117,13 +117,21 @@ struct CommentInfo { }; 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 +144,17 @@ struct Reference { 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 +187,46 @@ struct TypeInfo { // 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. + llvm::Optional Specialization; +}; + // Info for field types. struct FieldTypeInfo : public TypeInfo { FieldTypeInfo() = default; @@ -317,6 +368,13 @@ struct FunctionInfo : public SymbolInfo { // 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. + llvm::Optional Template; }; // TODO: Expand to allow for documenting templating, inheritance access, @@ -332,6 +390,13 @@ struct RecordInfo : public SymbolInfo { // 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. + llvm::Optional Template; + // Indicates if the record was declared using a typedef. Things like anonymous // structs in a typedef: // typedef struct { ... } foo_t; @@ -433,7 +498,7 @@ struct Index : public Reference { 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; diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index 66a938d..50cda2c 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -250,7 +250,7 @@ TypeInfo getTypeInfoForType(const QualType &T) { 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 @@ static bool shouldSerializeInfo(bool PublicOnly, bool IsInAnonymousNamespace, // 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 @@ static void parseParameters(FunctionInfo &I, const FunctionDecl *D) { for (const ParmVarDecl *P : D->parameters()) { FieldTypeInfo &FieldInfo = I.Params.emplace_back( getTypeInfoForType(P->getOriginalType()), P->getNameAsString()); - - if (const Expr *DefaultArg = P->getDefaultArg()) { - FieldInfo.DefaultValue = getSourceCode(D, DefaultArg->getSourceRange()); - } + FieldInfo.DefaultValue = getSourceCode(D, P->getDefaultArgRange()); } } @@ -424,18 +421,19 @@ static void parseBases(RecordInfo &I, const CXXRecordDecl *D) { 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 @@ populateParentNamespaces(llvm::SmallVector &Namespaces, } 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 @@ populateParentNamespaces(llvm::SmallVector &Namespaces, InfoType::IT_namespace); } +void PopulateTemplateParameters(llvm::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 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D, 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 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, } 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 @@ emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, 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 @@ emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, 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 index 0e662c0..047a9a1 100644 --- 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(CommentInfo) 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 @@ static void RecordInfoMapping(IO &IO, RecordInfo &I) { 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 @@ template <> struct MappingTraits { 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 @@ template <> struct MappingTraits { // 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, + 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 index 91c3146..82e8174 100644 --- 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 @@ void Record::function_public() {} // 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 @@ void Record::function_public() {} // 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 index 3703f5d..211afb3 100644 --- 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); 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 index 0000000..eb7f459 --- /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 index d912253..5141259 100644 --- a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp @@ -44,9 +44,10 @@ TEST(HTMLGeneratorTest, emitNamespaceHTML) { 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 @@ TEST(HTMLGeneratorTest, emitRecordHTML) { 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 @@ TEST(HTMLGeneratorTest, emitRecordHTML) {

Members

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

Records

    @@ -277,8 +273,8 @@ TEST(HTMLGeneratorTest, emitFunctionHTML) { 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 index 05b6245..ca370de 100644 --- a/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/SerializeTest.cpp @@ -390,7 +390,7 @@ class J : public I {} ;)raw", 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 @@ class J : public I {} ;)raw", 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 @@ class J : public I {} ;)raw", 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 @@ TEST(SerializeTest, emitChildRecords) { 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 @@ TEST(SerializeTest, emitChildRecords) { 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 @@ TEST(SerializeTest, emitChildNamespaces) { 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 @@ TEST(SerializeTests, emitTypedefs) { 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 index d6410f2..535d491 100644 --- a/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp @@ -28,10 +28,11 @@ TEST(YAMLGeneratorTest, emitNamespaceYAML) { 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 @@ Path: 'path/to/A' 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 @@ TEST(YAMLGeneratorTest, emitRecordYAML) { 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 @@ TEST(YAMLGeneratorTest, emitRecordYAML) { 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 @@ Path: 'path/to/A' Namespace: - Type: Namespace Name: 'A' + QualName: 'A' DefLocation: LineNumber: 10 Filename: 'test.cpp' @@ -142,7 +146,7 @@ IsTypeDef: true Members: - Type: Name: 'int' - Path: 'path/to/int' + QualName: 'int' Name: 'X' Access: Private Description: @@ -161,7 +165,7 @@ Bases: Members: - Type: Name: 'int' - Path: 'path/to/int' + QualName: 'int' Name: 'N' Access: Private ChildFunctions: @@ -178,10 +182,12 @@ Parents: 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 @@ TEST(YAMLGeneratorTest, emitFunctionYAML) { 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 @@ Name: 'f' Namespace: - Type: Namespace Name: 'A' + QualName: 'A' DefLocation: LineNumber: 10 Filename: 'test.cpp' @@ -237,20 +243,21 @@ IsMethod: true 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 @@ Name: 'e' Namespace: - Type: Namespace Name: 'A' + QualName: 'A' DefLocation: LineNumber: 10 Filename: 'test.cpp' @@ -322,6 +330,7 @@ Scoped: true BaseType: Type: Name: 'short' + QualName: 'short' Members: - Name: 'X' Value: '-9876' @@ -349,6 +358,7 @@ USR: '0000000000000000000000000000000000000000' Name: 'MyUsing' Underlying: Name: 'int' + QualName: 'int' IsUsing: true ... )raw"; @@ -548,13 +558,16 @@ DefLocation: Params: - Type: Name: 'int' + QualName: 'int' Name: 'I' - Type: Name: 'int' + QualName: 'int' Name: 'J' ReturnType: Type: Name: 'void' + QualName: 'void' ... )raw"; -- 2.7.4