[clang][extract-api] Add Objective-C interface support

Add support for Objective-C interface declarations in ExtractAPI.

Depends on D122495

Differential Revision: https://reviews.llvm.org/D122446
This commit is contained in:
Zixu Wang 2022-03-24 18:19:30 -07:00
parent 686dcbe8b0
commit 9b36e126fd
8 changed files with 1147 additions and 19 deletions

View File

@ -19,6 +19,7 @@
#define LLVM_CLANG_EXTRACTAPI_API_H
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/RawCommentList.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/ExtractAPI/AvailabilityInfo.h"
@ -77,6 +78,10 @@ struct APIRecord {
RK_Enum,
RK_StructField,
RK_Struct,
RK_ObjCProperty,
RK_ObjCIvar,
RK_ObjCMethod,
RK_ObjCInterface,
};
private:
@ -201,6 +206,154 @@ private:
virtual void anchor();
};
/// This holds information associated with Objective-C properties.
struct ObjCPropertyRecord : APIRecord {
/// The attributes associated with an Objective-C property.
enum AttributeKind : unsigned {
NoAttr = 0,
ReadOnly = 1,
Class = 1 << 1,
Dynamic = 1 << 2,
};
AttributeKind Attributes;
StringRef GetterName;
StringRef SetterName;
bool IsOptional;
ObjCPropertyRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
const AvailabilityInfo &Availability,
const DocComment &Comment,
DeclarationFragments Declaration,
DeclarationFragments SubHeading, AttributeKind Attributes,
StringRef GetterName, StringRef SetterName,
bool IsOptional)
: APIRecord(RK_ObjCProperty, Name, USR, Loc, Availability,
LinkageInfo::none(), Comment, Declaration, SubHeading),
Attributes(Attributes), GetterName(GetterName), SetterName(SetterName),
IsOptional(IsOptional) {}
bool isReadOnly() const { return Attributes & ReadOnly; }
bool isDynamic() const { return Attributes & Dynamic; }
bool isClassProperty() const { return Attributes & Class; }
static bool classof(const APIRecord *Record) {
return Record->getKind() == RK_ObjCProperty;
}
private:
virtual void anchor();
};
/// This holds information associated with Objective-C instance variables.
struct ObjCInstanceVariableRecord : APIRecord {
using AccessControl = ObjCIvarDecl::AccessControl;
AccessControl Access;
ObjCInstanceVariableRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
const AvailabilityInfo &Availability,
const DocComment &Comment,
DeclarationFragments Declaration,
DeclarationFragments SubHeading,
AccessControl Access)
: APIRecord(RK_ObjCIvar, Name, USR, Loc, Availability,
LinkageInfo::none(), Comment, Declaration, SubHeading),
Access(Access) {}
static bool classof(const APIRecord *Record) {
return Record->getKind() == RK_ObjCIvar;
}
private:
virtual void anchor();
};
/// This holds information associated with Objective-C methods.
struct ObjCMethodRecord : APIRecord {
FunctionSignature Signature;
bool IsInstanceMethod;
ObjCMethodRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
const AvailabilityInfo &Availability,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading, FunctionSignature Signature,
bool IsInstanceMethod)
: APIRecord(RK_ObjCMethod, Name, USR, Loc, Availability,
LinkageInfo::none(), Comment, Declaration, SubHeading),
Signature(Signature), IsInstanceMethod(IsInstanceMethod) {}
static bool classof(const APIRecord *Record) {
return Record->getKind() == RK_ObjCMethod;
}
private:
virtual void anchor();
};
/// This represents a reference to another symbol that might come from external
/// sources.
struct SymbolReference {
StringRef Name;
StringRef USR;
/// The source project/module/product of the referred symbol.
StringRef Source;
SymbolReference() = default;
SymbolReference(StringRef Name, StringRef USR = "", StringRef Source = "")
: Name(Name), USR(USR), Source(Source) {}
SymbolReference(const APIRecord &Record)
: Name(Record.Name), USR(Record.USR) {}
/// Determine if this SymbolReference is empty.
///
/// \returns true if and only if all \c Name, \c USR, and \c Source is empty.
bool empty() const { return Name.empty() && USR.empty() && Source.empty(); }
};
/// The base representation of an Objective-C container record. Holds common
/// information associated with Objective-C containers.
struct ObjCContainerRecord : APIRecord {
SmallVector<std::unique_ptr<ObjCMethodRecord>> Methods;
SmallVector<std::unique_ptr<ObjCPropertyRecord>> Properties;
SmallVector<std::unique_ptr<ObjCInstanceVariableRecord>> Ivars;
SmallVector<SymbolReference> Protocols;
ObjCContainerRecord() = delete;
ObjCContainerRecord(RecordKind Kind, StringRef Name, StringRef USR,
PresumedLoc Loc, const AvailabilityInfo &Availability,
LinkageInfo Linkage, const DocComment &Comment,
DeclarationFragments Declaration,
DeclarationFragments SubHeading)
: APIRecord(Kind, Name, USR, Loc, Availability, Linkage, Comment,
Declaration, SubHeading) {}
virtual ~ObjCContainerRecord() = 0;
};
/// This holds information associated with Objective-C interfaces/classes.
struct ObjCInterfaceRecord : ObjCContainerRecord {
SymbolReference SuperClass;
ObjCInterfaceRecord(StringRef Name, StringRef USR, PresumedLoc Loc,
const AvailabilityInfo &Availability, LinkageInfo Linkage,
const DocComment &Comment,
DeclarationFragments Declaration,
DeclarationFragments SubHeading,
SymbolReference SuperClass)
: ObjCContainerRecord(RK_ObjCInterface, Name, USR, Loc, Availability,
Linkage, Comment, Declaration, SubHeading),
SuperClass(SuperClass) {}
static bool classof(const APIRecord *Record) {
return Record->getKind() == RK_ObjCInterface;
}
private:
virtual void anchor();
};
/// APISet holds the set of API records collected from given inputs.
class APISet {
public:
@ -292,6 +445,58 @@ public:
DeclarationFragments Declaration,
DeclarationFragments SubHeading);
/// Create and add an Objective-C interface 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.
ObjCInterfaceRecord *
addObjCInterface(StringRef Name, StringRef USR, PresumedLoc Loc,
const AvailabilityInfo &Availability, LinkageInfo Linkage,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading, SymbolReference SuperClass);
/// Create and add an Objective-C method 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.
ObjCMethodRecord *
addObjCMethod(ObjCContainerRecord *Container, StringRef Name, StringRef USR,
PresumedLoc Loc, const AvailabilityInfo &Availability,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading, FunctionSignature Signature,
bool IsInstanceMethod);
/// Create and add an Objective-C property 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.
ObjCPropertyRecord *
addObjCProperty(ObjCContainerRecord *Container, StringRef Name, StringRef USR,
PresumedLoc Loc, const AvailabilityInfo &Availability,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading,
ObjCPropertyRecord::AttributeKind Attributes,
StringRef GetterName, StringRef SetterName, bool IsOptional);
/// Create and add an Objective-C instance variable 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.
ObjCInstanceVariableRecord *addObjCInstanceVariable(
ObjCContainerRecord *Container, StringRef Name, StringRef USR,
PresumedLoc Loc, const AvailabilityInfo &Availability,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading,
ObjCInstanceVariableRecord::AccessControl Access);
/// A map to store the set of GlobalRecord%s with the declaration name as the
/// key.
using GlobalRecordMap =
@ -306,6 +511,11 @@ public:
using StructRecordMap =
llvm::MapVector<StringRef, std::unique_ptr<StructRecord>>;
/// A map to store the set of ObjCInterfaceRecord%s with the declaration name
/// as the key.
using ObjCInterfaceRecordMap =
llvm::MapVector<StringRef, std::unique_ptr<ObjCInterfaceRecord>>;
/// Get the target triple for the ExtractAPI invocation.
const llvm::Triple &getTarget() const { return Target; }
@ -315,6 +525,9 @@ public:
const GlobalRecordMap &getGlobals() const { return Globals; }
const EnumRecordMap &getEnums() const { return Enums; }
const StructRecordMap &getStructs() const { return Structs; }
const ObjCInterfaceRecordMap &getObjCInterfaces() const {
return ObjCInterfaces;
}
/// Generate and store the USR of declaration \p D.
///
@ -343,6 +556,7 @@ private:
GlobalRecordMap Globals;
EnumRecordMap Enums;
StructRecordMap Structs;
ObjCInterfaceRecordMap ObjCInterfaces;
};
} // namespace extractapi

