[clang] [Clang][C23] Fix typedef-name after 'auto' as storage-class use (PR #224019)
Zahira Ammarguellat via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 24 06:49:29 PDT 2026
https://github.com/zahiraam updated https://github.com/llvm/llvm-project/pull/224019
>From 22306d4baabb27701a4ba3bdec21f1b83d738a6e Mon Sep 17 00:00:00 2001
From: Zahira Ammarguellat <Zahira.Ammarguellat at intel.com>
Date: Wed, 16 Sep 2026 06:49:13 -0700
Subject: [PATCH 1/5] [Clang][C23] Fix typedef-name after 'auto' as
storage-class use
---
clang/lib/Parse/ParseDecl.cpp | 31 +++++++++++++++++++++++++++++++
clang/test/C/C23/n3007.c | 18 ++++++++++++++++++
2 files changed, 49 insertions(+)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index a4bdec00ca80ac..e123a07204ebcf 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4119,6 +4119,27 @@ void Parser::ParseDeclarationSpecifiers(
break;
case tok::kw_auto:
if (getLangOpts().CPlusPlus11 || getLangOpts().C23) {
+ // FIXME: In C++, `auto` as a storage-class specifier is a
+ // deprecated extension. This lookahead runs for C only; teaching it
+ // to also recognize typedef-names in C++ would require broader
+ // design discussion around `ext_auto_storage_class`. See
+ // https://github.com/llvm/llvm-project/issues/164930.
+ auto IsTypedefName = [&](const Token &T) {
+ if (!T.is(tok::identifier))
+ return false;
+ IdentifierInfo *II = T.getIdentifierInfo();
+ if (!II)
+ return false;
+ // Use a raw suppressed lookup (rather than Sema::getTypeName) to
+ // avoid emitting deprecation/availability diagnostics on the
+ // typedef during this speculative peek — the real parse will look
+ // the name up again and emit them at the right time.
+ LookupResult R(Actions, II, T.getLocation(), Sema::LookupOrdinaryName);
+ Actions.LookupName(R, getCurScope(),
+ /*AllowBuiltinCreation=*/false);
+ R.suppressDiagnostics();
+ return R.isSingleResult() && isa<TypeDecl>(R.getFoundDecl());
+ };
auto MayBeTypeSpecifier = [&]() {
// In pre-C23 C, auto can be used as a storage-class specifier.
// C23 removes auto from the storage-class specifiers and repurposes
@@ -4133,6 +4154,16 @@ void Parser::ParseDeclarationSpecifiers(
if (isKnownToBeTypeSpecifier(T))
return true;
+ // C23: a bare identifier that names a typedef is a type
+ // specifier here, so `auto typedefName varName;` should be
+ // parsed with `auto` as the storage-class specifier — not as
+ // type inference. Without this check the parser would consume
+ // `auto` as type-inference and then error on the missing
+ // initializer for what it thinks is `typedefName` (issue
+ // #164930).
+ if (getLangOpts().C23 && IsTypedefName(T))
+ return true;
+
if (getLangOpts().C23 && isTypeSpecifierQualifier(T))
++I;
else
diff --git a/clang/test/C/C23/n3007.c b/clang/test/C/C23/n3007.c
index a881b894434629..143a2fcc6796e8 100644
--- a/clang/test/C/C23/n3007.c
+++ b/clang/test/C/C23/n3007.c
@@ -209,3 +209,21 @@ void test_macros(int in_int) {
_Static_assert(_Generic(c, int : 1));
_Static_assert(_Generic(result, int : 1));
}
+
+// Regression test for #164930: `auto <typedef-name> <var>;` should parse as a
+// declaration of <var> with type <typedef-name> (auto used as storage-class in
+// C23 with an explicit type-name), not as inferred type deduction on the
+// typedef.
+void test_auto_typedef(void) {
+ typedef int T;
+ {
+ auto T at_local;
+ at_local = 42;
+ _Static_assert(_Generic(at_local, int : 1));
+ }
+ {
+ // Also works with qualifiers.
+ const auto T at_const = 1;
+ _Static_assert(_Generic(&at_const, const int * : 1));
+ }
+}
>From 8b0a6ac5b03cb684bbc3db984ab6cebd7b1ca7b1 Mon Sep 17 00:00:00 2001
From: Zahira Ammarguellat <Zahira.Ammarguellat at intel.com>
Date: Wed, 16 Sep 2026 07:21:40 -0700
Subject: [PATCH 2/5] Fix format
---
clang/lib/Parse/ParseDecl.cpp | 11 ++++-------
clang/test/C/C23/n3007.c | 8 ++++----
2 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index e123a07204ebcf..ad0836a94a17b2 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4120,10 +4120,7 @@ void Parser::ParseDeclarationSpecifiers(
case tok::kw_auto:
if (getLangOpts().CPlusPlus11 || getLangOpts().C23) {
// FIXME: In C++, `auto` as a storage-class specifier is a
- // deprecated extension. This lookahead runs for C only; teaching it
- // to also recognize typedef-names in C++ would require broader
- // design discussion around `ext_auto_storage_class`. See
- // https://github.com/llvm/llvm-project/issues/164930.
+ // deprecated extension. This lookahead runs for C only.
auto IsTypedefName = [&](const Token &T) {
if (!T.is(tok::identifier))
return false;
@@ -4134,7 +4131,8 @@ void Parser::ParseDeclarationSpecifiers(
// avoid emitting deprecation/availability diagnostics on the
// typedef during this speculative peek — the real parse will look
// the name up again and emit them at the right time.
- LookupResult R(Actions, II, T.getLocation(), Sema::LookupOrdinaryName);
+ LookupResult R(Actions, II, T.getLocation(),
+ Sema::LookupOrdinaryName);
Actions.LookupName(R, getCurScope(),
/*AllowBuiltinCreation=*/false);
R.suppressDiagnostics();
@@ -4159,8 +4157,7 @@ void Parser::ParseDeclarationSpecifiers(
// parsed with `auto` as the storage-class specifier — not as
// type inference. Without this check the parser would consume
// `auto` as type-inference and then error on the missing
- // initializer for what it thinks is `typedefName` (issue
- // #164930).
+ // initializer for what it thinks is `typedefName`.
if (getLangOpts().C23 && IsTypedefName(T))
return true;
diff --git a/clang/test/C/C23/n3007.c b/clang/test/C/C23/n3007.c
index 143a2fcc6796e8..e2aaee1cd08b9f 100644
--- a/clang/test/C/C23/n3007.c
+++ b/clang/test/C/C23/n3007.c
@@ -3,6 +3,7 @@
/* WG14 N3007: Yes
* Type Inference for object definitions
*/
+
void test_auto_int(void) {
auto int auto_int = 12;
}
@@ -210,10 +211,9 @@ void test_macros(int in_int) {
_Static_assert(_Generic(result, int : 1));
}
-// Regression test for #164930: `auto <typedef-name> <var>;` should parse as a
-// declaration of <var> with type <typedef-name> (auto used as storage-class in
-// C23 with an explicit type-name), not as inferred type deduction on the
-// typedef.
+// `auto <typedef-name> <var>;` should parse as a declaration of <var> with
+// type <typedef-name> (auto used as storage-class in C23 with an explicit
+// type-name), not as inferred type deduction on the typedef.
void test_auto_typedef(void) {
typedef int T;
{
>From 390561b7e538e8a362207817d6f36d297c7d55a5 Mon Sep 17 00:00:00 2001
From: Zahira Ammarguellat <Zahira.Ammarguellat at intel.com>
Date: Thu, 17 Sep 2026 08:36:51 -0700
Subject: [PATCH 3/5] Addressed review comments
---
clang/include/clang/Parse/Parser.h | 4 ++++
clang/lib/Parse/ParseDecl.cpp | 16 +++++++++-------
clang/lib/Parse/Parser.cpp | 10 ++++++++++
clang/test/C/C23/n3007.c | 24 +++++++++++++++++++-----
4 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 6913c42884a367..3b68aa5a5e583b 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -647,6 +647,10 @@ class Parser : public CodeCompletionHandler {
unsigned NumCachedScopes;
Scope *ScopeCache[ScopeCacheSize];
+ /// Cache mapping an identifier to whether it resolves to a typedef in the
+ /// current scope.
+ llvm::DenseMap<IdentifierInfo *, bool> IsTypedefNameCache;
+
/// Identifiers used for SEH handling in Borland. These are only
/// allowed in particular circumstances
// __except block
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index ad0836a94a17b2..37daee3e894250 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4119,24 +4119,26 @@ void Parser::ParseDeclarationSpecifiers(
break;
case tok::kw_auto:
if (getLangOpts().CPlusPlus11 || getLangOpts().C23) {
- // FIXME: In C++, `auto` as a storage-class specifier is a
- // deprecated extension. This lookahead runs for C only.
auto IsTypedefName = [&](const Token &T) {
if (!T.is(tok::identifier))
return false;
IdentifierInfo *II = T.getIdentifierInfo();
if (!II)
return false;
- // Use a raw suppressed lookup (rather than Sema::getTypeName) to
- // avoid emitting deprecation/availability diagnostics on the
- // typedef during this speculative peek — the real parse will look
- // the name up again and emit them at the right time.
+ // Memoize per scope so we do at most one LookupName per
+ // identifier per scope.
+ auto It = IsTypedefNameCache.find(II);
+ if (It != IsTypedefNameCache.end())
+ return It->second;
+ // Suppress diagnostics; the real parse will emit them later.
LookupResult R(Actions, II, T.getLocation(),
Sema::LookupOrdinaryName);
Actions.LookupName(R, getCurScope(),
/*AllowBuiltinCreation=*/false);
R.suppressDiagnostics();
- return R.isSingleResult() && isa<TypeDecl>(R.getFoundDecl());
+ bool Result = R.isSingleResult() && isa<TypeDecl>(R.getFoundDecl());
+ IsTypedefNameCache[II] = Result;
+ return Result;
};
auto MayBeTypeSpecifier = [&]() {
// In pre-C23 C, auto can be used as a storage-class specifier.
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index da5f23c4ca30e9..3d6ca45cdc13ee 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -425,6 +425,11 @@ bool Parser::SkipUntil(ArrayRef<tok::TokenKind> Toks, SkipUntilFlags Flags) {
//===----------------------------------------------------------------------===//
void Parser::EnterScope(unsigned ScopeFlags) {
+ // Invalidate the typedef-name cache: an identifier may resolve differently
+ // in the new scope.
+ if (!IsTypedefNameCache.empty())
+ IsTypedefNameCache.clear();
+
if (NumCachedScopes) {
Scope *N = ScopeCache[--NumCachedScopes];
N->Init(getCurScope(), ScopeFlags);
@@ -437,6 +442,11 @@ void Parser::EnterScope(unsigned ScopeFlags) {
void Parser::ExitScope() {
assert(getCurScope() && "Scope imbalance!");
+ // Invalidate the typedef-name cache: an identifier may resolve differently
+ // in the enclosing scope.
+ if (!IsTypedefNameCache.empty())
+ IsTypedefNameCache.clear();
+
// Inform the actions module that this scope is going away if there are any
// decls in it.
Actions.ActOnPopScope(Tok.getLocation(), getCurScope());
diff --git a/clang/test/C/C23/n3007.c b/clang/test/C/C23/n3007.c
index e2aaee1cd08b9f..4457dd535b5056 100644
--- a/clang/test/C/C23/n3007.c
+++ b/clang/test/C/C23/n3007.c
@@ -211,19 +211,33 @@ void test_macros(int in_int) {
_Static_assert(_Generic(result, int : 1));
}
-// `auto <typedef-name> <var>;` should parse as a declaration of <var> with
-// type <typedef-name> (auto used as storage-class in C23 with an explicit
-// type-name), not as inferred type deduction on the typedef.
void test_auto_typedef(void) {
typedef int T;
{
auto T at_local;
- at_local = 42;
+ at_local = 10;
_Static_assert(_Generic(at_local, int : 1));
}
{
- // Also works with qualifiers.
const auto T at_const = 1;
_Static_assert(_Generic(&at_const, const int * : 1));
}
+ {
+ auto T a;
+ auto T b;
+ auto T c;
+ a = 1; b = 2; c = 3;
+ _Static_assert(_Generic(a, int : 1));
+ _Static_assert(_Generic(b, int : 1));
+ _Static_assert(_Generic(c, int : 1));
+ }
+ {
+ int T = 7;
+ (void)T;
+ }
+ {
+ auto T at_after_shadow;
+ at_after_shadow = 5;
+ _Static_assert(_Generic(at_after_shadow, int : 1));
+ }
}
>From a4e664d59034fa2a8168ebdb81f866fe77c112e1 Mon Sep 17 00:00:00 2001
From: Zahira Ammarguellat <Zahira.Ammarguellat at intel.com>
Date: Tue, 22 Sep 2026 07:36:56 -0700
Subject: [PATCH 4/5] Dropped the cache
---
clang/include/clang/Parse/Parser.h | 4 ----
clang/lib/Parse/ParseDecl.cpp | 9 +--------
clang/lib/Parse/Parser.cpp | 10 ----------
3 files changed, 1 insertion(+), 22 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 3b68aa5a5e583b..6913c42884a367 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -647,10 +647,6 @@ class Parser : public CodeCompletionHandler {
unsigned NumCachedScopes;
Scope *ScopeCache[ScopeCacheSize];
- /// Cache mapping an identifier to whether it resolves to a typedef in the
- /// current scope.
- llvm::DenseMap<IdentifierInfo *, bool> IsTypedefNameCache;
-
/// Identifiers used for SEH handling in Borland. These are only
/// allowed in particular circumstances
// __except block
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 37daee3e894250..f14f2b32da99e9 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4125,20 +4125,13 @@ void Parser::ParseDeclarationSpecifiers(
IdentifierInfo *II = T.getIdentifierInfo();
if (!II)
return false;
- // Memoize per scope so we do at most one LookupName per
- // identifier per scope.
- auto It = IsTypedefNameCache.find(II);
- if (It != IsTypedefNameCache.end())
- return It->second;
// Suppress diagnostics; the real parse will emit them later.
LookupResult R(Actions, II, T.getLocation(),
Sema::LookupOrdinaryName);
Actions.LookupName(R, getCurScope(),
/*AllowBuiltinCreation=*/false);
R.suppressDiagnostics();
- bool Result = R.isSingleResult() && isa<TypeDecl>(R.getFoundDecl());
- IsTypedefNameCache[II] = Result;
- return Result;
+ return R.isSingleResult() && isa<TypeDecl>(R.getFoundDecl());
};
auto MayBeTypeSpecifier = [&]() {
// In pre-C23 C, auto can be used as a storage-class specifier.
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 3d6ca45cdc13ee..da5f23c4ca30e9 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -425,11 +425,6 @@ bool Parser::SkipUntil(ArrayRef<tok::TokenKind> Toks, SkipUntilFlags Flags) {
//===----------------------------------------------------------------------===//
void Parser::EnterScope(unsigned ScopeFlags) {
- // Invalidate the typedef-name cache: an identifier may resolve differently
- // in the new scope.
- if (!IsTypedefNameCache.empty())
- IsTypedefNameCache.clear();
-
if (NumCachedScopes) {
Scope *N = ScopeCache[--NumCachedScopes];
N->Init(getCurScope(), ScopeFlags);
@@ -442,11 +437,6 @@ void Parser::EnterScope(unsigned ScopeFlags) {
void Parser::ExitScope() {
assert(getCurScope() && "Scope imbalance!");
- // Invalidate the typedef-name cache: an identifier may resolve differently
- // in the enclosing scope.
- if (!IsTypedefNameCache.empty())
- IsTypedefNameCache.clear();
-
// Inform the actions module that this scope is going away if there are any
// decls in it.
Actions.ActOnPopScope(Tok.getLocation(), getCurScope());
>From 49e18b8687d639d22cd003557a31669ceedb282b Mon Sep 17 00:00:00 2001
From: Zahira Ammarguellat <Zahira.Ammarguellat at intel.com>
Date: Thu, 24 Sep 2026 06:49:07 -0700
Subject: [PATCH 5/5] Added RN
---
clang/docs/ReleaseNotes.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index db710b64a6ae53..dec9aea0f29922 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -538,6 +538,7 @@ features cannot lower the translation-unit ABI level;
- Fixed an assertion when `#pragma omp declare simd` or `#pragma omp declare variant` is followed by another OpenMP declarative directive containing a qualified identifier. (#GH217204)
- Fixed a crash when an `asm` label names the register for a global variable of incomplete type. (#GH219746)
- Fixed an ICE hat occurred when using `__imag int/float` as lvalue in assignment. (#GH119498)
+- Fixed a C23 rejects-valid where `auto T x;` (with `T` a typedef-name) was rejected as a missing initializer. (#GH164930)
#### Bug Fixes to Compiler Builtins
More information about the cfe-commits
mailing list