[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