https://github.com/bassiounix updated 
https://github.com/llvm/llvm-project/pull/233908

>From 9dcbf7a0fc51bb9abec4b91651995d12dba11262 Mon Sep 17 00:00:00 2001
From: bassiounix <[email protected]>
Date: Tue, 20 Oct 2026 19:38:07 +0300
Subject: [PATCH 1/4] [Clang][C2y] Reject declarations in the condition of a
 while loop

N3356 only extends the selection statements (`if` and `switch`), but the
shared condition parsing accepted `while (int x = 12) {}` in C as a C2y
extension. `while` passes a null init-statement into
`ParseParenExprOrCondition`, so use that to diagnose a condition
declaration as "expected expression" (matching GCC) instead of emitting
the compatibility/extension warning.

Also only emit the extension warning for a non-simple declaration in the
first clause when the declaration is actually permitted there.

Fixes #233839

Co-Authored-By: Claude Fable 5.1 <[email protected]>
---
 clang/lib/Parse/ParseExprCXX.cpp      |  5 +++-
 clang/lib/Parse/ParseStmt.cpp         | 16 ++++++++---
 clang/test/C/C2y/n3267.c              | 41 +++++++++++++++++++++++++++
 clang/test/Parser/c2y-if-decl-while.c | 16 +++++++++++
 4 files changed, 73 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/Parser/c2y-if-decl-while.c

diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 3745ea53fc6d0d..9158c1c4ce0a78 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -1939,7 +1939,10 @@ Sema::ConditionResult Parser::ParseCondition(StmtResult 
*InitStmt,
   if (!getLangOpts().CPlusPlus) {
     if (isDeclarationStatement() && !isCXXSimpleDeclaration(false)) {
       // Accept a C2y declaration, *only* if it's not a simple declaration.
-      WarnOnInit();
+      // Only warn when the declaration is actually permitted here; otherwise
+      // we diagnose an error below.
+      if (InitStmt != nullptr)
+        WarnOnInit();
       DeclGroupPtrTy DG;
       SourceLocation DeclStart = Tok.getLocation(), DeclEnd;
       ParsedAttributes DeclSpecAttrs(AttrFactory);
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index de82003f109328..b2fb2bd7f0fc7c 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1331,10 +1331,18 @@ bool Parser::ParseParenExprOrCondition(StmtResult 
*InitStmt,
         // C2y only permits expression in the second clause of an if condition.
         Diag(Cond.get().first->getBeginLoc(), diag::err_expected_expression)
             << Cond.get().first->getSourceRange();
-    } else if (Cond.get().first != nullptr)
-      // Handle: if (int decl = 0) {}.
-      DiagCompat(Cond.get().first->getBeginLoc(), diag_compat::decl_statement)
-          << (CK == Sema::ConditionKind::Switch);
+    } else if (Cond.get().first != nullptr) {
+      if (InitStmt == nullptr)
+        // C2y only permits a declaration in the condition of an 'if' or
+        // 'switch' statement; a 'while' statement still requires an
+        // expression. Handle: while (int decl = 0) {}.
+        Diag(Cond.get().first->getBeginLoc(), diag::err_expected_expression)
+            << Cond.get().first->getSourceRange();
+      else
+        // Handle: if (int decl = 0) {}.
+        DiagCompat(Cond.get().first->getBeginLoc(), 
diag_compat::decl_statement)
+            << (CK == Sema::ConditionKind::Switch);
+    }
   }
 
   if (Tok.is(tok::comma)) {
diff --git a/clang/test/C/C2y/n3267.c b/clang/test/C/C2y/n3267.c
index b517c5d33f42f6..4f08308b690c25 100644
--- a/clang/test/C/C2y/n3267.c
+++ b/clang/test/C/C2y/n3267.c
@@ -211,3 +211,44 @@ int negative_test_switch() {
   switch (int x, y) {default:} /* expected-error {{multiple declarations are 
not allowed}}
                                   expected-error {{variable declaration in 
condition must have an initializer}} */
 }
+
+void negative_test_while() {
+  // N3356 only extends the selection statements ('if' and 'switch'); the
+  // controlling clause of an iteration statement is still an expression.
+  int y = 1;
+  while (int x = 12) {} // expected-error {{expected expression}}
+  while ([[maybe_unused]] int x = 1) {} // expected-error {{expected 
expression}}
+  while (int x [[maybe_unused]] = 1) {} // expected-error {{expected 
expression}}
+  while (__extension__ int x = 1) {} // expected-error {{expected expression}}
+  while (auto x = 1) {} // expected-error {{expected expression}}
+  while (auto x = &y) {} // expected-error {{expected expression}}
+  while (static_assert(true); true) {} // expected-error {{expected 
expression}}
+  while (int a = 1, b = 2) {} /* expected-error {{expected expression}}
+                                 expected-error {{multiple declarations are 
not allowed}} */
+  while (int x) {} /* expected-error {{variable declaration in condition must 
have an initializer}}
+                      expected-error {{expected expression}} */
+  do {} while (int x = 1); // expected-error {{expected expression}}
+  for (int i = 0; int j = i;) {} // expected-error {{expected expression}}
+
+  // Declarations in 'if' and 'switch' nested in a loop (and vice versa) are
+  // still fine.
+  while (y) { if (int x = 1) {} }
+  if (int x = 1) { while (x) {} }
+}
+
+void negative_test_while_struct_init_clause() {
+  // Kept separate because error recovery skips the rest of the enclosing
+  // function.
+  while (struct S { int x; } s = {}; s.x) {} /* expected-error {{expected 
expression}}
+                                                expected-error {{statement 
requires expression of scalar type ('struct S' invalid)}}
+                                                expected-error {{use of 
undeclared identifier 's'}} */
+}
+
+void negative_test_while_init_clause() {
+  // Kept separate because recovery from the missing ')' skips the rest of the
+  // enclosing function.
+  while (int x = 12; x) {} /* expected-error {{expected expression}}
+                              expected-error {{expected ')'}}
+                              expected-note {{to match this '('}}
+                              expected-error {{use of undeclared identifier 
'x'}} */
+}
diff --git a/clang/test/Parser/c2y-if-decl-while.c 
b/clang/test/Parser/c2y-if-decl-while.c
new file mode 100644
index 00000000000000..cb9d2c9913335f
--- /dev/null
+++ b/clang/test/Parser/c2y-if-decl-while.c
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -std=c2y -pedantic -Wpre-c2y-compat -verify %s
+// RUN: %clang_cc1 -std=c23 -pedantic -verify=expected,ext %s
+// RUN: %clang_cc1 -std=c17 -pedantic -verify=expected,ext %s
+
+// N3356 ('if' declarations) only applies to selection statements. A 
declaration
+// in the controlling clause of a 'while' (or 'do'/'while') statement must be
+// rejected rather than accepted as a C2y extension.
+
+void test(void) {
+  if (int x = 1) {} // ext-warning {{'if' declaration statements are a C2y 
extension}}
+  switch (int x = 1) {} // ext-warning {{'switch' declaration statements are a 
C2y extension}}
+
+  while (int x = 12) {} // expected-error {{expected expression}}
+  while (_Static_assert(1, ""); 1) {} // expected-error {{expected expression}}
+  do {} while (int x = 1); // expected-error {{expected expression}}
+}

>From b0499a4cd8e2a2353817530854e578e8e82258f6 Mon Sep 17 00:00:00 2001
From: bassiounix <[email protected]>
Date: Wed, 21 Oct 2026 16:17:40 +0300
Subject: [PATCH 2/4] [Clang][C2y] Simplify condition-declaration diagnostics
 in C

Key the diagnostics on whether a declaration is permitted as the
condition (only as the sole clause of an 'if' or 'switch'), so the two
"expected expression" emissions collapse into one and the first-clause
check is no longer nested inside the branching. No functional change.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
---
 clang/lib/Parse/ParseStmt.cpp | 42 +++++++++++++++++------------------
 1 file changed, 20 insertions(+), 22 deletions(-)

diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index b2fb2bd7f0fc7c..05ad46aba1e8fb 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1317,30 +1317,28 @@ bool Parser::ParseParenExprOrCondition(StmtResult 
*InitStmt,
   }
 
   if (!getLangOpts().CPlusPlus) {
-    if (InitStmt != nullptr && InitStmt->isUsable()) {
-      // Handle the 2 clauses of declaration: (clause1; clause2). We need to
-      // allow NullStmt because that’s what we end up with if we have an empty
-      // attribute-specifier-sequence, which is valid: if ([[]]; true).
-      if (!isa<DeclStmt, AttributedStmt, NullStmt>(InitStmt->get()))
-        // C2y only permits declaration in the first clause of an if condition.
-        Diag(InitStmt->get()->getBeginLoc(),
-             diag::err_c2y_first_condition_clause_is_not_declaration)
-            << InitStmt->get()->getSourceRange();
-
-      if (Cond.get().first != nullptr)
-        // C2y only permits expression in the second clause of an if condition.
-        Diag(Cond.get().first->getBeginLoc(), diag::err_expected_expression)
-            << Cond.get().first->getSourceRange();
-    } else if (Cond.get().first != nullptr) {
-      if (InitStmt == nullptr)
-        // C2y only permits a declaration in the condition of an 'if' or
-        // 'switch' statement; a 'while' statement still requires an
-        // expression. Handle: while (int decl = 0) {}.
-        Diag(Cond.get().first->getBeginLoc(), diag::err_expected_expression)
-            << Cond.get().first->getSourceRange();
+    bool HasInitStmt = InitStmt != nullptr && InitStmt->isUsable();
+    // Handle the 2 clauses of declaration: (clause1; clause2). We need to
+    // allow NullStmt because that’s what we end up with if we have an empty
+    // attribute-specifier-sequence, which is valid: if ([[]]; true).
+    if (HasInitStmt &&
+        !isa<DeclStmt, AttributedStmt, NullStmt>(InitStmt->get()))
+      // C2y only permits declaration in the first clause of an if condition.
+      Diag(InitStmt->get()->getBeginLoc(),
+           diag::err_c2y_first_condition_clause_is_not_declaration)
+          << InitStmt->get()->getSourceRange();
+
+    if (Decl *CondDecl = Cond.get().first) {
+      // C2y only permits a declaration as the sole clause of an 'if' or
+      // 'switch' condition. Diagnose it as an expected expression when there
+      // is no init-statement slot at all (while (int decl = 0) {}) or when the
+      // declaration is in the second clause (if (int x = 0; int y = 0) {}).
+      if (InitStmt == nullptr || HasInitStmt)
+        Diag(CondDecl->getBeginLoc(), diag::err_expected_expression)
+            << CondDecl->getSourceRange();
       else
         // Handle: if (int decl = 0) {}.
-        DiagCompat(Cond.get().first->getBeginLoc(), 
diag_compat::decl_statement)
+        DiagCompat(CondDecl->getBeginLoc(), diag_compat::decl_statement)
             << (CK == Sema::ConditionKind::Switch);
     }
   }

>From 1f1e8885568c801f505c7c2596bb9b3dfae7ef94 Mon Sep 17 00:00:00 2001
From: bassiounix <[email protected]>
Date: Wed, 21 Oct 2026 16:21:04 +0300
Subject: [PATCH 3/4] [Clang][C2y] Make the 'if' declaration extension warning
 on by default

Declarations in the condition of 'if' and 'switch' statements (N3356)
were previously only diagnosed in pre-C2y modes under -pedantic, since
the C compatibility warnings default to Extension. Promote the
diagnostic to an ExtWarn so users on older language modes are told
they are relying on a C2y extension; -Wno-c2y-extensions silences it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
---
 clang/include/clang/Basic/DiagnosticParseKinds.td | 3 ++-
 clang/test/Parser/c2y-if-decl-while.c             | 4 ++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td 
b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 7665688686b5d2..c7c70a930372d5 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -24,7 +24,8 @@ defm label_followed_by_declaration : C23Compat<
   "label followed by a declaration is">;
 
 // C2y compatibility with C23 and earlier.
-defm decl_statement : C2yCompat<"'%select{if|switch}0' declaration statements 
are">;
+defm decl_statement : C2yCompat<"'%select{if|switch}0' declaration statements 
are",
+                                /*ext_warn=*/true>;
 defm generic_with_type_arg : C2yCompat<
   "passing a type argument as the first operand to '_Generic' is">;
 
diff --git a/clang/test/Parser/c2y-if-decl-while.c 
b/clang/test/Parser/c2y-if-decl-while.c
index cb9d2c9913335f..3ecfb5ac55a9c3 100644
--- a/clang/test/Parser/c2y-if-decl-while.c
+++ b/clang/test/Parser/c2y-if-decl-while.c
@@ -1,6 +1,10 @@
 // RUN: %clang_cc1 -std=c2y -pedantic -Wpre-c2y-compat -verify %s
 // RUN: %clang_cc1 -std=c23 -pedantic -verify=expected,ext %s
 // RUN: %clang_cc1 -std=c17 -pedantic -verify=expected,ext %s
+// The extension warning is on by default, not only under -pedantic.
+// RUN: %clang_cc1 -std=c23 -verify=expected,ext %s
+// RUN: %clang_cc1 -std=c17 -verify=expected,ext %s
+// RUN: %clang_cc1 -std=c17 -Wno-c2y-extensions -verify %s
 
 // N3356 ('if' declarations) only applies to selection statements. A 
declaration
 // in the controlling clause of a 'while' (or 'do'/'while') statement must be

>From 4c740d281fd4a23e69a295c2f7e6c0c6ce29dccb Mon Sep 17 00:00:00 2001
From: bassiounix <[email protected]>
Date: Wed, 21 Oct 2026 13:50:03 +0300
Subject: [PATCH 4/4] [Clang][C2y] Fix pre-c2y-compat expectations in
 c2y-if-decl-while.c

The -std=c2y run line passes -Wpre-c2y-compat, so the compat form of the
'if'/'switch' declaration diagnostic fires. Give that run a 'compat'
prefix and expect the warnings.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
---
 clang/test/Parser/c2y-if-decl-while.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/clang/test/Parser/c2y-if-decl-while.c 
b/clang/test/Parser/c2y-if-decl-while.c
index 3ecfb5ac55a9c3..c169e71a6ebd83 100644
--- a/clang/test/Parser/c2y-if-decl-while.c
+++ b/clang/test/Parser/c2y-if-decl-while.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -std=c2y -pedantic -Wpre-c2y-compat -verify %s
+// RUN: %clang_cc1 -std=c2y -pedantic -Wpre-c2y-compat -verify=expected,compat 
%s
 // RUN: %clang_cc1 -std=c23 -pedantic -verify=expected,ext %s
 // RUN: %clang_cc1 -std=c17 -pedantic -verify=expected,ext %s
 // The extension warning is on by default, not only under -pedantic.
@@ -11,8 +11,10 @@
 // rejected rather than accepted as a C2y extension.
 
 void test(void) {
-  if (int x = 1) {} // ext-warning {{'if' declaration statements are a C2y 
extension}}
-  switch (int x = 1) {} // ext-warning {{'switch' declaration statements are a 
C2y extension}}
+  if (int x = 1) {} // ext-warning {{'if' declaration statements are a C2y 
extension}} \
+                    // compat-warning {{'if' declaration statements are 
incompatible with C standards before C2y}}
+  switch (int x = 1) {} // ext-warning {{'switch' declaration statements are a 
C2y extension}} \
+                        // compat-warning {{'switch' declaration statements 
are incompatible with C standards before C2y}}
 
   while (int x = 12) {} // expected-error {{expected expression}}
   while (_Static_assert(1, ""); 1) {} // expected-error {{expected expression}}

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

Reply via email to