https://github.com/dmaclach updated 
https://github.com/llvm/llvm-project/pull/211119

>From 64395057d2563aaf1db82b2bf4fa24a786756193 Mon Sep 17 00:00:00 2001
From: Dave MacLachlan <[email protected]>
Date: Tue, 21 Jul 2026 14:56:35 -0700
Subject: [PATCH] Add Objective-C support to include-cleaner's AST walker.

Extend WalkAST to recognize and report references in Objective-C constructs,
including interfaces, protocols, message expressions, properties, categories,
compatible aliases, and instance variables. Also update the test helper to
support custom compiler arguments and add corresponding unit tests.
---
 .../include-cleaner/lib/WalkAST.cpp           | 103 +++++++
 .../include-cleaner/unittests/WalkASTTest.cpp | 265 +++++++++++++++++-
 2 files changed, 365 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp 
b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
index d444ddd90839d..71eb89dfae5ea 100644
--- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
+++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
@@ -395,6 +395,109 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> {
     report(E->getExprLoc(), E->getOperatorDelete(), RefType::Ambiguous);
     return true;
   }
+
+  // Objective-C support
+
+  bool VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc TL) {
+    reportType(TL.getNameLoc(), TL.getIFaceDecl());
+    return true;
+  }
+
+  // Protocols are odd in that they are covered by Traverse instead of Visit.
+  bool TraverseObjCProtocolLoc(ObjCProtocolLoc ProtocolLoc) {
+    if (auto *Proto = ProtocolLoc.getProtocol()) {
+      report(ProtocolLoc.getLocation(), Proto);
+    }
+    return true;
+  }
+
+  bool VisitObjCImplementationDecl(ObjCImplementationDecl *D) {
+    if (auto *Interface = D->getClassInterface()) {
+      report(D->getLocation(), Interface);
+    }
+    return true;
+  }
+
+  bool VisitObjCMessageExpr(ObjCMessageExpr *E) {
+    // Identify the selector and the method declaration
+    if (auto *Method = E->getMethodDecl()) {
+      // Report the method as a used symbol
+      report(E->getSelectorStartLoc(), Method);
+    }
+
+    // If it's a class message, report the interface/class as used
+    if (E->getReceiverKind() == ObjCMessageExpr::Class) {
+      if (auto *Interface = E->getReceiverInterface()) {
+        report(E->getReceiverRange().getBegin(), Interface);
+      }
+    }
+    return true;
+  }
+
+  bool VisitObjCPropertyDecl(clang::ObjCPropertyDecl *PD) {
+    reportType(PD->getLocation(), PD);
+    return true;
+  }
+
+  bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *E) {
+    if (E->isMessagingGetter()) {
+      if (auto *Getter = E->isExplicitProperty()
+                             ? E->getExplicitProperty()->getGetterMethodDecl()
+                             : E->getImplicitPropertyGetter()) {
+        report(E->getLocation(), Getter);
+      }
+    }
+    if (E->isMessagingSetter()) {
+      if (auto *Setter = E->isExplicitProperty()
+                             ? E->getExplicitProperty()->getSetterMethodDecl()
+                             : E->getImplicitPropertySetter()) {
+        report(E->getLocation(), Setter);
+      }
+    }
+    return true;
+  }
+
+  bool VisitObjCProtocolExpr(ObjCProtocolExpr *E) {
+    if (auto *Proto = E->getProtocol()) {
+      report(E->getProtocolIdLoc(), Proto);
+    }
+    return true;
+  }
+
+  bool VisitObjCCategoryDecl(ObjCCategoryDecl *D) {
+    // A category declaration depends on its base interface.
+    if (auto *Interface = D->getClassInterface()) {
+      report(D->getLocation(), Interface);
+    }
+    return true;
+  }
+
+  bool VisitObjCCategoryImplDecl(ObjCCategoryImplDecl *D) {
+    // Implementation requires the base interface.
+    if (auto *Interface = D->getClassInterface()) {
+      report(D->getLocation(), Interface);
+    }
+    // Implementation requires the category declaration.
+    if (auto *Category = D->getCategoryDecl()) {
+      report(D->getCategoryNameLoc(), Category);
+    }
+    return true;
+  }
+
+  bool VisitObjCCompatibleAliasDecl(ObjCCompatibleAliasDecl *D) {
+    // An alias declaration requires the underlying class.
+    if (auto *Aliased = D->getClassInterface()) {
+      report(D->getLocation(), Aliased);
+    }
+    return true;
+  }
+
+  bool VisitObjCIvarRefExpr(ObjCIvarRefExpr *E) {
+    if (auto *Ivar = E->getDecl()) {
+      report(E->getLocation(), Ivar);
+    }
+    return true;
+  }
 };
 
 } // namespace
diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp 
b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
index 3487f24f2af8f..db8948926a344 100644
--- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
+++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
@@ -41,8 +41,9 @@ using testing::ElementsAre;
 //   Referencing: int x = ^foo();
 // There must be exactly one referencing location marked.
 // Returns target decls.
