Author: Oleksandr Tarasiuk
Date: 2026-04-07T14:16:35+03:00
New Revision: 211b88b64b0e409a2e294a1fe444b305aa94a2a6

URL: 
https://github.com/llvm/llvm-project/commit/211b88b64b0e409a2e294a1fe444b305aa94a2a6
DIFF: 
https://github.com/llvm/llvm-project/commit/211b88b64b0e409a2e294a1fe444b305aa94a2a6.diff

LOG: [Clang] prevent incorrect rejection of auto with reordered declaration 
specifiers in C23 (#177865)

Fixes #164121

---

This patch addresses the issue where `auto` was incorrectly rejected
with reordered declaration specifiers in C23.

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Parse/Parser.h
    clang/lib/Parse/ParseDecl.cpp
    clang/lib/Parse/ParseObjc.cpp
    clang/lib/Sema/DeclSpec.cpp
    
clang/test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p3-generic-lambda-1y.cpp
    clang/test/Parser/c2x-auto.c
    clang/test/Sema/c2x-auto.c
    clang/test/Sema/constexpr.c
    clang/test/SemaCXX/auto-cxx0x.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 08d7ffd826b41..1a70fdea4bb04 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -399,6 +399,7 @@ Bug Fixes in This Version
   to "lambda" instead of "block". (#GH188661)
 - Fixed a crash on _BitInt(N) arrays where 129 ≤ N ≤ 192 due to incorrect 
array filler lowering. (#GH189643)
 - Fixed the behavior in C23 of ``auto``, by emitting an error when an array 
type is specified for a ``char *``. (#GH162694)
+- Fixed incorrect rejection of ``auto`` with reordered declaration specifiers 
in C23. (#GH164121)
 
 Bug Fixes to Compiler Builtins
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

diff  --git a/clang/include/clang/Parse/Parser.h 
b/clang/include/clang/Parse/Parser.h
index 08a3d88ee6a36..0919525fbf117 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2029,7 +2029,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
@@ -4350,7 +4350,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.
@@ -4361,7 +4361,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).
@@ -5054,7 +5054,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 d448cb8a552bc..e2ac86bc5e064 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -4093,7 +4093,28 @@ void Parser::ParseDeclarationSpecifiers(
       break;
     case tok::kw_auto:
       if (getLangOpts().CPlusPlus11 || getLangOpts().C23) {
-        if (isKnownToBeTypeSpecifier(GetLookAheadToken(1))) {
+        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
+          // it for type inference (6.7.10).
+          if (getLangOpts().C23 && DS.hasTypeSpecifier() &&
+              DS.getTypeSpecType() != DeclSpec::TST_auto)
+            return true;
+
+          unsigned I = 1;
+          while (true) {
+            const Token &T = GetLookAheadToken(I);
+            if (isKnownToBeTypeSpecifier(T))
+              return true;
+
+            if (getLangOpts().C23 && isTypeSpecifierQualifier(T))
+              ++I;
+            else
+              return false;
+          }
+        };
+
+        if (MayBeTypeSpecifier()) {
           isInvalid = DS.SetStorageClassSpec(Actions, DeclSpec::SCS_auto, Loc,
                                              PrevSpec, DiagID, Policy);
           if (!isInvalid && !getLangOpts().C23)
@@ -5575,13 +5596,19 @@ bool Parser::isKnownToBeTypeSpecifier(const Token &Tok) 
const {
     // enum-specifier
   case tok::kw_enum:
 
+  case tok::kw_typeof:
+  case tok::kw_typeof_unqual:
+
+  // C11 _Atomic
+  case tok::kw__Atomic:
+
     // typedef-name
   case tok::annot_typename:
     return true;
   }
 }
 
-bool Parser::isTypeSpecifierQualifier() {
+bool Parser::isTypeSpecifierQualifier(const Token &Tok) {
   switch (Tok.getKind()) {
   default: return false;
 
@@ -5594,9 +5621,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
@@ -5605,7 +5632,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/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp
index 479a959e0aadc..660b1805c450e 100644
--- a/clang/lib/Sema/DeclSpec.cpp
+++ b/clang/lib/Sema/DeclSpec.cpp
@@ -1398,13 +1398,12 @@ void DeclSpec::Finish(Sema &S, const PrintingPolicy 
&Policy) {
     }
   }
 
-  if (S.getLangOpts().C23 &&
+  if (S.getLangOpts().C23 && getTypeSpecType() != DeclSpec::TST_unspecified &&
       getConstexprSpecifier() == ConstexprSpecKind::Constexpr &&
-      getTypeSpecType() != TST_unspecified &&
       (StorageClassSpec == SCS_extern || StorageClassSpec == SCS_auto)) {
-    S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination)
-        << DeclSpec::getSpecifierName(getStorageClassSpec())
-        << SourceRange(getStorageClassSpecLoc());
+    S.Diag(getStorageClassSpecLoc(), diag::err_invalid_decl_spec_combination)
+        << DeclSpec::getSpecifierName(getConstexprSpecifier())
+        << SourceRange(getConstexprSpecLoc());
   }
 
   // If no type specifier was provided and we're parsing a language where

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..ca8d50fe22728 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 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 ()'}}
+    auto l2 = [](char auto::*pm) { };       // expected-error {{cannot combine 
with previous 'char' declaration specifier}} \
+                                               expected-error{{'pm' does not 
point into a class}}
+    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 7f80b0717ab25..d60c96e2b168f 100644
--- a/clang/test/Parser/c2x-auto.c
+++ b/clang/test/Parser/c2x-auto.c
@@ -133,27 +133,88 @@ void attributes(void) {
 
 /** GH163090 */
 constexpr auto int a1 = 0; // c23-error {{illegal storage class on file-scoped 
variable}} \
-                              c23-error {{cannot combine with previous 'auto' 
declaration specifier}} \
+                              c23-error {{cannot combine with previous 
'constexpr' declaration specifier}} \
                               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 {{illegal storage class on file-scoped 
variable}} \
+                              c23-error {{cannot combine with previous 
'constexpr' declaration specifier}} \
                               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}}
 
-void f() {
-  constexpr auto int c1 = 0; // c23-error {{cannot combine with previous 
'auto' declaration specifier}} \
+long auto long b3 = 0; // c23-error {{illegal storage class on file-scoped 
variable}} \
+                          c17-error {{illegal storage class on file-scoped 
variable}}
+
+const long auto long unsigned volatile _Atomic int b4 = 0; // c23-error 
{{illegal storage class on file-scoped variable}} \
+                                                              c17-error 
{{illegal storage class on file-scoped variable}}
+
+signed int _Atomic auto b5 = 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 
'constexpr' 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 
'constexpr' 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() {
+  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)}}
+}
+
+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) a1 = 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 a2 = 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) a3 = 0;
+  _Atomic(int) auto a4 = 0;
+}
+
+void t7(void) {
+  signed int _Atomic auto a1 = 0;
 }

