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 @@ -22,6 +22,7 @@ #include "clang/AST/DeclObjC.h" #include "clang/AST/RawCommentList.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Basic/Specifiers.h" #include "clang/ExtractAPI/AvailabilityInfo.h" #include "clang/ExtractAPI/DeclarationFragments.h" #include "llvm/ADT/MapVector.h" @@ -64,6 +65,14 @@ RK_Enum, RK_StructField, RK_Struct, + RK_Union, + RK_StaticField, + RK_CXXField, + RK_CXXClass, + RK_CXXStaticMethod, + RK_CXXInstanceMethod, + RK_CXXConstructorMethod, + RK_CXXDestructorMethod, RK_ObjCInstanceProperty, RK_ObjCClassProperty, RK_ObjCIvar, @@ -266,6 +275,132 @@ virtual void anchor(); }; +struct CXXFieldRecord : APIRecord { + AccessControl Access; + + CXXFieldRecord(StringRef USR, StringRef Name, PresumedLoc Loc, + AvailabilitySet Availabilities, const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, AccessControl Access, + bool IsFromSystemHeader) + : APIRecord(RK_CXXField, USR, Name, Loc, std::move(Availabilities), + LinkageInfo::none(), Comment, Declaration, SubHeading, + IsFromSystemHeader), + Access(Access) {} + + CXXFieldRecord(RecordKind Kind, StringRef USR, StringRef Name, + PresumedLoc Loc, AvailabilitySet Availabilities, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading, AccessControl Access, + bool IsFromSystemHeader) + : APIRecord(Kind, USR, Name, Loc, std::move(Availabilities), + LinkageInfo::none(), Comment, Declaration, SubHeading, + IsFromSystemHeader), + Access(Access) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_CXXField; + } + +private: + virtual void anchor(); +}; + +struct CXXMethodRecord : APIRecord { + FunctionSignature Signature; + AccessControl Access; + + CXXMethodRecord() = delete; + + CXXMethodRecord(RecordKind Kind, StringRef USR, StringRef Name, + PresumedLoc Loc, AvailabilitySet Availabilities, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading, FunctionSignature Signature, + AccessControl Access, bool IsFromSystemHeader) + : APIRecord(Kind, USR, Name, Loc, std::move(Availabilities), + LinkageInfo::none(), Comment, Declaration, SubHeading, + IsFromSystemHeader), + Signature(Signature), Access(Access) {} + + virtual ~CXXMethodRecord() = 0; +}; + +struct CXXConstructorRecord : CXXMethodRecord { + CXXConstructorRecord(StringRef USR, StringRef Name, PresumedLoc Loc, + AvailabilitySet Availabilities, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, + FunctionSignature Signature, AccessControl Access, + bool IsFromSystemHeader) + : CXXMethodRecord(RK_CXXConstructorMethod, USR, Name, Loc, + std::move(Availabilities), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader) {} + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_CXXConstructorMethod; + } + +private: + virtual void anchor(); +}; + +struct CXXDestructorRecord : CXXMethodRecord { + CXXDestructorRecord(StringRef USR, StringRef Name, PresumedLoc Loc, + AvailabilitySet Availabilities, const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, + FunctionSignature Signature, AccessControl Access, + bool IsFromSystemHeader) + : CXXMethodRecord(RK_CXXDestructorMethod, USR, Name, Loc, + std::move(Availabilities), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader) {} + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_CXXDestructorMethod; + } + +private: + virtual void anchor(); +}; + +struct CXXStaticMethodRecord : CXXMethodRecord { + CXXStaticMethodRecord(StringRef USR, StringRef Name, PresumedLoc Loc, + AvailabilitySet Availabilities, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, + FunctionSignature Signature, AccessControl Access, + bool IsFromSystemHeader) + : CXXMethodRecord(RK_CXXStaticMethod, USR, Name, Loc, + std::move(Availabilities), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader) {} + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_CXXStaticMethod; + } + +private: + virtual void anchor(); +}; + +struct CXXInstanceMethodRecord : CXXMethodRecord { + CXXInstanceMethodRecord(StringRef USR, StringRef Name, PresumedLoc Loc, + AvailabilitySet Availabilities, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, + FunctionSignature Signature, AccessControl Access, + bool IsFromSystemHeader) + : CXXMethodRecord(RK_CXXInstanceMethod, USR, Name, Loc, + std::move(Availabilities), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_CXXInstanceMethod; + } + +private: + virtual void anchor(); +}; + /// This holds information associated with Objective-C properties. struct ObjCPropertyRecord : APIRecord { /// The attributes associated with an Objective-C property. @@ -444,6 +579,24 @@ bool empty() const { return Name.empty() && USR.empty() && Source.empty(); } }; +struct StaticFieldRecord : CXXFieldRecord { + SymbolReference Context; + + StaticFieldRecord(StringRef USR, StringRef Name, PresumedLoc Loc, + AvailabilitySet Availabilities, LinkageInfo Linkage, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading, SymbolReference Context, + AccessControl Access, bool IsFromSystemHeader) + : CXXFieldRecord(RK_StaticField, USR, Name, Loc, + std::move(Availabilities), Comment, Declaration, + SubHeading, Access, IsFromSystemHeader), + Context(Context) {} + + static bool classof(const APIRecord *Record) { + return Record->getKind() == RK_StaticField; + } +}; + /// The base representation of an Objective-C container record. Holds common /// information associated with Objective-C containers. struct ObjCContainerRecord : APIRecord { @@ -465,6 +618,29 @@ virtual ~ObjCContainerRecord() = 0; }; +struct CXXClassRecord : APIRecord { + SmallVector> Fields; + SmallVector> Methods; + SmallVector Bases; + + CXXClassRecord(StringRef USR, StringRef Name, PresumedLoc Loc, + AvailabilitySet Availabilities, const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, RecordKind Kind, + bool IsFromSystemHeader) + : APIRecord(Kind, USR, Name, Loc, std::move(Availabilities), + LinkageInfo::none(), Comment, Declaration, SubHeading, + IsFromSystemHeader) {} + + static bool classof(const APIRecord *Record) { + return (Record->getKind() == RK_CXXClass || + Record->getKind() == RK_Struct || Record->getKind() == RK_Union); + } + +private: + virtual void anchor(); +}; + /// This holds information associated with Objective-C categories. struct ObjCCategoryRecord : ObjCContainerRecord { SymbolReference Interface; @@ -592,6 +768,11 @@ template <> struct has_function_signature : public std::true_type {}; +template <> +struct has_function_signature : public std::true_type {}; +template struct has_access : public std::false_type {}; +template <> struct has_access : public std::true_type {}; +template <> struct has_access : public std::true_type {}; /// APISet holds the set of API records collected from given inputs. class APISet { public: @@ -668,6 +849,41 @@ DeclarationFragments SubHeading, bool IsFromSystemHeader); + StaticFieldRecord * + addStaticField(StringRef Name, StringRef USR, PresumedLoc Loc, + AvailabilitySet Availabilities, LinkageInfo Linkage, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading, SymbolReference Context, + AccessControl Access, bool IsFromSystemHeaderg); + + CXXFieldRecord *addCXXField(CXXClassRecord *CXXClass, StringRef Name, + StringRef USR, PresumedLoc Loc, + AvailabilitySet Availabilities, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, + AccessControl Access, bool IsFromSystemHeader); + + CXXClassRecord * + addCXXClass(StringRef Name, StringRef USR, PresumedLoc Loc, + AvailabilitySet Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading, + APIRecord::RecordKind Kind, bool IsFromSystemHeader); + + CXXMethodRecord * + addCXXMethod(CXXClassRecord *CXXClassRecord, StringRef Name, StringRef USR, + PresumedLoc Loc, AvailabilitySet Availability, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading, FunctionSignature Signature, + bool IsStatic, AccessControl Access, bool IsFromSystemHeader); + + CXXMethodRecord *addCXXSpecialMethod( + CXXClassRecord *CXXClassRecord, StringRef Name, StringRef USR, + PresumedLoc Loc, AvailabilitySet Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading, + FunctionSignature Signature, bool IsConstructor, AccessControl Access, + bool IsFromSystemHeader); + /// Create and add an Objective-C category record into the API set. /// /// Note: the caller is responsible for keeping the StringRef \p Name and @@ -790,8 +1006,12 @@ const RecordMap &getGlobalVariables() const { return GlobalVariables; } + const RecordMap &getStaticFields() const { + return StaticFields; + } const RecordMap &getEnums() const { return Enums; } const RecordMap &getStructs() const { return Structs; } + const RecordMap &getCXXClasses() const { return CXXClasses; } const RecordMap &getObjCCategories() const { return ObjCCategories; } @@ -845,8 +1065,10 @@ llvm::DenseMap USRBasedLookupTable; RecordMap GlobalFunctions; RecordMap GlobalVariables; + RecordMap StaticFields; RecordMap Enums; RecordMap Structs; + RecordMap CXXClasses; RecordMap ObjCCategories; RecordMap ObjCInterfaces; RecordMap ObjCProtocols; 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 @@ -20,8 +20,10 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" +#include "clang/Basic/Specifiers.h" #include "clang/Lex/MacroInfo.h" #include "llvm/ADT/StringRef.h" #include @@ -173,10 +175,29 @@ /// Get the corresponding FragmentKind from string \p S. static FragmentKind parseFragmentKindFromString(StringRef S); + static DeclarationFragments + getExceptionSpecificationString(ExceptionSpecificationType ExceptionSpec); + + static DeclarationFragments getStructureTypeFragment(const RecordDecl *Decl); + private: std::vector Fragments; }; +class AccessControl { +public: + AccessControl() = default; + + const StringRef &getAccess() const { return Access; } + + void setAccess(StringRef Acc) { Access = Acc; } + + bool empty() const { return Access.empty(); } + +private: + StringRef Access; +}; + /// Store function signature information with DeclarationFragments of the /// return type and parameters. class FunctionSignature { @@ -219,6 +240,49 @@ /// A factory class to build DeclarationFragments for different kinds of Decl. class DeclarationFragmentsBuilder { public: + /// 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 *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; + } + + static AccessControl getAccessControl(const Decl *Decl) { + AccessControl AccessControl; + + switch (Decl->getAccess()) { + case AS_public: + AccessControl.setAccess("public"); + break; + case AS_private: + AccessControl.setAccess("private"); + break; + case AS_protected: + AccessControl.setAccess("protected"); + break; + case AS_none: + AccessControl.setAccess("none"); + break; + } + return AccessControl; + } + /// Build DeclarationFragments for a variable declaration VarDecl. static DeclarationFragments getFragmentsForVar(const VarDecl *); @@ -239,6 +303,10 @@ /// Build DeclarationFragments for a struct record declaration RecordDecl. static DeclarationFragments getFragmentsForStruct(const RecordDecl *); + static DeclarationFragments getFragmentsForCXXClass(const CXXRecordDecl *); + + static DeclarationFragments getFragmentsForCXXMethod(const CXXMethodDecl *); + /// Build DeclarationFragments for an Objective-C category declaration /// ObjCCategoryDecl. static DeclarationFragments @@ -283,15 +351,6 @@ /// Build a sub-heading for macro \p Name. static DeclarationFragments getSubHeadingForMacro(StringRef Name); - /// 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/ExtractAPIVisitor.h b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h --- a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h +++ b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h @@ -14,6 +14,7 @@ #ifndef LLVM_CLANG_EXTRACTAPI_EXTRACT_API_VISITOR_H #define LLVM_CLANG_EXTRACTAPI_EXTRACT_API_VISITOR_H +#include "clang/ExtractAPI/DeclarationFragments.h" #include "llvm/ADT/FunctionExtras.h" #include "clang/AST/ASTContext.h" @@ -44,8 +45,12 @@ bool VisitEnumDecl(const EnumDecl *Decl); + bool WalkUpFromRecordDecl(const RecordDecl *Decl); + bool VisitRecordDecl(const RecordDecl *Decl); + bool VisitCXXRecordDecl(const CXXRecordDecl *Decl); + bool VisitObjCInterfaceDecl(const ObjCInterfaceDecl *Decl); bool VisitObjCProtocolDecl(const ObjCProtocolDecl *Decl); @@ -69,6 +74,17 @@ void recordStructFields(StructRecord *StructRecord, const RecordDecl::field_range Fields); + /// Collect API information for the class fields and associate with the parent + /// struct + void recordCXXFields(CXXClassRecord *CXXClassRecord, + const RecordDecl::field_range Fields); + + void recordCXXMethods(CXXClassRecord *CXXClassRecord, + const CXXRecordDecl::method_range Methods); + + void recordSpecialCXXMethod(CXXClassRecord *CXXClassRecord, + const CXXMethodDecl *SpecialCXXMethod); + /// Collect API information for the Objective-C methods and associate with the /// parent container. void recordObjCMethods(ObjCContainerRecord *Container, @@ -131,8 +147,9 @@ if (isa(Decl)) return true; - // Skip non-global variables in records (struct/union/class). - if (Decl->getDeclContext()->isRecord()) + // Skip non-global variables in records (struct/union/class) but not static + // members. + if (Decl->getDeclContext()->isRecord() && !Decl->isStaticDataMember()) return true; // Skip local variables inside function or method. @@ -165,9 +182,19 @@ DeclarationFragments SubHeading = DeclarationFragmentsBuilder::getSubHeading(Decl); - // Add the global variable record to the API set. - API.addGlobalVar(Name, USR, Loc, AvailabilitySet(Decl), Linkage, Comment, - Declaration, SubHeading, isInSystemHeader(Decl)); + if (Decl->isStaticDataMember()) { + SymbolReference Context; + auto Record = dyn_cast(Decl->getDeclContext()); + Context.Name = Record->getName(); + Context.USR = API.recordUSR(Record); + auto Access = DeclarationFragmentsBuilder::getAccessControl(Decl); + API.addStaticField(Name, USR, Loc, AvailabilitySet(Decl), Linkage, Comment, + Declaration, SubHeading, Context, Access, + isInSystemHeader(Decl)); + } else + // Add the global variable record to the API set. + API.addGlobalVar(Name, USR, Loc, AvailabilitySet(Decl), Linkage, Comment, + Declaration, SubHeading, isInSystemHeader(Decl)); return true; } @@ -280,15 +307,20 @@ } template -bool ExtractAPIVisitorBase::VisitRecordDecl(const RecordDecl *Decl) { - // Skip C++ structs/classes/unions - // TODO: support C++ records - if (isa(Decl)) - return true; +bool ExtractAPIVisitorBase::WalkUpFromRecordDecl( + const RecordDecl *Decl) { + if (isa(Decl)) { + VisitCXXRecordDecl(dyn_cast(Decl)); + } else { + VisitRecordDecl(Decl); + } + return true; +} +template +bool ExtractAPIVisitorBase::VisitRecordDecl(const RecordDecl *Decl) { if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl)) return true; - // Collect symbol information. StringRef Name = Decl->getName(); if (Name.empty()) @@ -322,6 +354,54 @@ return true; } +template +bool ExtractAPIVisitorBase::VisitCXXRecordDecl( + const CXXRecordDecl *Decl) { + if (!getDerivedExtractAPIVisitor().shouldDeclBeIncluded(Decl)) + return true; + + StringRef Name = Decl->getName(); + StringRef USR = API.recordUSR(Decl); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Decl->getLocation()); + DocComment Comment; + if (auto *RawComment = + getDerivedExtractAPIVisitor().fetchRawCommentForDecl(Decl)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForCXXClass(Decl); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Decl); + + APIRecord::RecordKind Kind; + if (Decl->isUnion()) + Kind = APIRecord::RecordKind::RK_Union; + else if (Decl->isStruct()) + Kind = APIRecord::RecordKind::RK_Struct; + else + Kind = APIRecord::RecordKind::RK_CXXClass; + + CXXClassRecord *CXXClassRecord = + API.addCXXClass(Name, USR, Loc, AvailabilitySet(Decl), Comment, + Declaration, SubHeading, Kind, isInSystemHeader(Decl)); + + // FIXME: store AccessSpecifier given by inheritance + for (const auto BaseSpecifier : Decl->bases()) { + SymbolReference BaseClass; + CXXRecordDecl *BaseClassDecl = + BaseSpecifier.getType().getTypePtr()->getAsCXXRecordDecl(); + BaseClass.Name = BaseClassDecl->getName(); + BaseClass.USR = API.recordUSR(BaseClassDecl); + CXXClassRecord->Bases.emplace_back(BaseClass); + } + + getDerivedExtractAPIVisitor().recordCXXFields(CXXClassRecord, Decl->fields()); + getDerivedExtractAPIVisitor().recordCXXMethods(CXXClassRecord, + Decl->methods()); + return true; +} + template bool ExtractAPIVisitorBase::VisitObjCInterfaceDecl( const ObjCInterfaceDecl *Decl) { @@ -558,6 +638,107 @@ } } +template +void ExtractAPIVisitorBase::recordCXXFields( + CXXClassRecord *CXXClassRecord, const RecordDecl::field_range Fields) { + for (const auto *Field : Fields) { + // Collect symbol information. + StringRef Name = Field->getName(); + StringRef USR = API.recordUSR(Field); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Field->getLocation()); + Context.getSourceManager().getPresumedLoc(Field->getLocation()); + DocComment Comment; + if (auto *RawComment = + getDerivedExtractAPIVisitor().fetchRawCommentForDecl(Field)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + + // Build declaration fragments and sub-heading for the struct field. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForField(Field); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Field); + AccessControl Access = DeclarationFragmentsBuilder::getAccessControl(Field); + + API.addCXXField(CXXClassRecord, Name, USR, Loc, AvailabilitySet(Field), + Comment, Declaration, SubHeading, Access, + isInSystemHeader(Field)); + } +} + +template +void ExtractAPIVisitorBase::recordSpecialCXXMethod( + CXXClassRecord *CXXClassRecord, const CXXMethodDecl *CXXSpecialMethod) { + StringRef Name; + bool isConstructor = false; + if (isa(CXXSpecialMethod)) { + isConstructor = true; + Name = CXXClassRecord->Name; + } else if (isa(CXXSpecialMethod)) { + Name = CXXClassRecord->Name; + } + + StringRef USR = API.recordUSR(CXXSpecialMethod); + PresumedLoc Loc = Context.getSourceManager().getPresumedLoc( + CXXSpecialMethod->getLocation()); + DocComment Comment; + if (auto *RawComment = getDerivedExtractAPIVisitor().fetchRawCommentForDecl( + CXXSpecialMethod)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + + // Build declaration fragments, sub-heading, and signature for the method. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForCXXMethod(CXXSpecialMethod); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(CXXSpecialMethod); + FunctionSignature Signature = + DeclarationFragmentsBuilder::getFunctionSignature(CXXSpecialMethod); + AccessControl Access = + DeclarationFragmentsBuilder::getAccessControl(CXXSpecialMethod); + + API.addCXXSpecialMethod(CXXClassRecord, Name, USR, Loc, + AvailabilitySet(CXXSpecialMethod), Comment, + Declaration, SubHeading, Signature, isConstructor, + Access, isInSystemHeader(CXXSpecialMethod)); +} + +template +void ExtractAPIVisitorBase::recordCXXMethods( + CXXClassRecord *CXXClassRecord, const CXXRecordDecl::method_range Methods) { + for (const auto *Method : Methods) { + if (isa(Method) || isa(Method)) { + recordSpecialCXXMethod(CXXClassRecord, Method); + continue; + } + + StringRef Name = API.copyString(Method->getNameAsString()); + StringRef USR = API.recordUSR(Method); + PresumedLoc Loc = + Context.getSourceManager().getPresumedLoc(Method->getLocation()); + DocComment Comment; + if (auto *RawComment = + getDerivedExtractAPIVisitor().fetchRawCommentForDecl(Method)) + Comment = RawComment->getFormattedLines(Context.getSourceManager(), + Context.getDiagnostics()); + + // Build declaration fragments, sub-heading, and signature for the method. + DeclarationFragments Declaration = + DeclarationFragmentsBuilder::getFragmentsForCXXMethod(Method); + DeclarationFragments SubHeading = + DeclarationFragmentsBuilder::getSubHeading(Method); + FunctionSignature Signature = + DeclarationFragmentsBuilder::getFunctionSignature(Method); + AccessControl Access = + DeclarationFragmentsBuilder::getAccessControl(Method); + + API.addCXXMethod(CXXClassRecord, Name, USR, Loc, AvailabilitySet(Method), + Comment, Declaration, SubHeading, Signature, + Method->isStatic(), Access, isInSystemHeader(Method)); + } +} + /// Collect API information for the Objective-C methods and associate with the /// parent container. template diff --git a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h --- a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h +++ b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h @@ -29,6 +29,10 @@ getDerived()->traverseEnumRecords(); + getDerived()->traverseStaticFieldRecords(); + + getDerived()->traverseCXXClassRecords(); + getDerived()->traverseStructRecords(); getDerived()->traverseObjCInterfaces(); @@ -60,6 +64,16 @@ getDerived()->visitStructRecord(*Struct.second); } + void traverseStaticFieldRecords() { + for (const auto &StaticField : API.getStaticFields()) + getDerived()->visitStaticFieldRecord(*StaticField.second); + } + + void traverseCXXClassRecords() { + for (const auto &Class : API.getCXXClasses()) + getDerived()->visitCXXClassRecord(*Class.second); + } + void traverseObjCInterfaces() { for (const auto &Interface : API.getObjCInterfaces()) getDerived()->visitObjCContainerRecord(*Interface.second); @@ -92,6 +106,10 @@ /// Visit a struct record. void visitStructRecord(const StructRecord &Record){}; + void visitStaticFieldRecord(const StaticFieldRecord &Record){}; + + void visitCXXClassRecord(const CXXClassRecord &Record){}; + /// Visit an Objective-C container record. void visitObjCContainerRecord(const ObjCContainerRecord &Record){}; 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 @@ -160,6 +160,10 @@ /// Visit a struct record. void visitStructRecord(const StructRecord &Record); + void visitStaticFieldRecord(const StaticFieldRecord &Record); + + void visitCXXClassRecord(const CXXClassRecord &Record); + /// Visit an Objective-C container record. void visitObjCContainerRecord(const ObjCContainerRecord &Record); 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 @@ -16,6 +16,7 @@ #include "clang/AST/CommentCommandTraits.h" #include "clang/AST/CommentLexer.h" #include "clang/AST/RawCommentList.h" +#include "clang/ExtractAPI/DeclarationFragments.h" #include "clang/Index/USRGeneration.h" #include "llvm/ADT/STLFunctionalExtras.h" #include "llvm/ADT/StringRef.h" @@ -41,7 +42,6 @@ USRLookupTable.insert({USR, Record}); return Record; } - } // namespace GlobalVariableRecord * @@ -120,6 +120,91 @@ SubHeading, IsFromSystemHeader); } +StaticFieldRecord * +APISet::addStaticField(StringRef Name, StringRef USR, PresumedLoc Loc, + AvailabilitySet Availabilities, LinkageInfo Linkage, + const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, SymbolReference Context, + AccessControl Access, bool IsFromSystemHeader) { + return addTopLevelRecord(USRBasedLookupTable, StaticFields, USR, Name, Loc, + std::move(Availabilities), Linkage, Comment, + Declaration, SubHeading, Context, Access, + IsFromSystemHeader); +} + +CXXFieldRecord * +APISet::addCXXField(CXXClassRecord *CXXClass, StringRef Name, StringRef USR, + PresumedLoc Loc, AvailabilitySet Availabilities, + const DocComment &Comment, DeclarationFragments Declaration, + DeclarationFragments SubHeading, AccessControl Access, + bool IsFromSystemHeader) { + auto Record = std::make_unique( + USR, Name, Loc, std::move(Availabilities), Comment, Declaration, + SubHeading, Access, IsFromSystemHeader); + Record->ParentInformation = APIRecord::HierarchyInformation( + CXXClass->USR, CXXClass->Name, CXXClass->getKind(), CXXClass); + USRBasedLookupTable.insert({USR, Record.get()}); + return CXXClass->Fields.emplace_back(std::move(Record)).get(); +} + +CXXClassRecord * +APISet::addCXXClass(StringRef Name, StringRef USR, PresumedLoc Loc, + AvailabilitySet Availabilities, const DocComment &Comment, + DeclarationFragments Declaration, + DeclarationFragments SubHeading, APIRecord::RecordKind Kind, + bool IsFromSystemHeader) { + return addTopLevelRecord(USRBasedLookupTable, CXXClasses, USR, Name, Loc, + std::move(Availabilities), Comment, Declaration, + SubHeading, Kind, IsFromSystemHeader); +} + +CXXMethodRecord *APISet::addCXXMethod( + CXXClassRecord *CXXClassRecord, StringRef Name, StringRef USR, + PresumedLoc Loc, AvailabilitySet Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading, + FunctionSignature Signature, bool IsStatic, AccessControl Access, + bool IsFromSystemHeader) { + std::unique_ptr Record; + if (IsStatic) + Record = std::make_unique( + USR, Name, Loc, std::move(Availability), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader); + else + Record = std::make_unique( + USR, Name, Loc, std::move(Availability), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader); + + Record->ParentInformation = APIRecord::HierarchyInformation( + CXXClassRecord->USR, CXXClassRecord->Name, CXXClassRecord->getKind(), + CXXClassRecord); + USRBasedLookupTable.insert({USR, Record.get()}); + return CXXClassRecord->Methods.emplace_back(std::move(Record)).get(); +} + +CXXMethodRecord *APISet::addCXXSpecialMethod( + CXXClassRecord *CXXClassRecord, StringRef Name, StringRef USR, + PresumedLoc Loc, AvailabilitySet Availability, const DocComment &Comment, + DeclarationFragments Declaration, DeclarationFragments SubHeading, + FunctionSignature Signature, bool IsConstructor, AccessControl Access, + bool IsFromSystemHeader) { + std::unique_ptr Record; + if (IsConstructor) + Record = std::make_unique( + USR, Name, Loc, std::move(Availability), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader); + else + Record = std::make_unique( + USR, Name, Loc, std::move(Availability), Comment, Declaration, + SubHeading, Signature, Access, IsFromSystemHeader); + + Record->ParentInformation = APIRecord::HierarchyInformation( + CXXClassRecord->USR, CXXClassRecord->Name, CXXClassRecord->getKind(), + CXXClassRecord); + USRBasedLookupTable.insert({USR, Record.get()}); + return CXXClassRecord->Methods.emplace_back(std::move(Record)).get(); +} + ObjCCategoryRecord *APISet::addObjCCategory( StringRef Name, StringRef USR, PresumedLoc Loc, AvailabilitySet Availabilities, const DocComment &Comment, @@ -285,6 +370,7 @@ ObjCContainerRecord::~ObjCContainerRecord() {} ObjCMethodRecord::~ObjCMethodRecord() {} ObjCPropertyRecord::~ObjCPropertyRecord() {} +CXXMethodRecord::~CXXMethodRecord() {} void GlobalFunctionRecord::anchor() {} void GlobalVariableRecord::anchor() {} @@ -292,6 +378,12 @@ void EnumRecord::anchor() {} void StructFieldRecord::anchor() {} void StructRecord::anchor() {} +void CXXFieldRecord::anchor() {} +void CXXClassRecord::anchor() {} +void CXXConstructorRecord::anchor() {} +void CXXDestructorRecord::anchor() {} +void CXXInstanceMethodRecord::anchor() {} +void CXXStaticMethodRecord::anchor() {} void ObjCInstancePropertyRecord::anchor() {} void ObjCClassPropertyRecord::anchor() {} void ObjCInstanceVariableRecord::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 @@ -12,8 +12,10 @@ //===----------------------------------------------------------------------===// #include "clang/ExtractAPI/DeclarationFragments.h" +#include "clang/AST/DeclCXX.h" #include "clang/ExtractAPI/TypedefUnderlyingTypeResolver.h" #include "clang/Index/USRGeneration.h" +#include "clang/Parse/Parser.h" #include "llvm/ADT/StringSwitch.h" using namespace clang::extractapi; @@ -84,6 +86,43 @@ .Default(DeclarationFragments::FragmentKind::None); } +DeclarationFragments DeclarationFragments::getExceptionSpecificationString( + ExceptionSpecificationType ExceptionSpec) { + DeclarationFragments Fragments; + switch (ExceptionSpec) { + case ExceptionSpecificationType::EST_BasicNoexcept: + Fragments.append(" noexcept", DeclarationFragments::FragmentKind::Keyword); + return Fragments; + case ExceptionSpecificationType::EST_DynamicNone: + Fragments.append(" throw()", DeclarationFragments::FragmentKind::Keyword); + return Fragments; + case ExceptionSpecificationType::EST_NoexceptFalse: + Fragments.append(" noexcept(false)", + DeclarationFragments::FragmentKind::Keyword); + return Fragments; + case ExceptionSpecificationType::EST_NoexceptTrue: + Fragments.append(" noexcept(true)", + DeclarationFragments::FragmentKind::Keyword); + return Fragments; + case ExceptionSpecificationType::EST_None: + default: + return Fragments; + } +} + +DeclarationFragments +DeclarationFragments::getStructureTypeFragment(const RecordDecl *Record) { + DeclarationFragments Fragments; + if (Record->isStruct()) + Fragments.append("struct", DeclarationFragments::FragmentKind::Keyword); + else if (Record->isUnion()) + Fragments.append("union", DeclarationFragments::FragmentKind::Keyword); + else + Fragments.append("class", DeclarationFragments::FragmentKind::Keyword); + + return Fragments; +} + // NNS stores C++ nested name specifiers, which are prefixes to qualified names. // Build declaration fragments for NNS recursively so that we have the USR for // every part in a qualified name, and also leaves the actual underlying type @@ -369,6 +408,9 @@ DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForVar(const VarDecl *Var) { DeclarationFragments Fragments; + if (Var->isConstexpr()) + Fragments.append("constexpr ", DeclarationFragments::FragmentKind::Keyword); + StorageClass SC = Var->getStorageClass(); if (SC != SC_None) Fragments @@ -437,7 +479,10 @@ case SC_Register: llvm_unreachable("invalid for functions"); } - // FIXME: Handle C++ function specifiers: constexpr, consteval, explicit, etc. + if (Func->isConsteval()) // if consteval, it is also constexpr + Fragments.append("consteval ", DeclarationFragments::FragmentKind::Keyword); + else if (Func->isConstexpr()) + Fragments.append("constexpr ", DeclarationFragments::FragmentKind::Keyword); // FIXME: Is `after` actually needed here? DeclarationFragments After; @@ -456,6 +501,9 @@ } Fragments.append(")", DeclarationFragments::FragmentKind::Text); + Fragments.append(DeclarationFragments::getExceptionSpecificationString( + Func->getExceptionSpecType())); + // FIXME: Handle exception specifiers: throw, noexcept return Fragments.append(";", DeclarationFragments::FragmentKind::Text); } @@ -492,7 +540,12 @@ DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForField(const FieldDecl *Field) { DeclarationFragments After; - return getFragmentsForType(Field->getType(), Field->getASTContext(), After) + DeclarationFragments Fragments; + if (Field->isMutable()) + Fragments.append("mutable ", DeclarationFragments::FragmentKind::Keyword); + return Fragments + .append( + getFragmentsForType(Field->getType(), Field->getASTContext(), After)) .appendSpace() .append(Field->getName(), DeclarationFragments::FragmentKind::Identifier) .append(std::move(After)); @@ -513,6 +566,77 @@ return Fragments.append(";", DeclarationFragments::FragmentKind::Text); } +DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForCXXClass( + const CXXRecordDecl *Record) { + if (const auto *TypedefNameDecl = Record->getTypedefNameForAnonDecl()) + return getFragmentsForTypedef(TypedefNameDecl); + + DeclarationFragments Fragments; + Fragments.append(DeclarationFragments::getStructureTypeFragment(Record)); + + if (!Record->getName().empty()) + Fragments.appendSpace().append( + Record->getName(), DeclarationFragments::FragmentKind::Identifier); + + return Fragments.append(";", DeclarationFragments::FragmentKind::Text); +} + +DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForCXXMethod( + const CXXMethodDecl *Method) { + DeclarationFragments Fragments; + StringRef Name; + if (isa(Method)) { + auto Constructor = dyn_cast(Method); + Name = cast(Constructor->getDeclContext())->getName(); + if (Constructor->isExplicit()) + Fragments.append("explicit ", + DeclarationFragments::FragmentKind::Keyword); + } else if (isa(Method)) + // TODO: add '~' + Name = cast(Method->getDeclContext())->getName(); + else if (isa(Method)) { + Name = "Conversion"; // placeholder + auto Conversion = dyn_cast(Method); + if (Conversion->isExplicit()) + Fragments.append("explicit ", + DeclarationFragments::FragmentKind::Keyword); + } else { + Name = Method->getName(); + if (Method->isStatic()) + Fragments.append("static ", DeclarationFragments::FragmentKind::Keyword); + if (Method->isConstexpr()) + Fragments.append("constexpr ", + DeclarationFragments::FragmentKind::Keyword); + if (Method->isVolatile()) + Fragments.append("volatile ", + DeclarationFragments::FragmentKind::Keyword); + } + + // Build return type + DeclarationFragments After; + Fragments + .append(getFragmentsForType(Method->getReturnType(), + Method->getASTContext(), After)) + .appendSpace() + .append(Name, DeclarationFragments::FragmentKind::Identifier) + .append(std::move(After)); + Fragments.append("(", DeclarationFragments::FragmentKind::Text); + for (unsigned i = 0, end = Method->getNumParams(); i != end; ++i) { + if (i) + Fragments.append(", ", DeclarationFragments::FragmentKind::Text); + Fragments.append(getFragmentsForParam(Method->getParamDecl(i))); + } + Fragments.append(")", DeclarationFragments::FragmentKind::Text); + + if (Method->isConst()) + Fragments.append(" const", DeclarationFragments::FragmentKind::Keyword); + + Fragments.append(DeclarationFragments::getExceptionSpecificationString( + Method->getExceptionSpecType())); + + return Fragments.append(";", DeclarationFragments::FragmentKind::Text); +} + DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForMacro(StringRef Name, const MacroDirective *MD) { @@ -763,24 +887,6 @@ return Fragments.append(";", DeclarationFragments::FragmentKind::Text); } -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 *); @@ -793,7 +899,10 @@ DeclarationFragments DeclarationFragmentsBuilder::getSubHeading(const NamedDecl *Decl) { DeclarationFragments Fragments; - if (!Decl->getName().empty()) + if (isa(Decl) || isa(Decl)) + Fragments.append(cast(Decl->getDeclContext())->getName(), + DeclarationFragments::FragmentKind::Identifier); + else if (!Decl->getName().empty()) Fragments.append(Decl->getName(), DeclarationFragments::FragmentKind::Identifier); return Fragments; 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 @@ -17,6 +17,7 @@ #include "clang/ExtractAPI/DeclarationFragments.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLFunctionalExtras.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Path.h" @@ -38,6 +39,14 @@ Paren[Key] = std::move(*Obj); } +/// Helper function to inject a StringRef \p String into an object \p Paren at +/// position \p Key +void serializeString(Object &Paren, StringRef Key, + std::optional String) { + if (String) + Paren[Key] = std::move(*String); +} + /// Helper function to inject a JSON array \p Array into object \p Paren at /// position \p Key. void serializeArray(Object &Paren, StringRef Key, std::optional Array) { @@ -366,6 +375,38 @@ Kind["identifier"] = AddLangPrefix("struct"); Kind["displayName"] = "Structure"; break; + case APIRecord::RK_CXXField: + Kind["identifier"] = AddLangPrefix("property"); + Kind["displayName"] = "Instance Property"; + break; + case APIRecord::RK_Union: + Kind["identifier"] = AddLangPrefix("union"); + Kind["displayName"] = "Union"; + break; + case APIRecord::RK_StaticField: + Kind["identifier"] = AddLangPrefix("type.property"); + Kind["displayName"] = "Type Property"; + break; + case APIRecord::RK_CXXClass: + Kind["identifier"] = AddLangPrefix("class"); + Kind["displayName"] = "Class"; + break; + case APIRecord::RK_CXXStaticMethod: + Kind["identifier"] = AddLangPrefix("type.method"); + Kind["displayName"] = "Static Method"; + break; + case APIRecord::RK_CXXInstanceMethod: + Kind["identifier"] = AddLangPrefix("method"); + Kind["displayName"] = "Instance Method"; + break; + case APIRecord::RK_CXXConstructorMethod: + Kind["identifier"] = AddLangPrefix("method"); + Kind["displayName"] = "Constructor"; + break; + case APIRecord::RK_CXXDestructorMethod: + Kind["identifier"] = AddLangPrefix("method"); + Kind["displayName"] = "Destructor"; + break; case APIRecord::RK_ObjCIvar: Kind["identifier"] = AddLangPrefix("ivar"); Kind["displayName"] = "Instance Variable"; @@ -470,6 +511,31 @@ Record, has_function_signature())); } +template +std::optional serializeAccessMixinImpl(const RecordTy &Record, + std::true_type) { + const auto &AccessControl = Record.Access; + StringRef Access; + if (AccessControl.empty()) + return std::nullopt; + Access = AccessControl.getAccess(); + return Access; +} + +template +std::optional serializeAccessMixinImpl(const RecordTy &Record, + std::false_type) { + return std::nullopt; +} + +template +void serializeAccessMixin(Object &Paren, const RecordTy &Record) { + auto accessLevel = serializeAccessMixinImpl(Record, has_access()); + if (!accessLevel.has_value()) + accessLevel = "public"; + serializeString(Paren, "accessLevel", accessLevel); +} + struct PathComponent { StringRef USR; StringRef Name; @@ -543,7 +609,6 @@ return ParentContexts; } - } // namespace /// Defines the format version emitted by SymbolGraphSerializer. @@ -602,9 +667,6 @@ serializeObject(Obj, "docComment", serializeDocComment(Record.Comment)); serializeArray(Obj, "declarationFragments", serializeDeclarationFragments(Record.Declaration)); - // TODO: Once we keep track of symbol access information serialize it - // correctly here. - Obj["accessLevel"] = "public"; SmallVector PathComponentsNames; // If this returns true it indicates that we couldn't find a symbol in the // hierarchy. @@ -617,6 +679,7 @@ serializeArray(Obj, "pathComponents", Array(PathComponentsNames)); serializeFunctionSignatureMixin(Obj, Record); + serializeAccessMixin(Obj, Record); return Obj; } @@ -698,6 +761,28 @@ serializeMembers(Record, Record.Fields); } +void SymbolGraphSerializer::visitStaticFieldRecord( + const StaticFieldRecord &Record) { + auto StaticField = serializeAPIRecord(Record); + if (!StaticField) + return; + Symbols.emplace_back(std::move(*StaticField)); + serializeRelationship(RelationshipKind::MemberOf, Record, Record.Context); +} + +void SymbolGraphSerializer::visitCXXClassRecord(const CXXClassRecord &Record) { + auto Class = serializeAPIRecord(Record); + if (!Class) + return; + + Symbols.emplace_back(std::move(*Class)); + serializeMembers(Record, Record.Fields); + serializeMembers(Record, Record.Methods); + + for (const auto Base : Record.Bases) + serializeRelationship(RelationshipKind::InheritsFrom, Record, Base); +} + void SymbolGraphSerializer::visitObjCContainerRecord( const ObjCContainerRecord &Record) { auto ObjCContainer = serializeAPIRecord(Record); @@ -761,6 +846,12 @@ case APIRecord::RK_Struct: visitStructRecord(*cast(Record)); break; + case APIRecord::RK_StaticField: + visitStaticFieldRecord(*cast(Record)); + break; + case APIRecord::RK_CXXClass: + visitCXXClassRecord(*cast(Record)); + break; case APIRecord::RK_ObjCInterface: visitObjCContainerRecord(*cast(Record)); break;