[clang] [Clang] prevent incorrect rejection of auto with reordered declaration specifiers in C23 (PR #177865)
Oleksandr Tarasiuk via cfe-commits
cfe-commits at lists.llvm.org
Tue Jan 27 03:27:27 PST 2026
https://github.com/a-tarasyuk updated https://github.com/llvm/llvm-project/pull/177865
>From 886c342fcf9a2a84929f6fb3eb51ea74de5c6b83 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Sun, 25 Jan 2026 19:05:45 +0200
Subject: [PATCH 1/2] [Clang] prevent incorrect rejection of auto with
reordered declaration specifiers in C23
---
clang/docs/ReleaseNotes.rst | 1 +
clang/lib/Parse/ParseDecl.cpp | 14 +++++++++++++-
clang/test/Parser/c2x-auto.c | 24 +++++++++++++++++++++++-
3 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a734804865c57..47e578448e1e8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -169,6 +169,7 @@ Bug Fixes in This Version
-------------------------
- Fix lifetime extension of temporaries in for-range-initializers in templates. (#GH165182)
+- Fixed incorrect rejection of ``auto`` with reordered declaration specifiers in C23. (#GH164121)
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index f8c49646fcf3f..404f0a312b8bf 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4093,7 +4093,19 @@ void Parser::ParseDeclarationSpecifiers(
break;
case tok::kw_auto:
if (getLangOpts().CPlusPlus11 || getLangOpts().C23) {
- if (isKnownToBeTypeSpecifier(GetLookAheadToken(1))) {
+ auto IsTypeSpecifier = [&]() {
+ if (DS.getTypeSpecWidth() != TypeSpecifierWidth::Unspecified)
+ return true;
+
+ unsigned I = 1;
+ while (GetLookAheadToken(I).isOneOf(tok::kw_const, tok::kw_volatile,
+ tok::kw_restrict))
+ ++I;
+
+ return isKnownToBeTypeSpecifier(GetLookAheadToken(I));
+ };
+
+ if (IsTypeSpecifier()) {
isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc,
PrevSpec, DiagID, Policy);
if (!isInvalid && !getLangOpts().C23)
diff --git a/clang/test/Parser/c2x-auto.c b/clang/test/Parser/c2x-auto.c
index 7f80b0717ab25..cee670bf7b952 100644
--- a/clang/test/Parser/c2x-auto.c
+++ b/clang/test/Parser/c2x-auto.c
@@ -147,7 +147,7 @@ auto int b1 = 0; // c23-error {{illegal storage class on file-scoped variable}}
int auto b2 = 0; // c23-error {{cannot combine with previous 'int' declaration specifier}} \
c17-error {{illegal storage class on file-scoped variable}}
-void f() {
+void t1() {
constexpr auto int c1 = 0; // c23-error {{cannot combine with previous 'auto' declaration specifier}} \
c17-error {{use of undeclared identifier 'constexpr'}}
@@ -157,3 +157,25 @@ void f() {
auto int d1 = 0;
int auto d2 = 0; // c23-error {{cannot combine with previous 'int' declaration specifier}}
}
+
+void t2() {
+ auto long long a1 = 0;
+ long auto long a2 = 0;
+ long long auto a3 = 0;
+
+ auto const long long b1 = 0;
+ long long const auto b2 = 0;
+ long long auto const b3 = 0;
+}
+
+void t3() {
+ const auto int a1 = 0;
+ auto const int a2 = 0;
+
+ volatile auto int a3 = 0;
+ auto volatile int a4 = 0;
+ auto volatile const int a5 = 0;
+ auto const volatile int a6 = 0;
+
+ auto restrict int a7 = 0; // expected-error {{restrict requires a pointer or reference ('int' is invalid}}
+}
>From 16e85becd7905f2896892541e76f6f3e7bb1b1b1 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Tue, 27 Jan 2026 13:27:10 +0200
Subject: [PATCH 2/2] handle reordered c23 auto with type-specifier qualifiers
---
clang/include/clang/Parse/Parser.h | 8 ++--
clang/lib/Parse/ParseDecl.cpp | 31 ++++++++++-----
clang/lib/Parse/ParseObjc.cpp | 2 +-
clang/test/AST/ByteCode/constexpr.c | 4 +-
.../dcl.spec.auto/p3-generic-lambda-1y.cpp | 6 +--
clang/test/Parser/c2x-auto.c | 39 ++++++++++++++++---
clang/test/Sema/c2x-auto.c | 4 +-
clang/test/Sema/constexpr.c | 4 +-
clang/test/SemaCXX/auto-cxx0x.cpp | 2 +-
9 files changed, 71 insertions(+), 29 deletions(-)
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index cd7dc14701914..1af9a91a76c41 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2017,7 +2017,7 @@ class Parser : public CodeCompletionHandler {
/// isTypeSpecifierQualifier - Return true if the current token could be the
/// start of a specifier-qualifier-list.
- bool isTypeSpecifierQualifier();
+ bool isTypeSpecifierQualifier(const Token &Tok);
/// isKnownToBeTypeSpecifier - Return true if we know that the specified token
/// is definitely a type-specifier. Return false if it isn't part of a type
@@ -4335,7 +4335,7 @@ class Parser : public CodeCompletionHandler {
return isCXXTypeId(TentativeCXXTypeIdContext::AsGenericSelectionArgument,
isAmbiguous);
}
- return isTypeSpecifierQualifier();
+ return isTypeSpecifierQualifier(Tok);
}
/// Checks if the current tokens form type-id or expression.
@@ -4346,7 +4346,7 @@ class Parser : public CodeCompletionHandler {
bool isAmbiguous;
return isCXXTypeId(TentativeCXXTypeIdContext::Unambiguous, isAmbiguous);
}
- return isTypeSpecifierQualifier();
+ return isTypeSpecifierQualifier(Tok);
}
/// ParseBlockId - Parse a block-id, which roughly looks like int (int x).
@@ -5015,7 +5015,7 @@ class Parser : public CodeCompletionHandler {
if (getLangOpts().CPlusPlus)
return isCXXTypeId(TentativeCXXTypeIdContext::InParens, isAmbiguous);
isAmbiguous = false;
- return isTypeSpecifierQualifier();
+ return isTypeSpecifierQualifier(Tok);
}
bool isTypeIdInParens() {
bool isAmbiguous;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 404f0a312b8bf..55fe7769765e5 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4094,15 +4094,26 @@ void Parser::ParseDeclarationSpecifiers(
case tok::kw_auto:
if (getLangOpts().CPlusPlus11 || getLangOpts().C23) {
auto IsTypeSpecifier = [&]() {
- if (DS.getTypeSpecWidth() != TypeSpecifierWidth::Unspecified)
+ if (DS.hasTypeSpecifier() &&
+ DS.getTypeSpecType() != DeclSpec::TST_auto)
return true;
unsigned I = 1;
- while (GetLookAheadToken(I).isOneOf(tok::kw_const, tok::kw_volatile,
- tok::kw_restrict))
- ++I;
-
- return isKnownToBeTypeSpecifier(GetLookAheadToken(I));
+ while (true) {
+ const Token &T = GetLookAheadToken(I);
+ if (isKnownToBeTypeSpecifier(T))
+ return true;
+
+ if (T.isOneOf(tok::kw_typeof, tok::kw_typeof_unqual,
+ tok::kw__Atomic) &&
+ GetLookAheadToken(I + 1).is(tok::l_paren))
+ return true;
+
+ if (isTypeSpecifierQualifier(T))
+ ++I;
+ else
+ return false;
+ }
};
if (IsTypeSpecifier()) {
@@ -5569,7 +5580,7 @@ bool Parser::isKnownToBeTypeSpecifier(const Token &Tok) const {
}
}
-bool Parser::isTypeSpecifierQualifier() {
+bool Parser::isTypeSpecifierQualifier(const Token &Tok) {
switch (Tok.getKind()) {
default: return false;
@@ -5582,9 +5593,9 @@ bool Parser::isTypeSpecifierQualifier() {
// recurse to handle whatever we get.
if (TryAnnotateTypeOrScopeToken())
return true;
- if (Tok.is(tok::identifier))
+ if (getCurToken().is(tok::identifier))
return false;
- return isTypeSpecifierQualifier();
+ return isTypeSpecifierQualifier(getCurToken());
case tok::coloncolon: // ::foo::bar
if (NextToken().is(tok::kw_new) || // ::new
@@ -5593,7 +5604,7 @@ bool Parser::isTypeSpecifierQualifier() {
if (TryAnnotateTypeOrScopeToken())
return true;
- return isTypeSpecifierQualifier();
+ return isTypeSpecifierQualifier(getCurToken());
// GNU attributes support.
case tok::kw___attribute:
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 0b9f113d9edc7..eb50b0e8546c6 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -1114,7 +1114,7 @@ ParsedType Parser::ParseObjCTypeName(ObjCDeclSpec &DS,
SourceLocation TypeStartLoc = Tok.getLocation();
ParsedType Ty;
- if (isTypeSpecifierQualifier() || isObjCInstancetype()) {
+ if (isTypeSpecifierQualifier(Tok) || isObjCInstancetype()) {
// Parse an abstract declarator.
DeclSpec declSpec(AttrFactory);
declSpec.setObjCQualifiers(&DS);
diff --git a/clang/test/AST/ByteCode/constexpr.c b/clang/test/AST/ByteCode/constexpr.c
index af96bf3a06f37..2869f55442631 100644
--- a/clang/test/AST/ByteCode/constexpr.c
+++ b/clang/test/AST/ByteCode/constexpr.c
@@ -39,7 +39,9 @@ constexpr auto Ulong = 1L;
constexpr auto CompoundLiteral = (int){13};
constexpr auto DoubleCast = (double)(1 / 3);
constexpr auto String = "this is a string"; // both-error {{constexpr pointer initializer is not null}}
-constexpr signed auto Long = 1L; // both-error {{'auto' cannot be signed or unsigned}}
+// both-error at +2 {{cannot combine with previous 'auto' declaration specifier}}
+// both-error at +1 {{illegal storage class on file-scoped variable}}
+constexpr signed auto Long = 1L;
_Static_assert(_Generic(Ulong, long : 1));
_Static_assert(_Generic(CompoundLiteral, int : 1));
_Static_assert(_Generic(DoubleCast, double : 1));
diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p3-generic-lambda-1y.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p3-generic-lambda-1y.cpp
index 07bc884e7d5ee..10272eaf9b68b 100644
--- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p3-generic-lambda-1y.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p3-generic-lambda-1y.cpp
@@ -63,12 +63,10 @@ int main()
auto l = [](auto
(*)(auto)) { }; //expected-error{{'auto' not allowed}}
//FIXME: These diagnostics might need some work.
- auto l2 = [](char auto::*pm) { }; //expected-error{{cannot combine with previous}}\
- expected-error{{'pm' does not point into a class}}
+ auto l2 = [](char auto::*pm) { }; // expected-error {{'pm' does not point into a class}} \
+ expected-warning {{'auto' storage class specifier is not permitted in C++11, and will not be supported in future releases}}
auto l3 = [](char (auto::*pmf)()) { }; //expected-error{{'auto' not allowed}}\
expected-error{{'pmf' does not point into a class}}\
expected-error{{function cannot return function type 'char ()'}}
}
}
-
-
diff --git a/clang/test/Parser/c2x-auto.c b/clang/test/Parser/c2x-auto.c
index cee670bf7b952..2ae0dcf4f0aac 100644
--- a/clang/test/Parser/c2x-auto.c
+++ b/clang/test/Parser/c2x-auto.c
@@ -137,25 +137,29 @@ constexpr auto int a1 = 0; // c23-error {{illegal storage class on file-scoped v
c17-error {{illegal storage class on file-scoped variable}} \
c17-error {{unknown type name 'constexpr'}}
-constexpr int auto a2 = 0; // c23-error {{cannot combine with previous 'int' declaration specifier}} \
+constexpr int auto a2 = 0; // c23-error {{cannot combine with previous 'auto' declaration specifier}} \
+ c23-error {{illegal storage class on file-scoped variable}} \
c17-error {{illegal storage class on file-scoped variable}} \
c17-error {{unknown type name 'constexpr'}}
auto int b1 = 0; // c23-error {{illegal storage class on file-scoped variable}} \
c17-error {{illegal storage class on file-scoped variable}}
-int auto b2 = 0; // c23-error {{cannot combine with previous 'int' declaration specifier}} \
+int auto b2 = 0; // c23-error {{illegal storage class on file-scoped variable}} \
c17-error {{illegal storage class on file-scoped variable}}
+long auto long i = 0; // c23-error {{illegal storage class on file-scoped variable}} \
+ c17-error {{illegal storage class on file-scoped variable}}
+
void t1() {
constexpr auto int c1 = 0; // c23-error {{cannot combine with previous 'auto' declaration specifier}} \
c17-error {{use of undeclared identifier 'constexpr'}}
- constexpr int auto c2 = 0; // c23-error {{cannot combine with previous 'int' declaration specifier}} \
+ constexpr int auto c2 = 0; // c23-error {{cannot combine with previous 'auto' declaration specifier}} \
c17-error {{use of undeclared identifier 'constexpr'}}
auto int d1 = 0;
- int auto d2 = 0; // c23-error {{cannot combine with previous 'int' declaration specifier}}
+ int auto d2 = 0;
}
void t2() {
@@ -177,5 +181,30 @@ void t3() {
auto volatile const int a5 = 0;
auto const volatile int a6 = 0;
- auto restrict int a7 = 0; // expected-error {{restrict requires a pointer or reference ('int' is invalid}}
+ auto restrict int a7 = 0; // expected-error {{restrict requires a pointer or reference ('int' is invalid)}}
+}
+
+void t4() {
+ static long auto long s1 = 0; // c23-error {{cannot combine with previous 'static' declaration specifier}} \
+ c17-error {{cannot combine with previous 'static' declaration specifier}}
+ extern long auto long e2; // c23-error {{cannot combine with previous 'extern' declaration specifier}} \
+ c17-error {{cannot combine with previous 'extern' declaration specifier}}
+}
+
+void t5(void) {
+ const long auto long unsigned volatile _Atomic int x = 0;
+}
+
+void t6(void) {
+ auto typeof(0) t1 = 0; // c17-error {{expected parameter declarator}} \
+ c17-error {{expected ')'}} \
+ c17-error {{type specifier missing, defaults to 'int'; ISO C99 and later do not support implicit int}} \
+ c17-error {{expected ';' at end of declaration}} \
+ c17-error {{illegal storage class on function}} \
+ c17-note {{to match this '('}}
+ typeof(0) auto t2 = 0; // c17-error {{expected ';' after expression}} \
+ c17-error {{type specifier missing, defaults to 'int'; ISO C99 and later do not support implicit int}}
+
+ auto _Atomic(int) t3 = 0;
+ _Atomic(int) auto t4 = 0;
}
diff --git a/clang/test/Sema/c2x-auto.c b/clang/test/Sema/c2x-auto.c
index 7d62db9ea6c28..c6128cba88689 100644
--- a/clang/test/Sema/c2x-auto.c
+++ b/clang/test/Sema/c2x-auto.c
@@ -6,14 +6,14 @@ void test_basic_types(void) {
auto auto_int = 4;
auto auto_long = 4UL;
auto int auto_int_ts = 12;
- signed auto a = 1L; // expected-error {{'auto' cannot be signed or unsigned}}
+ signed auto a = 1L;
_Static_assert(_Generic(auto_int, int : 1));
_Static_assert(_Generic(auto_long, unsigned long : 1));
}
void test_complex_types(void) {
- _Complex auto i = 12.0; // expected-error {{'_Complex auto' is invalid}}
+ _Complex auto i = 12.0; // expected-warning {{plain '_Complex' requires a type specifier; assuming '_Complex double'}}
}
void test_gnu_extensions(void) {
diff --git a/clang/test/Sema/constexpr.c b/clang/test/Sema/constexpr.c
index ae01c71e09b06..4b72289d4d3d3 100644
--- a/clang/test/Sema/constexpr.c
+++ b/clang/test/Sema/constexpr.c
@@ -39,7 +39,9 @@ constexpr auto Ulong = 1L;
constexpr auto CompoundLiteral = (int){13};
constexpr auto DoubleCast = (double)(1 / 3);
constexpr auto String = "this is a string"; // expected-error {{constexpr pointer initializer is not null}}
-constexpr signed auto Long = 1L; // expected-error {{'auto' cannot be signed or unsigned}}
+// expected-error at +2 {{cannot combine with previous 'auto' declaration specifier}}
+// expected-error at +1 {{illegal storage class on file-scoped variable}}
+constexpr signed auto Long = 1L;
_Static_assert(_Generic(Ulong, long : 1));
_Static_assert(_Generic(CompoundLiteral, int : 1));
_Static_assert(_Generic(DoubleCast, double : 1));
diff --git a/clang/test/SemaCXX/auto-cxx0x.cpp b/clang/test/SemaCXX/auto-cxx0x.cpp
index 07687b6066790..1ce4932c00ff0 100644
--- a/clang/test/SemaCXX/auto-cxx0x.cpp
+++ b/clang/test/SemaCXX/auto-cxx0x.cpp
@@ -2,7 +2,7 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++1y
void f() {
auto int a; // expected-warning {{'auto' storage class specifier is not permitted in C++11, and will not be supported in future releases}}
- int auto b; // expected-error{{cannot combine with previous 'int' declaration specifier}}
+ int auto b; // expected-warning {{'auto' storage class specifier is not permitted in C++11, and will not be supported in future releases}}
}
typedef auto PR25449(); // expected-error {{'auto' not allowed in typedef}}
More information about the cfe-commits
mailing list