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