diff  --git a/clang/test/Sema/c2x-auto.c b/clang/test/Sema/c2x-auto.c
index a11c42aa6fb49..158414f8f35ac 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 3a6b49fd90748..0b8de906e1838 100644
--- a/clang/test/Sema/constexpr.c
+++ b/clang/test/Sema/constexpr.c
@@ -39,7 +39,8 @@ 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}}
+constexpr signed auto Long = 1L; // expected-error {{illegal storage class on 
file-scoped variable}}
+// expected-error@-1 {{cannot combine with previous 'constexpr' declaration 
specifier}}
 _Static_assert(_Generic(Ulong, long : 1));
 _Static_assert(_Generic(CompoundLiteral, int : 1));
 _Static_assert(_Generic(DoubleCast, double : 1));
@@ -56,7 +57,7 @@ void f3(constexpr register int P1) { // expected-error 
{{function parameter cann
 constexpr thread_local int V11 = 38; // expected-error {{cannot combine with 
previous '_Thread_local' declaration specifier}}
 constexpr static thread_local double V12 = 38; // expected-error {{cannot 
combine with previous '_Thread_local' declaration specifier}}
 constexpr extern thread_local char V13; // expected-error {{cannot combine 
with previous '_Thread_local' declaration specifier}}
-// expected-error@-1 {{cannot combine with previous 'extern' declaration 
specifier}}
+// expected-error@-1 {{cannot combine with previous 'constexpr' declaration 
specifier}}
 // expected-error@-2 {{constexpr variable declaration must be a definition}}
 constexpr thread_local short V14 = 38; // expected-error {{cannot combine with 
previous '_Thread_local' declaration specifier}}
 
@@ -68,7 +69,7 @@ constexpr volatile int V17 = 0; // expected-error {{constexpr 
variable cannot ha
 
 constexpr int * restrict V18 = 0; // expected-error {{constexpr variable 
cannot have type 'int *const restrict'}}
 
-constexpr extern char Oops = 1; // expected-error {{cannot combine with 
previous 'extern' declaration specifier}} \
+constexpr extern char Oops = 1; // expected-error {{cannot combine with 
previous 'constexpr' declaration specifier}} \
                                 // expected-warning {{'extern' variable has an 
initializer}}
 
 constexpr int * restrict * Oops1 = 0;

diff  --git a/clang/test/SemaCXX/auto-cxx0x.cpp 
b/clang/test/SemaCXX/auto-cxx0x.cpp
index 07687b6066790..f429bebb9941a 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-error {{cannot combine with previous 'int' 
declaration specifier}}
 }
 
 typedef auto PR25449(); // expected-error {{'auto' not allowed in typedef}}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to