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 @@ -17,6 +17,7 @@ using Record = llvm::SmallVector; +// This implements decode for strings. llvm::Error decodeRecord(const Record &R, llvm::SmallVectorImpl &Field, llvm::StringRef Blob) { Field.assign(Blob.begin(), Blob.end()); @@ -49,6 +50,27 @@ return llvm::Error::success(); } +llvm::Error decodeRecord(const Record &R, llvm::APSInt &Field, llvm::StringRef Blob) { + auto ByteWidth = R[0]; + // The writer only ever writes out the number of bytes in the whole words stored by APSInt. + assert(ByteWidth % llvm::APSInt::APINT_WORD_SIZE == 0); + assert(Blob.size() == ByteWidth); + + auto WordWidth = ByteWidth / llvm::APSInt::APINT_WORD_SIZE; + assert(WordWidth > 0); + auto BitWidth = ByteWidth * 8; + + auto IsUnsigned = R[1]; + + llvm::SmallVector AsWords; + AsWords.resize(WordWidth); + memcpy(AsWords.data(), Blob.data(), ByteWidth); + + llvm::APInt IntValue(static_cast(BitWidth), makeArrayRef(AsWords)); + Field = llvm::APSInt(IntValue, IsUnsigned); + return llvm::Error::success(); +} + llvm::Error decodeRecord(const Record &R, AccessSpecifier &Field, llvm::StringRef Blob) { switch (R[0]) { @@ -218,8 +240,6 @@ return decodeRecord(R, I->DefLoc, Blob); case ENUM_LOCATION: return decodeRecord(R, I->Loc, Blob); - case ENUM_MEMBER: - return decodeRecord(R, I->Members, Blob); case ENUM_SCOPED: return decodeRecord(R, I->Scoped, Blob); default: @@ -228,6 +248,21 @@ } } +llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, + EnumValueInfo *I) { + switch (ID) { + case ENUM_VALUE_NAME: + return decodeRecord(R, I->Name, Blob); + case ENUM_VALUE_VALUE: + return decodeRecord(R, I->Value, Blob); + case ENUM_VALUE_EXPR: + return decodeRecord(R, I->ValueExpr, Blob); + default: + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid field for EnumValueInfo"); + } +} + llvm::Error parseRecord(const Record &R, unsigned ID, llvm::StringRef Blob, FunctionInfo *I) { switch (ID) { @@ -372,6 +407,9 @@ return getCommentInfo(I.get()); } +// When readSubBlock encounters a TypeInfo sub-block, it calls addTypeInfo on +// the parent block to set it. The template specializations define what to do +// for each supported parent block. template llvm::Error addTypeInfo(T I, TTypeInfo &&TI) { return llvm::createStringError(llvm::inconvertibleErrorCode(), @@ -398,6 +436,11 @@ return llvm::Error::success(); } +template <> llvm::Error addTypeInfo(EnumInfo *I, TypeInfo &&T) { + I->BaseType = 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"); @@ -524,6 +567,10 @@ I->ChildEnums.emplace_back(std::move(R)); } +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)); } @@ -587,8 +634,7 @@ template llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { switch (ID) { - // Blocks can only have Comment, Reference, TypeInfo, FunctionInfo, or - // EnumInfo subblocks + // Blocks can only have certain types of sub blocks. case BI_COMMENT_BLOCK_ID: { auto Comment = getCommentInfo(I); if (!Comment) @@ -650,6 +696,13 @@ addChild(I, std::move(E)); return llvm::Error::success(); } + case BI_ENUM_VALUE_BLOCK_ID: { + EnumValueInfo EV; + if (auto Err = readBlock(ID, &EV)) + return Err; + addChild(I, std::move(EV)); + return llvm::Error::success(); + } default: return llvm::createStringError(llvm::inconvertibleErrorCode(), "invalid subblock type"); diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h --- a/clang-tools-extra/clang-doc/BitcodeWriter.h +++ b/clang-tools-extra/clang-doc/BitcodeWriter.h @@ -17,6 +17,7 @@ #include "Representation.h" #include "clang/AST/AST.h" +#include "llvm/ADT/APSInt.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" @@ -54,6 +55,7 @@ BI_VERSION_BLOCK_ID = llvm::bitc::FIRST_APPLICATION_BLOCKID, BI_NAMESPACE_BLOCK_ID, BI_ENUM_BLOCK_ID, + BI_ENUM_VALUE_BLOCK_ID, BI_TYPE_BLOCK_ID, BI_FIELD_TYPE_BLOCK_ID, BI_MEMBER_TYPE_BLOCK_ID, @@ -100,6 +102,9 @@ ENUM_LOCATION, ENUM_MEMBER, ENUM_SCOPED, + ENUM_VALUE_NAME, + ENUM_VALUE_VALUE, + ENUM_VALUE_EXPR, RECORD_USR, RECORD_NAME, RECORD_PATH, @@ -155,6 +160,7 @@ void emitBlock(const BaseRecordInfo &I); void emitBlock(const FunctionInfo &I); void emitBlock(const EnumInfo &I); + void emitBlock(const EnumValueInfo &I); void emitBlock(const TypeInfo &B); void emitBlock(const FieldTypeInfo &B); void emitBlock(const MemberTypeInfo &B); @@ -205,6 +211,7 @@ void emitRecord(bool Value, RecordId ID); void emitRecord(int Value, RecordId ID); void emitRecord(unsigned Value, RecordId ID); + void emitRecord(llvm::APSInt Value, RecordId ID); bool prepRecordData(RecordId ID, bool ShouldEmit = true); // Emission of appropriate abbreviation type. diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp --- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp +++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp @@ -36,6 +36,18 @@ Abbrev->Add(Op); } +static void ApsIntAbbrev(std::shared_ptr &Abbrev) { + AbbrevGen(Abbrev, + {// 0. Fixed-size integer (length of the following bytes) + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::StringLengthSize), + // 1. Type unsigned flag. + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Fixed, + BitCodeConstants::BoolSize), + // 2. The APSInt value blob. + llvm::BitCodeAbbrevOp(llvm::BitCodeAbbrevOp::Blob)}); +} + static void BoolAbbrev(std::shared_ptr &Abbrev) { AbbrevGen(Abbrev, {// 0. Boolean @@ -112,6 +124,7 @@ {BI_VERSION_BLOCK_ID, "VersionBlock"}, {BI_NAMESPACE_BLOCK_ID, "NamespaceBlock"}, {BI_ENUM_BLOCK_ID, "EnumBlock"}, + {BI_ENUM_VALUE_BLOCK_ID, "EnumValueBlock"}, {BI_TYPE_BLOCK_ID, "TypeBlock"}, {BI_FIELD_TYPE_BLOCK_ID, "FieldTypeBlock"}, {BI_MEMBER_TYPE_BLOCK_ID, "MemberTypeBlock"}, @@ -160,6 +173,9 @@ {ENUM_LOCATION, {"Location", &LocationAbbrev}}, {ENUM_MEMBER, {"Member", &StringAbbrev}}, {ENUM_SCOPED, {"Scoped", &BoolAbbrev}}, + {ENUM_VALUE_NAME, {"Name", &StringAbbrev}}, + {ENUM_VALUE_VALUE, {"Value", &ApsIntAbbrev}}, + {ENUM_VALUE_EXPR, {"Expr", &StringAbbrev}}, {RECORD_USR, {"USR", &SymbolIDAbbrev}}, {RECORD_NAME, {"Name", &StringAbbrev}}, {RECORD_PATH, {"Path", &StringAbbrev}}, @@ -215,6 +231,9 @@ {BI_ENUM_BLOCK_ID, {ENUM_USR, ENUM_NAME, ENUM_DEFLOCATION, ENUM_LOCATION, ENUM_MEMBER, ENUM_SCOPED}}, + // Enum Value Block + {BI_ENUM_VALUE_BLOCK_ID, + {ENUM_VALUE_NAME, ENUM_VALUE_VALUE, ENUM_VALUE_EXPR}}, // Namespace Block {BI_NAMESPACE_BLOCK_ID, {NAMESPACE_USR, NAMESPACE_NAME, NAMESPACE_PATH}}, @@ -367,6 +386,19 @@ Stream.EmitRecordWithAbbrev(Abbrevs.get(ID), Record); } +void ClangDocBitcodeWriter::emitRecord(llvm::APSInt Value, RecordId ID) { + assert(RecordIdNameMap[ID] && "Unknown RecordId."); + assert(RecordIdNameMap[ID].Abbrev == &ApsIntAbbrev && "Abbrev type mismatch."); + if (!prepRecordData(ID, true)) + return; + + auto ByteSize = Value.getNumWords() * llvm::APSInt::APINT_WORD_SIZE; + assert(ByteSize < (1U << BitCodeConstants::StringLengthSize)); + Record.push_back(ByteSize); + Record.push_back(Value.isUnsigned()); + Stream.EmitRecordWithBlob(Abbrevs.get(ID), Record, reinterpret_cast(Value.getRawData()), ByteSize); +} + bool ClangDocBitcodeWriter::prepRecordData(RecordId ID, bool ShouldEmit) { assert(RecordIdNameMap[ID] && "Unknown RecordId."); if (!ShouldEmit) @@ -486,8 +518,17 @@ for (const auto &L : I.Loc) emitRecord(L, ENUM_LOCATION); emitRecord(I.Scoped, ENUM_SCOPED); + if (I.BaseType) + emitBlock(*I.BaseType); for (const auto &N : I.Members) - emitRecord(N, ENUM_MEMBER); + emitBlock(N); +} + +void ClangDocBitcodeWriter::emitBlock(const EnumValueInfo &I) { + StreamSubBlockGuard Block(Stream, BI_ENUM_VALUE_BLOCK_ID); + emitRecord(I.Name, ENUM_VALUE_NAME); + emitRecord(I.Value, ENUM_VALUE_VALUE); + emitRecord(I.ValueExpr, ENUM_VALUE_EXPR); } void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) { diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -361,13 +361,13 @@ } static std::unique_ptr -genEnumMembersBlock(const llvm::SmallVector, 4> &Members) { +genEnumMembersBlock(const llvm::SmallVector &Members) { if (Members.empty()) return nullptr; auto List = std::make_unique(HTMLTag::TAG_UL); for (const auto &M : Members) - List->Children.emplace_back(std::make_unique(HTMLTag::TAG_LI, M)); + List->Children.emplace_back(std::make_unique(HTMLTag::TAG_LI, M.Name)); return List; } diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp --- a/clang-tools-extra/clang-doc/MDGenerator.cpp +++ b/clang-tools-extra/clang-doc/MDGenerator.cpp @@ -136,7 +136,7 @@ llvm::raw_string_ostream Members(Buffer); if (!I.Members.empty()) for (const auto &N : I.Members) - Members << "| " << N << " |\n"; + Members << "| " << N.Name << " |\n"; writeLine(Members.str(), OS); if (I.DefLoc) writeFileDefinition(CDCtx, *I.DefLoc, OS); 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 @@ -17,6 +17,7 @@ #include "clang/AST/Type.h" #include "clang/Basic/Specifiers.h" #include "clang/Tooling/StandaloneExecution.h" +#include "llvm/ADT/APSInt.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" @@ -397,6 +398,33 @@ bool IsParent = false; // Indicates if this base is a direct parent }; +// Information for a single possible value of an enumeration. +struct EnumValueInfo { + explicit EnumValueInfo(StringRef Name = StringRef(), + llvm::APSInt Value = llvm::APSInt(), + StringRef ValueExpr = StringRef()) + : Name(Name), Value(Value), ValueExpr(ValueExpr) {} + + bool operator==(const EnumValueInfo &Other) const { + // Be permissive in comparison for tests so compare the values rather than + // their exact representation (which is what operator== does). + if (llvm::APSInt::compareValues(Value, Other.Value) != 0) + return false; + return std::tie(Name, ValueExpr) == std::tie(Other.Name, Other.ValueExpr); + } + + SmallString<16> Name; + + // The computed value of the enumeration constant. This could be the result of + // evaluating the ValueExpr, or it could be automatically generated according + // to C rules. + llvm::APSInt Value; + + // Stores the user-supplied initialization expression for this enumeration + // constant. This will be empty for implicit enumeration values. + SmallString<16> ValueExpr; +}; + // TODO: Expand to allow for documenting templating. // Info for types. struct EnumInfo : public SymbolInfo { @@ -405,9 +433,15 @@ void merge(EnumInfo &&I); - bool Scoped = - false; // Indicates whether this enum is scoped (e.g. enum class). - llvm::SmallVector, 4> Members; // List of enum members. + // Indicates whether this enum is scoped (e.g. enum class). + bool Scoped = false; + + // Set to nonempty to the type when this is an explicitly typed enum. For + // enum Foo : short { ... }; + // this will be "short". + llvm::Optional BaseType; + + llvm::SmallVector Members; // List of enum members. }; struct Index : public Reference { 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 @@ -182,6 +182,13 @@ // Serializing functions. +SmallString<128> getSourceCode(const Decl* D, const SourceRange& R) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(R), + D->getASTContext().getSourceManager(), + D->getASTContext().getLangOpts()); +} + template static std::string serialize(T &I) { SmallString<2048> Buffer; llvm::BitstreamWriter Stream(Buffer); @@ -305,8 +312,12 @@ } static void parseEnumerators(EnumInfo &I, const EnumDecl *D) { - for (const EnumConstantDecl *E : D->enumerators()) - I.Members.emplace_back(E->getNameAsString()); + for (const EnumConstantDecl *E : D->enumerators()) { + SmallString<128> ValueExpr; + if (const Expr* InitExpr = E->getInitExpr()) + ValueExpr = getSourceCode(D, InitExpr->getSourceRange()); + I.Members.emplace_back(E->getNameAsString(), E->getInitVal(), ValueExpr); + } } static void parseParameters(FunctionInfo &I, const FunctionDecl *D) { @@ -331,10 +342,7 @@ } if (const Expr *DefaultArg = P->getDefaultArg()) { - FieldInfo->DefaultValue = Lexer::getSourceText( - CharSourceRange::getTokenRange(DefaultArg->getSourceRange()), - D->getASTContext().getSourceManager(), - D->getASTContext().getLangOpts()); + FieldInfo->DefaultValue = getSourceCode(D, DefaultArg->getSourceRange()); } } } @@ -657,6 +665,8 @@ return {}; Enum.Scoped = D->isScoped(); + if (D->isFixed()) + Enum.BaseType = TypeInfo(D->getIntegerType().getAsString()); parseEnumerators(Enum, D); // Put in global namespace 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 @@ -14,6 +14,7 @@ using namespace clang::doc; +// These define YAML traits for decoding the listed values within a vector. LLVM_YAML_IS_SEQUENCE_VECTOR(FieldTypeInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(MemberTypeInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(Reference) @@ -21,6 +22,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(CommentInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(FunctionInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(EnumInfo) +LLVM_YAML_IS_SEQUENCE_VECTOR(EnumValueInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(BaseRecordInfo) LLVM_YAML_IS_SEQUENCE_VECTOR(std::unique_ptr) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::SmallString<16>) @@ -226,10 +228,22 @@ } }; +template <> struct MappingTraits { + static void mapping(IO &IO, EnumValueInfo &I) { + IO.mapOptional("Name", I.Name); + + SmallString<16> ValueStr; + I.Value.toString(ValueStr); + IO.mapOptional("Value", ValueStr); + IO.mapOptional("Expr", I.ValueExpr, SmallString<16>()); + } +}; + template <> struct MappingTraits { static void mapping(IO &IO, EnumInfo &I) { SymbolInfoMapping(IO, I); IO.mapOptional("Scoped", I.Scoped, false); + IO.mapOptional("BaseType", I.BaseType); IO.mapOptional("Members", I.Members); } }; 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 @@ -266,8 +266,8 @@ EnumInfo E; E.Name = "E"; E.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); - E.Members.emplace_back("X"); - E.Members.emplace_back("Y"); + E.Members.emplace_back("X", llvm::APSInt::get(0)); + E.Members.emplace_back("Y", llvm::APSInt::get(1)); ExpectedNamespaceWithEnum.ChildEnums.emplace_back(std::move(E)); CheckNamespaceInfo(&ExpectedNamespaceWithEnum, NamespaceWithEnum); @@ -277,8 +277,8 @@ G.Name = "G"; G.Scoped = true; G.DefLoc = Location(0, llvm::SmallString<16>{"test.cpp"}); - G.Members.emplace_back("A"); - G.Members.emplace_back("B"); + G.Members.emplace_back("A", llvm::APSInt::get(0)); + G.Members.emplace_back("B", llvm::APSInt::get(1)); ExpectedNamespaceWithScopedEnum.ChildEnums.emplace_back(std::move(G)); CheckNamespaceInfo(&ExpectedNamespaceWithScopedEnum, NamespaceWithScopedEnum); } 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 @@ -256,7 +256,11 @@ EXPECT_EQ(Expected, Actual.str()); } -TEST(YAMLGeneratorTest, emitEnumYAML) { +// Tests the equivalent of: +// namespace A { +// enum e { X }; +// } +TEST(YAMLGeneratorTest, emitSimpleEnumYAML) { EnumInfo I; I.Name = "e"; I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); @@ -265,7 +269,7 @@ I.Loc.emplace_back(12, llvm::SmallString<16>{"test.cpp"}); I.Members.emplace_back("X"); - I.Scoped = true; + I.Scoped = false; auto G = getYAMLGenerator(); assert(G); @@ -286,9 +290,42 @@ Location: - LineNumber: 12 Filename: 'test.cpp' +Members: + - Name: 'X' + Value: '0' +... +)raw"; + EXPECT_EQ(Expected, Actual.str()); +} + +// Tests the equivalent of: +// enum class e : short { X = FOO_BAR + 2 }; +TEST(YAMLGeneratorTest, enumTypedScopedEnumYAML) { + EnumInfo I; + I.Name = "e"; + + I.Members.emplace_back("X", llvm::APSInt::get(-9876), "FOO_BAR + 2"); + I.Scoped = true; + I.BaseType = TypeInfo("short"); + + 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: 'e' Scoped: true +BaseType: + Type: + Name: 'short' Members: - - 'X' + - Name: 'X' + Value: '-9876' + Expr: 'FOO_BAR + 2' ... )raw"; EXPECT_EQ(Expected, Actual.str());