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 @@ -24,12 +24,6 @@ return llvm::Error::success(); } -llvm::Error decodeRecord(const Record &R, std::string &Field, - llvm::StringRef Blob) { - Field.assign(Blob.begin(), Blob.end()); - return llvm::Error::success(); -} - llvm::Error decodeRecord(const Record &R, SymbolID &Field, llvm::StringRef Blob) { if (R[0] != BitCodeConstants::USRHashSize) @@ -104,6 +98,7 @@ case InfoType::IT_function: case InfoType::IT_default: case InfoType::IT_enum: + case InfoType::IT_typedef: Field = IT; return llvm::Error::success(); } @@ -233,6 +228,23 @@ } } +llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, + TypedefInfo *I) { + switch (ID) { + case TYPEDEF_USR: + return decodeRecord(R, I->USR, Blob); + case TYPEDEF_NAME: + return decodeRecord(R, I->Name, Blob); + case TYPEDEF_DEFLOCATION: + return decodeRecord(R, I->DefLoc, Blob); + case TYPEDEF_IS_USING: + return decodeRecord(R, I->IsUsing, Blob); + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for TypedefInfo"); + } +} + llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, EnumValueInfo *I) { switch (ID) { @@ -424,6 +436,11 @@ return llvm::Error::success(); } +template <> llvm::Error addTypeInfo(TypedefInfo *I, TypeInfo &&T) { + I->Underlying = std::move(T); + return llvm::Error::success(); +} + template llvm::Error addReference(T I, Reference &&R, FieldId F) { return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid type cannot contain Reference"); @@ -475,6 +492,17 @@ } } +template <> llvm::Error addReference(TypedefInfo *I, Reference &&R, FieldId F) { + switch (F) { + case FieldId::F_namespace: + I->Namespace.emplace_back(std::move(R)); + return llvm::Error::success(); + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid type cannot contain Reference"); + } +} + template <> llvm::Error addReference(NamespaceInfo *I, Reference &&R, FieldId F) { switch (F) { @@ -534,30 +562,35 @@ exit(1); } +// Namespace children: template <> void addChild(NamespaceInfo *I, FunctionInfo &&R) { I->ChildFunctions.emplace_back(std::move(R)); } - template <> void addChild(NamespaceInfo *I, EnumInfo &&R) { I->ChildEnums.emplace_back(std::move(R)); } +template <> void addChild(NamespaceInfo *I, TypedefInfo &&R) { + I->ChildTypedefs.emplace_back(std::move(R)); +} +// Record children: template <> void addChild(RecordInfo *I, FunctionInfo &&R) { I->ChildFunctions.emplace_back(std::move(R)); } - template <> void addChild(RecordInfo *I, EnumInfo &&R) { I->ChildEnums.emplace_back(std::move(R)); } +template <> void addChild(RecordInfo *I, TypedefInfo &&R) { + I->ChildTypedefs.emplace_back(std::move(R)); +} +// Other types of children: template <> void addChild(EnumInfo *I, EnumValueInfo &&R) { I->Members.emplace_back(std::move(R)); } - template <> void addChild(RecordInfo *I, BaseRecordInfo &&R) { I->Bases.emplace_back(std::move(R)); } - template <> void addChild(BaseRecordInfo *I, FunctionInfo &&R) { I->ChildFunctions.emplace_back(std::move(R)); } @@ -686,6 +719,13 @@ addChild(I, std::move(EV)); return llvm::Error::success(); } + case BI_TYPEDEF_BLOCK_ID: { + TypedefInfo TI; + if (auto Err = readBlock(ID, &TI)) + return Err; + addChild(I, std::move(TI)); + return llvm::Error::success(); + } default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid subblock type"); @@ -786,6 +826,8 @@ return createInfo(ID); case BI_ENUM_BLOCK_ID: return createInfo(ID); + case BI_TYPEDEF_BLOCK_ID: + return createInfo(ID); case BI_FUNCTION_BLOCK_ID: return createInfo(ID); default: @@ -825,6 +867,7 @@ case BI_NAMESPACE_BLOCK_ID: case BI_RECORD_BLOCK_ID: case BI_ENUM_BLOCK_ID: + case BI_TYPEDEF_BLOCK_ID: case BI_FUNCTION_BLOCK_ID: { auto InfoOrErr = readBlockToInfo(ID); if (!InfoOrErr) 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 @@ -64,6 +64,7 @@ BI_FUNCTION_BLOCK_ID, BI_COMMENT_BLOCK_ID, BI_REFERENCE_BLOCK_ID, + BI_TYPEDEF_BLOCK_ID, BI_LAST, BI_FIRST = BI_VERSION_BLOCK_ID }; @@ -123,6 +124,10 @@ REFERENCE_TYPE, REFERENCE_PATH, REFERENCE_FIELD, + TYPEDEF_USR, + TYPEDEF_NAME, + TYPEDEF_DEFLOCATION, + TYPEDEF_IS_USING, RI_LAST, RI_FIRST = VERSION }; @@ -160,8 +165,9 @@ void emitBlock(const EnumInfo &I); void emitBlock(const EnumValueInfo &I); void emitBlock(const TypeInfo &B); + void emitBlock(const TypedefInfo &B); void emitBlock(const FieldTypeInfo &B); - void emitBlock(const MemberTypeInfo &B); + void emitBlock(const MemberTypeInfo &T); void emitBlock(const CommentInfo &B); void emitBlock(const Reference &B, FieldId F); 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 @@ -113,6 +113,7 @@ {BI_NAMESPACE_BLOCK_ID, "NamespaceBlock"}, {BI_ENUM_BLOCK_ID, "EnumBlock"}, {BI_ENUM_VALUE_BLOCK_ID, "EnumValueBlock"}, + {BI_TYPEDEF_BLOCK_ID, "TypedefBlock"}, {BI_TYPE_BLOCK_ID, "TypeBlock"}, {BI_FIELD_TYPE_BLOCK_ID, "FieldTypeBlock"}, {BI_MEMBER_TYPE_BLOCK_ID, "MemberTypeBlock"}, @@ -187,7 +188,11 @@ {REFERENCE_NAME, {"Name", &StringAbbrev}}, {REFERENCE_TYPE, {"RefType", &IntAbbrev}}, {REFERENCE_PATH, {"Path", &StringAbbrev}}, - {REFERENCE_FIELD, {"Field", &IntAbbrev}}}; + {REFERENCE_FIELD, {"Field", &IntAbbrev}}, + {TYPEDEF_USR, {"USR", &SymbolIDAbbrev}}, + {TYPEDEF_NAME, {"Name", &StringAbbrev}}, + {TYPEDEF_DEFLOCATION, {"DefLocation", &LocationAbbrev}}, + {TYPEDEF_IS_USING, {"IsUsing", &BoolAbbrev}}}; assert(Inits.size() == RecordIdCount); for (const auto &Init : Inits) { RecordIdNameMap[Init.first] = Init.second; @@ -218,6 +223,9 @@ // Enum Value Block {BI_ENUM_VALUE_BLOCK_ID, {ENUM_VALUE_NAME, ENUM_VALUE_VALUE, ENUM_VALUE_EXPR}}, + // Typedef Block + {BI_TYPEDEF_BLOCK_ID, + {TYPEDEF_USR, TYPEDEF_NAME, TYPEDEF_DEFLOCATION, TYPEDEF_IS_USING}}, // Namespace Block {BI_NAMESPACE_BLOCK_ID, {NAMESPACE_USR, NAMESPACE_NAME, NAMESPACE_PATH}}, @@ -418,6 +426,18 @@ emitBlock(T.Type, FieldId::F_type); } +void ClangDocBitcodeWriter::emitBlock(const TypedefInfo &T) { + StreamSubBlockGuard Block(Stream, BI_TYPEDEF_BLOCK_ID); + emitRecord(T.USR, TYPEDEF_USR); + emitRecord(T.Name, TYPEDEF_NAME); + for (const auto &N : T.Namespace) + emitBlock(N, FieldId::F_namespace); + if (T.DefLoc) + emitRecord(*T.DefLoc, TYPEDEF_DEFLOCATION); + emitRecord(T.IsUsing, TYPEDEF_IS_USING); + emitBlock(T.Underlying); +} + void ClangDocBitcodeWriter::emitBlock(const FieldTypeInfo &T) { StreamSubBlockGuard Block(Stream, BI_FIELD_TYPE_BLOCK_ID); emitBlock(T.Type, FieldId::F_type); @@ -473,6 +493,8 @@ emitBlock(C); for (const auto &C : I.ChildEnums) emitBlock(C); + for (const auto &C : I.ChildTypedefs) + emitBlock(C); } void ClangDocBitcodeWriter::emitBlock(const EnumInfo &I) { @@ -530,6 +552,8 @@ emitBlock(C); for (const auto &C : I.ChildEnums) emitBlock(C); + for (const auto &C : I.ChildTypedefs) + emitBlock(C); } void ClangDocBitcodeWriter::emitBlock(const BaseRecordInfo &I) { @@ -581,6 +605,9 @@ case InfoType::IT_function: emitBlock(*static_cast(I)); break; + case InfoType::IT_typedef: + emitBlock(*static_cast(I)); + break; default: llvm::errs() << "Unexpected info, unable to write.\n"; return true; diff --git a/clang-tools-extra/clang-doc/Mapper.h b/clang-tools-extra/clang-doc/Mapper.h --- a/clang-tools-extra/clang-doc/Mapper.h +++ b/clang-tools-extra/clang-doc/Mapper.h @@ -39,6 +39,8 @@ bool VisitEnumDecl(const EnumDecl *D); bool VisitCXXMethodDecl(const CXXMethodDecl *D); bool VisitFunctionDecl(const FunctionDecl *D); + bool VisitTypedefDecl(const TypedefDecl *D); + bool VisitTypeAliasDecl(const TypeAliasDecl *D); private: template bool mapDecl(const T *D); diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp --- a/clang-tools-extra/clang-doc/Mapper.cpp +++ b/clang-tools-extra/clang-doc/Mapper.cpp @@ -71,6 +71,14 @@ return mapDecl(D); } +bool MapASTVisitor::VisitTypedefDecl(const TypedefDecl *D) { + return mapDecl(D); +} + +bool MapASTVisitor::VisitTypeAliasDecl(const TypeAliasDecl *D) { + return mapDecl(D); +} + comments::FullComment * MapASTVisitor::getComment(const NamedDecl *D, const ASTContext &Context) const { RawComment *Comment = Context.getRawCommentForDeclNoCache(D); 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 @@ -30,17 +30,19 @@ // SHA1'd hash of a USR. using SymbolID = std::array; -struct Info; -struct FunctionInfo; -struct EnumInfo; struct BaseRecordInfo; +struct EnumInfo; +struct FunctionInfo; +struct Info; +struct TypedefInfo; enum class InfoType { IT_default, IT_namespace, IT_record, IT_function, - IT_enum + IT_enum, + IT_typedef }; // A representation of a parsed comment. @@ -142,6 +144,22 @@ llvm::SmallString<128> Path; }; +// Holds the children of a record or namespace. +struct ScopeHasChildren { + // Namespaces and Records are references because they will be properly + // documented in their own info, while the entirety of Functions and Enums are + // included here because they should not have separate documentation from + // their scope. + // + // Namespaces are not syntactically valid as children of records, but making + // this general for all possible container types reduces code complexity. + std::vector ChildNamespaces; + std::vector ChildRecords; + std::vector ChildFunctions; + std::vector ChildEnums; + std::vector ChildTypedefs; +}; + // A base struct for TypeInfos struct TypeInfo { TypeInfo() = default; @@ -259,21 +277,12 @@ }; // Info for namespaces. -struct NamespaceInfo : public Info { +struct NamespaceInfo : public Info, public ScopeHasChildren { NamespaceInfo(SymbolID USR = SymbolID(), StringRef Name = StringRef(), StringRef Path = StringRef()) : Info(InfoType::IT_namespace, USR, Name, Path) {} void merge(NamespaceInfo &&I); - - // Namespaces and Records are references because they will be properly - // documented in their own info, while the entirety of Functions and Enums are - // included here because they should not have separate documentation from - // their scope. - std::vector ChildNamespaces; - std::vector ChildRecords; - std::vector ChildFunctions; - std::vector ChildEnums; }; // Info for symbols. @@ -310,7 +319,7 @@ // TODO: Expand to allow for documenting templating, inheritance access, // friend classes // Info for types. -struct RecordInfo : public SymbolInfo { +struct RecordInfo : public SymbolInfo, public ScopeHasChildren { RecordInfo(SymbolID USR = SymbolID(), StringRef Name = StringRef(), StringRef Path = StringRef()) : SymbolInfo(InfoType::IT_record, USR, Name, Path) {} @@ -337,13 +346,22 @@ std::vector Bases; // List of base/parent records; this includes inherited methods and // attributes +}; - // Records are references because they will be properly documented in their - // own info, while the entirety of Functions and Enums are included here - // because they should not have separate documentation from their scope. - std::vector ChildRecords; - std::vector ChildFunctions; - std::vector ChildEnums; +// Info for typedef and using statements. +struct TypedefInfo : public SymbolInfo { + TypedefInfo(SymbolID USR = SymbolID()) + : SymbolInfo(InfoType::IT_typedef, USR) {} + + void merge(TypedefInfo &&I); + + TypeInfo Underlying; + + // Inidicates if this is a new C++ "using"-style typedef: + // using MyVector = std::vector + // False means it's a C-style typedef: + // typedef std::vector MyVector; + bool IsUsing; }; struct BaseRecordInfo : public RecordInfo { 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 @@ -90,6 +90,18 @@ } } +void reduceChildren(std::vector &Children, + std::vector &&ChildrenToMerge) { + for (auto &ChildToMerge : ChildrenToMerge) { + int mergeIdx = getChildIndexIfExists(Children, ChildToMerge); + if (mergeIdx == -1) { + Children.push_back(std::move(ChildToMerge)); + continue; + } + Children[mergeIdx].merge(std::move(ChildToMerge)); + } +} + } // namespace // Dispatch function. @@ -108,6 +120,8 @@ return reduce(Values); case InfoType::IT_function: return reduce(Values); + case InfoType::IT_typedef: + return reduce(Values); default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected info type"); @@ -213,6 +227,7 @@ reduceChildren(ChildRecords, std::move(Other.ChildRecords)); reduceChildren(ChildFunctions, std::move(Other.ChildFunctions)); reduceChildren(ChildEnums, std::move(Other.ChildEnums)); + reduceChildren(ChildTypedefs, std::move(Other.ChildTypedefs)); mergeBase(std::move(Other)); } @@ -233,6 +248,7 @@ reduceChildren(ChildRecords, std::move(Other.ChildRecords)); reduceChildren(ChildFunctions, std::move(Other.ChildFunctions)); reduceChildren(ChildEnums, std::move(Other.ChildEnums)); + reduceChildren(ChildTypedefs, std::move(Other.ChildTypedefs)); SymbolInfo::merge(std::move(Other)); } @@ -260,6 +276,15 @@ SymbolInfo::merge(std::move(Other)); } +void TypedefInfo::merge(TypedefInfo &&Other) { + assert(mergeable(Other)); + if (!IsUsing) + IsUsing = Other.IsUsing; + if (Underlying.Type.Name == "") + Underlying = Other.Underlying; + SymbolInfo::merge(std::move(Other)); +} + llvm::SmallString<16> Info::extractName() const { if (!Name.empty()) return Name; @@ -282,6 +307,9 @@ case InfoType::IT_enum: return llvm::SmallString<16>("@nonymous_enum_" + toHex(llvm::toStringRef(USR))); + case InfoType::IT_typedef: + return llvm::SmallString<16>("@nonymous_typedef_" + + toHex(llvm::toStringRef(USR))); case InfoType::IT_function: return llvm::SmallString<16>("@nonymous_function_" + toHex(llvm::toStringRef(USR))); diff --git a/clang-tools-extra/clang-doc/Serialize.h b/clang-tools-extra/clang-doc/Serialize.h --- a/clang-tools-extra/clang-doc/Serialize.h +++ b/clang-tools-extra/clang-doc/Serialize.h @@ -39,19 +39,31 @@ std::pair, std::unique_ptr> emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, StringRef File, bool IsFileInRootDir, bool PublicOnly); + std::pair, std::unique_ptr> emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber, StringRef File, bool IsFileInRootDir, bool PublicOnly); + std::pair, std::unique_ptr> emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber, StringRef File, bool IsFileInRootDir, bool PublicOnly); + std::pair, std::unique_ptr> emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber, StringRef File, bool IsFileInRootDir, bool PublicOnly); + std::pair, std::unique_ptr> emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber, StringRef File, bool IsFileInRootDir, bool PublicOnly); +std::pair, std::unique_ptr> +emitInfo(const TypedefDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool IsFileInRootDir, bool PublicOnly); + +std::pair, std::unique_ptr> +emitInfo(const TypeAliasDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool IsFileInRootDir, bool PublicOnly); + // Function to hash a given USR value for storage. // As USRs (Unified Symbol Resolution) could be large, especially for functions // with long type arguments, we use 160-bits SHA1(USR) values to 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 @@ -273,6 +273,76 @@ isPublic(D->getAccessUnsafe(), D->getLinkageInternal())); } +// The InsertChild functions insert the given info into the given scope using +// the method appropriate for that type. Some types are moved into the +// appropriate vector, while other types have Reference objects generated to +// refer to them. +// +// See MakeAndInsertIntoParent(). +static void InsertChild(ScopeHasChildren &Scope, const NamespaceInfo &Info) { + Scope.ChildNamespaces.emplace_back(Info.USR, Info.Name, + InfoType::IT_namespace, + getInfoRelativePath(Info.Namespace)); +} + +static void InsertChild(ScopeHasChildren &Scope, const RecordInfo &Info) { + Scope.ChildRecords.emplace_back(Info.USR, Info.Name, InfoType::IT_record, + getInfoRelativePath(Info.Namespace)); +} + +static void InsertChild(ScopeHasChildren &Scope, EnumInfo Info) { + Scope.ChildEnums.push_back(std::move(Info)); +} + +static void InsertChild(ScopeHasChildren &Scope, FunctionInfo Info) { + Scope.ChildFunctions.push_back(std::move(Info)); +} + +static void InsertChild(ScopeHasChildren &Scope, TypedefInfo Info) { + Scope.ChildTypedefs.push_back(std::move(Info)); +} + +// Creates a parent of the correct type for the given child and inserts it into +// that parent. +// +// This is complicated by the fact that namespaces and records are inserted by +// reference (constructing a "Reference" object with that namespace/record's +// info), while everything else is inserted by moving it directly into the child +// vectors. +// +// For namespaces and records, explicitly specify a const& template parameter +// when invoking this function: +// MakeAndInsertIntoParent(...); +// Otherwise, specify an rvalue reference and move into the +// parameter. Since each variant is used once, it's not worth having a more +// elaborate system to automatically deduce this information. +template +std::unique_ptr MakeAndInsertIntoParent(ChildType Child) { + if (Child.Namespace.empty()) { + // Insert into unnamed parent namespace. + auto ParentNS = std::make_unique(); + InsertChild(*ParentNS, std::forward(Child)); + return ParentNS; + } + + switch (Child.Namespace[0].RefType) { + case InfoType::IT_namespace: { + auto ParentNS = std::make_unique(); + ParentNS->USR = Child.Namespace[0].USR; + InsertChild(*ParentNS, std::forward(Child)); + return ParentNS; + } + case InfoType::IT_record: { + auto ParentRec = std::make_unique(); + ParentRec->USR = Child.Namespace[0].USR; + InsertChild(*ParentRec, std::forward(Child)); + return ParentRec; + } + default: + llvm_unreachable("Invalid reference type for parent namespace"); + } +} + // There are two uses for this function. // 1) Getting the resulting mode of inheritance of a record. // Example: class A {}; class B : private A {}; class C : public B {}; @@ -376,8 +446,8 @@ static void populateParentNamespaces(llvm::SmallVector &Namespaces, const T *D, bool &IsInAnonymousNamespace) { - const auto *DC = cast(D); - while ((DC = DC->getParent())) { + const DeclContext *DC = D->getDeclContext(); + do { if (const auto *N = dyn_cast(DC)) { std::string Namespace; if (N->isAnonymousNamespace()) { @@ -396,7 +466,7 @@ else if (const auto *N = dyn_cast(DC)) Namespaces.emplace_back(getUSRForDecl(N), N->getNameAsString(), InfoType::IT_enum); - } + } while ((DC = DC->getParent())); // The global namespace should be added to the list of namespaces if the decl // corresponds to a Record and if it doesn't have any namespace (because this // means it's in the global namespace). Also if its outermost namespace is a @@ -530,14 +600,9 @@ if (I->Namespace.empty() && I->USR == SymbolID()) return {std::unique_ptr{std::move(I)}, nullptr}; - auto ParentI = std::make_unique(); - ParentI->USR = I->Namespace.empty() ? SymbolID() : I->Namespace[0].USR; - ParentI->ChildNamespaces.emplace_back(I->USR, I->Name, InfoType::IT_namespace, - getInfoRelativePath(I->Namespace)); - if (I->Namespace.empty()) - ParentI->Path = getInfoRelativePath(ParentI->Namespace); - return {std::unique_ptr{std::move(I)}, - std::unique_ptr{std::move(ParentI)}}; + // Namespaces are inserted into the parent by reference, so we need to return + // both the parent and the record itself. + return {std::move(I), MakeAndInsertIntoParent(*I)}; } std::pair, std::unique_ptr> @@ -563,26 +628,9 @@ } I->Path = getInfoRelativePath(I->Namespace); - switch (I->Namespace[0].RefType) { - case InfoType::IT_namespace: { - auto ParentI = std::make_unique(); - ParentI->USR = I->Namespace[0].USR; - ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record, - getInfoRelativePath(I->Namespace)); - return {std::unique_ptr{std::move(I)}, - std::unique_ptr{std::move(ParentI)}}; - } - case InfoType::IT_record: { - auto ParentI = std::make_unique(); - ParentI->USR = I->Namespace[0].USR; - ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record, - getInfoRelativePath(I->Namespace)); - return {std::unique_ptr{std::move(I)}, - std::unique_ptr{std::move(ParentI)}}; - } - default: - llvm_unreachable("Invalid reference type for parent namespace"); - } + // Records are inserted into the parent by reference, so we need to return + // both the parent and the record itself. + return {std::move(I), MakeAndInsertIntoParent(*I)}; } std::pair, std::unique_ptr> @@ -596,17 +644,8 @@ if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; - // Wrap in enclosing scope - auto ParentI = std::make_unique(); - if (!Func.Namespace.empty()) - ParentI->USR = Func.Namespace[0].USR; - else - ParentI->USR = SymbolID(); - if (Func.Namespace.empty()) - ParentI->Path = getInfoRelativePath(ParentI->Namespace); - ParentI->ChildFunctions.emplace_back(std::move(Func)); - // Info is wrapped in its parent scope so it's returned in the second position - return {nullptr, std::unique_ptr{std::move(ParentI)}}; + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; } std::pair, std::unique_ptr> @@ -633,12 +672,52 @@ Reference{ParentUSR, Parent->getNameAsString(), InfoType::IT_record}; Func.Access = D->getAccess(); - // Wrap in enclosing scope - auto ParentI = std::make_unique(); - ParentI->USR = ParentUSR; - ParentI->ChildFunctions.emplace_back(std::move(Func)); - // Info is wrapped in its parent scope so it's returned in the second position - return {nullptr, std::unique_ptr{std::move(ParentI)}}; + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Func))}; +} + +std::pair, std::unique_ptr> +emitInfo(const TypedefDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool IsFileInRootDir, bool PublicOnly) { + TypedefInfo Info; + + bool IsInAnonymousNamespace = false; + populateInfo(Info, D, FC, IsInAnonymousNamespace); + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + Info.DefLoc.emplace(LineNumber, File, IsFileInRootDir); + Info.Underlying = getTypeInfoForType(D->getUnderlyingType()); + if (Info.Underlying.Type.Name.empty()) { + // Typedef for an unnamed type. This is like "typedef struct { } Foo;" + // The record serializer explicitly checks for this syntax and constructs + // a record with that name, so we don't want to emit a duplicate here. + return {}; + } + Info.IsUsing = false; + + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; +} + +// A type alias is a C++ "using" declaration for a type. It gets mapped to a +// TypedefInfo with the IsUsing flag set. +std::pair, std::unique_ptr> +emitInfo(const TypeAliasDecl *D, const FullComment *FC, int LineNumber, + StringRef File, bool IsFileInRootDir, bool PublicOnly) { + TypedefInfo Info; + + bool IsInAnonymousNamespace = false; + populateInfo(Info, D, FC, IsInAnonymousNamespace); + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) + return {}; + + Info.DefLoc.emplace(LineNumber, File, IsFileInRootDir); + Info.Underlying = getTypeInfoForType(D->getUnderlyingType()); + Info.IsUsing = true; + + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Info))}; } std::pair, std::unique_ptr> @@ -656,38 +735,8 @@ Enum.BaseType = TypeInfo(D->getIntegerType().getAsString()); parseEnumerators(Enum, D); - // Put in global namespace - if (Enum.Namespace.empty()) { - auto ParentI = std::make_unique(); - ParentI->USR = SymbolID(); - ParentI->ChildEnums.emplace_back(std::move(Enum)); - ParentI->Path = getInfoRelativePath(ParentI->Namespace); - // Info is wrapped in its parent scope so it's returned in the second - // position - return {nullptr, std::unique_ptr{std::move(ParentI)}}; - } - - // Wrap in enclosing scope - switch (Enum.Namespace[0].RefType) { - case InfoType::IT_namespace: { - auto ParentI = std::make_unique(); - ParentI->USR = Enum.Namespace[0].USR; - ParentI->ChildEnums.emplace_back(std::move(Enum)); - // Info is wrapped in its parent scope so it's returned in the second - // position - return {nullptr, std::unique_ptr{std::move(ParentI)}}; - } - case InfoType::IT_record: { - auto ParentI = std::make_unique(); - ParentI->USR = Enum.Namespace[0].USR; - ParentI->ChildEnums.emplace_back(std::move(Enum)); - // Info is wrapped in its parent scope so it's returned in the second - // position - return {nullptr, std::unique_ptr{std::move(ParentI)}}; - } - default: - llvm_unreachable("Invalid reference type for parent namespace"); - } + // Info is wrapped in its parent scope so is returned in the second position. + return {nullptr, MakeAndInsertIntoParent(std::move(Enum))}; } } // namespace serialize 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 @@ -23,6 +23,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(EnumValueInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(TypedefInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(BaseRecordInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>) @@ -140,6 +141,7 @@ IO.mapOptional("ChildRecords", I.ChildRecords, std::vector()); IO.mapOptional("ChildFunctions", I.ChildFunctions); IO.mapOptional("ChildEnums", I.ChildEnums); + IO.mapOptional("ChildTypedefs", I.ChildTypedefs); } static void CommentInfoMapping(IO &IO, CommentInfo &I) { @@ -208,6 +210,7 @@ IO.mapOptional("ChildRecords", I.ChildRecords, std::vector()); IO.mapOptional("ChildFunctions", I.ChildFunctions); IO.mapOptional("ChildEnums", I.ChildEnums); + IO.mapOptional("ChildTypedefs", I.ChildTypedefs); } }; @@ -244,6 +247,14 @@ } }; +template <> struct MappingTraits { + static void mapping(IO &IO, TypedefInfo &I) { + SymbolInfoMapping(IO, I); + IO.mapOptional("Underlying", I.Underlying.Type); + IO.mapOptional("IsUsing", I.IsUsing, false); + } +}; + template <> struct MappingTraits { static void mapping(IO &IO, FunctionInfo &I) { SymbolInfoMapping(IO, I); @@ -302,6 +313,9 @@ case InfoType::IT_function: InfoYAML << *static_cast(I); break; + case InfoType::IT_typedef: + InfoYAML << *static_cast(I); + break; case InfoType::IT_default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "unexpected InfoType"); diff --git a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp --- a/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp @@ -35,6 +35,8 @@ return writeInfo(*static_cast(I)); case InfoType::IT_function: return writeInfo(*static_cast(I)); + case InfoType::IT_typedef: + return writeInfo(*static_cast(I)); default: return ""; } @@ -172,6 +174,22 @@ CheckEnumInfo(&I, InfoAsEnum(ReadResults[0].get())); } +TEST(BitcodeTest, emitTypedefInfoBitcode) { + TypedefInfo I; + I.Name = "MyInt"; + I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); + + I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"}); + I.Underlying = TypeInfo("unsigned"); + I.IsUsing = true; + + std::string WriteResult = writeInfo(&I); + EXPECT_TRUE(WriteResult.size() > 0); + std::vector> ReadResults = readInfo(WriteResult, 1); + + CheckTypedefInfo(&I, InfoAsTypedef(ReadResults[0].get())); +} + TEST(SerializeTest, emitInfoWithCommentBitcode) { FunctionInfo F; F.Name = "F"; diff --git a/clang-tools-extra/unittests/clang-doc/ClangDocTest.h b/clang-tools-extra/unittests/clang-doc/ClangDocTest.h --- a/clang-tools-extra/unittests/clang-doc/ClangDocTest.h +++ b/clang-tools-extra/unittests/clang-doc/ClangDocTest.h @@ -27,6 +27,7 @@ RecordInfo *InfoAsRecord(Info *I); FunctionInfo *InfoAsFunction(Info *I); EnumInfo *InfoAsEnum(Info *I); +TypedefInfo *InfoAsTypedef(Info *I); // Unlike the operator==, these functions explicitly does not check USRs, as // that may change and it would be better to not rely on its implementation. @@ -41,6 +42,7 @@ void CheckSymbolInfo(SymbolInfo *Expected, SymbolInfo *Actual); void CheckFunctionInfo(FunctionInfo *Expected, FunctionInfo *Actual); void CheckEnumInfo(EnumInfo *Expected, EnumInfo *Actual); +void CheckTypedefInfo(TypedefInfo *Expected, TypedefInfo *Actual); void CheckNamespaceInfo(NamespaceInfo *Expected, NamespaceInfo *Actual); void CheckRecordInfo(RecordInfo *Expected, RecordInfo *Actual); void CheckBaseRecordInfo(BaseRecordInfo *Expected, BaseRecordInfo *Actual); diff --git a/clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp b/clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp --- a/clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp @@ -34,6 +34,11 @@ return static_cast(I); } +TypedefInfo *InfoAsTypedef(Info *I) { + assert(I->IT == InfoType::IT_typedef); + return static_cast(I); +} + void CheckCommentInfo(const std::vector &Expected, const std::vector &Actual); void CheckCommentInfo(const std::vector> &Expected, @@ -144,6 +149,12 @@ EXPECT_EQ(Expected->Members[Idx], Actual->Members[Idx]); } +void CheckTypedefInfo(TypedefInfo *Expected, TypedefInfo *Actual) { + CheckSymbolInfo(Expected, Actual); + EXPECT_EQ(Expected->IsUsing, Actual->IsUsing); + CheckTypeInfo(&Expected->Underlying, &Actual->Underlying); +} + void CheckNamespaceInfo(NamespaceInfo *Expected, NamespaceInfo *Actual) { CheckBaseInfo(Expected, Actual); 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 @@ -59,6 +59,10 @@ bool VisitRecordDecl(const RecordDecl *D) { return mapDecl(D); } bool VisitEnumDecl(const EnumDecl *D) { return mapDecl(D); } + + bool VisitTypedefDecl(const TypedefDecl *D) { return mapDecl(D); } + + bool VisitTypeAliasDecl(const TypeAliasDecl *D) { return mapDecl(D); } }; void ExtractInfosFromCode(StringRef Code, size_t NumExpectedInfos, bool Public, @@ -591,5 +595,32 @@ CheckNamespaceInfo(&ExpectedParentB, ParentB); } +TEST(SerializeTests, emitTypedefs) { + EmittedInfoList Infos; + ExtractInfosFromCode("typedef int MyInt; using MyDouble = double;", 2, + /*Public=*/false, Infos); + + // First info will be the global namespace with the typedef in it. + NamespaceInfo *GlobalNS1 = InfoAsNamespace(Infos[0].get()); + ASSERT_EQ(1u, GlobalNS1->ChildTypedefs.size()); + + const TypedefInfo &FirstTD = GlobalNS1->ChildTypedefs[0]; + EXPECT_EQ("MyInt", FirstTD.Name); + EXPECT_FALSE(FirstTD.IsUsing); + EXPECT_EQ("int", FirstTD.Underlying.Type.Name); + + // The second will be another global namespace with the using 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->ChildTypedefs.size()); + + // Second is the "using" typedef. + const TypedefInfo &SecondTD = GlobalNS2->ChildTypedefs[0]; + EXPECT_EQ("MyDouble", SecondTD.Name); + EXPECT_TRUE(SecondTD.IsUsing); + EXPECT_EQ("double", SecondTD.Underlying.Type.Name); +} + } // 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 @@ -330,6 +330,30 @@ EXPECT_EQ(Expected, Actual.str()); } +TEST(YAMLGeneratorTest, enumTypedefYAML) { + TypedefInfo I; + I.Name = "MyUsing"; + I.Underlying = TypeInfo("int"); + I.IsUsing = true; + + auto G = getYAMLGenerator(); + assert(G); + std::string Buffer; + llvm::raw_string_ostream Actual(Buffer); + auto Err = G->generateDocForInfo(&I, Actual, ClangDocContext()); + assert(!Err); + std::string Expected = + R"raw(--- +USR: '0000000000000000000000000000000000000000' +Name: 'MyUsing' +Underlying: + Name: 'int' +IsUsing: true +... +)raw"; + EXPECT_EQ(Expected, Actual.str()); +} + TEST(YAMLGeneratorTest, emitCommentYAML) { FunctionInfo I; I.Name = "f";