View File

@ -21,6 +21,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjC.h"
#include "llvm/ADT/StringRef.h"
#include <vector>
@ -202,11 +203,34 @@ public:
/// Build DeclarationFragments for a struct record declaration RecordDecl.
static DeclarationFragments getFragmentsForStruct(const RecordDecl *);
/// Build DeclarationFragments for an Objective-C interface declaration
/// ObjCInterfaceDecl.
static DeclarationFragments
getFragmentsForObjCInterface(const ObjCInterfaceDecl *);
/// Build DeclarationFragments for an Objective-C method declaration
/// ObjCMethodDecl.
static DeclarationFragments getFragmentsForObjCMethod(const ObjCMethodDecl *);
/// Build DeclarationFragments for an Objective-C property declaration
/// ObjCPropertyDecl.
static DeclarationFragments
getFragmentsForObjCProperty(const ObjCPropertyDecl *);
/// Build sub-heading fragments for a NamedDecl.
static DeclarationFragments getSubHeading(const NamedDecl *);
/// Build FunctionSignature for a function declaration FunctionDecl.
static FunctionSignature getFunctionSignature(const FunctionDecl *);
/// Build sub-heading fragments for an Objective-C method.
static DeclarationFragments getSubHeading(const ObjCMethodDecl *);
/// Build FunctionSignature for a function-like declaration \c FunctionT like
/// FunctionDecl or ObjCMethodDecl.
///
/// The logic and implementation of building a signature for a FunctionDecl
/// and an ObjCMethodDecl are exactly the same, but they do not share a common
/// base. This template helps reuse the code.
template <typename FunctionT>
static FunctionSignature getFunctionSignature(const FunctionT *);
private:
DeclarationFragmentsBuilder() = delete;

View File

