Index: clang-tools-extra/clang-doc/BitcodeReader.cpp =================================================================== --- clang-tools-extra/clang-doc/BitcodeReader.cpp +++ clang-tools-extra/clang-doc/BitcodeReader.cpp @@ -179,6 +179,29 @@ } } +llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob, + BaseRecordInfo *I) { + switch (ID) { + case BASE_RECORD_USR: + return decodeRecord(R, I->USR, Blob); + case BASE_RECORD_NAME: + return decodeRecord(R, I->Name, Blob); + case BASE_RECORD_PATH: + return decodeRecord(R, I->Path, Blob); + case BASE_RECORD_TAG_TYPE: + return decodeRecord(R, I->TagType, Blob); + case BASE_RECORD_IS_VIRTUAL: + return decodeRecord(R, I->IsVirtual, Blob); + case BASE_RECORD_ACCESS: + return decodeRecord(R, I->Access, Blob); + case BASE_RECORD_IS_PARENT: + return decodeRecord(R, I->IsParent, Blob); + default: + return llvm::make_error( + "Invalid field for BaseRecordInfo.\n", llvm::inconvertibleErrorCode()); + } +} + llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob, EnumInfo *I) { switch (ID) { @@ -350,6 +373,11 @@ return llvm::Error::success(); } +template <> llvm::Error addTypeInfo(BaseRecordInfo *I, MemberTypeInfo &&T) { + I->Members.emplace_back(std::move(T)); + return llvm::Error::success(); +} + template <> llvm::Error addTypeInfo(FunctionInfo *I, TypeInfo &&T) { I->ReturnType = std::move(T); return llvm::Error::success(); @@ -494,6 +522,14 @@ I->ChildEnums.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)); +} + // Read records from bitcode into a given info. template llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) { @@ -598,6 +634,13 @@ addChild(I, std::move(F)); return llvm::Error::success(); } + case BI_BASE_RECORD_BLOCK_ID: { + BaseRecordInfo BR; + if (auto Err = readBlock(ID, &BR)) + return Err; + addChild(I, std::move(BR)); + return llvm::Error::success(); + } case BI_ENUM_BLOCK_ID: { EnumInfo E; if (auto Err = readBlock(ID, &E)) Index: clang-tools-extra/clang-doc/BitcodeWriter.h =================================================================== --- clang-tools-extra/clang-doc/BitcodeWriter.h +++ clang-tools-extra/clang-doc/BitcodeWriter.h @@ -30,7 +30,7 @@ // Current version number of clang-doc bitcode. // Should be bumped when removing or changing BlockIds, RecordIds, or // BitCodeConstants, though they can be added without breaking it. -static const unsigned VersionNumber = 2; +static const unsigned VersionNumber = 3; struct BitCodeConstants { static constexpr unsigned RecordSize = 32U; @@ -58,6 +58,7 @@ BI_FIELD_TYPE_BLOCK_ID, BI_MEMBER_TYPE_BLOCK_ID, BI_RECORD_BLOCK_ID, + BI_BASE_RECORD_BLOCK_ID, BI_FUNCTION_BLOCK_ID, BI_COMMENT_BLOCK_ID, BI_REFERENCE_BLOCK_ID, @@ -105,6 +106,13 @@ RECORD_LOCATION, RECORD_TAG_TYPE, RECORD_IS_TYPE_DEF, + BASE_RECORD_USR, + BASE_RECORD_NAME, + BASE_RECORD_PATH, + BASE_RECORD_TAG_TYPE, + BASE_RECORD_IS_VIRTUAL, + BASE_RECORD_ACCESS, + BASE_RECORD_IS_PARENT, REFERENCE_USR, REFERENCE_NAME, REFERENCE_TYPE, @@ -143,6 +151,7 @@ // Block emission of different info types. void emitBlock(const NamespaceInfo &I); void emitBlock(const RecordInfo &I); + void emitBlock(const BaseRecordInfo &I); void emitBlock(const FunctionInfo &I); void emitBlock(const EnumInfo &I); void emitBlock(const TypeInfo &B); Index: clang-tools-extra/clang-doc/BitcodeWriter.cpp =================================================================== --- clang-tools-extra/clang-doc/BitcodeWriter.cpp +++ clang-tools-extra/clang-doc/BitcodeWriter.cpp @@ -116,6 +116,7 @@ {BI_FIELD_TYPE_BLOCK_ID, "FieldTypeBlock"}, {BI_MEMBER_TYPE_BLOCK_ID, "MemberTypeBlock"}, {BI_RECORD_BLOCK_ID, "RecordBlock"}, + {BI_BASE_RECORD_BLOCK_ID, "BaseRecordBlock"}, {BI_FUNCTION_BLOCK_ID, "FunctionBlock"}, {BI_COMMENT_BLOCK_ID, "CommentBlock"}, {BI_REFERENCE_BLOCK_ID, "ReferenceBlock"}}; @@ -165,6 +166,13 @@ {RECORD_LOCATION, {"Location", &LocationAbbrev}}, {RECORD_TAG_TYPE, {"TagType", &IntAbbrev}}, {RECORD_IS_TYPE_DEF, {"IsTypeDef", &BoolAbbrev}}, + {BASE_RECORD_USR, {"USR", &SymbolIDAbbrev}}, + {BASE_RECORD_NAME, {"Name", &StringAbbrev}}, + {BASE_RECORD_PATH, {"Path", &StringAbbrev}}, + {BASE_RECORD_TAG_TYPE, {"TagType", &IntAbbrev}}, + {BASE_RECORD_IS_VIRTUAL, {"IsVirtual", &BoolAbbrev}}, + {BASE_RECORD_ACCESS, {"Access", &IntAbbrev}}, + {BASE_RECORD_IS_PARENT, {"IsParent", &BoolAbbrev}}, {FUNCTION_USR, {"USR", &SymbolIDAbbrev}}, {FUNCTION_NAME, {"Name", &StringAbbrev}}, {FUNCTION_DEFLOCATION, {"DefLocation", &LocationAbbrev}}, @@ -213,6 +221,11 @@ {BI_RECORD_BLOCK_ID, {RECORD_USR, RECORD_NAME, RECORD_PATH, RECORD_DEFLOCATION, RECORD_LOCATION, RECORD_TAG_TYPE, RECORD_IS_TYPE_DEF}}, + // BaseRecord Block + {BI_BASE_RECORD_BLOCK_ID, + {BASE_RECORD_USR, BASE_RECORD_NAME, BASE_RECORD_PATH, + BASE_RECORD_TAG_TYPE, BASE_RECORD_IS_VIRTUAL, BASE_RECORD_ACCESS, + BASE_RECORD_IS_PARENT}}, // Function Block {BI_FUNCTION_BLOCK_ID, {FUNCTION_USR, FUNCTION_NAME, FUNCTION_DEFLOCATION, FUNCTION_LOCATION, @@ -494,6 +507,8 @@ emitBlock(P, FieldId::F_parent); for (const auto &P : I.VirtualParents) emitBlock(P, FieldId::F_vparent); + for (const auto &PB : I.Bases) + emitBlock(PB); for (const auto &C : I.ChildRecords) emitBlock(C, FieldId::F_child_record); for (const auto &C : I.ChildFunctions) @@ -502,6 +517,21 @@ emitBlock(C); } +void ClangDocBitcodeWriter::emitBlock(const BaseRecordInfo &I) { + StreamSubBlockGuard Block(Stream, BI_BASE_RECORD_BLOCK_ID); + emitRecord(I.USR, BASE_RECORD_USR); + emitRecord(I.Name, BASE_RECORD_NAME); + emitRecord(I.Path, BASE_RECORD_PATH); + emitRecord(I.TagType, BASE_RECORD_TAG_TYPE); + emitRecord(I.IsVirtual, BASE_RECORD_IS_VIRTUAL); + emitRecord(I.Access, BASE_RECORD_ACCESS); + emitRecord(I.IsParent, BASE_RECORD_IS_PARENT); + for (const auto &M : I.Members) + emitBlock(M); + for (const auto &C : I.ChildFunctions) + emitBlock(C); +} + void ClangDocBitcodeWriter::emitBlock(const FunctionInfo &I) { StreamSubBlockGuard Block(Stream, BI_FUNCTION_BLOCK_ID); emitRecord(I.USR, FUNCTION_USR); Index: clang-tools-extra/clang-doc/Representation.h =================================================================== --- clang-tools-extra/clang-doc/Representation.h +++ clang-tools-extra/clang-doc/Representation.h @@ -32,6 +32,7 @@ struct Info; struct FunctionInfo; struct EnumInfo; +struct BaseRecordInfo; enum class InfoType { IT_default, @@ -345,15 +346,33 @@ llvm::SmallVector VirtualParents; // List of virtual base/parent records. - // 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 + 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; }; +struct BaseRecordInfo : public RecordInfo { + BaseRecordInfo() : RecordInfo() {} + BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, bool IsVirtual, + AccessSpecifier Access, bool IsParent) + : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access), + IsParent(IsParent) {} + + // Indicates if base corresponds to a virtual inheritance + bool IsVirtual = false; + // Access level associated with this inherited info (public, protected, + // private). + AccessSpecifier Access = AccessSpecifier::AS_public; + bool IsParent = false; // Indicates if this base is a direct parent +}; + // TODO: Expand to allow for documenting templating. // Info for types. struct EnumInfo : public SymbolInfo { Index: clang-tools-extra/clang-doc/Representation.cpp =================================================================== --- clang-tools-extra/clang-doc/Representation.cpp +++ clang-tools-extra/clang-doc/Representation.cpp @@ -178,6 +178,8 @@ TagType = Other.TagType; if (Members.empty()) Members = std::move(Other.Members); + if (Bases.empty()) + Bases = std::move(Other.Bases); if (Parents.empty()) Parents = std::move(Other.Parents); if (VirtualParents.empty()) Index: clang-tools-extra/clang-doc/Serialize.cpp =================================================================== --- clang-tools-extra/clang-doc/Serialize.cpp +++ clang-tools-extra/clang-doc/Serialize.cpp @@ -230,27 +230,72 @@ return false; // otherwise, linkage is some form of internal linkage } -static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly) { +static bool shouldSerializeInfo(bool PublicOnly, bool IsInAnonymousNamespace, + const NamedDecl *D) { + bool IsAnonymousNamespace = false; + if (const auto *N = dyn_cast(D)) + IsAnonymousNamespace = N->isAnonymousNamespace(); + return !PublicOnly || + (!IsInAnonymousNamespace && !IsAnonymousNamespace && + isPublic(D->getAccessUnsafe(), D->getLinkageInternal())); +} + +// There are two uses for this function. +// 1) Getting the resulting mode of inheritance of a record. +// Example: class A {}; class B : private A {}; class C : public B {}; +// It's explicit that C is publicly inherited from C and B is privately +// inherited from A. It's not explicit but C is also privately inherited from +// A. This is the AS that this function calculates. FirstAS is the +// inheritance mode of `class C : B` and SecondAS is the inheritance mode of +// `class B : A`. +// 2) Getting the inheritance mode of an inherited attribute / method. +// Example : class A { public: int M; }; class B : private A {}; +// Class B is inherited from class A, which has a public attribute. This +// attribute is now part of the derived class B but it's not public. This +// will be private because the inheritance is private. This is the AS that +// this function calculates. FirstAS is the inheritance mode and SecondAS is +// the AS of the attribute / method. +static AccessSpecifier getFinalAccessSpecifier(AccessSpecifier FirstAS, + AccessSpecifier SecondAS) { + if (FirstAS == AccessSpecifier::AS_none || + SecondAS == AccessSpecifier::AS_none) + return AccessSpecifier::AS_none; + if (FirstAS == AccessSpecifier::AS_private || + SecondAS == AccessSpecifier::AS_private) + return AccessSpecifier::AS_private; + if (FirstAS == AccessSpecifier::AS_protected || + SecondAS == AccessSpecifier::AS_protected) + return AccessSpecifier::AS_protected; + return AccessSpecifier::AS_public; +} + +// The Access parameter is only provided when parsing the field of an inherited +// record, the access specification of the field depends on the inheritance mode +static void parseFields(RecordInfo &I, const RecordDecl *D, bool PublicOnly, + AccessSpecifier Access = AccessSpecifier::AS_public) { for (const FieldDecl *F : D->fields()) { - if (PublicOnly && !isPublic(F->getAccessUnsafe(), F->getLinkageInternal())) + if (!shouldSerializeInfo(PublicOnly, /*IsInAnonymousNamespace=*/false, F)) continue; if (const auto *T = getDeclForType(F->getTypeSourceInfo()->getType())) { // Use getAccessUnsafe so that we just get the default AS_none if it's not // valid, as opposed to an assert. if (const auto *N = dyn_cast(T)) { - I.Members.emplace_back(getUSRForDecl(T), N->getNameAsString(), - InfoType::IT_enum, getInfoRelativePath(N), - F->getNameAsString(), N->getAccessUnsafe()); + I.Members.emplace_back( + getUSRForDecl(T), N->getNameAsString(), InfoType::IT_enum, + getInfoRelativePath(N), F->getNameAsString(), + getFinalAccessSpecifier(Access, N->getAccessUnsafe())); continue; } else if (const auto *N = dyn_cast(T)) { - I.Members.emplace_back(getUSRForDecl(T), N->getNameAsString(), - InfoType::IT_record, getInfoRelativePath(N), - F->getNameAsString(), N->getAccessUnsafe()); + I.Members.emplace_back( + getUSRForDecl(T), N->getNameAsString(), InfoType::IT_record, + getInfoRelativePath(N), F->getNameAsString(), + getFinalAccessSpecifier(Access, N->getAccessUnsafe())); continue; } } - I.Members.emplace_back(F->getTypeSourceInfo()->getType().getAsString(), - F->getNameAsString(), F->getAccessUnsafe()); + I.Members.emplace_back( + F->getTypeSourceInfo()->getType().getAsString(), F->getNameAsString(), + getFinalAccessSpecifier(Access, F->getAccessUnsafe())); } } @@ -279,6 +324,8 @@ } } +// TODO: Remove the serialization of Parents and VirtualParents, this +// information is also extracted in the other definition of parseBases. static void parseBases(RecordInfo &I, const CXXRecordDecl *D) { // Don't parse bases if this isn't a definition. if (!D->isThisDeclarationADefinition()) @@ -376,15 +423,71 @@ parseParameters(I, D); } +static void +parseBases(RecordInfo &I, const CXXRecordDecl *D, bool IsFileInRootDir, + bool PublicOnly, bool IsParent, + AccessSpecifier ParentAccess = AccessSpecifier::AS_public) { + // Don't parse bases if this isn't a definition. + if (!D->isThisDeclarationADefinition()) + return; + for (const CXXBaseSpecifier &B : D->bases()) { + if (const RecordType *Ty = B.getType()->getAs()) { + if (const CXXRecordDecl *Base = + cast_or_null(Ty->getDecl()->getDefinition())) { + // Initialized without USR and name, this will be set in the following + // if-else stmt. + BaseRecordInfo BI( + {}, "", getInfoRelativePath(Base), B.isVirtual(), + getFinalAccessSpecifier(ParentAccess, B.getAccessSpecifier()), + IsParent); + if (const auto *Ty = B.getType()->getAs()) { + const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl(); + BI.USR = getUSRForDecl(D); + BI.Name = B.getType().getAsString(); + } else { + BI.USR = getUSRForDecl(Base); + BI.Name = Base->getNameAsString(); + } + parseFields(BI, Base, PublicOnly, BI.Access); + for (const auto &Decl : Base->decls()) + if (const auto *MD = dyn_cast(Decl)) { + // Don't serialize private methods + if (MD->getAccessUnsafe() == AccessSpecifier::AS_private || + !MD->isUserProvided()) + continue; + FunctionInfo FI; + FI.IsMethod = true; + // The seventh arg in populateFunctionInfo is a boolean passed by + // reference, its value is not relevant in here so it's not used + // anywhere besides the function call. + bool IsInAnonymousNamespace; + populateFunctionInfo(FI, MD, /*FullComment=*/{}, /*LineNumber=*/{}, + /*FileName=*/{}, IsFileInRootDir, + IsInAnonymousNamespace); + FI.Access = + getFinalAccessSpecifier(BI.Access, MD->getAccessUnsafe()); + BI.ChildFunctions.emplace_back(std::move(FI)); + } + I.Bases.emplace_back(std::move(BI)); + // Call this function recursively to get the inherited classes of + // this base; these new bases will also get stored in the original + // RecordInfo: I. + parseBases(I, Base, IsFileInRootDir, PublicOnly, false, + I.Bases.back().Access); + } + } + } +} + std::pair, std::unique_ptr> emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber, llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) { auto I = std::make_unique(); bool IsInAnonymousNamespace = false; populateInfo(*I, D, FC, IsInAnonymousNamespace); - if (PublicOnly && ((IsInAnonymousNamespace || D->isAnonymousNamespace()) || - !isPublic(D->getAccess(), D->getLinkageInternal()))) + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; + I->Name = D->isAnonymousNamespace() ? llvm::SmallString<16>("@nonymous_namespace") : I->Name; @@ -409,8 +512,7 @@ bool IsInAnonymousNamespace = false; populateSymbolInfo(*I, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); - if (PublicOnly && ((IsInAnonymousNamespace || - !isPublic(D->getAccess(), D->getLinkageInternal())))) + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; I->TagType = D->getTagKind(); @@ -420,7 +522,9 @@ I->Name = TD->getNameAsString(); I->IsTypeDef = true; } + // TODO: remove first call to parseBases, that function should be deleted parseBases(*I, C); + parseBases(*I, C, IsFileInRootDir, PublicOnly, true); } I->Path = getInfoRelativePath(I->Namespace); @@ -464,8 +568,7 @@ populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); Func.Access = clang::AccessSpecifier::AS_none; - if (PublicOnly && ((IsInAnonymousNamespace || - !isPublic(D->getAccess(), D->getLinkageInternal())))) + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; // Wrap in enclosing scope @@ -488,8 +591,7 @@ bool IsInAnonymousNamespace = false; populateFunctionInfo(Func, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); - if (PublicOnly && ((IsInAnonymousNamespace || - !isPublic(D->getAccess(), D->getLinkageInternal())))) + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; Func.IsMethod = true; @@ -523,8 +625,7 @@ bool IsInAnonymousNamespace = false; populateSymbolInfo(Enum, D, FC, LineNumber, File, IsFileInRootDir, IsInAnonymousNamespace); - if (PublicOnly && ((IsInAnonymousNamespace || - !isPublic(D->getAccess(), D->getLinkageInternal())))) + if (!shouldSerializeInfo(PublicOnly, IsInAnonymousNamespace, D)) return {}; Enum.Scoped = D->isScoped(); Index: clang-tools-extra/clang-doc/YAMLGenerator.cpp =================================================================== --- clang-tools-extra/clang-doc/YAMLGenerator.cpp +++ clang-tools-extra/clang-doc/YAMLGenerator.cpp @@ -21,6 +21,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(CommentInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(BaseRecordInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>) @@ -124,6 +125,19 @@ IO.mapOptional("Location", I.Loc, llvm::SmallVector()); } +static void RecordInfoMapping(IO &IO, RecordInfo &I) { + SymbolInfoMapping(IO, I); + IO.mapOptional("TagType", I.TagType, clang::TagTypeKind::TTK_Struct); + IO.mapOptional("Members", I.Members); + IO.mapOptional("Bases", I.Bases); + IO.mapOptional("Parents", I.Parents, llvm::SmallVector()); + IO.mapOptional("VirtualParents", I.VirtualParents, + llvm::SmallVector()); + IO.mapOptional("ChildRecords", I.ChildRecords, std::vector()); + IO.mapOptional("ChildFunctions", I.ChildFunctions); + IO.mapOptional("ChildEnums", I.ChildEnums); +} + static void CommentInfoMapping(IO &IO, CommentInfo &I) { IO.mapOptional("Kind", I.Kind, SmallString<16>()); IO.mapOptional("Text", I.Text, SmallString<64>()); @@ -193,16 +207,18 @@ }; template <> struct MappingTraits { - static void mapping(IO &IO, RecordInfo &I) { - SymbolInfoMapping(IO, I); - IO.mapOptional("TagType", I.TagType, clang::TagTypeKind::TTK_Struct); - IO.mapOptional("Members", I.Members); - IO.mapOptional("Parents", I.Parents, llvm::SmallVector()); - IO.mapOptional("VirtualParents", I.VirtualParents, - llvm::SmallVector()); - IO.mapOptional("ChildRecords", I.ChildRecords, std::vector()); - IO.mapOptional("ChildFunctions", I.ChildFunctions); - IO.mapOptional("ChildEnums", I.ChildEnums); + static void mapping(IO &IO, RecordInfo &I) { RecordInfoMapping(IO, I); } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, BaseRecordInfo &I) { + RecordInfoMapping(IO, I); + IO.mapOptional("IsVirtual", I.IsVirtual, false); + // clang::AccessSpecifier::AS_none is used as the default here because it's + // 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("IsParent", I.IsParent, false); } }; Index: clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp +++ clang-tools-extra/unittests/clang-doc/BitcodeTest.cpp @@ -81,6 +81,10 @@ I.Members.emplace_back("int", "X", AccessSpecifier::AS_private); I.TagType = TagTypeKind::TTK_Class; I.IsTypeDef = true; + I.Bases.emplace_back(EmptySID, "F", "path/to/F", true, + AccessSpecifier::AS_public, true); + I.Bases.back().ChildFunctions.emplace_back(); + I.Bases.back().Members.emplace_back("int", "X", AccessSpecifier::AS_private); I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record); I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); Index: clang-tools-extra/unittests/clang-doc/ClangDocTest.h =================================================================== --- clang-tools-extra/unittests/clang-doc/ClangDocTest.h +++ clang-tools-extra/unittests/clang-doc/ClangDocTest.h @@ -43,6 +43,7 @@ void CheckEnumInfo(EnumInfo *Expected, EnumInfo *Actual); void CheckNamespaceInfo(NamespaceInfo *Expected, NamespaceInfo *Actual); void CheckRecordInfo(RecordInfo *Expected, RecordInfo *Actual); +void CheckBaseRecordInfo(BaseRecordInfo *Expected, BaseRecordInfo *Actual); void CheckIndex(Index &Expected, Index &Actual); Index: clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp +++ clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "ClangDocTest.h" #include "Representation.h" #include "clang/AST/RecursiveASTVisitor.h" #include "gtest/gtest.h" @@ -168,6 +169,10 @@ for (size_t Idx = 0; Idx < Actual->VirtualParents.size(); ++Idx) CheckReference(Expected->VirtualParents[Idx], Actual->VirtualParents[Idx]); + ASSERT_EQ(Expected->Bases.size(), Actual->Bases.size()); + for (size_t Idx = 0; Idx < Actual->Bases.size(); ++Idx) + CheckBaseRecordInfo(&Expected->Bases[Idx], &Actual->Bases[Idx]); + ASSERT_EQ(Expected->ChildRecords.size(), Actual->ChildRecords.size()); for (size_t Idx = 0; Idx < Actual->ChildRecords.size(); ++Idx) CheckReference(Expected->ChildRecords[Idx], Actual->ChildRecords[Idx]); @@ -182,6 +187,14 @@ CheckEnumInfo(&Expected->ChildEnums[Idx], &Actual->ChildEnums[Idx]); } +void CheckBaseRecordInfo(BaseRecordInfo *Expected, BaseRecordInfo *Actual) { + CheckRecordInfo(Expected, Actual); + + EXPECT_EQ(Expected->IsVirtual, Actual->IsVirtual); + EXPECT_EQ(Expected->Access, Actual->Access); + EXPECT_EQ(Expected->IsParent, Actual->IsParent); +} + void CheckIndex(Index &Expected, Index &Actual) { CheckReference(Expected, Actual); ASSERT_EQ(Expected.Children.size(), Actual.Children.size()); Index: clang-tools-extra/unittests/clang-doc/MergeTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/MergeTest.cpp +++ clang-tools-extra/unittests/clang-doc/MergeTest.cpp @@ -87,6 +87,8 @@ One.Parents.emplace_back(EmptySID, "F", InfoType::IT_record); One.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); + One.Bases.emplace_back(EmptySID, "F", "path/to/F", true, + AccessSpecifier::AS_protected, true); One.ChildRecords.emplace_back(NonEmptySID, "SharedChildStruct", InfoType::IT_record); One.ChildFunctions.emplace_back(); @@ -126,6 +128,8 @@ Expected->TagType = TagTypeKind::TTK_Class; Expected->Parents.emplace_back(EmptySID, "F", InfoType::IT_record); Expected->VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); + Expected->Bases.emplace_back(EmptySID, "F", "path/to/F", true, + AccessSpecifier::AS_protected, true); Expected->ChildRecords.emplace_back(NonEmptySID, "SharedChildStruct", InfoType::IT_record, "path"); Index: clang-tools-extra/unittests/clang-doc/SerializeTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/SerializeTest.cpp +++ clang-tools-extra/unittests/clang-doc/SerializeTest.cpp @@ -321,15 +321,16 @@ CheckNamespaceInfo(&ExpectedBWithFunction, BWithFunction); } -TEST(SerializeTest, ) { +TEST(SerializeTest, emitInheritedRecordInfo) { EmittedInfoList Infos; - ExtractInfosFromCode(R"raw(class F {}; -class G {} ; + ExtractInfosFromCode(R"raw(class F { protected: void set(int N); }; +class G { public: int get() { return 1; } protected: int I; }; class E : public F, virtual private G {}; +class H : private E {}; template -class H {} ; -class I : public H {} ;)raw", - 10, /*Public=*/false, Infos); +class I {} ; +class J : public I {} ;)raw", + 14, /*Public=*/false, Infos); RecordInfo *F = InfoAsRecord(Infos[0].get()); RecordInfo ExpectedF(EmptySID, "F"); @@ -337,32 +338,91 @@ ExpectedF.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedF, F); - RecordInfo *G = InfoAsRecord(Infos[2].get()); + RecordInfo *G = InfoAsRecord(Infos[3].get()); RecordInfo ExpectedG(EmptySID, "G"); ExpectedG.TagType = TagTypeKind::TTK_Class; ExpectedG.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); + ExpectedG.Members.emplace_back("int", "I", AccessSpecifier::AS_protected); CheckRecordInfo(&ExpectedG, G); - RecordInfo *E = InfoAsRecord(Infos[4].get()); + RecordInfo *E = InfoAsRecord(Infos[6].get()); RecordInfo ExpectedE(EmptySID, "E"); ExpectedE.Parents.emplace_back(EmptySID, "F", InfoType::IT_record); ExpectedE.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); + ExpectedE.Bases.emplace_back(EmptySID, "F", "", false, + AccessSpecifier::AS_public, true); + FunctionInfo FunctionSet; + FunctionSet.Name = "set"; + FunctionSet.ReturnType = TypeInfo(EmptySID, "void", InfoType::IT_default); + FunctionSet.Loc.emplace_back(); + FunctionSet.Params.emplace_back("int", "N"); + FunctionSet.Namespace.emplace_back(EmptySID, "F", InfoType::IT_record); + FunctionSet.Access = AccessSpecifier::AS_protected; + FunctionSet.IsMethod = true; + ExpectedE.Bases.back().ChildFunctions.emplace_back(std::move(FunctionSet)); + ExpectedE.Bases.emplace_back(EmptySID, "G", "", true, + AccessSpecifier::AS_private, true); + FunctionInfo FunctionGet; + FunctionGet.Name = "get"; + FunctionGet.ReturnType = TypeInfo(EmptySID, "int", InfoType::IT_default); + FunctionGet.DefLoc = Location(); + FunctionGet.Namespace.emplace_back(EmptySID, "G", InfoType::IT_record); + FunctionGet.Access = AccessSpecifier::AS_private; + FunctionGet.IsMethod = true; + ExpectedE.Bases.back().ChildFunctions.emplace_back(std::move(FunctionGet)); + ExpectedE.Bases.back().Members.emplace_back("int", "I", + AccessSpecifier::AS_private); ExpectedE.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); ExpectedE.TagType = TagTypeKind::TTK_Class; CheckRecordInfo(&ExpectedE, E); - RecordInfo *H = InfoAsRecord(Infos[6].get()); + RecordInfo *H = InfoAsRecord(Infos[8].get()); RecordInfo ExpectedH(EmptySID, "H"); ExpectedH.TagType = TagTypeKind::TTK_Class; ExpectedH.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); + ExpectedH.Parents.emplace_back(EmptySID, "E", InfoType::IT_record); + ExpectedH.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); + ExpectedH.Bases.emplace_back(EmptySID, "E", "", false, + AccessSpecifier::AS_private, true); + ExpectedH.Bases.emplace_back(EmptySID, "F", "", false, + AccessSpecifier::AS_private, false); + FunctionInfo FunctionSetNew; + FunctionSetNew.Name = "set"; + FunctionSetNew.ReturnType = TypeInfo(EmptySID, "void", InfoType::IT_default); + FunctionSetNew.Loc.emplace_back(); + FunctionSetNew.Params.emplace_back("int", "N"); + FunctionSetNew.Namespace.emplace_back(EmptySID, "F", InfoType::IT_record); + FunctionSetNew.Access = AccessSpecifier::AS_private; + FunctionSetNew.IsMethod = true; + ExpectedH.Bases.back().ChildFunctions.emplace_back(std::move(FunctionSetNew)); + ExpectedH.Bases.emplace_back(EmptySID, "G", "", true, + AccessSpecifier::AS_private, false); + FunctionInfo FunctionGetNew; + FunctionGetNew.Name = "get"; + FunctionGetNew.ReturnType = TypeInfo(EmptySID, "int", InfoType::IT_default); + FunctionGetNew.DefLoc = Location(); + FunctionGetNew.Namespace.emplace_back(EmptySID, "G", InfoType::IT_record); + FunctionGetNew.Access = AccessSpecifier::AS_private; + FunctionGetNew.IsMethod = true; + ExpectedH.Bases.back().ChildFunctions.emplace_back(std::move(FunctionGetNew)); + ExpectedH.Bases.back().Members.emplace_back("int", "I", + AccessSpecifier::AS_private); CheckRecordInfo(&ExpectedH, H); - RecordInfo *I = InfoAsRecord(Infos[8].get()); + RecordInfo *I = InfoAsRecord(Infos[10].get()); RecordInfo ExpectedI(EmptySID, "I"); - ExpectedI.Parents.emplace_back(EmptySID, "H", InfoType::IT_record); - ExpectedI.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); ExpectedI.TagType = TagTypeKind::TTK_Class; + ExpectedI.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); CheckRecordInfo(&ExpectedI, I); + + RecordInfo *J = InfoAsRecord(Infos[12].get()); + RecordInfo ExpectedJ(EmptySID, "J"); + ExpectedJ.Parents.emplace_back(EmptySID, "I", InfoType::IT_record); + ExpectedJ.Bases.emplace_back(EmptySID, "I", "", false, + AccessSpecifier::AS_public, true); + ExpectedJ.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); + ExpectedJ.TagType = TagTypeKind::TTK_Class; + CheckRecordInfo(&ExpectedJ, J); } TEST(SerializeTest, emitModulePublicLFunctions) { Index: clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp +++ clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp @@ -84,6 +84,12 @@ I.Members.emplace_back("int", "path/to/int", "X", AccessSpecifier::AS_private); I.TagType = TagTypeKind::TTK_Class; + I.Bases.emplace_back(EmptySID, "F", "path/to/F", true, + AccessSpecifier::AS_public, true); + I.Bases.back().ChildFunctions.emplace_back(); + I.Bases.back().ChildFunctions.back().Name = "InheritedFunctionOne"; + I.Bases.back().Members.emplace_back("int", "path/to/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, @@ -123,6 +129,24 @@ Path: 'path/to/int' Name: 'X' Access: Private +Bases: + - USR: '0000000000000000000000000000000000000000' + Name: 'F' + Path: 'path/to/F' + Members: + - Type: + Name: 'int' + Path: 'path/to/int' + Name: 'N' + Access: Private + ChildFunctions: + - USR: '0000000000000000000000000000000000000000' + Name: 'InheritedFunctionOne' + ReturnType: {} + Access: Public + IsVirtual: true + Access: Public + IsParent: true Parents: - Type: Record Name: 'F'