[clang-tools-extra] [include-cleaner] Support Objective-C literals and boxed expressions in WalkAST (PR #216201)
Dave MacLachlan via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 5 13:46:03 PDT 2026
https://github.com/dmaclach updated https://github.com/llvm/llvm-project/pull/216201
>From 23010f10d8c513f542a78b59e7d41a5f6e2e9152 Mon Sep 17 00:00:00 2001
From: Dave MacLachlan <dmaclach at gmail.com>
Date: Thu, 13 Aug 2026 15:23:11 -0700
Subject: [PATCH 1/2] [include-cleaner] Support Objective-C literals and boxed
expressions in WalkAST
This change adds AST visitors for ObjCBoxedExpr, ObjCArrayLiteral, ObjCDictionaryLiteral, and ObjCStringLiteral. This ensures that the underlying class interfaces (such as NSNumber, NSArray, NSDictionary, and NSString) and any associated categories used for these literals are correctly reported as referenced. Unit tests are included to verify the new behavior.
---
.../include-cleaner/lib/WalkAST.cpp | 28 +++++++
.../include-cleaner/unittests/WalkASTTest.cpp | 73 +++++++++++++++++++
2 files changed, 101 insertions(+)
diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
index 17e7bd6ed683f..976e998e1d45f 100644
--- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
+++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
@@ -622,6 +622,34 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> {
}
return true;
}
+
+ void ReportObjCMethodDecl(SourceLocation Loc, ObjCMethodDecl *Method) {
+ if (!Method)
+ return;
+ report(Loc, Method->getClassInterface(), RefType::Implicit);
+ report(Loc, dyn_cast<ObjCCategoryDecl>(Method->getDeclContext()));
+ }
+
+ bool VisitObjCBoxedExpr(ObjCBoxedExpr *E) {
+ ReportObjCMethodDecl(E->getBeginLoc(), E->getBoxingMethod());
+ return true;
+ }
+
+ bool VisitObjCArrayLiteral(ObjCArrayLiteral *E) {
+ ReportObjCMethodDecl(E->getBeginLoc(), E->getArrayWithObjectsMethod());
+ return true;
+ }
+
+ bool VisitObjCDictionaryLiteral(ObjCDictionaryLiteral *E) {
+ ReportObjCMethodDecl(E->getBeginLoc(), E->getDictWithObjectsMethod());
+ return true;
+ }
+
+ bool VisitObjCStringLiteral(ObjCStringLiteral *E) {
+ if (const auto *ObjCPtr = E->getType()->getAs<ObjCObjectPointerType>())
+ report(E->getBeginLoc(), ObjCPtr->getInterfaceDecl(), RefType::Implicit);
+ return true;
+ }
};
} // namespace
diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
index ace55ea7aa23f..71bc455ca00ed 100644
--- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
+++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
@@ -1250,6 +1250,7 @@ TEST(WalkAST, ObjCTollFreeBridgeCStyleCast) {
{"-x", "objective-c"});
}
+
TEST(WalkAST, ObjCBridgedCastExprToProtocol) {
// Note this test case is handled by TraverseObjCProtocolLoc instead of
// VisitCastExpr.
@@ -1545,5 +1546,77 @@ TEST(WalkAST, ObjCEncodeExpr) {
{"-x", "objective-c"});
}
+TEST(WalkAST, ObjCBoxedExprInt) {
+ testWalk(R"objc(
+ @interface $implicit^NSNumber
+ + (id)numberWithInt:(int)val;
+ @end
+ )objc",
+ R"objc(
+ void test() {
+ id x = ^@42;
+ }
+ )objc",
+ {"-x", "objective-c"});
+}
+
+
+TEST(WalkAST, ObjCBoxedExprCategory) {
+ testWalk(R"objc(
+ @interface $implicit^NSNumber
+ @end
+ @interface $explicit^NSNumber (CustomCategory)
+ + (id)numberWithInt:(int)val;
+ @end
+ )objc",
+ R"objc(
+ void test() {
+ id x = ^@42;
+ }
+ )objc",
+ {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCArrayLiteral) {
+ testWalk(R"objc(
+ @interface $implicit^NSArray
+ + (id)arrayWithObjects:(const id *)objects count:(unsigned long)cnt;
+ @end
+ )objc",
+ R"objc(
+ void test(id a, id b) {
+ id arr = ^@[a, b];
+ }
+ )objc",
+ {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCDictionaryLiteral) {
+ testWalk(R"objc(
+ @interface $implicit^NSDictionary
+ + (id)dictionaryWithObjects:(const id *)objects forKeys:(const id *)keys count:(unsigned long)cnt;
+ @end
+ )objc",
+ R"objc(
+ void test(id k, id v) {
+ id dict = ^@{k: v};
+ }
+ )objc",
+ {"-x", "objective-c"});
+}
+
+TEST(WalkAST, ObjCStringLiteral) {
+ testWalk(R"objc(
+ @interface $implicit^NSString
+ @end
+ )objc",
+ R"objc(
+ void test() {
+ id s = ^@"hello";
+ }
+ )objc",
+ {"-x", "objective-c"});
+}
+
} // namespace
} // namespace clang::include_cleaner
>From 90228f581f914f637e43076f6c51b5464349ba29 Mon Sep 17 00:00:00 2001
From: Dave MacLachlan <dmaclach at gmail.com>
Date: Mon, 24 Aug 2026 10:23:14 -0700
Subject: [PATCH 2/2] Cleaned up based on comments - Changed references to
explicit - Removed unneeded references for categories - Added test for
Boxed C Structures
---
.../include-cleaner/lib/WalkAST.cpp | 16 +++++------
.../include-cleaner/unittests/WalkASTTest.cpp | 27 ++++++++++---------
2 files changed, 23 insertions(+), 20 deletions(-)
diff --git a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
index 976e998e1d45f..5449411371e60 100644
--- a/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
+++ b/clang-tools-extra/include-cleaner/lib/WalkAST.cpp
@@ -623,31 +623,31 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> {
return true;
}
- void ReportObjCMethodDecl(SourceLocation Loc, ObjCMethodDecl *Method) {
+ void reportObjCLiteralMethod(SourceLocation Loc, ObjCMethodDecl *Method) {
if (!Method)
return;
- report(Loc, Method->getClassInterface(), RefType::Implicit);
- report(Loc, dyn_cast<ObjCCategoryDecl>(Method->getDeclContext()));
+ report(Loc, Method->getClassInterface());
}
bool VisitObjCBoxedExpr(ObjCBoxedExpr *E) {
- ReportObjCMethodDecl(E->getBeginLoc(), E->getBoxingMethod());
+ // Handles NSNumber literals and NSValue literals.
+ reportObjCLiteralMethod(E->getBeginLoc(), E->getBoxingMethod());
return true;
}
bool VisitObjCArrayLiteral(ObjCArrayLiteral *E) {
- ReportObjCMethodDecl(E->getBeginLoc(), E->getArrayWithObjectsMethod());
+ reportObjCLiteralMethod(E->getBeginLoc(), E->getArrayWithObjectsMethod());
return true;
}
bool VisitObjCDictionaryLiteral(ObjCDictionaryLiteral *E) {
- ReportObjCMethodDecl(E->getBeginLoc(), E->getDictWithObjectsMethod());
+ reportObjCLiteralMethod(E->getBeginLoc(), E->getDictWithObjectsMethod());
return true;
}
bool VisitObjCStringLiteral(ObjCStringLiteral *E) {
- if (const auto *ObjCPtr = E->getType()->getAs<ObjCObjectPointerType>())
- report(E->getBeginLoc(), ObjCPtr->getInterfaceDecl(), RefType::Implicit);
+ report(E->getBeginLoc(),
+ E->getType()->getAs<ObjCObjectPointerType>()->getInterfaceDecl());
return true;
}
};
diff --git a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
index 71bc455ca00ed..bcc1df3f618e4 100644
--- a/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
+++ b/clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
@@ -1548,7 +1548,7 @@ TEST(WalkAST, ObjCEncodeExpr) {
TEST(WalkAST, ObjCBoxedExprInt) {
testWalk(R"objc(
- @interface $implicit^NSNumber
+ @interface $explicit^NSNumber
+ (id)numberWithInt:(int)val;
@end
)objc",
@@ -1560,18 +1560,19 @@ TEST(WalkAST, ObjCBoxedExprInt) {
{"-x", "objective-c"});
}
-
-TEST(WalkAST, ObjCBoxedExprCategory) {
+TEST(WalkAST, ObjCBoxedExprStruct) {
testWalk(R"objc(
- @interface $implicit^NSNumber
- @end
- @interface $explicit^NSNumber (CustomCategory)
- + (id)numberWithInt:(int)val;
+ struct __attribute__((objc_boxable)) Point {
+ int x, y;
+ };
+ @interface $explicit^NSValue
+ + (id)valueWithBytes:(const void *)bytes objCType:(const char *)type;
@end
)objc",
R"objc(
void test() {
- id x = ^@42;
+ struct Point p = {1, 2};
+ id x = ^@(p);
}
)objc",
{"-x", "objective-c"});
@@ -1579,7 +1580,7 @@ TEST(WalkAST, ObjCBoxedExprCategory) {
TEST(WalkAST, ObjCArrayLiteral) {
testWalk(R"objc(
- @interface $implicit^NSArray
+ @interface $explicit^NSArray
+ (id)arrayWithObjects:(const id *)objects count:(unsigned long)cnt;
@end
)objc",
@@ -1593,8 +1594,10 @@ TEST(WalkAST, ObjCArrayLiteral) {
TEST(WalkAST, ObjCDictionaryLiteral) {
testWalk(R"objc(
- @interface $implicit^NSDictionary
- + (id)dictionaryWithObjects:(const id *)objects forKeys:(const id *)keys count:(unsigned long)cnt;
+ @interface $explicit^NSDictionary
+ + (id)dictionaryWithObjects:(const id *)objects
+ forKeys:(const id *)keys
+ count:(unsigned long)cnt;
@end
)objc",
R"objc(
@@ -1607,7 +1610,7 @@ TEST(WalkAST, ObjCDictionaryLiteral) {
TEST(WalkAST, ObjCStringLiteral) {
testWalk(R"objc(
- @interface $implicit^NSString
+ @interface $explicit^NSString
@end
)objc",
R"objc(
More information about the cfe-commits
mailing list