@ -62,6 +62,13 @@ public:
/// For example enum constants are members of the enum, class/instance
/// methods are members of the class, etc.
MemberOf,
/// The source symbol is inherited from the target symbol.
InheritsFrom,
/// The source symbol conforms to the target symbol.
/// For example Objective-C protocol conformances.
ConformsTo,
};
/// Get the string representation of the relationship kind.
@ -101,8 +108,8 @@ private:
///
/// Record the relationship between the two symbols in
/// SymbolGraphSerializer::Relationships.
void serializeRelationship(RelationshipKind Kind, const APIRecord &Source,
const APIRecord &Target);
void serializeRelationship(RelationshipKind Kind, SymbolReference Source,
SymbolReference Target);
/// Serialize a global record.
void serializeGlobalRecord(const GlobalRecord &Record);
@ -113,6 +120,9 @@ private:
/// Serialize a struct record.
void serializeStructRecord(const StructRecord &Record);
/// Serialize an Objective-C container record.
void serializeObjCContainerRecord(const ObjCContainerRecord &Record);
public:
SymbolGraphSerializer(const APISet &API, StringRef ProductName,
APISerializerOption Options = {})

View File

@ -109,6 +109,58 @@ StructRecord *APISet::addStruct(StringRef Name, StringRef USR, PresumedLoc Loc,
return Result.first->second.get();
}
ObjCInterfaceRecord *APISet::addObjCInterface(
StringRef Name, StringRef USR, PresumedLoc Loc,
const AvailabilityInfo &Availability, LinkageInfo Linkage,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading, SymbolReference SuperClass) {
auto Result = ObjCInterfaces.insert({Name, nullptr});
if (Result.second) {
// Create the record if it does not already exist.
auto Record = std::make_unique<ObjCInterfaceRecord>(
Name, USR, Loc, Availability, Linkage, Comment, Declaration, SubHeading,
SuperClass);
Result.first->second = std::move(Record);
}
return Result.first->second.get();
}
ObjCMethodRecord *APISet::addObjCMethod(
ObjCContainerRecord *Container, StringRef Name, StringRef USR,
PresumedLoc Loc, const AvailabilityInfo &Availability,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading, FunctionSignature Signature,
bool IsInstanceMethod) {
auto Record = std::make_unique<ObjCMethodRecord>(
Name, USR, Loc, Availability, Comment, Declaration, SubHeading, Signature,
IsInstanceMethod);
return Container->Methods.emplace_back(std::move(Record)).get();
}
ObjCPropertyRecord *APISet::addObjCProperty(
ObjCContainerRecord *Container, StringRef Name, StringRef USR,
PresumedLoc Loc, const AvailabilityInfo &Availability,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading,
ObjCPropertyRecord::AttributeKind Attributes, StringRef GetterName,
StringRef SetterName, bool IsOptional) {
auto Record = std::make_unique<ObjCPropertyRecord>(
Name, USR, Loc, Availability, Comment, Declaration, SubHeading,
Attributes, GetterName, SetterName, IsOptional);
return Container->Properties.emplace_back(std::move(Record)).get();
}
ObjCInstanceVariableRecord *APISet::addObjCInstanceVariable(
ObjCContainerRecord *Container, StringRef Name, StringRef USR,
PresumedLoc Loc, const AvailabilityInfo &Availability,
const DocComment &Comment, DeclarationFragments Declaration,
DeclarationFragments SubHeading,
ObjCInstanceVariableRecord::AccessControl Access) {
auto Record = std::make_unique<ObjCInstanceVariableRecord>(
Name, USR, Loc, Availability, Comment, Declaration, SubHeading, Access);
return Container->Ivars.emplace_back(std::move(Record)).get();
}
StringRef APISet::recordUSR(const Decl *D) {
SmallString<128> USR;
index::generateUSRForDecl(D, USR);
@ -130,8 +182,14 @@ StringRef APISet::copyString(StringRef String) {
APIRecord::~APIRecord() {}
ObjCContainerRecord::~ObjCContainerRecord() {}
void GlobalRecord::anchor() {}
void EnumConstantRecord::anchor() {}
void EnumRecord::anchor() {}
void StructFieldRecord::anchor() {}
void StructRecord::anchor() {}
void ObjCPropertyRecord::anchor() {}
void ObjCInstanceVariableRecord::anchor() {}
void ObjCMethodRecord::anchor() {}
void ObjCInterfaceRecord::anchor() {}

View File

@ -245,6 +245,22 @@ DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForType(
// unqualified base type.
QualType Base = T->getCanonicalTypeUnqualified();
// Render Objective-C `id`/`instancetype` as keywords.
if (T->isObjCIdType())
return Fragments.append(Base.getAsString(),
DeclarationFragments::FragmentKind::Keyword);
// If the base type is an ObjCInterfaceType, use the underlying
// ObjCInterfaceDecl for the true USR.
if (const auto *ObjCIT = dyn_cast<ObjCInterfaceType>(Base)) {
const auto *Decl = ObjCIT->getDecl();
SmallString<128> USR;
index::generateUSRForDecl(Decl, USR);
return Fragments.append(Decl->getName(),
DeclarationFragments::FragmentKind::TypeIdentifier,
USR);
}
// Default fragment builder for other kinds of types (BuiltinType etc.)
SmallString<128> USR;
clang::index::generateUSRForType(Base, Context, USR);
@ -454,27 +470,183 @@ DeclarationFragmentsBuilder::getFragmentsForStruct(const RecordDecl *Record) {
return Fragments;
}
FunctionSignature
DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *Func) {
FunctionSignature Signature;
DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCInterface(
const ObjCInterfaceDecl *Interface) {
DeclarationFragments Fragments;
// Build the base of the Objective-C interface declaration.
Fragments.append("@interface", DeclarationFragments::FragmentKind::Keyword)
.appendSpace()
.append(Interface->getName(),
DeclarationFragments::FragmentKind::Identifier);
for (const auto *Param : Func->parameters()) {
StringRef Name = Param->getName();
DeclarationFragments Fragments = getFragmentsForParam(Param);
Signature.addParameter(Name, Fragments);
// Build the inheritance part of the declaration.
if (const ObjCInterfaceDecl *SuperClass = Interface->getSuperClass()) {
SmallString<128> SuperUSR;
index::generateUSRForDecl(SuperClass, SuperUSR);
Fragments.append(" : ", DeclarationFragments::FragmentKind::Text)
.append(SuperClass->getName(),
DeclarationFragments::FragmentKind::TypeIdentifier, SuperUSR);
}
DeclarationFragments After;
DeclarationFragments Returns =
getFragmentsForType(Func->getReturnType(), Func->getASTContext(), After)
.append(std::move(After));
return Fragments;
}
Signature.setReturnType(Returns);
DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCMethod(
const ObjCMethodDecl *Method) {
DeclarationFragments Fragments, After;
// Build the instance/class method indicator.
if (Method->isClassMethod())
Fragments.append("+ ", DeclarationFragments::FragmentKind::Text);
else if (Method->isInstanceMethod())
Fragments.append("- ", DeclarationFragments::FragmentKind::Text);
// Build the return type.
Fragments.append("(", DeclarationFragments::FragmentKind::Text)
.append(getFragmentsForType(Method->getReturnType(),
Method->getASTContext(), After))
.append(std::move(After))
.append(")", DeclarationFragments::FragmentKind::Text);
// Build the selector part.
Selector Selector = Method->getSelector();
if (Selector.getNumArgs() == 0)
// For Objective-C methods that don't take arguments, the first (and only)
// slot of the selector is the method name.
Fragments.appendSpace().append(
Selector.getNameForSlot(0),
DeclarationFragments::FragmentKind::Identifier);
// For Objective-C methods that take arguments, build the selector slots.
for (unsigned i = 0, end = Method->param_size(); i != end; ++i) {
Fragments.appendSpace()
.append(Selector.getNameForSlot(i),
// The first slot is the name of the method, record as an
// identifier, otherwise as exteranl parameters.
i == 0 ? DeclarationFragments::FragmentKind::Identifier
: DeclarationFragments::FragmentKind::ExternalParam)
.append(":", DeclarationFragments::FragmentKind::Text);
// Build the internal parameter.
const ParmVarDecl *Param = Method->getParamDecl(i);
Fragments.append(getFragmentsForParam(Param));
}
return Fragments;
}
DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForObjCProperty(
const ObjCPropertyDecl *Property) {
DeclarationFragments Fragments, After;
// Build the Objective-C property keyword.
Fragments.append("@property", DeclarationFragments::FragmentKind::Keyword);
const auto Attributes = Property->getPropertyAttributes();
// Build the attributes if there is any associated with the property.
if (Attributes != ObjCPropertyAttribute::kind_noattr) {
// No leading comma for the first attribute.
bool First = true;
Fragments.append(" (", DeclarationFragments::FragmentKind::Text);
// Helper function to render the attribute.
auto RenderAttribute =
[&](ObjCPropertyAttribute::Kind Kind, StringRef Spelling,
StringRef Arg = "",
DeclarationFragments::FragmentKind ArgKind =
DeclarationFragments::FragmentKind::Identifier) {
// Check if the `Kind` attribute is set for this property.
if ((Attributes & Kind) && !Spelling.empty()) {
// Add a leading comma if this is not the first attribute rendered.
if (!First)
Fragments.append(", ", DeclarationFragments::FragmentKind::Text);
// Render the spelling of this attribute `Kind` as a keyword.
Fragments.append(Spelling,
DeclarationFragments::FragmentKind::Keyword);
// If this attribute takes in arguments (e.g. `getter=getterName`),
// render the arguments.
if (!Arg.empty())
Fragments.append("=", DeclarationFragments::FragmentKind::Text)
.append(Arg, ArgKind);
First = false;
}
};
// Go through all possible Objective-C property attributes and render set
// ones.
RenderAttribute(ObjCPropertyAttribute::kind_class, "class");
RenderAttribute(ObjCPropertyAttribute::kind_direct, "direct");
RenderAttribute(ObjCPropertyAttribute::kind_nonatomic, "nonatomic");
RenderAttribute(ObjCPropertyAttribute::kind_atomic, "atomic");
RenderAttribute(ObjCPropertyAttribute::kind_assign, "assign");
RenderAttribute(ObjCPropertyAttribute::kind_retain, "retain");
RenderAttribute(ObjCPropertyAttribute::kind_strong, "strong");
RenderAttribute(ObjCPropertyAttribute::kind_copy, "copy");
RenderAttribute(ObjCPropertyAttribute::kind_weak, "weak");
RenderAttribute(ObjCPropertyAttribute::kind_unsafe_unretained,
"unsafe_unretained");
RenderAttribute(ObjCPropertyAttribute::kind_readwrite, "readwrite");
RenderAttribute(ObjCPropertyAttribute::kind_readonly, "readonly");
RenderAttribute(ObjCPropertyAttribute::kind_getter, "getter",
Property->getGetterName().getAsString());
RenderAttribute(ObjCPropertyAttribute::kind_setter, "setter",
Property->getSetterName().getAsString());
// Render nullability attributes.
if (Attributes & ObjCPropertyAttribute::kind_nullability) {
QualType Type = Property->getType();
if (const auto Nullability =
AttributedType::stripOuterNullability(Type)) {
if (!First)
Fragments.append(", ", DeclarationFragments::FragmentKind::Text);
if (*Nullability == NullabilityKind::Unspecified &&
(Attributes & ObjCPropertyAttribute::kind_null_resettable))
Fragments.append("null_resettable",
DeclarationFragments::FragmentKind::Keyword);
else
Fragments.append(
getNullabilitySpelling(*Nullability, /*isContextSensitive=*/true),
DeclarationFragments::FragmentKind::Keyword);
First = false;
}
}
Fragments.append(")", DeclarationFragments::FragmentKind::Text);
}
// Build the property type and name, and return the completed fragments.
return Fragments.appendSpace()
.append(getFragmentsForType(Property->getType(),
Property->getASTContext(), After))
.append(Property->getName(),
DeclarationFragments::FragmentKind::Identifier)
.append(std::move(After));
}
template <typename FunctionT>
FunctionSignature
DeclarationFragmentsBuilder::getFunctionSignature(const FunctionT *Function) {
FunctionSignature Signature;
DeclarationFragments ReturnType, After;
ReturnType
.append(getFragmentsForType(Function->getReturnType(),
Function->getASTContext(), After))
.append(std::move(After));
Signature.setReturnType(ReturnType);
for (const auto *Param : Function->parameters())
Signature.addParameter(Param->getName(), getFragmentsForParam(Param));
return Signature;
}
// Instantiate template for FunctionDecl.
template FunctionSignature
DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *);
// Instantiate template for ObjCMethodDecl.
template FunctionSignature
DeclarationFragmentsBuilder::getFunctionSignature(const ObjCMethodDecl *);
// Subheading of a symbol defaults to its name.
DeclarationFragments
DeclarationFragmentsBuilder::getSubHeading(const NamedDecl *Decl) {
@ -484,3 +656,17 @@ DeclarationFragmentsBuilder::getSubHeading(const NamedDecl *Decl) {
DeclarationFragments::FragmentKind::Identifier);
return Fragments;
}
// Subheading of an Objective-C method is a `+` or `-` sign indicating whether
// it's a class method or an instance method, followed by the selector name.
DeclarationFragments
DeclarationFragmentsBuilder::getSubHeading(const ObjCMethodDecl *Method) {
DeclarationFragments Fragments;
if (Method->isClassMethod())
Fragments.append("+ ", DeclarationFragments::FragmentKind::Text);
else if (Method->isInstanceMethod())
Fragments.append("- ", DeclarationFragments::FragmentKind::Text);
return Fragments.append(Method->getNameAsString(),
DeclarationFragments::FragmentKind::Identifier);
}

View File

@ -216,6 +216,50 @@ public:
return true;
}
bool VisitObjCInterfaceDecl(const ObjCInterfaceDecl *Decl) {
// Skip forward declaration for classes (@class)
if (!Decl->isThisDeclarationADefinition())
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);
LinkageInfo Linkage = Decl->getLinkageAndVisibility();
DocComment Comment;
if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
Comment = RawComment->getFormattedLines(Context.getSourceManager(),
Context.getDiagnostics());
// Build declaration fragments and sub-heading for the interface.
DeclarationFragments Declaration =
DeclarationFragmentsBuilder::getFragmentsForObjCInterface(Decl);
DeclarationFragments SubHeading =
DeclarationFragmentsBuilder::getSubHeading(Decl);
// Collect super class information.
SymbolReference SuperClass;
if (const auto *SuperClassDecl = Decl->getSuperClass()) {
SuperClass.Name = SuperClassDecl->getObjCRuntimeNameAsString();
SuperClass.USR = API.recordUSR(SuperClassDecl);
}
ObjCInterfaceRecord *ObjCInterfaceRecord =
API.addObjCInterface(Name, USR, Loc, Availability, Linkage, Comment,
Declaration, SubHeading, SuperClass);
// Record all methods (selectors). This doesn't include automatically
// synthesized property methods.
recordObjCMethods(ObjCInterfaceRecord, Decl->methods());
recordObjCProperties(ObjCInterfaceRecord, Decl->properties());
recordObjCInstanceVariables(ObjCInterfaceRecord, Decl->ivars());
recordObjCProtocols(ObjCInterfaceRecord, Decl->protocols());
return true;
}
private:
/// Get availability information of the declaration \p D.
AvailabilityInfo getAvailability(const Decl *D) const {
@ -302,6 +346,116 @@ private:
}
}
/// Collect API information for the Objective-C methods and associate with the
/// parent container.
void recordObjCMethods(ObjCContainerRecord *Container,
const ObjCContainerDecl::method_range Methods) {
for (const auto *Method : Methods) {
// Don't record selectors for properties.
if (Method->isPropertyAccessor())
continue;
StringRef Name = API.copyString(Method->getSelector().getAsString());
StringRef USR = API.recordUSR(Method);
PresumedLoc Loc =
Context.getSourceManager().getPresumedLoc(Method->getLocation());
AvailabilityInfo Availability = getAvailability(Method);
DocComment Comment;
if (auto *RawComment = Context.getRawCommentForDeclNoCache(Method))
Comment = RawComment->getFormattedLines(Context.getSourceManager(),
Context.getDiagnostics());
// Build declaration fragments, sub-heading, and signature for the method.
DeclarationFragments Declaration =
DeclarationFragmentsBuilder::getFragmentsForObjCMethod(Method);
DeclarationFragments SubHeading =
DeclarationFragmentsBuilder::getSubHeading(Method);
FunctionSignature Signature =
DeclarationFragmentsBuilder::getFunctionSignature(Method);
API.addObjCMethod(Container, Name, USR, Loc, Availability, Comment,
Declaration, SubHeading, Signature,
Method->isInstanceMethod());
}
}
void recordObjCProperties(ObjCContainerRecord *Container,
const ObjCContainerDecl::prop_range Properties) {
for (const auto *Property : Properties) {
StringRef Name = Property->getName();
StringRef USR = API.recordUSR(Property);
PresumedLoc Loc =
Context.getSourceManager().getPresumedLoc(Property->getLocation());
AvailabilityInfo Availability = getAvailability(Property);
DocComment Comment;
if (auto *RawComment = Context.getRawCommentForDeclNoCache(Property))
Comment = RawComment->getFormattedLines(Context.getSourceManager(),
Context.getDiagnostics());
// Build declaration fragments and sub-heading for the property.
DeclarationFragments Declaration =
DeclarationFragmentsBuilder::getFragmentsForObjCProperty(Property);
DeclarationFragments SubHeading =
DeclarationFragmentsBuilder::getSubHeading(Property);
StringRef GetterName =
API.copyString(Property->getGetterName().getAsString());
StringRef SetterName =
API.copyString(Property->getSetterName().getAsString());
// Get the attributes for property.
unsigned Attributes = ObjCPropertyRecord::NoAttr;
if (Property->getPropertyAttributes() &
ObjCPropertyAttribute::kind_readonly)
Attributes |= ObjCPropertyRecord::ReadOnly;
if (Property->getPropertyAttributes() & ObjCPropertyAttribute::kind_class)
Attributes |= ObjCPropertyRecord::Class;
API.addObjCProperty(
Container, Name, USR, Loc, Availability, Comment, Declaration,
SubHeading,
static_cast<ObjCPropertyRecord::AttributeKind>(Attributes),
GetterName, SetterName, Property->isOptional());
}
}
void recordObjCInstanceVariables(
ObjCContainerRecord *Container,
const llvm::iterator_range<
DeclContext::specific_decl_iterator<ObjCIvarDecl>>
Ivars) {
for (const auto *Ivar : Ivars) {
StringRef Name = Ivar->getName();
StringRef USR = API.recordUSR(Ivar);
PresumedLoc Loc =
Context.getSourceManager().getPresumedLoc(Ivar->getLocation());
AvailabilityInfo Availability = getAvailability(Ivar);
DocComment Comment;
if (auto *RawComment = Context.getRawCommentForDeclNoCache(Ivar))
Comment = RawComment->getFormattedLines(Context.getSourceManager(),
Context.getDiagnostics());
// Build declaration fragments and sub-heading for the instance variable.
DeclarationFragments Declaration =
DeclarationFragmentsBuilder::getFragmentsForField(Ivar);
DeclarationFragments SubHeading =
DeclarationFragmentsBuilder::getSubHeading(Ivar);
ObjCInstanceVariableRecord::AccessControl Access =
Ivar->getCanonicalAccessControl();
API.addObjCInstanceVariable(Container, Name, USR, Loc, Availability,
Comment, Declaration, SubHeading, Access);
}
}
void recordObjCProtocols(ObjCContainerRecord *Container,
ObjCInterfaceDecl::protocol_range Protocols) {
for (const auto *Protocol : Protocols)
Container->Protocols.emplace_back(Protocol->getName(),
API.recordUSR(Protocol));
}
ASTContext &Context;
APISet API;
};

