[clang] 1849318 - [clang][ExtractAPI] Add support for Objective-C categories
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 15 08:34:41 PDT 2023
Author: ruturaj4
Date: 2023-08-15T10:34:21-05:00
New Revision: 184931853924b53f5f07602229b9d129540ab02a
URL: https://github.com/llvm/llvm-project/commit/184931853924b53f5f07602229b9d129540ab02a
DIFF: https://github.com/llvm/llvm-project/commit/184931853924b53f5f07602229b9d129540ab02a.diff
LOG: [clang][ExtractAPI] Add support for Objective-C categories
Differential Revision: https://reviews.llvm.org/D152770
Added:
clang/test/ExtractAPI/objc_module_category.m
clang/test/ExtractAPI/objc_various_categories.m
Modified:
clang/include/clang/ExtractAPI/API.h
clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
clang/lib/ExtractAPI/API.cpp
clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h
index 437ac953b7409d..a965f49c8e91b2 100644
--- a/clang/include/clang/ExtractAPI/API.h
+++ b/clang/include/clang/ExtractAPI/API.h
@@ -80,6 +80,7 @@ struct APIRecord {
RK_ObjCInstanceMethod,
RK_ObjCInterface,
RK_ObjCCategory,
+ RK_ObjCCategoryModule,
RK_ObjCProtocol,
RK_MacroDefinition,
RK_Typedef,
@@ -153,6 +154,9 @@ struct APIRecord {
Comment(Comment), Declaration(Declaration), SubHeading(SubHeading),
IsFromSystemHeader(IsFromSystemHeader), Kind(Kind) {}
+ APIRecord(RecordKind Kind, StringRef USR, StringRef Name)
+ : USR(USR), Name(Name), Kind(Kind) {}
+
// Pure virtual destructor to make APIRecord abstract
virtual ~APIRecord() = 0;
};
@@ -643,6 +647,8 @@ struct CXXClassRecord : APIRecord {
/// This holds information associated with Objective-C categories.
struct ObjCCategoryRecord : ObjCContainerRecord {
SymbolReference Interface;
+ /// Determine whether the Category is derived from external class interface.
+ bool IsFromExternalModule = false;
ObjCCategoryRecord(StringRef USR, StringRef Name, PresumedLoc Loc,
AvailabilitySet Availabilities, const DocComment &Comment,
@@ -895,7 +901,7 @@ class APISet {
AvailabilitySet Availability, const DocComment &Comment,
DeclarationFragments Declaration,
DeclarationFragments SubHeading, SymbolReference Interface,
- bool IsFromSystemHeader);
+ bool IsFromSystemHeader, bool IsFromExternalModule);
/// Create and add an Objective-C interface record into the API set.
///
diff --git a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
index bc35ee73ca8ad5..904942a69691a6 100644
--- a/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
+++ b/clang/include/clang/ExtractAPI/ExtractAPIVisitor.h
@@ -578,9 +578,17 @@ bool ExtractAPIVisitorBase<Derived>::VisitObjCCategoryDecl(
SymbolReference Interface(InterfaceDecl->getName(),
API.recordUSR(InterfaceDecl));
+ bool IsFromExternalModule = true;
+ for (const auto &Interface : API.getObjCInterfaces()) {
+ if (InterfaceDecl->getName() == Interface.second.get()->Name) {
+ IsFromExternalModule = false;
+ break;
+ }
+ }
+
ObjCCategoryRecord *ObjCCategoryRecord = API.addObjCCategory(
Name, USR, Loc, AvailabilitySet(Decl), Comment, Declaration, SubHeading,
- Interface, isInSystemHeader(Decl));
+ Interface, isInSystemHeader(Decl), IsFromExternalModule);
getDerivedExtractAPIVisitor().recordObjCMethods(ObjCCategoryRecord,
Decl->methods());
diff --git a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
index afb84c20ddefea..68c20b3f6c6f9d 100644
--- a/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
+++ b/clang/include/clang/ExtractAPI/Serialization/SerializerBase.h
@@ -39,6 +39,8 @@ template <typename Derived> class APISetVisitor {
getDerived()->traverseObjCProtocols();
+ getDerived()->traverseObjCCategories();
+
getDerived()->traverseMacroDefinitionRecords();
getDerived()->traverseTypedefRecords();
@@ -84,6 +86,11 @@ template <typename Derived> class APISetVisitor {
getDerived()->visitObjCContainerRecord(*Protocol.second);
}
+ void traverseObjCCategories() {
+ for (const auto &Category : API.getObjCCategories())
+ getDerived()->visitObjCCategoryRecord(*Category.second);
+ }
+
void traverseMacroDefinitionRecords() {
for (const auto &Macro : API.getMacros())
getDerived()->visitMacroDefinitionRecord(*Macro.second);
@@ -113,6 +120,9 @@ template <typename Derived> class APISetVisitor {
/// Visit an Objective-C container record.
void visitObjCContainerRecord(const ObjCContainerRecord &Record){};
+ /// Visit an Objective-C category record.
+ void visitObjCCategoryRecord(const ObjCCategoryRecord &Record){};
+
/// Visit a macro definition record.
void visitMacroDefinitionRecord(const MacroDefinitionRecord &Record){};
diff --git a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
index d975e7a61345da..ab60b5c71f2289 100644
--- a/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
+++ b/clang/include/clang/ExtractAPI/Serialization/SymbolGraphSerializer.h
@@ -21,6 +21,7 @@
#include "clang/ExtractAPI/APIIgnoresList.h"
#include "clang/ExtractAPI/Serialization/SerializerBase.h"
#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringSet.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/raw_ostream.h"
@@ -87,6 +88,10 @@ class SymbolGraphSerializer : public APISetVisitor<SymbolGraphSerializer> {
/// The source symbol conforms to the target symbol.
/// For example Objective-C protocol conformances.
ConformsTo,
+
+ /// The source symbol is an extension to the target symbol.
+ /// For example Objective-C categories extending an external type.
+ ExtensionTo,
};
/// Get the string representation of the relationship kind.
@@ -147,6 +152,8 @@ class SymbolGraphSerializer : public APISetVisitor<SymbolGraphSerializer> {
SymbolGraphSerializerOption Options;
+ llvm::StringSet<> visitedCategories;
+
public:
/// Visit a global function record.
void visitGlobalFunctionRecord(const GlobalFunctionRecord &Record);
@@ -167,6 +174,9 @@ class SymbolGraphSerializer : public APISetVisitor<SymbolGraphSerializer> {
/// Visit an Objective-C container record.
void visitObjCContainerRecord(const ObjCContainerRecord &Record);
+ /// Visit an Objective-C category record.
+ void visitObjCCategoryRecord(const ObjCCategoryRecord &Record);
+
/// Visit a macro definition record.
void visitMacroDefinitionRecord(const MacroDefinitionRecord &Record);
diff --git a/clang/lib/ExtractAPI/API.cpp b/clang/lib/ExtractAPI/API.cpp
index 02875325099360..d9baa188f1be5b 100644
--- a/clang/lib/ExtractAPI/API.cpp
+++ b/clang/lib/ExtractAPI/API.cpp
@@ -209,15 +209,16 @@ ObjCCategoryRecord *APISet::addObjCCategory(
StringRef Name, StringRef USR, PresumedLoc Loc,
AvailabilitySet Availabilities, const DocComment &Comment,
DeclarationFragments Declaration, DeclarationFragments SubHeading,
- SymbolReference Interface, bool IsFromSystemHeader) {
+ SymbolReference Interface, bool IsFromSystemHeader,
+ bool IsFromExternalModule) {
// Create the category record.
auto *Record =
addTopLevelRecord(USRBasedLookupTable, ObjCCategories, USR, Name, Loc,
std::move(Availabilities), Comment, Declaration,
SubHeading, Interface, IsFromSystemHeader);
- // If this category is extending a known interface, associate it with the
- // ObjCInterfaceRecord.
+ Record->IsFromExternalModule = IsFromExternalModule;
+
auto It = ObjCInterfaces.find(Interface.USR);
if (It != ObjCInterfaces.end())
It->second->Categories.push_back(Record);
diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
index c9fbf0d87ac218..7783035ba1ec36 100644
--- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
+++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp
@@ -328,7 +328,13 @@ serializeDeclarationFragments(const DeclarationFragments &DF) {
/// Objective-C methods). Can be used as sub-headings for documentation.
Object serializeNames(const APIRecord &Record) {
Object Names;
- Names["title"] = Record.Name;
+ if (auto *CategoryRecord =
+ dyn_cast_or_null<const ObjCCategoryRecord>(&Record))
+ Names["title"] =
+ (CategoryRecord->Interface.Name + " (" + Record.Name + ")").str();
+ else
+ Names["title"] = Record.Name;
+
serializeArray(Names, "subHeading",
serializeDeclarationFragments(Record.SubHeading));
DeclarationFragments NavigatorFragments;
@@ -432,9 +438,12 @@ Object serializeSymbolKind(APIRecord::RecordKind RK, Language Lang) {
Kind["displayName"] = "Class";
break;
case APIRecord::RK_ObjCCategory:
- // We don't serialize out standalone Objective-C category symbols yet.
- llvm_unreachable("Serializing standalone Objective-C category symbols is "
- "not supported.");
+ Kind["identifier"] = AddLangPrefix("class.extension");
+ Kind["displayName"] = "Class Extension";
+ break;
+ case APIRecord::RK_ObjCCategoryModule:
+ Kind["identifier"] = AddLangPrefix("module.extension");
+ Kind["displayName"] = "Module Extension";
break;
case APIRecord::RK_ObjCProtocol:
Kind["identifier"] = AddLangPrefix("protocol");
@@ -563,14 +572,16 @@ bool generatePathComponents(
if (!ParentRecord)
ParentRecord = API.findRecordForUSR(CurrentParent->ParentUSR);
- // If the parent is a category then we need to pretend this belongs to the
- // associated interface.
+ // If the parent is a category extended from internal module then we need to
+ // pretend this belongs to the associated interface.
if (auto *CategoryRecord =
dyn_cast_or_null<ObjCCategoryRecord>(ParentRecord)) {
- ParentRecord = API.findRecordForUSR(CategoryRecord->Interface.USR);
- CurrentParentComponent = PathComponent(CategoryRecord->Interface.USR,
- CategoryRecord->Interface.Name,
- APIRecord::RK_ObjCInterface);
+ if (!CategoryRecord->IsFromExternalModule) {
+ ParentRecord = API.findRecordForUSR(CategoryRecord->Interface.USR);
+ CurrentParentComponent = PathComponent(CategoryRecord->Interface.USR,
+ CategoryRecord->Interface.Name,
+ APIRecord::RK_ObjCInterface);
+ }
}
// The parent record doesn't exist which means the symbol shouldn't be
@@ -709,6 +720,8 @@ StringRef SymbolGraphSerializer::getRelationshipString(RelationshipKind Kind) {
return "inheritsFrom";
case RelationshipKind::ConformsTo:
return "conformsTo";
+ case RelationshipKind::ExtensionTo:
+ return "extensionTo";
}
llvm_unreachable("Unhandled relationship kind");
}
@@ -820,6 +833,45 @@ void SymbolGraphSerializer::visitObjCContainerRecord(
}
}
+void SymbolGraphSerializer::visitObjCCategoryRecord(
+ const ObjCCategoryRecord &Record) {
+ if (!Record.IsFromExternalModule)
+ return;
+
+ // Check if the current Category' parent has been visited before, if so skip.
+ if (!(visitedCategories.contains(Record.Interface.Name) > 0)) {
+ visitedCategories.insert(Record.Interface.Name);
+ Object Obj;
+ serializeObject(Obj, "identifier",
+ serializeIdentifier(Record, API.getLanguage()));
+ serializeObject(Obj, "kind",
+ serializeSymbolKind(APIRecord::RK_ObjCCategoryModule,
+ API.getLanguage()));
+ Obj["accessLevel"] = "public";
+ Symbols.emplace_back(std::move(Obj));
+ }
+
+ Object Relationship;
+ Relationship["source"] = Record.USR;
+ Relationship["target"] = Record.Interface.USR;
+ Relationship["targetFallback"] = Record.Interface.Name;
+ Relationship["kind"] = getRelationshipString(RelationshipKind::ExtensionTo);
+ Relationships.emplace_back(std::move(Relationship));
+
+ auto ObjCCategory = serializeAPIRecord(Record);
+
+ if (!ObjCCategory)
+ return;
+
+ Symbols.emplace_back(std::move(*ObjCCategory));
+ serializeMembers(Record, Record.Methods);
+ serializeMembers(Record, Record.Properties);
+
+ // Surface the protocols of the category to the interface.
+ for (const auto &Protocol : Record.Protocols)
+ serializeRelationship(RelationshipKind::ConformsTo, Record, Protocol);
+}
+
void SymbolGraphSerializer::visitMacroDefinitionRecord(
const MacroDefinitionRecord &Record) {
auto Macro = serializeAPIRecord(Record);
@@ -858,6 +910,9 @@ void SymbolGraphSerializer::serializeSingleRecord(const APIRecord *Record) {
case APIRecord::RK_ObjCProtocol:
visitObjCContainerRecord(*cast<ObjCProtocolRecord>(Record));
break;
+ case APIRecord::RK_ObjCCategory:
+ visitObjCCategoryRecord(*cast<ObjCCategoryRecord>(Record));
+ break;
case APIRecord::RK_MacroDefinition:
visitMacroDefinitionRecord(*cast<MacroDefinitionRecord>(Record));
break;
@@ -926,9 +981,6 @@ SymbolGraphSerializer::serializeSingleSymbolSGF(StringRef USR,
if (!Record)
return {};
- if (isa<ObjCCategoryRecord>(Record))
- return {};
-
Object Root;
APIIgnoresList EmptyIgnores;
SymbolGraphSerializer Serializer(API, EmptyIgnores,
diff --git a/clang/test/ExtractAPI/objc_module_category.m b/clang/test/ExtractAPI/objc_module_category.m
new file mode 100644
index 00000000000000..351c96e1fbad0c
--- /dev/null
+++ b/clang/test/ExtractAPI/objc_module_category.m
@@ -0,0 +1,404 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.output.json.in >> %t/reference.output.json
+// RUN: %clang -extract-api -x objective-c-header \
+// RUN: -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
+#import "Foundation.h"
+
+/// Doc comment 1
+ at interface NSString (Category1)
+-(void)method1;
+ at end
+
+/// Doc comment 2
+ at interface NSString (Category2)
+-(void)method2;
+ at end
+
+//--- Foundation.h
+ at interface NSString
+ at 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"
+ }
+ },
+ "relationships": [
+ {
+ "kind": "extensionTo",
+ "source": "c:objc(cy)NSString at Category1",
+ "target": "c:objc(cs)NSString",
+ "targetFallback": "NSString"
+ },
+ {
+ "kind": "memberOf",
+ "source": "c:objc(cs)NSString(im)method1",
+ "target": "c:objc(cy)NSString at Category1",
+ "targetFallback": "Category1"
+ },
+ {
+ "kind": "extensionTo",
+ "source": "c:objc(cy)NSString at Category2",
+ "target": "c:objc(cs)NSString",
+ "targetFallback": "NSString"
+ },
+ {
+ "kind": "memberOf",
+ "source": "c:objc(cs)NSString(im)method2",
+ "target": "c:objc(cy)NSString at Category2",
+ "targetFallback": "Category2"
+ }
+ ],
+ "symbols": [
+ {
+ "accessLevel": "public",
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cy)NSString at Category1"
+ },
+ "kind": {
+ "displayName": "Module Extension",
+ "identifier": "objective-c.module.extension"
+ }
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "@interface"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:objc(cs)NSString",
+ "spelling": "NSString"
+ },
+ {
+ "kind": "text",
+ "spelling": " ("
+ },
+ {
+ "kind": "identifier",
+ "spelling": "Category1"
+ },
+ {
+ "kind": "text",
+ "spelling": ")"
+ }
+ ],
+ "docComment": {
+ "lines": [
+ {
+ "range": {
+ "end": {
+ "character": 18,
+ "line": 3
+ },
+ "start": {
+ "character": 5,
+ "line": 3
+ }
+ },
+ "text": "Doc comment 1"
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cy)NSString at Category1"
+ },
+ "kind": {
+ "displayName": "Class Extension",
+ "identifier": "objective-c.class.extension"
+ },
+ "location": {
+ "position": {
+ "character": 12,
+ "line": 4
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "Category1"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "identifier",
+ "spelling": "Category1"
+ }
+ ],
+ "title": "NSString (Category1)"
+ },
+ "pathComponents": [
+ "Category1"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "text",
+ "spelling": "- ("
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ },
+ {
+ "kind": "text",
+ "spelling": ") "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "method1"
+ },
+ {
+ "kind": "text",
+ "spelling": ";"
+ }
+ ],
+ "functionSignature": {
+ "returns": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cs)NSString(im)method1"
+ },
+ "kind": {
+ "displayName": "Instance Method",
+ "identifier": "objective-c.method"
+ },
+ "location": {
+ "position": {
+ "character": 1,
+ "line": 5
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "method1"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "text",
+ "spelling": "- "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "method1"
+ }
+ ],
+ "title": "method1"
+ },
+ "pathComponents": [
+ "Category1",
+ "method1"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "@interface"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:objc(cs)NSString",
+ "spelling": "NSString"
+ },
+ {
+ "kind": "text",
+ "spelling": " ("
+ },
+ {
+ "kind": "identifier",
+ "spelling": "Category2"
+ },
+ {
+ "kind": "text",
+ "spelling": ")"
+ }
+ ],
+ "docComment": {
+ "lines": [
+ {
+ "range": {
+ "end": {
+ "character": 18,
+ "line": 8
+ },
+ "start": {
+ "character": 5,
+ "line": 8
+ }
+ },
+ "text": "Doc comment 2"
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cy)NSString at Category2"
+ },
+ "kind": {
+ "displayName": "Class Extension",
+ "identifier": "objective-c.class.extension"
+ },
+ "location": {
+ "position": {
+ "character": 12,
+ "line": 9
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "Category2"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "identifier",
+ "spelling": "Category2"
+ }
+ ],
+ "title": "NSString (Category2)"
+ },
+ "pathComponents": [
+ "Category2"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "text",
+ "spelling": "- ("
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ },
+ {
+ "kind": "text",
+ "spelling": ") "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "method2"
+ },
+ {
+ "kind": "text",
+ "spelling": ";"
+ }
+ ],
+ "functionSignature": {
+ "returns": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cs)NSString(im)method2"
+ },
+ "kind": {
+ "displayName": "Instance Method",
+ "identifier": "objective-c.method"
+ },
+ "location": {
+ "position": {
+ "character": 1,
+ "line": 10
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "method2"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "text",
+ "spelling": "- "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "method2"
+ }
+ ],
+ "title": "method2"
+ },
+ "pathComponents": [
+ "Category2",
+ "method2"
+ ]
+ }
+ ]
+}
diff --git a/clang/test/ExtractAPI/objc_various_categories.m b/clang/test/ExtractAPI/objc_various_categories.m
new file mode 100644
index 00000000000000..f06663c8696d7b
--- /dev/null
+++ b/clang/test/ExtractAPI/objc_various_categories.m
@@ -0,0 +1,507 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%{/t:regex_replacement}@g" \
+// RUN: %t/reference.output.json.in >> %t/reference.output.json
+// RUN: %clang -extract-api -x objective-c-header \
+// RUN: -target arm64-apple-macosx \
+// RUN: %t/myclass_1.h \
+// 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
+#import "myclass_1.h"
+#import "Foundation.h"
+
+ at interface MyClass1 (MyCategory1)
+- (int) SomeMethod;
+ at end
+
+ at interface NSString (Category1)
+-(void) StringMethod;
+ at end
+
+ at interface NSString (Category2)
+-(void) StringMethod2;
+ at end
+
+//--- myclass_1.h
+ at interface MyClass1
+ at end
+
+//--- Foundation.h
+ at interface NSString
+ at 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"
+ }
+ },
+ "relationships": [
+ {
+ "kind": "memberOf",
+ "source": "c:objc(cs)MyClass1(im)SomeMethod",
+ "target": "c:objc(cs)MyClass1",
+ "targetFallback": "MyClass1"
+ },
+ {
+ "kind": "extensionTo",
+ "source": "c:objc(cy)NSString at Category1",
+ "target": "c:objc(cs)NSString",
+ "targetFallback": "NSString"
+ },
+ {
+ "kind": "memberOf",
+ "source": "c:objc(cs)NSString(im)StringMethod",
+ "target": "c:objc(cy)NSString at Category1",
+ "targetFallback": "Category1"
+ },
+ {
+ "kind": "extensionTo",
+ "source": "c:objc(cy)NSString at Category2",
+ "target": "c:objc(cs)NSString",
+ "targetFallback": "NSString"
+ },
+ {
+ "kind": "memberOf",
+ "source": "c:objc(cs)NSString(im)StringMethod2",
+ "target": "c:objc(cy)NSString at Category2",
+ "targetFallback": "Category2"
+ }
+ ],
+ "symbols": [
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "@interface"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "MyClass1"
+ }
+ ],
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cs)MyClass1"
+ },
+ "kind": {
+ "displayName": "Class",
+ "identifier": "objective-c.class"
+ },
+ "location": {
+ "position": {
+ "character": 12,
+ "line": 1
+ },
+ "uri": "file://INPUT_DIR/myclass_1.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "MyClass1"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "identifier",
+ "spelling": "MyClass1"
+ }
+ ],
+ "title": "MyClass1"
+ },
+ "pathComponents": [
+ "MyClass1"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "text",
+ "spelling": "- ("
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": ") "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "SomeMethod"
+ },
+ {
+ "kind": "text",
+ "spelling": ";"
+ }
+ ],
+ "functionSignature": {
+ "returns": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cs)MyClass1(im)SomeMethod"
+ },
+ "kind": {
+ "displayName": "Instance Method",
+ "identifier": "objective-c.method"
+ },
+ "location": {
+ "position": {
+ "character": 1,
+ "line": 5
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "SomeMethod"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "text",
+ "spelling": "- "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "SomeMethod"
+ }
+ ],
+ "title": "SomeMethod"
+ },
+ "pathComponents": [
+ "MyClass1",
+ "SomeMethod"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cy)NSString at Category1"
+ },
+ "kind": {
+ "displayName": "Module Extension",
+ "identifier": "objective-c.module.extension"
+ }
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "@interface"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:objc(cs)NSString",
+ "spelling": "NSString"
+ },
+ {
+ "kind": "text",
+ "spelling": " ("
+ },
+ {
+ "kind": "identifier",
+ "spelling": "Category1"
+ },
+ {
+ "kind": "text",
+ "spelling": ")"
+ }
+ ],
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cy)NSString at Category1"
+ },
+ "kind": {
+ "displayName": "Class Extension",
+ "identifier": "objective-c.class.extension"
+ },
+ "location": {
+ "position": {
+ "character": 12,
+ "line": 8
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "Category1"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "identifier",
+ "spelling": "Category1"
+ }
+ ],
+ "title": "NSString (Category1)"
+ },
+ "pathComponents": [
+ "Category1"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "text",
+ "spelling": "- ("
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ },
+ {
+ "kind": "text",
+ "spelling": ") "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "StringMethod"
+ },
+ {
+ "kind": "text",
+ "spelling": ";"
+ }
+ ],
+ "functionSignature": {
+ "returns": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cs)NSString(im)StringMethod"
+ },
+ "kind": {
+ "displayName": "Instance Method",
+ "identifier": "objective-c.method"
+ },
+ "location": {
+ "position": {
+ "character": 1,
+ "line": 9
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "StringMethod"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "text",
+ "spelling": "- "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "StringMethod"
+ }
+ ],
+ "title": "StringMethod"
+ },
+ "pathComponents": [
+ "Category1",
+ "StringMethod"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "@interface"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:objc(cs)NSString",
+ "spelling": "NSString"
+ },
+ {
+ "kind": "text",
+ "spelling": " ("
+ },
+ {
+ "kind": "identifier",
+ "spelling": "Category2"
+ },
+ {
+ "kind": "text",
+ "spelling": ")"
+ }
+ ],
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cy)NSString at Category2"
+ },
+ "kind": {
+ "displayName": "Class Extension",
+ "identifier": "objective-c.class.extension"
+ },
+ "location": {
+ "position": {
+ "character": 12,
+ "line": 12
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "Category2"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "identifier",
+ "spelling": "Category2"
+ }
+ ],
+ "title": "NSString (Category2)"
+ },
+ "pathComponents": [
+ "Category2"
+ ]
+ },
+ {
+ "accessLevel": "public",
+ "declarationFragments": [
+ {
+ "kind": "text",
+ "spelling": "- ("
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ },
+ {
+ "kind": "text",
+ "spelling": ") "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "StringMethod2"
+ },
+ {
+ "kind": "text",
+ "spelling": ";"
+ }
+ ],
+ "functionSignature": {
+ "returns": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "objective-c",
+ "precise": "c:objc(cs)NSString(im)StringMethod2"
+ },
+ "kind": {
+ "displayName": "Instance Method",
+ "identifier": "objective-c.method"
+ },
+ "location": {
+ "position": {
+ "character": 1,
+ "line": 13
+ },
+ "uri": "file://INPUT_DIR/input.h"
+ },
+ "names": {
+ "navigator": [
+ {
+ "kind": "identifier",
+ "spelling": "StringMethod2"
+ }
+ ],
+ "subHeading": [
+ {
+ "kind": "text",
+ "spelling": "- "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "StringMethod2"
+ }
+ ],
+ "title": "StringMethod2"
+ },
+ "pathComponents": [
+ "Category2",
+ "StringMethod2"
+ ]
+ }
+ ]
+}
More information about the cfe-commits
mailing list