diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h --- 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 @@ RK_Enum, RK_StructField, RK_Struct, + RK_ObjCProperty, + RK_ObjCIvar, + RK_ObjCMethod, + RK_ObjCInterface, }; private: @@ -201,6 +206,154 @@ 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 @@ 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,15 +511,23 @@ 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; } - /// Get the language options used to parse the APIs. - const LangOptions &getLangOpts() const { return LangOpts; } + /// Get the language used by the APIs. + Language getLanguage() const { return Lang; } 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. /// @@ -328,8 +541,8 @@ /// \returns a StringRef of the copied string in APISet::Allocator. StringRef copyString(StringRef String); - APISet(const llvm::Triple &Target, const LangOptions &LangOpts) - : Target(Target), LangOpts(LangOpts) {} + APISet(const llvm::Triple &Target, Language Lang) + : Target(Target), Lang(Lang) {} private: /// BumpPtrAllocator to store generated/copied strings. @@ -338,11 +551,12 @@ llvm::BumpPtrAllocator StringAllocator; const llvm::Triple Target; - const LangOptions LangOpts; + Language Lang; 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 --- 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,12 +203,32 @@ /// 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 sub-heading fragments for an Objective-C method. + static DeclarationFragments getSubHeading(const ObjCMethodDecl *); + /// Build FunctionSignature for a function declaration FunctionDecl. static FunctionSignature getFunctionSignature(const FunctionDecl *); + /// Build FunctionSignature for an Objective-C method. + static FunctionSignature getFunctionSignature(const ObjCMethodDecl *); + private: DeclarationFragmentsBuilder() = delete; diff --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h --- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h +++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h @@ -62,6 +62,13 @@ /// 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 @@ /// /// 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 @@ /// 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 --- a/clang/lib/ExtractAPI/API.cpp +++ b/clang/lib/ExtractAPI/API.cpp @@ -109,6 +109,58 @@ 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 @@ 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 --- a/clang/lib/ExtractAPI/DeclarationFragments.cpp +++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp @@ -245,6 +245,22 @@ // 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,6 +470,157 @@ return Fragments; } +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); + } + + return 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)); + } + + 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)); +} + FunctionSignature DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *Func) { FunctionSignature Signature; @@ -475,6 +642,23 @@ return Signature; } +FunctionSignature DeclarationFragmentsBuilder::getFunctionSignature( + const ObjCMethodDecl *Method) { + FunctionSignature Signature; + + DeclarationFragments ReturnType, After; + ReturnType + .append(getFragmentsForType(Method->getReturnType(), + Method->getASTContext(), After)) + .append(std::move(After)); + Signature.setReturnType(ReturnType); + + for (const auto *Param : Method->parameters()) + Signature.addParameter(Param->getName(), getFragmentsForParam(Param)); + + return Signature; +} + // Subheading of a symbol defaults to its name. DeclarationFragments DeclarationFragmentsBuilder::getSubHeading(const NamedDecl *Decl) { @@ -484,3 +668,17 @@ 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 --- a/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp +++ b/clang/lib/ExtractAPI/ExtractAPIConsumer.cpp @@ -41,9 +41,8 @@ /// information. class ExtractAPIVisitor : public RecursiveASTVisitor { public: - explicit ExtractAPIVisitor(ASTContext &Context) - : Context(Context), - API(Context.getTargetInfo().getTriple(), Context.getLangOpts()) {} + ExtractAPIVisitor(ASTContext &Context, Language Lang) + : Context(Context), API(Context.getTargetInfo().getTriple(), Lang) {} const APISet &getAPI() const { return API; } @@ -217,6 +216,50 @@ 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 { @@ -303,15 +346,125 @@ } } + /// 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; }; class ExtractAPIConsumer : public ASTConsumer { public: - ExtractAPIConsumer(ASTContext &Context, StringRef ProductName, + ExtractAPIConsumer(ASTContext &Context, StringRef ProductName, Language Lang, std::unique_ptr OS) - : Visitor(Context), ProductName(ProductName), OS(std::move(OS)) {} + : Visitor(Context, Lang), ProductName(ProductName), OS(std::move(OS)) {} void HandleTranslationUnit(ASTContext &Context) override { // Use ExtractAPIVisitor to traverse symbol declarations in the context. @@ -339,6 +492,7 @@ return nullptr; return std::make_unique( CI.getASTContext(), CI.getInvocation().getFrontendOpts().ProductName, + CI.getFrontendOpts().Inputs.back().getKind().getLanguage(), std::move(OS)); } diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -152,10 +152,8 @@ } /// Get the short language name string for interface language references. -StringRef getLanguageName(const LangOptions &LangOpts) { - auto LanguageKind = - LangStandard::getLangStandardForKind(LangOpts.LangStd).getLanguage(); - switch (LanguageKind) { +StringRef getLanguageName(Language Lang) { + switch (Lang) { case Language::C: return "c"; case Language::ObjC: @@ -184,11 +182,10 @@ /// /// The identifier property of a symbol contains the USR for precise and unique /// references, and the interface language name. -Object serializeIdentifier(const APIRecord &Record, - const LangOptions &LangOpts) { +Object serializeIdentifier(const APIRecord &Record, Language Lang) { Object Identifier; Identifier["precise"] = Record.USR; - Identifier["interfaceLanguage"] = getLanguageName(LangOpts); + Identifier["interfaceLanguage"] = getLanguageName(Lang); return Identifier; } @@ -334,10 +331,9 @@ /// The Symbol Graph symbol kind property contains a shorthand \c identifier /// which is prefixed by the source language name, useful for tooling to parse /// the kind, and a \c displayName for rendering human-readable names. -Object serializeSymbolKind(const APIRecord &Record, - const LangOptions &LangOpts) { - auto AddLangPrefix = [&LangOpts](StringRef S) -> std::string { - return (getLanguageName(LangOpts) + "." + S).str(); +Object serializeSymbolKind(const APIRecord &Record, Language Lang) { + auto AddLangPrefix = [&Lang](StringRef S) -> std::string { + return (getLanguageName(Lang) + "." + S).str(); }; Object Kind; @@ -375,6 +371,27 @@ 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; @@ -419,8 +436,8 @@ Object Obj; serializeObject(Obj, "identifier", - serializeIdentifier(Record, API.getLangOpts())); - serializeObject(Obj, "kind", serializeSymbolKind(Record, API.getLangOpts())); + serializeIdentifier(Record, API.getLanguage())); + serializeObject(Obj, "kind", serializeSymbolKind(Record, API.getLanguage())); serializeObject(Obj, "names", serializeNames(Record)); serializeObject( Obj, "location", @@ -438,13 +455,17 @@ 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; @@ -499,6 +520,57 @@ } } +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()); @@ -516,6 +588,10 @@ 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 --- /dev/null +++ b/clang/test/ExtractAPI/objc_interface.m @@ -0,0 +1,403 @@ +// 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 +@end + +@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": 4, + "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": 6, + "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": 5, + "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": 9, + "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": 10, + "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": 12, + "uri": "file://INPUT_DIR/input.h" + }, + "names": { + "subHeading": [ + { + "kind": "text", + "spelling": "- " + }, + { + "kind": "identifier", + "spelling": "getIvar" + } + ], + "title": "getIvar" + } + } + ] +}