View File

@ -372,6 +372,27 @@ Object serializeSymbolKind(const APIRecord &Record, Language Lang) {
Kind["identifier"] = AddLangPrefix("struct");
Kind["displayName"] = "Structure";
break;
case APIRecord::RK_ObjCIvar:
Kind["identifier"] = AddLangPrefix("ivar");
Kind["displayName"] = "Instance Variable";
break;
case APIRecord::RK_ObjCMethod:
if (dyn_cast<ObjCMethodRecord>(&Record)->IsInstanceMethod) {
Kind["identifier"] = AddLangPrefix("method");
Kind["displayName"] = "Instance Method";
} else {
Kind["identifier"] = AddLangPrefix("type.method");
Kind["displayName"] = "Type Method";
}
break;
case APIRecord::RK_ObjCProperty:
Kind["identifier"] = AddLangPrefix("property");
Kind["displayName"] = "Instance Property";
break;
case APIRecord::RK_ObjCInterface:
Kind["identifier"] = AddLangPrefix("class");
Kind["displayName"] = "Class";
break;
}
return Kind;
@ -435,13 +456,17 @@ StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) {
switch (Kind) {
case RelationshipKind::MemberOf:
return "memberOf";
case RelationshipKind::InheritsFrom:
return "inheritsFrom";
case RelationshipKind::ConformsTo:
return "conformsTo";
}
llvm_unreachable("Unhandled relationship kind");
}
void SymbolGraphSerializer::serializeRelationship(RelationshipKind Kind,
const APIRecord &Source,
const APIRecord &Target) {
SymbolReference Source,
SymbolReference Target) {
Object Relationship;
Relationship["source"] = Source.USR;
Relationship["target"] = Target.USR;
@ -496,6 +521,57 @@ void SymbolGraphSerializer::serializeStructRecord(const StructRecord &Record) {
}
}
void SymbolGraphSerializer::serializeObjCContainerRecord(
const ObjCContainerRecord &Record) {
auto ObjCContainer = serializeAPIRecord(Record);
if (!ObjCContainer)
return;
Symbols.emplace_back(std::move(*ObjCContainer));
// Record instance variables and that the instance variables are members of
// the container.
for (const auto &Ivar : Record.Ivars) {
auto ObjCIvar = serializeAPIRecord(*Ivar);
if (!ObjCIvar)
continue;
Symbols.emplace_back(std::move(*ObjCIvar));
serializeRelationship(RelationshipKind::MemberOf, *Ivar, Record);
}
// Record methods and that the methods are members of the container.
for (const auto &Method : Record.Methods) {
auto ObjCMethod = serializeAPIRecord(*Method);
if (!ObjCMethod)
continue;
Symbols.emplace_back(std::move(*ObjCMethod));
serializeRelationship(RelationshipKind::MemberOf, *Method, Record);
}
// Record properties and that the properties are members of the container.
for (const auto &Property : Record.Properties) {
auto ObjCProperty = serializeAPIRecord(*Property);
if (!ObjCProperty)
continue;
Symbols.emplace_back(std::move(*ObjCProperty));
serializeRelationship(RelationshipKind::MemberOf, *Property, Record);
}
for (const auto &Protocol : Record.Protocols)
// Record that Record conforms to Protocol.
serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol);
if (auto *ObjCInterface = dyn_cast<ObjCInterfaceRecord>(&Record))
if (!ObjCInterface->SuperClass.empty())
// If Record is an Objective-C interface record and it has a super class,
// record that Record is inherited from SuperClass.
serializeRelationship(RelationshipKind::InheritsFrom, Record,
ObjCInterface->SuperClass);
}
Object SymbolGraphSerializer::serialize() {
Object Root;
serializeObject(Root, "metadata", serializeMetadata());
@ -513,6 +589,10 @@ Object SymbolGraphSerializer::serialize() {
for (const auto &Struct : API.getStructs())
serializeStructRecord(*Struct.second);
// Serialize Objective-C interface records in the API set.
for (const auto &ObjCInterface : API.getObjCInterfaces())
serializeObjCContainerRecord(*ObjCInterface.second);
Root["symbols"] = std::move(Symbols);
Root["relationhips"] = std::move(Relationships);

View File

@ -0,0 +1,402 @@
// 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 -x objective-c-header -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
@protocol Protocol;
@interface Super <Protocol>
@property(readonly, getter=getProperty) unsigned Property;
+ (id)getWithProperty:(unsigned) Property;
@end
@interface Derived : Super {
char Ivar;
}
- (char)getIvar;
@end
//--- 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:objc(cs)Super(cm)getWithProperty:",
"target": "c:objc(cs)Super"
},
{
"kind": "memberOf",
"source": "c:objc(cs)Super(py)Property",
"target": "c:objc(cs)Super"
},
{
"kind": "conformsTo",
"source": "c:objc(cs)Super",
"target": "c:objc(pl)Protocol"
},
{
"kind": "memberOf",
"source": "c:objc(cs)Derived@Ivar",
"target": "c:objc(cs)Derived"
},
{
"kind": "memberOf",
"source": "c:objc(cs)Derived(im)getIvar",
"target": "c:objc(cs)Derived"
},
{
"kind": "inheritsFrom",
"source": "c:objc(cs)Derived",
"target": "c:objc(cs)Super"
}
],
"symbols": [
{
"declarationFragments": [
{
"kind": "keyword",
"spelling": "@interface"
},
{
"kind": "text",
"spelling": " "
},
{
"kind": "identifier",
"spelling": "Super"
}
],
"identifier": {
"interfaceLanguage": "objective-c",
"precise": "c:objc(cs)Super"
},
"kind": {
"displayName": "Class",
"identifier": "objective-c.class"
},
"location": {
"character": 12,
"line": 3,
"uri": "file://INPUT_DIR/input.h"
},
"names": {
"subHeading": [
{
"kind": "identifier",
"spelling": "Super"
}
],
"title": "Super"
}
},
{
"declarationFragments": [
{
"kind": "text",
"spelling": "+ ("
},
{
"kind": "keyword",
"spelling": "id"
},
{
"kind": "text",
"spelling": ")"
},
{
"kind": "identifier",
"spelling": "getWithProperty"
},
{
"kind": "text",
"spelling": ":"
},
{
"kind": "text",
"spelling": "("
},
{
"kind": "typeIdentifier",
"preciseIdentifier": "c:i",
"spelling": "unsigned int"
},
{
"kind": "text",
"spelling": ")"
},
{
"kind": "internalParam",
"spelling": "Property"
}
],
"identifier": {
"interfaceLanguage": "objective-c",
"precise": "c:objc(cs)Super(cm)getWithProperty:"
},
"kind": {
"displayName": "Type Method",
"identifier": "objective-c.type.method"
},
"location": {
"character": 1,
"line": 5,
"uri": "file://INPUT_DIR/input.h"
},
"names": {
"subHeading": [
{
"kind": "text",
"spelling": "+ "
},
{
"kind": "identifier",
"spelling": "getWithProperty:"
}
],
"title": "getWithProperty:"
}
},
{
"declarationFragments": [
{
"kind": "keyword",
"spelling": "@property"
},
{
"kind": "text",
"spelling": " ("
},
{
"kind": "keyword",
"spelling": "atomic"
},
{
"kind": "text",
"spelling": ", "
},
{
"kind": "keyword",
"spelling": "readonly"
},
{
"kind": "text",
"spelling": ", "
},
{
"kind": "keyword",
"spelling": "getter"
},
{
"kind": "text",
"spelling": "="
},
{
"kind": "identifier",
"spelling": "getProperty"
},
{
"kind": "text",
"spelling": ")"
},
{
"kind": "typeIdentifier",
"preciseIdentifier": "c:i",
"spelling": "unsigned int"
},
{
"kind": "identifier",
"spelling": "Property"
}
],
"identifier": {
"interfaceLanguage": "objective-c",
"precise": "c:objc(cs)Super(py)Property"
},
"kind": {
"displayName": "Instance Property",
"identifier": "objective-c.property"
},
"location": {
"character": 50,
"line": 4,
"uri": "file://INPUT_DIR/input.h"
},
"names": {
"subHeading": [
{
"kind": "identifier",
"spelling": "Property"
}
],
"title": "Property"
}
},
{
"declarationFragments": [
{
"kind": "keyword",
"spelling": "@interface"
},
{
"kind": "text",
"spelling": " "
},
{
"kind": "identifier",
"spelling": "Derived"
},
{
"kind": "text",
"spelling": " : "
},
{
"kind": "typeIdentifier",
"preciseIdentifier": "c:objc(cs)Super",
"spelling": "Super"
}
],
"identifier": {
"interfaceLanguage": "objective-c",
"precise": "c:objc(cs)Derived"
},
"kind": {
"displayName": "Class",
"identifier": "objective-c.class"
},
"location": {
"character": 12,
"line": 8,
"uri": "file://INPUT_DIR/input.h"
},
"names": {
"subHeading": [
{
"kind": "identifier",
"spelling": "Derived"
}
],
"title": "Derived"
}
},
{
"declarationFragments": [
{
"kind": "typeIdentifier",
"preciseIdentifier": "c:C",
"spelling": "char"
},
{
"kind": "text",
"spelling": " "
},
{
"kind": "identifier",
"spelling": "Ivar"
}
],
"identifier": {
"interfaceLanguage": "objective-c",
"precise": "c:objc(cs)Derived@Ivar"
},
"kind": {
"displayName": "Instance Variable",
"identifier": "objective-c.ivar"
},
"location": {
"character": 8,
"line": 9,
"uri": "file://INPUT_DIR/input.h"
},
"names": {
"subHeading": [
{
"kind": "identifier",
"spelling": "Ivar"
}
],
"title": "Ivar"
}
},
{
"declarationFragments": [
{
"kind": "text",
"spelling": "- ("
},
{
"kind": "typeIdentifier",
"preciseIdentifier": "c:C",
"spelling": "char"
},
{
"kind": "text",
"spelling": ")"
},
{
"kind": "identifier",
"spelling": "getIvar"
}
],
"identifier": {
"interfaceLanguage": "objective-c",
"precise": "c:objc(cs)Derived(im)getIvar"
},
"kind": {
"displayName": "Instance Method",
"identifier": "objective-c.method"
},
"location": {
"character": 1,
"line": 11,
"uri": "file://INPUT_DIR/input.h"
},
"names": {
"subHeading": [
{
"kind": "text",
"spelling": "- "
},
{
"kind": "identifier",
"spelling": "getIvar"
}
],
"title": "getIvar"
}
}
]
}