[clang-doc] Serialize inherited attributes and methods

clang-doc now serializes the inherited attributes and methods, not only the name of the base class.
All inherited are tracked, if B:A and C:B, info of A is included in C.
This data is stored in attribute Bases in a RecordInfo.
Previously tracked inheritance data, stored in Parents and VParents, hasn't been removed to reduce review load.

Differential revision: https://reviews.llvm.org/D66238

llvm-svn: 369075
This commit is contained in:
Diego Astiazaran 2019-08-16 00:10:49 +00:00
parent 76053297bd
commit ba3d595f93
13 changed files with 373 additions and 47 deletions

View File

@ -179,6 +179,29 @@ llvm::Error parseRecord(Record R, unsigned ID, llvm::StringRef Blob,
}
}
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<llvm::StringError>(
"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 @@ template <> llvm::Error addTypeInfo(RecordInfo *I, MemberTypeInfo &&T) {
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 @@ template <> void addChild(RecordInfo *I, EnumInfo &&R) {
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 <typename T>
llvm::Error ClangDocBitcodeReader::readRecord(unsigned ID, T I) {
@ -598,6 +634,13 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) {
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))

View File

@ -116,6 +116,7 @@ static const llvm::IndexedMap<llvm::StringRef, BlockIdToIndexFunctor>
{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 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
{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 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
{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 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
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 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
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);

View File

@ -30,7 +30,7 @@ namespace doc {
// 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 @@ enum BlockId {
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 @@ enum RecordId {
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 @@ public:
// 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);

View File

@ -178,6 +178,8 @@ void RecordInfo::merge(RecordInfo &&Other) {
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())

View File

@ -32,6 +32,7 @@ using SymbolID = std::array<uint8_t, 20>;
struct Info;
struct FunctionInfo;
struct EnumInfo;
struct BaseRecordInfo;
enum class InfoType {
IT_default,
@ -345,15 +346,33 @@ struct RecordInfo : public SymbolInfo {
llvm::SmallVector<Reference, 4>
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<BaseRecordInfo>
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<Reference> ChildRecords;
std::vector<FunctionInfo> ChildFunctions;
std::vector<EnumInfo> 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 {

View File

@ -230,27 +230,72 @@ static bool isPublic(const clang::AccessSpecifier AS,
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<NamespaceDecl>(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<EnumDecl>(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<RecordDecl>(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 @@ static void parseParameters(FunctionInfo &I, const FunctionDecl *D) {
}
}
// 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 @@ static void populateFunctionInfo(FunctionInfo &I, const FunctionDecl *D,
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<RecordType>()) {
if (const CXXRecordDecl *Base =
cast_or_null<CXXRecordDecl>(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<TemplateSpecializationType>()) {
const TemplateDecl *D = Ty->getTemplateName().getAsTemplateDecl();
BI.USR = getUSRForDecl(D);
BI.Name = B.getType().getAsString();
} else {
BI.USR = getUSRForDecl(Base);
BI.Name = Base->getNameAsString();
}
parseFields(BI, Base, PublicOnly, BI.Access);
for (const auto &Decl : Base->decls())
if (const auto *MD = dyn_cast<CXXMethodDecl>(Decl)) {
// Don't serialize private methods
if (MD->getAccessUnsafe() == AccessSpecifier::AS_private ||
!MD->isUserProvided())
continue;
FunctionInfo FI;
FI.IsMethod = true;
// 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<Info>, std::unique_ptr<Info>>
emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber,
llvm::StringRef File, bool IsFileInRootDir, bool PublicOnly) {
auto I = std::make_unique<NamespaceInfo>();
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 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
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 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
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 @@ emitInfo(const FunctionDecl *D, const FullComment *FC, int LineNumber,
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 @@ emitInfo(const CXXMethodDecl *D, const FullComment *FC, int LineNumber,
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 @@ emitInfo(const EnumDecl *D, const FullComment *FC, int LineNumber,
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();

View File

@ -21,6 +21,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(Location)
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<CommentInfo>)
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>)
@ -124,6 +125,19 @@ static void SymbolInfoMapping(IO &IO, SymbolInfo &I) {
IO.mapOptional("Location", I.Loc, llvm::SmallVector<Location, 2>());
}
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<Reference, 4>());
IO.mapOptional("VirtualParents", I.VirtualParents,
llvm::SmallVector<Reference, 4>());
IO.mapOptional("ChildRecords", I.ChildRecords, std::vector<Reference>());
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<NamespaceInfo> {
};
template <> struct MappingTraits<RecordInfo> {
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<Reference, 4>());
IO.mapOptional("VirtualParents", I.VirtualParents,
llvm::SmallVector<Reference, 4>());
IO.mapOptional("ChildRecords", I.ChildRecords, std::vector<Reference>());
IO.mapOptional("ChildFunctions", I.ChildFunctions);
IO.mapOptional("ChildEnums", I.ChildEnums);
static void mapping(IO &IO, RecordInfo &I) { RecordInfoMapping(IO, I); }
};
template <> struct MappingTraits<BaseRecordInfo> {
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);
}
};

View File

@ -81,6 +81,10 @@ TEST(BitcodeTest, emitRecordInfoBitcode) {
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);

View File

@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "ClangDocTest.h"
#include "Representation.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "gtest/gtest.h"
@ -168,6 +169,10 @@ void CheckRecordInfo(RecordInfo *Expected, RecordInfo *Actual) {
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 @@ void CheckRecordInfo(RecordInfo *Expected, RecordInfo *Actual) {
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());

View File

@ -43,6 +43,7 @@ void CheckFunctionInfo(FunctionInfo *Expected, FunctionInfo *Actual);
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);

View File

@ -87,6 +87,8 @@ TEST(MergeTest, mergeRecordInfos) {
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 @@ TEST(MergeTest, mergeRecordInfos) {
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");

View File

@ -321,15 +321,16 @@ TEST(SerializeTest, emitInlinedFunctionInfo) {
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 <typename T>
class H {} ;
class I : public H<int> {} ;)raw",
10, /*Public=*/false, Infos);
class I {} ;
class J : public I<int> {} ;)raw",
14, /*Public=*/false, Infos);
RecordInfo *F = InfoAsRecord(Infos[0].get());
RecordInfo ExpectedF(EmptySID, "F");
@ -337,32 +338,91 @@ class I : public H<int> {} ;)raw",
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<int>", 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<int>", InfoType::IT_record);
ExpectedJ.Bases.emplace_back(EmptySID, "I<int>", "", 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) {

View File

@ -84,6 +84,12 @@ TEST(YAMLGeneratorTest, emitRecordYAML) {
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 @@ Members:
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'