[clang] [clang][Sema] Fix ObjC file-scope literal diagnostic under -fobjc-arc (PR #209688)
Peter Rong via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 14 23:59:51 PDT 2026
https://github.com/DataCorrupted updated https://github.com/llvm/llvm-project/pull/209688
>From 799be7a1beba3452e5a4d73a64ea515f84320f39 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Tue, 14 Jul 2026 23:46:59 -0700
Subject: [PATCH 1/2] [clang][Sema] Fix ObjC file-scope literal diagnostic
under -fobjc-arc
---
clang/lib/Sema/SemaDecl.cpp | 11 ++++-
...c-constant-literal-dict-key-restrictions.m | 45 +++++++++++++++++++
2 files changed, 54 insertions(+), 2 deletions(-)
create mode 100644 clang/test/SemaObjC/objc-constant-literal-dict-key-restrictions.m
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 7e1b23c971a9c..5dbc5b653ec42 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -12917,6 +12917,14 @@ bool Sema::CheckForConstantInitializer(Expr *Init, unsigned DiagID) {
if (Init->isConstantInitializer(Context, /*ForRef=*/false, &Culprit))
return false;
+ // Under ARC, the initializer is wrapped in `ExprWithCleanups` +
+ // `ImplicitCastExpr <ARCReclaimReturnedObject>`. Unwrap both
+ // here so the ObjC-specific path fires under ARC too.
+ Init = Init->IgnoreImpCasts();
+ if (auto *EWC = dyn_cast<ExprWithCleanups>(Init))
+ Init = EWC->getSubExpr()->IgnoreImpCasts();
+ Culprit = Culprit->IgnoreImpCasts();
+
// Emit ObjC-specific diagnostics for non-constant literals at file scope.
if (getLangOpts().ObjCConstantLiterals && isa<ObjCObjectLiteral>(Culprit)) {
@@ -12937,8 +12945,7 @@ bool Sema::CheckForConstantInitializer(Expr *Init, unsigned DiagID) {
for (size_t I = 0, N = DLE->getNumElements(); I != N; ++I) {
const ObjCDictionaryElement Elm = DLE->getKeyValueElement(I);
- // Check that the key is a string literal and is constant.
- if (!isa<ObjCStringLiteral>(Elm.Key) ||
+ if (!isa<ObjCStringLiteral>(Elm.Key->IgnoreImpCasts()) ||
!Elm.Key->isConstantInitializer(Context)) {
Diag(Elm.Key->getExprLoc(),
diag::err_objc_literal_nonconstant_at_file_scope)
diff --git a/clang/test/SemaObjC/objc-constant-literal-dict-key-restrictions.m b/clang/test/SemaObjC/objc-constant-literal-dict-key-restrictions.m
new file mode 100644
index 0000000000000..14eb989eafb08
--- /dev/null
+++ b/clang/test/SemaObjC/objc-constant-literal-dict-key-restrictions.m
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -fsyntax-only -triple x86_64-apple-macosx11.0.0 -fobjc-runtime=macosx-11.0.0 -fobjc-constant-literals -fconstant-nsnumber-literals -fconstant-nsarray-literals -fconstant-nsdictionary-literals -verify %s
+// RUN: %clang_cc1 -fsyntax-only -triple arm64-apple-ios15.1.0-simulator -fobjc-runtime=ios-15.1.0 -fobjc-arc -fobjc-constant-literals -fconstant-nsnumber-literals -fconstant-nsarray-literals -fconstant-nsdictionary-literals -verify %s
+
+#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
+typedef unsigned long NSUInteger;
+#else
+typedef unsigned int NSUInteger;
+#endif
+
+ at class NSString;
+
+ at interface NSNumber
++ (NSNumber *)numberWithInt:(int)value;
++ (NSNumber *)numberWithBool:(unsigned char)value;
+ at end
+
+ at interface NSArray
++ (id)arrayWithObjects:(const id[])objects count:(NSUInteger)cnt;
+ at end
+
+ at interface NSDictionary
++ (id)dictionaryWithObjects:(const id[])objects forKeys:(const id[])keys count:(NSUInteger)cnt;
+ at end
+
+// ---- Accepted: NSString literal keys with constant literal values ---------
+
+static NSDictionary *const dASCII = @{@"a" : @1, @"m" : @2, @"z" : @3};
+static NSDictionary *const dEmpty = @{};
+
+// ---- Rejected: non-NSString-literal key -----------------------------------
+
+static NSDictionary *const dNumberKey = @{@5 : @1}; // expected-error {{a dictionary literal can only be used at file scope if its contents are all also constant literals and its keys are string literals}}
+static NSDictionary *const dBoxedKey = @{@(1 + 2) : @1}; // expected-error {{a dictionary literal can only be used at file scope if its contents are all also constant literals and its keys are string literals}}
+static NSDictionary *const dBoolKey = @{@__objc_yes : @1}; // expected-error {{a dictionary literal can only be used at file scope if its contents are all also constant literals and its keys are string literals}}
+
+// The error points at the specific offending key, not the whole `@{...}`.
+static NSDictionary *const dSecondKeyBad = @{@"ok" : @1, @2 : @2}; // expected-error {{a dictionary literal can only be used at file scope if its contents are all also constant literals and its keys are string literals}}
+
+// ---- Rejected: non-literal key (constant variable reference) --------------
+//
+// `someStringConstantVar` is an NSString, but it's a DeclRefExpr, not an
+// ObjCStringLiteral. Hits the same "not a string literal" branch as `@5`.
+
+static NSString *const someStringConstantVar = @"foo";
+static NSDictionary *const dConstVarKey = @{someStringConstantVar : @1}; // expected-error {{a dictionary literal can only be used at file scope if its contents are all also constant literals and its keys are string literals}}
>From 09cee40adc634e4152d4fa65112064126756b842 Mon Sep 17 00:00:00 2001
From: Peter Rong <PeterRong at meta.com>
Date: Tue, 14 Jul 2026 23:59:35 -0700
Subject: [PATCH 2/2] formatter
---
clang/lib/Sema/SemaDecl.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 5dbc5b653ec42..885083c8bab9c 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -12919,7 +12919,7 @@ bool Sema::CheckForConstantInitializer(Expr *Init, unsigned DiagID) {
// Under ARC, the initializer is wrapped in `ExprWithCleanups` +
// `ImplicitCastExpr <ARCReclaimReturnedObject>`. Unwrap both
- // here so the ObjC-specific path fires under ARC too.
+ // here so the ObjC-specific path fires under ARC too.
Init = Init->IgnoreImpCasts();
if (auto *EWC = dyn_cast<ExprWithCleanups>(Init))
Init = EWC->getSubExpr()->IgnoreImpCasts();
More information about the cfe-commits
mailing list