-std::vector<Decl::Kind> testWalk(llvm::StringRef TargetCode,
-                                 llvm::StringRef ReferencingCode) {
+std::vector<Decl::Kind>
+testWalk(llvm::StringRef TargetCode, llvm::StringRef ReferencingCode,
+         std::vector<std::string> ExtraArgs = {"-std=c++20"}) {
   llvm::Annotations Target(TargetCode);
   llvm::Annotations Referencing(ReferencingCode);
 
@@ -50,7 +51,8 @@ std::vector<Decl::Kind> testWalk(llvm::StringRef TargetCode,
   Inputs.ExtraFiles["target.h"] = Target.code().str();
   Inputs.ExtraArgs.push_back("-include");
   Inputs.ExtraArgs.push_back("target.h");
-  Inputs.ExtraArgs.push_back("-std=c++20");
+  for (const auto &Arg : ExtraArgs)
+    Inputs.ExtraArgs.push_back(Arg);
   TestAST AST(Inputs);
   const auto &SM = AST.sourceManager();
 
@@ -576,5 +578,262 @@ TEST(WalkAST, CleanupAttr) {
            "void foo() { __attribute__((__cleanup__(^freep))) char* x = 0; }");
 }
 
+TEST(WalkAST, ObjCInterfaceTypeLoc) {
+  testWalk(R"objc(
+    @interface $explicit^MyClass
+    @end
+  )objc",
+           R"objc(
+    void test() {
+      ^MyClass *obj;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCImplementationDeclDependsOnInterface) {
+  testWalk(R"objc(
+    @interface $explicit^MyClass
+    @end
+  )objc",
+           R"objc(
+    @implementation ^MyClass
+    @end
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCMessageExprSelectorLoc) {
+  testWalk(R"objc(
+    @interface MyClass
+    $explicit^- (void)doSomething;
+    @end
+  )objc",
+           R"objc(
+    void test(MyClass *obj) {
+      [obj ^doSomething];
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCMessageExprClassReceiver) {
+  testWalk(R"objc(
+    @interface $explicit^MyClass
+    + (void)classMethod;
+    @end
+  )objc",
+           R"objc(
+    void test() {
+      [^MyClass classMethod];
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCPropertyRefExprExplicit) {
+  testWalk(R"objc(
+    @interface MyClass
+    @property(nonatomic) int $explicit^foo;
+    @end
+  )objc",
+           R"objc(
+    void test(MyClass *obj) {
+      int x = obj.^foo;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCPropertyRefExprImplicitGetter) {
+  testWalk(R"objc(
+    @interface MyClass
+    $explicit^- (int)foo;
+    @end
+  )objc",
+           R"objc(
+    void test(MyClass *obj) {
+      int x = obj.^foo;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCPropertyRefExprImplicitSetter) {
+  testWalk(R"objc(
+    @interface MyClass
+    $explicit^- (void)setFoo:(int)val;
+    @end
+  )objc",
+           R"objc(
+    void test(MyClass *obj) {
+      obj.^foo = 42;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCPropertyRefExprExplicitSetter) {
+  testWalk(R"objc(
+    @interface MyClass
+    @property(nonatomic) int $explicit^foo;
+    @end
+  )objc",
+           R"objc(
+    void test(MyClass *obj) {
+      obj.^foo = 42;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCProtocolInType) {
+  testWalk(R"objc(
+    @protocol $explicit^MyProtocol
+    @end
+  )objc",
+           R"objc(
+    void test() {
+      id<^MyProtocol> obj;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCProtocolInClassInterface) {
+  testWalk(R"objc(
+    @protocol $explicit^MyProtocol
+    @end
+  )objc",
+           R"objc(
+    @interface MyClass <^MyProtocol>
+    @end
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCProtocolInProtocolInheritance) {
+  testWalk(R"objc(
+    @protocol $explicit^ParentProtocol
+    @end
+  )objc",
+           R"objc(
+    @protocol MyProtocol <^ParentProtocol>
+    @end
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCProtocolExpr) {
+  testWalk(R"objc(
+    @protocol $explicit^MyProtocol
+    @end
+  )objc",
+           R"objc(
+    void test() {
+      Protocol* p = @protocol(^MyProtocol);
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCCategoryDeclDependsOnInterface) {
+  testWalk(R"objc(
+    @interface $explicit^MyClass
+    @end
+  )objc",
+           R"objc(
+    @interface ^MyClass (Category)
+    @end
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCCategoryImplDependsOnInterface) {
+  testWalk(R"objc(
+    @interface $explicit^MyClass
+    @end
+  )objc",
+           R"objc(
+    @interface MyClass (Category)
+    @end
+    @implementation ^MyClass (Category)
+    @end
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCCategoryImplDependsOnCategoryDecl) {
+  testWalk(R"objc(
+    @interface MyClass
+    @end
+    @interface $explicit^MyClass (Category)
+    @end
+  )objc",
+           R"objc(
+    @implementation MyClass (^Category)
+    @end
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCCompatibleAliasDecl) {
+  testWalk(R"objc(
+    @interface $explicit^MyClass
+    @end
+  )objc",
+           R"objc(
+    ^@compatibility_alias AliasName MyClass;
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCCompatibleAliasUsage) {
+  testWalk(R"objc(
+    @interface $explicit^MyClass
+    @end
+    @compatibility_alias AliasName MyClass;
+  )objc",
+           R"objc(
+    void test() {
+      ^AliasName *obj;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCIvarRefExprExplicit) {
+  testWalk(R"objc(
+    @interface MyClass {
+      @public
+      int $explicit^foo;
+    }
+    @end
+  )objc",
+           R"objc(
+    void test(MyClass *obj) {
+      int x = obj->^foo;
+    }
+  )objc",
+           {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCIvarRefExprFree) {
+  testWalk(R"objc(
+    @interface MyClass {
+      int $explicit^foo;
+    }
+    @end
+  )objc",
+           R"objc(
+    @implementation MyClass
+    - (void)test {
+      int x = ^foo;
+    }
+    @end
+  )objc",
+           {"-x", "objective-c"});
+}
+
 } // namespace
 } // namespace clang::include_cleaner

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to