From 9b36e126fdb1da4d7e255e089ef225dfb130ef63 Mon Sep 17 00:00:00 2001 From: Zixu Wang Date: Thu, 24 Mar 2022 18:19:30 -0700 Subject: [PATCH] [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 --- clang/include/clang/ExtractAPI/API.h | 214 +++++++++++ .../clang/ExtractAPI/DeclarationFragments.h | 28 +- .../Serialization/SymbolGraphSerializer.h | 14 +- clang/lib/ExtractAPI/API.cpp | 58 +++ clang/lib/ExtractAPI/DeclarationFragments.cpp | 210 ++++++++++- clang/lib/ExtractAPI/ExtractAPIConsumer.cpp | 154 ++++++++ .../Serialization/SymbolGraphSerializer.cpp | 84 ++++- clang/test/ExtractAPI/objc_interface.m | 402 +++++++++++++++++++++ 8 files changed, 1146 insertions(+), 18 deletions(-) create mode 100644 clang/test/ExtractAPI/objc_interface.m diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h index b22d11047590..a2a462be42cd 100644 --- a/clang/include/clang/ExtractAPI/API.h +++ b/clang/include/clang/ExtractAPI/API.h @@ -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> Methods; + SmallVector> Properties; + SmallVector> Ivars; + SmallVector 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>; + /// A map to store the set of ObjCInterfaceRecord%s with the declaration name + /// as the key. + using ObjCInterfaceRecordMap = + llvm::MapVector>; + /// 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 diff --git a/clang/include/clang/ExtractAPI/DeclarationFragments.h b/clang/include/clang/ExtractAPI/DeclarationFragments.h index f8ef431b8525..f147abb4f425 100644 --- a/clang/include/clang/ExtractAPI/DeclarationFragments.h +++ b/clang/include/clang/ExtractAPI/DeclarationFragments.h @@ -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 @@ -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 + static FunctionSignature getFunctionSignature(const FunctionT *); private: DeclarationFragmentsBuilder() = delete; diff --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h index 109833f47494..b5002cabfbbe 100644 --- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h +++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h @@ -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 = {}) diff --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp index 92c4e0ea4838..2456022523ad 100644 --- a/clang/lib/ExtractAPI/API.cpp +++ b/clang/lib/ExtractAPI/API.cpp @@ -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( + 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( + 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( + 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( + 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() {} diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp index 1980e8e6dcc6..b35388ec52ad 100644 --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -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(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); + + // 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); + } - for (const auto *Param : Func->parameters()) { - StringRef Name = Param->getName(); - DeclarationFragments Fragments = getFragmentsForParam(Param); + return Fragments; +} - Signature.addParameter(Name, Fragments); +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)); } - DeclarationFragments After; - DeclarationFragments Returns = - getFragmentsForType(Func->getReturnType(), Func->getASTContext(), After) - .append(std::move(After)); + 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)); +} - Signature.setReturnType(Returns); +template +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); +} diff --git a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp index dc3e067a837e..f8b1c9b20fb1 100644 --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -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(Attributes), + GetterName, SetterName, Property->isOptional()); + } + } + + void recordObjCInstanceVariables( + ObjCContainerRecord *Container, + const llvm::iterator_range< + DeclContext::specific_decl_iterator> + 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; }; diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp index cde81ecf0abd..3faf9cd75103 100644 --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -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(&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(&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); diff --git a/clang/test/ExtractAPI/objc_interface.m b/clang/test/ExtractAPI/objc_interface.m new file mode 100644 index 000000000000..1e903cfc359e --- /dev/null +++ b/clang/test/ExtractAPI/objc_interface.m @@ -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 +@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" + } + } + ] +} -- 2.11.4.GIT