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

>From f2520b1581449fb1e0db2aa24dbcb450e523e6f5 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           | 166 +++++++++-
 .../include-cleaner/unittests/WalkASTTest.cpp | 296 +++++++++++++++++-
 2 files changed, 457 insertions(+), 5 deletions(-)

diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp 
b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
index d444ddd90839d..e3e610b8c33d8 100644
--- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
+++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
@@ -15,7 +15,6 @@
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
-#include "clang/AST/NestedNameSpecifier.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/AST/TemplateBase.h"
 #include "clang/AST/TemplateName.h"
@@ -25,7 +24,6 @@
 #include "clang/Basic/OperatorKinds.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/Specifiers.h"
-#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Casting.h"
@@ -395,6 +393,170 @@ 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) {
+    // Unconditionally report property declarations and their backing accessor
+    // methods. Dot-notation or pseudo-object references (`foo.bar`) require
+    // the underlying property definition or getters/setters to compile.
+    // Bypassing transient compiler state flags (isMessagingGetter/
+    // isMessagingSetter) guarantees that the declaring
+    // header keeps the properties recorded as used.
+    if (E->isExplicitProperty()) {
+      if (auto *Prop = E->getExplicitProperty()) {
+        report(E->getLocation(), Prop);
+        if (auto *Getter = Prop->getGetterMethodDecl())
+          report(E->getLocation(), Getter);
+        if (auto *Setter = Prop->getSetterMethodDecl())
+          report(E->getLocation(), Setter);
+      }
+    } else {
+      if (auto *Getter = E->getImplicitPropertyGetter())
+        report(E->getLocation(), Getter);
+      if (auto *Setter = E->getImplicitPropertySetter())
+        report(E->getLocation(), Setter);
+    }
+    return true;
+  }
+
+  bool VisitObjCProtocolExpr(ObjCProtocolExpr *E) {
+    if (auto *Proto = E->getProtocol()) {
+      report(E->getProtocolIdLoc(), Proto);
+    }
+    return true;
+  }
+
+  bool VisitCastExpr(CastExpr *E) {
+    // Handle implicit or explicit casts between Objective-C object pointers
+    // aimed towards protocol-qualification (e.g., `ClassName *` to
+    // `id<Proto>`).
+    QualType SourceType = E->getSubExpr()->getType();
+    QualType DestType = E->getType();
+
+    const auto *SrcPtr = SourceType->getAs<ObjCObjectPointerType>();
+    const auto *DestPtr = DestType->getAs<ObjCObjectPointerType>();
+
+    // If we're casting from a known class pointer to protocol conformance.
+    if (SrcPtr && DestPtr && SrcPtr->getInterfaceDecl()) {
+      const ObjCInterfaceDecl *Class = SrcPtr->getInterfaceDecl();
+      ASTContext &Ctx = Class->getASTContext();
+
+      // For every protocol required by the destination type.
+      for (const ObjCProtocolDecl *Proto : DestPtr->quals()) {
+        const ObjCInterfaceDecl *Current = Class;
+        // Search the inheritance hierarchy for the provider of conformance.
+        while (Current) {
+          bool ConformsDirectly = false;
+          for (const auto *PI : Current->protocols()) {
+            if (Ctx.ProtocolCompatibleWithProtocol(
+                    const_cast<ObjCProtocolDecl *>(Proto),
+                    const_cast<ObjCProtocolDecl *>(PI))) {
+              ConformsDirectly = true;
+              break;
+            }
+          }
+          // If the class itself provides the conformance directly, we don't
+          // need to keep searching Categories.
+          if (ConformsDirectly)
+            break;
+
+          // If the class doesn't declare direct conformance but conformance is
+          // injected via a visible Category attached to this class, note that
+          // the category header is required by recording an Implicit reference
+          // to it.
+          for (const auto *Cat : Current->visible_categories()) {
+            for (auto *PI : Cat->protocols()) {
+              if (Ctx.ProtocolCompatibleWithProtocol(
+                      const_cast<ObjCProtocolDecl *>(Proto),
+                      const_cast<ObjCProtocolDecl *>(PI))) {
+                report(E->getExprLoc(), const_cast<ObjCCategoryDecl *>(Cat),
+                       RefType::Implicit);
+              }
+            }
+          }
+          Current = Current->getSuperClass();
+        }
+      }
+    }
+    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..6bf0bde9cfa10 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,293 @@ 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, ObjCPropertyRefExprProtocol) {
+  testWalk(R"objc(
+    @protocol MyProtocol
+    @property(nonatomic) int $explicit^foo;
+    @end
+  )objc",
+           R"objc(
+    void test(id<MyProtocol> obj) {
+      int x = obj.^foo;
+    }
+  )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, ObjCImplicitCastToProtocolConformingCategory) {
+  testWalk(R"objc(
+    @protocol MyProtocol
+    @end
+    @interface MyClass
+    @end
+    @interface $implicit^MyClass (MyCategory) <MyProtocol>
+    @end
+  )objc",
+           R"objc(
+    void test(MyClass *obj) {
+      id<MyProtocol> p = ^obj;
+    }
+  )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