forked from OSchip/llvm-project
[clang][extract-api] Add struct support
- Add `StructFieldRecord` and `StructRecord` to store API information for structs - Implement `VisitRecordDecl` in `ExtractAPIVisitor` - Implement Symbol Graph serialization for struct records. - Add test case for struct records. Depends on D121873 Differential Revision: https://reviews.llvm.org/D122202
This commit is contained in:
parent
311bdbc9b7
commit
5bb5704c1b
|
@ -94,6 +94,8 @@ struct APIRecord {
|
|||
RK_Global,
|
||||
RK_EnumConstant,
|
||||
RK_Enum,
|
||||
RK_StructField,
|
||||
RK_Struct,
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -176,6 +178,36 @@ struct EnumRecord : APIRecord {
|
|||
}
|
||||
};
|
||||
|
||||
/// This holds information associated with struct fields.
|
||||
struct StructFieldRecord : APIRecord {
|
||||
StructFieldRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
|
||||
const AvailabilityInfo &Availability,
|
||||
const DocComment &Comment, DeclarationFragments Declaration,
|
||||
DeclarationFragments SubHeading)
|
||||
: APIRecord(RK_StructField, Name, USR, Loc, Availability,
|
||||
LinkageInfo::none(), Comment, Declaration, SubHeading) {}
|
||||
|
||||
static bool classof(const APIRecord *Record) {
|
||||
return Record->getKind() == RK_StructField;
|
||||
}
|
||||
};
|
||||
|
||||
/// This holds information associated with structs.
|
||||
struct StructRecord : APIRecord {
|
||||
SmallVector<APIRecordUniquePtr<StructFieldRecord>> Fields;
|
||||
|
||||
StructRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
|
||||
const AvailabilityInfo &Availability, const DocComment &Comment,
|
||||
DeclarationFragments Declaration,
|
||||
DeclarationFragments SubHeading)
|
||||
: APIRecord(RK_Struct, Name, USR, Loc, Availability, LinkageInfo::none(),
|
||||
Comment, Declaration, SubHeading) {}
|
||||
|
||||
static bool classof(const APIRecord *Record) {
|
||||
return Record->getKind() == RK_Struct;
|
||||
}
|
||||
};
|
||||
|
||||
/// APISet holds the set of API records collected from given inputs.
|
||||
class APISet {
|
||||
public:
|
||||
|
@ -242,6 +274,31 @@ public:
|
|||
DeclarationFragments Declaration,
|
||||
DeclarationFragments SubHeading);
|
||||
|
||||
/// Create and add a struct field record into the API set.
|
||||
///
|
||||
/// Note: the caller is responsible for keeping the StringRef \p Name and
|
||||
/// \p USR alive. APISet::copyString provides a way to copy strings into
|
||||
/// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method
|
||||
/// to generate the USR for \c D and keep it alive in APISet.
|
||||
StructFieldRecord *addStructField(StructRecord *Struct, StringRef Name,
|
||||
StringRef USR, PresumedLoc Loc,
|
||||
const AvailabilityInfo &Availability,
|
||||
const DocComment &Comment,
|
||||
DeclarationFragments Declaration,
|
||||
DeclarationFragments SubHeading);
|
||||
|
||||
/// Create and add a struct record into the API set.
|
||||
///
|
||||
/// Note: the caller is responsible for keeping the StringRef \p Name and
|
||||
/// \p USR alive. APISet::copyString provides a way to copy strings into
|
||||
/// APISet itself, and APISet::recordUSR(const Decl *D) is a helper method
|
||||
/// to generate the USR for \c D and keep it alive in APISet.
|
||||
StructRecord *addStruct(StringRef Name, StringRef USR, PresumedLoc Loc,
|
||||
const AvailabilityInfo &Availability,
|
||||
const DocComment &Comment,
|
||||
DeclarationFragments Declaration,
|
||||
DeclarationFragments SubHeading);
|
||||
|
||||
/// A map to store the set of GlobalRecord%s with the declaration name as the
|
||||
/// key.
|
||||
using GlobalRecordMap =
|
||||
|
@ -252,6 +309,11 @@ public:
|
|||
using EnumRecordMap =
|
||||
llvm::MapVector<StringRef, APIRecordUniquePtr<EnumRecord>>;
|
||||
|
||||
/// A map to store the set of StructRecord%s with the declaration name as the
|
||||
/// key.
|
||||
using StructRecordMap =
|
||||
llvm::MapVector<StringRef, APIRecordUniquePtr<StructRecord>>;
|
||||
|
||||
/// Get the target triple for the ExtractAPI invocation.
|
||||
const llvm::Triple &getTarget() const { return Target; }
|
||||
|
||||
|
@ -260,6 +322,7 @@ public:
|
|||
|
||||
const GlobalRecordMap &getGlobals() const { return Globals; }
|
||||
const EnumRecordMap &getEnums() const { return Enums; }
|
||||
const StructRecordMap &getStructs() const { return Structs; }
|
||||
|
||||
/// Generate and store the USR of declaration \p D.
|
||||
///
|
||||
|
@ -285,6 +348,7 @@ private:
|
|||
|
||||
GlobalRecordMap Globals;
|
||||
EnumRecordMap Enums;
|
||||
StructRecordMap Structs;
|
||||
};
|
||||
|
||||
} // namespace extractapi
|
||||
|
|
|
@ -196,6 +196,12 @@ public:
|
|||
/// Build DeclarationFragments for an enum declaration EnumDecl.
|
||||
static DeclarationFragments getFragmentsForEnum(const EnumDecl *);
|
||||
|
||||
/// Build DeclarationFragments for a field declaration FieldDecl.
|
||||
static DeclarationFragments getFragmentsForField(const FieldDecl *);
|
||||
|
||||
/// Build DeclarationFragments for a struct record declaration RecordDecl.
|
||||
static DeclarationFragments getFragmentsForStruct(const RecordDecl *);
|
||||
|
||||
/// Build sub-heading fragments for a NamedDecl.
|
||||
static DeclarationFragments getSubHeading(const NamedDecl *);
|
||||
|
||||
|
|
|
@ -110,6 +110,9 @@ private:
|
|||
/// Serialize an enum record.
|
||||
void serializeEnumRecord(const EnumRecord &Record);
|
||||
|
||||
/// Serialize a struct record.
|
||||
void serializeStructRecord(const StructRecord &Record);
|
||||
|
||||
public:
|
||||
SymbolGraphSerializer(const APISet &API, StringRef ProductName,
|
||||
APISerializerOption Options = {})
|
||||
|
|
|
@ -84,6 +84,33 @@ EnumRecord *APISet::addEnum(StringRef Name, StringRef USR, PresumedLoc Loc,
|
|||
return Result.first->second.get();
|
||||
}
|
||||
|
||||
StructFieldRecord *APISet::addStructField(StructRecord *Struct, StringRef Name,
|
||||
StringRef USR, PresumedLoc Loc,
|
||||
const AvailabilityInfo &Availability,
|
||||
const DocComment &Comment,
|
||||
DeclarationFragments Declaration,
|
||||
DeclarationFragments SubHeading) {
|
||||
auto Record =
|
||||
APIRecordUniquePtr<StructFieldRecord>(new (Allocator) StructFieldRecord{
|
||||
Name, USR, Loc, Availability, Comment, Declaration, SubHeading});
|
||||
return Struct->Fields.emplace_back(std::move(Record)).get();
|
||||
}
|
||||
|
||||
StructRecord *APISet::addStruct(StringRef Name, StringRef USR, PresumedLoc Loc,
|
||||
const AvailabilityInfo &Availability,
|
||||
const DocComment &Comment,
|
||||
DeclarationFragments Declaration,
|
||||
DeclarationFragments SubHeading) {
|
||||
auto Result = Structs.insert({Name, nullptr});
|
||||
if (Result.second) {
|
||||
// Create the record if it does not already exist.
|
||||
auto Record = APIRecordUniquePtr<StructRecord>(new (Allocator) StructRecord{
|
||||
Name, USR, Loc, Availability, Comment, Declaration, SubHeading});
|
||||
Result.first->second = std::move(Record);
|
||||
}
|
||||
return Result.first->second.get();
|
||||
}
|
||||
|
||||
StringRef APISet::recordUSR(const Decl *D) {
|
||||
SmallString<128> USR;
|
||||
index::generateUSRForDecl(D, USR);
|
||||
|
|
|
@ -430,6 +430,30 @@ DeclarationFragmentsBuilder::getFragmentsForEnum(const EnumDecl *EnumDecl) {
|
|||
return Fragments;
|
||||
}
|
||||
|
||||
DeclarationFragments
|
||||
DeclarationFragmentsBuilder::getFragmentsForField(const FieldDecl *Field) {
|
||||
DeclarationFragments After;
|
||||
return getFragmentsForType(Field->getType(), Field->getASTContext(), After)
|
||||
.appendSpace()
|
||||
.append(Field->getName(), DeclarationFragments::FragmentKind::Identifier)
|
||||
.append(std::move(After));
|
||||
}
|
||||
|
||||
DeclarationFragments
|
||||
DeclarationFragmentsBuilder::getFragmentsForStruct(const RecordDecl *Record) {
|
||||
// TODO: After we support typedef records, if there's a typedef for this
|
||||
// struct just use the declaration fragments of the typedef decl.
|
||||
|
||||
DeclarationFragments Fragments;
|
||||
Fragments.append("struct", DeclarationFragments::FragmentKind::Keyword);
|
||||
|
||||
if (!Record->getName().empty())
|
||||
Fragments.appendSpace().append(
|
||||
Record->getName(), DeclarationFragments::FragmentKind::Identifier);
|
||||
|
||||
return Fragments;
|
||||
}
|
||||
|
||||
FunctionSignature
|
||||
DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *Func) {
|
||||
FunctionSignature Signature;
|
||||
|
|
|
@ -179,6 +179,41 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool VisitRecordDecl(const RecordDecl *Decl) {
|
||||
if (!Decl->isCompleteDefinition())
|
||||
return true;
|
||||
|
||||
// Skip C++ structs/classes/unions
|
||||
// TODO: support C++ records
|
||||
if (isa<CXXRecordDecl>(Decl))
|
||||
return true;
|
||||
|
||||
// Collect symbol information.
|
||||
StringRef Name = Decl->getName();
|
||||
StringRef USR = API.recordUSR(Decl);
|
||||
PresumedLoc Loc =
|
||||
Context.getSourceManager().getPresumedLoc(Decl->getLocation());
|
||||
AvailabilityInfo Availability = getAvailability(Decl);
|
||||
DocComment Comment;
|
||||
if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
|
||||
Comment = RawComment->getFormattedLines(Context.getSourceManager(),
|
||||
Context.getDiagnostics());
|
||||
|
||||
// Build declaration fragments and sub-heading for the struct.
|
||||
DeclarationFragments Declaration =
|
||||
DeclarationFragmentsBuilder::getFragmentsForStruct(Decl);
|
||||
DeclarationFragments SubHeading =
|
||||
DeclarationFragmentsBuilder::getSubHeading(Decl);
|
||||
|
||||
StructRecord *StructRecord = API.addStruct(
|
||||
Name, USR, Loc, Availability, Comment, Declaration, SubHeading);
|
||||
|
||||
// Now collect information about the fields in this struct.
|
||||
recordStructFields(StructRecord, Decl->fields());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Get availability information of the declaration \p D.
|
||||
AvailabilityInfo getAvailability(const Decl *D) const {
|
||||
|
@ -238,6 +273,33 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
/// Collect API information for the struct fields and associate with the
|
||||
/// parent struct.
|
||||
void recordStructFields(StructRecord *StructRecord,
|
||||
const RecordDecl::field_range Fields) {
|
||||
for (const auto *Field : Fields) {
|
||||
// Collect symbol information.
|
||||
StringRef Name = Field->getName();
|
||||
StringRef USR = API.recordUSR(Field);
|
||||
PresumedLoc Loc =
|
||||
Context.getSourceManager().getPresumedLoc(Field->getLocation());
|
||||
AvailabilityInfo Availability = getAvailability(Field);
|
||||
DocComment Comment;
|
||||
if (auto *RawComment = Context.getRawCommentForDeclNoCache(Field))
|
||||
Comment = RawComment->getFormattedLines(Context.getSourceManager(),
|
||||
Context.getDiagnostics());
|
||||
|
||||
// Build declaration fragments and sub-heading for the struct field.
|
||||
DeclarationFragments Declaration =
|
||||
DeclarationFragmentsBuilder::getFragmentsForField(Field);
|
||||
DeclarationFragments SubHeading =
|
||||
DeclarationFragmentsBuilder::getSubHeading(Field);
|
||||
|
||||
API.addStructField(StructRecord, Name, USR, Loc, Availability, Comment,
|
||||
Declaration, SubHeading);
|
||||
}
|
||||
}
|
||||
|
||||
ASTContext &Context;
|
||||
APISet API;
|
||||
};
|
||||
|
|
|
@ -367,6 +367,14 @@ Object serializeSymbolKind(const APIRecord &Record,
|
|||
Kind["identifier"] = AddLangPrefix("enum");
|
||||
Kind["displayName"] = "Enumeration";
|
||||
break;
|
||||
case APIRecord::RK_StructField:
|
||||
Kind["identifier"] = AddLangPrefix("property");
|
||||
Kind["displayName"] = "Instance Property";
|
||||
break;
|
||||
case APIRecord::RK_Struct:
|
||||
Kind["identifier"] = AddLangPrefix("struct");
|
||||
Kind["displayName"] = "Structure";
|
||||
break;
|
||||
}
|
||||
|
||||
return Kind;
|
||||
|
@ -474,6 +482,23 @@ void SymbolGraphSerializer::serializeEnumRecord(const EnumRecord &Record) {
|
|||
}
|
||||
}
|
||||
|
||||
void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) {
|
||||
auto Struct = serializeAPIRecord(Record);
|
||||
if (!Struct)
|
||||
return;
|
||||
|
||||
Symbols.emplace_back(std::move(*Struct));
|
||||
|
||||
for (const auto &Field : Record.Fields) {
|
||||
auto StructField = serializeAPIRecord(*Field);
|
||||
if (!StructField)
|
||||
continue;
|
||||
|
||||
Symbols.emplace_back(std::move(*StructField));
|
||||
serializeRelationship(RelationshipKind::MemberOf, *Field, Record);
|
||||
}
|
||||
}
|
||||
|
||||
Object SymbolGraphSerializer::serialize() {
|
||||
Object Root;
|
||||
serializeObject(Root, "metadata", serializeMetadata());
|
||||
|
@ -487,6 +512,10 @@ Object SymbolGraphSerializer::serialize() {
|
|||
for (const auto &Enum : API.getEnums())
|
||||
serializeEnumRecord(*Enum.second);
|
||||
|
||||
// Serialize struct records in the API set.
|
||||
for (const auto &Struct : API.getStructs())
|
||||
serializeStructRecord(*Struct.second);
|
||||
|
||||
Root["symbols"] = std::move(Symbols);
|
||||
Root["relationhips"] = std::move(Relationships);
|
||||
|
||||
|
|
|
@ -0,0 +1,303 @@
|
|||
// RUN: rm -rf %t
|
||||
// RUN: split-file %s %t
|
||||
// RUN: sed -e "s@INPUT_DIR@%/t@g" %t/reference.output.json.in >> \
|
||||
// RUN: %t/reference.output.json
|
||||
// RUN: %clang -extract-api -target arm64-apple-macosx \
|
||||
// RUN: %t/input.h -o %t/output.json | FileCheck -allow-empty %s
|
||||
|
||||
// Generator version is not consistent across test runs, normalize it.
|
||||
// RUN: sed -e "s@\"generator\": \".*\"@\"generator\": \"?\"@g" \
|
||||
// RUN: %t/output.json >> %t/output-normalized.json
|
||||
// RUN: diff %t/reference.output.json %t/output-normalized.json
|
||||
|
||||
// CHECK-NOT: error:
|
||||
// CHECK-NOT: warning:
|
||||
|
||||
//--- input.h
|
||||
/// Color in RGBA
|
||||
struct Color {
|
||||
unsigned Red;
|
||||
unsigned Green;
|
||||
unsigned Blue;
|
||||
/// Alpha channel for transparency
|
||||
unsigned Alpha;
|
||||
};
|
||||
|
||||
//--- reference.output.json.in
|
||||
{
|
||||
"metadata": {
|
||||
"formatVersion": {
|
||||
"major": 0,
|
||||
"minor": 5,
|
||||
"patch": 3
|
||||
},
|
||||
"generator": "?"
|
||||
},
|
||||
"module": {
|
||||
"name": "",
|
||||
"platform": {
|
||||
"architecture": "arm64",
|
||||
"operatingSystem": {
|
||||
"minimumVersion": {
|
||||
"major": 11,
|
||||
"minor": 0,
|
||||
"patch": 0
|
||||
},
|
||||
"name": "macosx"
|
||||
},
|
||||
"vendor": "apple"
|
||||
}
|
||||
},
|
||||
"relationhips": [
|
||||
{
|
||||
"kind": "memberOf",
|
||||
"source": "c:@S@Color@FI@Red",
|
||||
"target": "c:@S@Color"
|
||||
},
|
||||
{
|
||||
"kind": "memberOf",
|
||||
"source": "c:@S@Color@FI@Green",
|
||||
"target": "c:@S@Color"
|
||||
},
|
||||
{
|
||||
"kind": "memberOf",
|
||||
"source": "c:@S@Color@FI@Blue",
|
||||
"target": "c:@S@Color"
|
||||
},
|
||||
{
|
||||
"kind": "memberOf",
|
||||
"source": "c:@S@Color@FI@Alpha",
|
||||
"target": "c:@S@Color"
|
||||
}
|
||||
],
|
||||
"symbols": [
|
||||
{
|
||||
"declarationFragments": [
|
||||
{
|
||||
"kind": "keyword",
|
||||
"spelling": "struct"
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"spelling": " "
|
||||
},
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Color"
|
||||
}
|
||||
],
|
||||
"docComment": {
|
||||
"lines": [
|
||||
{
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 18,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 5,
|
||||
"line": 1
|
||||
}
|
||||
},
|
||||
"text": "Color in RGBA"
|
||||
}
|
||||
]
|
||||
},
|
||||
"identifier": {
|
||||
"interfaceLanguage": "c",
|
||||
"precise": "c:@S@Color"
|
||||
},
|
||||
"kind": {
|
||||
"displayName": "Structure",
|
||||
"identifier": "c.struct"
|
||||
},
|
||||
"location": {
|
||||
"character": 8,
|
||||
"line": 2,
|
||||
"uri": "file://INPUT_DIR/input.h"
|
||||
},
|
||||
"names": {
|
||||
"subHeading": [
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Color"
|
||||
}
|
||||
],
|
||||
"title": "Color"
|
||||
}
|
||||
},
|
||||
{
|
||||
"declarationFragments": [
|
||||
{
|
||||
"kind": "typeIdentifier",
|
||||
"preciseIdentifier": "c:i",
|
||||
"spelling": "unsigned int"
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"spelling": " "
|
||||
},
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Red"
|
||||
}
|
||||
],
|
||||
"identifier": {
|
||||
"interfaceLanguage": "c",
|
||||
"precise": "c:@S@Color@FI@Red"
|
||||
},
|
||||
"kind": {
|
||||
"displayName": "Instance Property",
|
||||
"identifier": "c.property"
|
||||
},
|
||||
"location": {
|
||||
"character": 12,
|
||||
"line": 3,
|
||||
"uri": "file://INPUT_DIR/input.h"
|
||||
},
|
||||
"names": {
|
||||
"subHeading": [
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Red"
|
||||
}
|
||||
],
|
||||
"title": "Red"
|
||||
}
|
||||
},
|
||||
{
|
||||
"declarationFragments": [
|
||||
{
|
||||
"kind": "typeIdentifier",
|
||||
"preciseIdentifier": "c:i",
|
||||
"spelling": "unsigned int"
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"spelling": " "
|
||||
},
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Green"
|
||||
}
|
||||
],
|
||||
"identifier": {
|
||||
"interfaceLanguage": "c",
|
||||
"precise": "c:@S@Color@FI@Green"
|
||||
},
|
||||
"kind": {
|
||||
"displayName": "Instance Property",
|
||||
"identifier": "c.property"
|
||||
},
|
||||
"location": {
|
||||
"character": 12,
|
||||
"line": 4,
|
||||
"uri": "file://INPUT_DIR/input.h"
|
||||
},
|
||||
"names": {
|
||||
"subHeading": [
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Green"
|
||||
}
|
||||
],
|
||||
"title": "Green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"declarationFragments": [
|
||||
{
|
||||
"kind": "typeIdentifier",
|
||||
"preciseIdentifier": "c:i",
|
||||
"spelling": "unsigned int"
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"spelling": " "
|
||||
},
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Blue"
|
||||
}
|
||||
],
|
||||
"identifier": {
|
||||
"interfaceLanguage": "c",
|
||||
"precise": "c:@S@Color@FI@Blue"
|
||||
},
|
||||
"kind": {
|
||||
"displayName": "Instance Property",
|
||||
"identifier": "c.property"
|
||||
},
|
||||
"location": {
|
||||
"character": 12,
|
||||
"line": 5,
|
||||
"uri": "file://INPUT_DIR/input.h"
|
||||
},
|
||||
"names": {
|
||||
"subHeading": [
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Blue"
|
||||
}
|
||||
],
|
||||
"title": "Blue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"declarationFragments": [
|
||||
{
|
||||
"kind": "typeIdentifier",
|
||||
"preciseIdentifier": "c:i",
|
||||
"spelling": "unsigned int"
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"spelling": " "
|
||||
},
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Alpha"
|
||||
}
|
||||
],
|
||||
"docComment": {
|
||||
"lines": [
|
||||
{
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 37,
|
||||
"line": 6
|
||||
},
|
||||
"start": {
|
||||
"character": 7,
|
||||
"line": 6
|
||||
}
|
||||
},
|
||||
"text": "Alpha channel for transparency"
|
||||
}
|
||||
]
|
||||
},
|
||||
"identifier": {
|
||||
"interfaceLanguage": "c",
|
||||
"precise": "c:@S@Color@FI@Alpha"
|
||||
},
|
||||
"kind": {
|
||||
"displayName": "Instance Property",
|
||||
"identifier": "c.property"
|
||||
},
|
||||
"location": {
|
||||
"character": 12,
|
||||
"line": 7,
|
||||
"uri": "file://INPUT_DIR/input.h"
|
||||
},
|
||||
"names": {
|
||||
"subHeading": [
|
||||
{
|
||||
"kind": "identifier",
|
||||
"spelling": "Alpha"
|
||||
}
|
||||
],
|
||||
"title": "Alpha"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue