Skip to content

[Clang][C2y] Add support for if declarations (N3356 paper)#198244

Merged
bassiounix merged 41 commits into
mainfrom
users/bassiounix/clang/c2y/if-decl
Jul 14, 2026
Merged

[Clang][C2y] Add support for if declarations (N3356 paper)#198244
bassiounix merged 41 commits into
mainfrom
users/bassiounix/clang/c2y/if-decl

Conversation

@bassiounix

@bassiounix bassiounix commented May 18, 2026

Copy link
Copy Markdown
Member

Add support for if declarations in C2y mode by exploiting existing C++ code.
No API change to the names.

The only new changes to the code is narrowing the code path to C mode and introduce new errors and warnings for some pitfalls with the syntax and what is expected from the standard and old language modes.

It should be noted that the first clause in the standard paper can only be declaration. This means we can't allow expression statement in the first clause of the condition, and that's the goal of the new warnings/errors.

Sources:

Resourses:

@bassiounix bassiounix added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" c2y labels May 18, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-clang

Author: Muhammad Bassiouni (bassiounix)

Changes

Add support for if declarations in C2y mode by exploiting existing C++ code.

No API change to the names except a signature change to Parser::ParseCXXCondition which got a new extra param isSecondCallForIfCond to indicate what clause are we parsing in the condition. This new param is specific to the if condition in C2y and doesn't affect any other code path.

I noticed that the usage of Parser::ParseCXXCondition when not dealing with for loops it sets false-y values to FRI and EnterForConditionScope, hence this is what I depended on for indicating that I'm parsing declaration condition using parsingIfOrSwitchCondition.

The only new changes to the code is narrowing the code path to C2y mode and introduce new errors and warnings for some pitfalls with the syntax and what is expected from the standard.

It should be noted that the first clause in the standard paper can only be declaration. If I understand correctly this means we can't allow expression statement in the first clause of the condition, and that's the goal of the new warnings.

Sources:

Resourses:


TBR: this footnote should be removed before merging!

There might still be C++-specific code paths that I didn't guard against in the code, so please address them if you notice one. I'm still not familiar with Clang codebase :)


Full diff: https://github.com/llvm/llvm-project/pull/198244.diff

7 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+2)
  • (modified) clang/include/clang/Parse/Parser.h (+4-6)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+3-2)
  • (modified) clang/lib/Parse/ParseExprCXX.cpp (+36-11)
  • (modified) clang/lib/Parse/ParseStmt.cpp (+1-1)
  • (modified) clang/lib/Parse/ParseTentative.cpp (+2-2)
  • (added) clang/test/C/C2y/n3267.c (+59)
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 7bcd1870a2600..9c4527764226b 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -220,6 +220,8 @@ def ext_c2y_case_range : Extension<
   "case ranges are a C2y extension">, InGroup<C2y>;
 def err_c2y_labeled_break_continue : Error<
   "named %select{'break'|'continue'}0 is only supported in C2y">;
+def err_c2y_first_condition_clause_is_not_declaration : Error<
+  "first clause in condition must be a declaration">;
 
 // Generic errors.
 def err_expected_expression : Error<"expected expression">;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index dc3dc8a4ae0e9..5182e2849f8d7 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -5039,12 +5039,10 @@ class Parser : public CodeCompletionHandler {
   /// appropriate moment for a 'for' loop.
   ///
   /// \returns The parsed condition.
-  Sema::ConditionResult ParseCXXCondition(StmtResult *InitStmt,
-                                          SourceLocation Loc,
-                                          Sema::ConditionKind CK,
-                                          bool MissingOK,
-                                          ForRangeInfo *FRI = nullptr,
-                                          bool EnterForConditionScope = false);
+  Sema::ConditionResult ParseCXXCondition(
+      StmtResult *InitStmt, SourceLocation Loc, Sema::ConditionKind CK,
+      bool MissingOK, ForRangeInfo *FRI = nullptr,
+      bool EnterForConditionScope = false, bool isSecondCallForIfCond = false);
   DeclGroupPtrTy ParseAliasDeclarationInInitStatement(DeclaratorContext Context,
                                                       ParsedAttributes &Attrs);
 
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 75ad821c245a5..0635508cd0713 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2162,10 +2162,11 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS,
   ParsedAttributes LocalAttrs(AttrFactory);
   LocalAttrs.takeAllPrependingFrom(Attrs);
   ParsingDeclarator D(*this, DS, LocalAttrs, Context);
-  if (TemplateInfo.TemplateParams)
+  if (!getLangOpts().C2y && TemplateInfo.TemplateParams)
     D.setTemplateParameterLists(*TemplateInfo.TemplateParams);
 
   bool IsTemplateSpecOrInst =
+      !getLangOpts().C2y &&
       (TemplateInfo.Kind == ParsedTemplateKind::ExplicitInstantiation ||
        TemplateInfo.Kind == ParsedTemplateKind::ExplicitSpecialization);
   SuppressAccessChecks SAC(*this, IsTemplateSpecOrInst);
@@ -2185,7 +2186,7 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS,
     while (MaybeParseHLSLAnnotations(D))
       ;
 
-  if (Tok.is(tok::kw_requires))
+  if (!getLangOpts().C2y && Tok.is(tok::kw_requires))
     ParseTrailingRequiresClauseWithScope(D);
 
   // Save late-parsed attributes for now; they need to be parsed in the
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 39c61f4b5bf5c..85782255a1b97 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -1868,7 +1868,8 @@ Parser::ParseAliasDeclarationInInitStatement(DeclaratorContext Context,
 Sema::ConditionResult
 Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
                           Sema::ConditionKind CK, bool MissingOK,
-                          ForRangeInfo *FRI, bool EnterForConditionScope) {
+                          ForRangeInfo *FRI, bool EnterForConditionScope,
+                          bool isSecondCallForIfCond) {
   // Helper to ensure we always enter a continue/break scope if requested.
   struct ForConditionScopeRAII {
     Scope *S;
@@ -1883,6 +1884,7 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
         S->setIsConditionVarScope(false);
     }
   } ForConditionScope{EnterForConditionScope ? getCurScope() : nullptr};
+  bool parsingIfOrSwitchCondition = !FRI && !EnterForConditionScope;
 
   ParenBraceBracketBalancer BalancerRAIIObj(*this);
   PreferredType.enterCondition(Actions, Tok.getLocation());
@@ -1898,10 +1900,11 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
   MaybeParseCXX11Attributes(attrs);
 
   const auto WarnOnInit = [this, &CK] {
-    Diag(Tok.getLocation(), getLangOpts().CPlusPlus17
-                                ? diag::warn_cxx14_compat_init_statement
-                                : diag::ext_init_statement)
-        << (CK == Sema::ConditionKind::Switch);
+    if (!getLangOpts().C2y)
+      Diag(Tok.getLocation(), getLangOpts().CPlusPlus17
+                                  ? diag::warn_cxx14_compat_init_statement
+                                  : diag::ext_init_statement)
+          << (CK == Sema::ConditionKind::Switch);
   };
 
   // Determine what kind of thing we have.
@@ -1924,7 +1927,9 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
       }
       ConsumeToken();
       *InitStmt = Actions.ActOnNullStmt(SemiLoc);
-      return ParseCXXCondition(nullptr, Loc, CK, MissingOK);
+      return ParseCXXCondition(nullptr, Loc, CK, MissingOK, FRI,
+                               EnterForConditionScope,
+                               parsingIfOrSwitchCondition);
     }
 
     EnterExpressionEvaluationContext Eval(
@@ -1939,10 +1944,22 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
       return Sema::ConditionError();
 
     if (InitStmt && Tok.is(tok::semi)) {
+      if (getLangOpts().C2y && parsingIfOrSwitchCondition &&
+          !isSecondCallForIfCond)
+        // C2y only permits declaration in the first clause of an if condition,
+        // so it makes sense to error out in other condition. We can stop
+        // parsing here and just report an error but we chose to continue to
+        // generate an error about the second clause of the condition since
+        // there's a ';' found.
+        Diag(Tok.getLocation(),
+             diag::err_c2y_first_condition_clause_is_not_declaration);
+
       WarnOnInit();
       *InitStmt = Actions.ActOnExprStmt(Expr.get());
       ConsumeToken();
-      return ParseCXXCondition(nullptr, Loc, CK, MissingOK);
+      return ParseCXXCondition(nullptr, Loc, CK, MissingOK, FRI,
+                               EnterForConditionScope,
+                               parsingIfOrSwitchCondition);
     }
 
     return Actions.ActOnCondition(getCurScope(), Loc, Expr.get(), CK,
@@ -1953,7 +1970,7 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
     WarnOnInit();
     DeclGroupPtrTy DG;
     SourceLocation DeclStart = Tok.getLocation(), DeclEnd;
-    if (Tok.is(tok::kw_using))
+    if (!getLangOpts().C2y && Tok.is(tok::kw_using))
       DG = ParseAliasDeclarationInInitStatement(
           DeclaratorContext::SelectionInit, attrs);
     else {
@@ -1962,7 +1979,9 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
                                   attrs, DeclSpecAttrs, /*RequireSemi=*/true);
     }
     *InitStmt = Actions.ActOnDeclStmt(DG, DeclStart, DeclEnd);
-    return ParseCXXCondition(nullptr, Loc, CK, MissingOK);
+    return ParseCXXCondition(nullptr, Loc, CK, MissingOK, FRI,
+                             EnterForConditionScope,
+                             parsingIfOrSwitchCondition);
   }
 
   case ConditionOrInitStatement::ForRangeDecl: {
@@ -1978,7 +1997,12 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
     return Sema::ConditionResult();
   }
 
-  case ConditionOrInitStatement::ConditionDecl:
+  case ConditionOrInitStatement::ConditionDecl: {
+    if (getLangOpts().C2y && isSecondCallForIfCond) {
+      Diag(Tok.getLocation(), diag::err_expected_expression);
+      return Sema::ConditionError();
+    }
+  } break;
   case ConditionOrInitStatement::Error:
     break;
   }
@@ -2007,7 +2031,8 @@ Parser::ParseCXXCondition(StmtResult *InitStmt, SourceLocation Loc,
   }
 
   // If attributes are present, parse them.
-  MaybeParseGNUAttributes(DeclaratorInfo);
+  if (!getLangOpts().C2y)
+    MaybeParseGNUAttributes(DeclaratorInfo);
 
   // Type-check the declaration itself.
   DeclResult Dcl = Actions.ActOnCXXConditionDeclaration(getCurScope(),
diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp
index 1a45ed66950be..301898fb3955f 100644
--- a/clang/lib/Parse/ParseStmt.cpp
+++ b/clang/lib/Parse/ParseStmt.cpp
@@ -1264,7 +1264,7 @@ bool Parser::ParseParenExprOrCondition(StmtResult *InitStmt,
   T.consumeOpen();
   SourceLocation Start = Tok.getLocation();
 
-  if (getLangOpts().CPlusPlus) {
+  if (getLangOpts().CPlusPlus || getLangOpts().C2y) {
     Cond = ParseCXXCondition(InitStmt, Loc, CK, false);
   } else {
     ExprResult CondExpr = ParseExpression();
diff --git a/clang/lib/Parse/ParseTentative.cpp b/clang/lib/Parse/ParseTentative.cpp
index f77b1001332fe..8c4b328fe538f 100644
--- a/clang/lib/Parse/ParseTentative.cpp
+++ b/clang/lib/Parse/ParseTentative.cpp
@@ -453,7 +453,7 @@ Parser::isCXXConditionDeclarationOrInitStatement(bool CanBeInitStatement,
   ConditionDeclarationOrInitStatementState State(*this, CanBeInitStatement,
                                                  CanBeForRangeDecl);
 
-  if (CanBeInitStatement && Tok.is(tok::kw_using))
+  if (!getLangOpts().C2y && CanBeInitStatement && Tok.is(tok::kw_using))
     return ConditionOrInitStatement::InitStmtDecl;
   if (State.update(isCXXDeclarationSpecifier(ImplicitTypenameContext::No)))
     return State.result();
@@ -1065,7 +1065,7 @@ Parser::isCXXDeclarationSpecifier(ImplicitTypenameContext AllowImplicitTypename,
 
     // Check for need to substitute AltiVec __vector keyword
     // for "vector" identifier.
-    if (TryAltiVecVectorToken())
+    if (!getLangOpts().C2y && TryAltiVecVectorToken())
       return TPResult::True;
 
     const Token &Next = NextToken();
diff --git a/clang/test/C/C2y/n3267.c b/clang/test/C/C2y/n3267.c
new file mode 100644
index 0000000000000..df3dbc7561ff2
--- /dev/null
+++ b/clang/test/C/C2y/n3267.c
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 -std=c2y -verify %s
+
+bool test_if() {
+  if (true) {}
+  if (bool x = true; x) {}
+  if (bool x = false) return x;
+  if ([[maybe_unused]] bool x = true) {}
+  if (bool x [[maybe_unused]] = true) {}
+  if ([[maybe_unused]] int x = 3; x > 0) {}
+  return false;
+}
+
+int test_switch() {
+  int y = 1;
+  switch (y) {}
+
+  switch (int x = 1; x) {
+  default:
+    y += x;
+  }
+
+  switch (int x [[maybe_unused]] = 1) {}
+  switch ([[maybe_unused]] int x = 1) {}
+
+  switch (int x = 1) {
+  default:
+    return y + x;
+  }
+}
+
+bool negative_test_if() {
+  if (true; true) {} /* expected-error {{first clause in condition must be a declaration}}
+                        expected-warning {{expression result unused}}*/
+  if (true; ) {} /* expected-error {{first clause in condition must be a declaration}}
+                    expected-error {{expected expression}}
+                    expected-warning {{expression result unused}} */
+  if (bool x = true; bool y = x) return y; /* expected-error {{expected expression}}
+                                              expected-error {{use of undeclared identifier 'y'}} */
+  if (true; bool y = true) return y; /* expected-error {{first clause in condition must be a declaration}}
+                                        expected-error {{expected expression}}
+                                        expected-error {{use of undeclared identifier 'y'}}
+                                        expected-warning {{expression result unused}}*/
+  return false;
+}
+
+int negative_test_switch() {
+  switch (true; 1) { /* expected-error {{first clause in condition must be a declaration}}
+                        expected-warning {{expression result unused}} */
+  default:
+    break;
+  }
+  switch (true; ) {} /* expected-error {{first clause in condition must be a declaration}}
+                        expected-error {{expected expression}}
+                        expected-warning {{expression result unused}} */
+  switch (int x = 1; int y = x) { // expected-error {{expected expression}}
+  default:
+    return y; // expected-error {{use of undeclared identifier 'y'}}
+  }
+}

@bassiounix bassiounix changed the title [Clang][C2y] Add support for if declaration [Clang][C2y] Add support for if declarations (N3356 paper) May 18, 2026
Comment thread clang/docs/ReleaseNotes.rst Outdated
Comment thread clang/lib/Parse/ParseDecl.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/test/C/C2y/n3267.c
@bassiounix bassiounix requested review from Sirraide and cor3ntin May 18, 2026 23:53
Comment thread clang/lib/Parse/ParseDecl.cpp Outdated
Comment thread clang/lib/Parse/ParseDecl.cpp Outdated
Comment thread clang/lib/Parse/ParseDecl.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseStmt.cpp Outdated
Comment thread clang/lib/Parse/ParseTentative.cpp Outdated
Comment thread clang/lib/Parse/ParseTentative.cpp Outdated
@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@bassiounix bassiounix requested a review from Sirraide May 20, 2026 06:54

@Sirraide Sirraide left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this is looking much better now; seems like doing the checks after calling ParseCXXCondition() was the right approach

Comment thread clang/lib/Parse/ParseStmt.cpp Outdated
Comment thread clang/lib/Parse/ParseStmt.cpp Outdated
Comment thread clang/lib/Parse/ParseStmt.cpp Outdated
Comment thread clang/lib/Parse/ParseStmt.cpp Outdated
Comment thread clang/lib/Parse/ParseStmt.cpp Outdated
Comment thread clang/test/C/C2y/n3267.c
Comment thread clang/test/C/C2y/n3267.c
Comment thread clang/lib/Parse/ParseExprCXX.cpp
@bassiounix bassiounix requested a review from AaronBallman May 29, 2026 00:35
Comment thread clang/test/C/C2y/n3267.c
Comment thread clang/test/C/C2y/n3267.c Outdated
Comment thread clang/test/C/C2y/n3267.c
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
ParsedAttributes attrs(AttrFactory);
MaybeParseCXX11Attributes(attrs);
bool ParsedAttrs = MaybeParseCXX11Attributes(attrs);
if (!getLangOpts().CPlusPlus)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this only necessary for C?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that because I don't know whether we should support GNU extensions in this case for C++ or not.
The original implementation did not, so I assumed that it's only needed for C2y only.

If that's not the case please let me know and I'll change that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would expect that we'd accept GNU and square bracket attributes but I'm a bit confused because GNU-style attributes are working in C++ already without this: https://godbolt.org/z/ob636jsh7 (also, note that GCC supports these attributes in C mode too, which is another reason we should).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok after investigating the behavior of the current code, we do want to parse this in C mode only because in C++ this code will raise an error and a warning in case we used [[]] or any attributes only in the init stmt.

if ([[]]; true) {}
if (__attribute__((assume(1 > 0))); true) {}
if (__attribute__(()); true) {}

But we do handle [[]]; in C mode since we parse CXX11 attributes and check them down with the existence of ;

    // Handle 'if (; true)' and 'if ([[...]]; true)'.
    if (Tok.is(tok::semi)) {

Yes GNU attributes are handled for ConditionOrInitStatement::InitStmtDecl, but since it has C++ specific code it raises error/warnings on empty declarations, which is not desired in C mode.

We can go ahead and suppress these reports but it will cause a parsing behavior that's completely wrong rejecting valid code like __attribute__((...)) __attribute__((...)) int x;. If we wanted to handle that we should have a state somewhere to know what to accept and what to reject, which will alter functions' signature and becomes a messy edit.

The best way to go around it in my opinion is to catch that case before even entering ConditionOrInitStatement::InitStmtDecl and handle it in if (Tok.is(tok::semi)) { like we already do with [[]] by MaybeParseCXX11Attributes(attrs);.

And since this is only a valid case in C mode, we don't want to do that in C++ mode and let the compiler report empty init stmt like desired.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes GNU attributes are handled for ConditionOrInitStatement::InitStmtDecl, but since it has C++ specific code it raises error/warnings on empty declarations, which is not desired in C mode.

You have example of these warnings?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes GNU attributes are handled for ConditionOrInitStatement::InitStmtDecl, but since it has C++ specific code it raises error/warnings on empty declarations, which is not desired in C mode.

You have an example?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this only necessary for C?

TL;DR because of the special case in C2y, C++ handles it properly. (__attribute__((...)); expr) specifically.

Part of the reason why it's C2y only is this #198244 (comment)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a GCC bug in the C implementation for [[]] because [[]]; is a declaration (an attribute-declaration, specifically). And that makes me wonder if rejecting __attribute__ in that same position is also a bug: https://godbolt.org/z/G1Ks5f5Yr

Another oddity that makes me think this could be a bug is that it is accepted in C++ mode, while [[]]; is not: https://godbolt.org/z/nrx8nYxxd

But then again, GCC is consistent about expecting an actual declaration as opposed to "it was easier for the standards committee to specify the grammar if this is a declaration" things: https://godbolt.org/z/E1a1xjrc1 so perhaps this is intentional?

CC @pinskia

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a GCC bug in the C implementation for [[]] because [[]]; is a declaration (an attribute-declaration, specifically). And that makes me wonder if rejecting __attribute__ in that same position is also a bug: https://godbolt.org/z/G1Ks5f5Yr

Another oddity that makes me think this could be a bug is that it is accepted in C++ mode, while [[]]; is not: https://godbolt.org/z/nrx8nYxxd

But then again, GCC is consistent about expecting an actual declaration as opposed to "it was easier for the standards committee to specify the grammar if this is a declaration" things: https://godbolt.org/z/E1a1xjrc1 so perhaps this is intentional?

CC @pinskia

How does that have any relevance to this PR or us? Are you going at that we should do the same as GCC and not the standard?

Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment on lines +2027 to +2029
// The Declarator's DeclarationAttrs only accepts [[]] and keyword attributes;
// move any GNU attributes onto the DeclSpec instead.
if (!getLangOpts().CPlusPlus)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems pretty suspicious to me, can you explain why it's needed a bit more? CC @erichkeane

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The declarator in the case of a simple declaration refuses to take GNU attributes or act on them, IIRC it causes a crash (I can't remember precisely), so I figured if the values are transformed from the GNU form to the CXX11 form then it can be handled fine.

I don't know exactly why this is the case but this is a workaround.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, it seems like we may be working around a deeper bug then, I think more investigation is needed here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for the crash is this assert and the intended use of Declarator

  /// `DS` and `DeclarationAttrs` must outlive the `Declarator`. In particular,
  /// take care not to pass temporary objects for these parameters.
  ///
  /// `DeclarationAttrs` contains [[]] attributes from the
  /// attribute-specifier-seq at the beginning of a declaration, which appertain
  /// to the declared entity itself. Attributes with other syntax (e.g. GNU)
  /// should not be placed in this attribute list; if they occur at the
  /// beginning of a declaration, they apply to the `DeclSpec` and should be
  /// attached to that instead.
  ///
  /// Here is an example of an attribute associated with a declaration:
  ///
  ///  [[deprecated]] int x, y;
  ///
  /// This attribute appertains to all of the entities declared in the
  /// declaration, i.e. `x` and `y` in this case.
  Declarator(const DeclSpec &DS, const ParsedAttributesView &DeclarationAttrs,
             DeclaratorContext C)
      : ... {
    assert(llvm::all_of(DeclarationAttrs,
                        [](const ParsedAttr &AL) {
                          return (AL.isStandardAttributeSyntax() ||
                                  AL.isRegularKeywordAttribute());
                        }) &&
           "DeclarationAttrs may only contain [[]] and keyword attributes");
  }

Specifically

/// ... Attributes with other syntax (e.g. GNU)
/// should not be placed in this attribute list; if they occur at the
/// beginning of a declaration, they apply to the DeclSpec and should be
/// attached to that instead.

So by doing this

  if (!getLangOpts().CPlusPlus)
    for (ParsedAttr &AL : attrs)
      if (AL.isGNUAttribute())
        DS.getAttributes().takeOneFrom(attrs, &AL);

I'm effectively applying the GNU attributes to the DeclSpec and attaching to that instead.
I don't think altering the predicate of all_of is the solution here, but what I did.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me. Though, I question what C++ does here? Why is this not shared code with C++?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to me. Though, I question what C++ does here? Why is this not shared code with C++?

C before this proposal didn't have a notion of declarators in conditions, so this class was intended to be used with C++ only originally (I guess).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK so C++ does handle all the cases properly: https://godbolt.org/z/KGfnjdsdf

The reason for not sharing it with C++ is we parse the GNU attribute as part of a declspec to handle the special case for (__attribute__((...)); expr) in C2y

    // Handle '(; expr)', '(__attribute__((...)); expr)' and '([[...]]; expr)'.
    if (Tok.is(tok::semi)) {
      StmtResult Null = Actions.ActOnNullStmt(ConsumeToken());
      if (ParsedAttrs) {
        WarnOnInit();
        *InitStmt = Actions.ActOnAttributedStmt(attrs, Null.get());
      } else
        Diag(Null.get()->getBeginLoc(),
             diag::err_c2y_first_condition_clause_is_not_declaration);
      return ParseCondition(nullptr, Loc, CK, MissingOK);
    }

This is not valid C++ case, it's C2y specific case.
So it makes sense to convert it from declspec attribute to become an attribute for the declarator itself

Refer back to this in clang/lib/Parse/ParseDecl.cpp Parser::ParseDeclGroup

    // Accept attributes in an init-declarator.  In the first declarator in a
    // declaration, these would be part of the declspec.  In subsequent
    // declarators, they become part of the declarator itself, so that they
    // don't apply to declarators after *this* one.  Examples:
    //    short __attribute__((common)) var;    -> declspec
    //    short var __attribute__((common));    -> declarator
    //    short x, __attribute__((common)) var;    -> declarator
    MaybeParseGNUAttributes(D);

So I'm effectively taking it from this form short __attribute__((common)) var; as declspec to this form short var __attribute__((common)); to be part of the declarator itself when it's a ConditionDecl.

I don't believe that there's any bugs here, let alone a deeper one. It's working as intended and handles the special case for C2y case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't see that as a reason to have a different parse in C vs C++.
This is valid c++ https://godbolt.org/z/WM4P6vYTc (per https://eel.is/c++draft/stmt.pre#nt:init-statement)
And we can (and do, afaict) support gnu attributes in there (ie that's outside of the scope of the standards)

if([[]] int x; true) is the only thing we should not support in C++ (and I'm not convinced that's not an omission in the wording frankly)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid c++ https://godbolt.org/z/WM4P6vYTc

Yes and it's not a valid C per the paper wording, even GCC doesn't support it: https://godbolt.org/z/YTqvPjh36

I have to specialize here to C only to reject this code.

I still don't see that as a reason to have a different parse in C vs C++.

There has to be a differentiation between C and C++ because they don't parse the same rules, and the reason I did parse the GNU attribute at the beginning is to handle the case of __attribute__((...)); expr. And because I did that at the beginning, it got parsed as a declaration specifier, so when it comes the time to parse (__attribute__((...)) int x = 1) the declarator cannot accept a declaration specifier attribute (it's a single declarator, we can't have (int x = 1, y = 2)), so it gets converted to a declarator attribute.

C++ already handles all types of cases where you would put the attribute in different places, it doesn't need this code. https://godbolt.org/z/KGfnjdsdf

The reason this code is special and C only is the case of attribute declaration or when you have __attribute__((...)); expr and I don't want to interfere with already working C++ path. I only modify/specialize for C paths.

if([[]] int x; true) is the only thing we should not support in C++ (and I'm not convinced that's not an omission in the wording frankly)

Well it is valid in C and C++ per wording of the paper, so I don't think there's a concern here related to this patch.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, I don't want to make you all feel that I'm pushing towards the wrong direction. I could be wrong with my reasoning here.

If that's the case, please just let me know and tell me to enable it for other modes not just C and I'll happily do that :)

@bassiounix bassiounix requested a review from AaronBallman June 15, 2026 15:39
@bassiounix bassiounix requested a review from erichkeane July 8, 2026 12:03
def warn_c2y_compat_generic_with_type_arg : Warning<
"passing a type argument as the first operand to '_Generic' is incompatible "
"with C standards before C2y">, InGroup<CPre2yCompat>, DefaultIgnore;
def warn_c2y_compat_decl_statement : Warning<

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by comment, nothing to be done for this PR: we have CXXCompat for defining these in a pair, but we don't have CCompat; we probably should generalize that at some point.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll look into it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread clang/include/clang/Basic/DiagnosticParseKinds.td
Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
ParsedAttributes attrs(AttrFactory);
MaybeParseCXX11Attributes(attrs);
bool ParsedAttrs = MaybeParseCXX11Attributes(attrs);
if (!getLangOpts().CPlusPlus)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a GCC bug in the C implementation for [[]] because [[]]; is a declaration (an attribute-declaration, specifically). And that makes me wonder if rejecting __attribute__ in that same position is also a bug: https://godbolt.org/z/G1Ks5f5Yr

Another oddity that makes me think this could be a bug is that it is accepted in C++ mode, while [[]]; is not: https://godbolt.org/z/nrx8nYxxd

But then again, GCC is consistent about expecting an actual declaration as opposed to "it was easier for the standards committee to specify the grammar if this is a declaration" things: https://godbolt.org/z/E1a1xjrc1 so perhaps this is intentional?

CC @pinskia

@bassiounix bassiounix requested a review from AaronBallman July 9, 2026 04:46
@vgvassilev

Copy link
Copy Markdown
Contributor

I am wondering if we can do something to incrementally move forward. That is, is there some conservative subset of this pull request that can land and have the more focused discussions under smaller, incremental additions? That would allow @bassiounix to declare some level of progress and success as part of his work.

@AaronBallman

Copy link
Copy Markdown
Contributor

I am wondering if we can do something to incrementally move forward. That is, is there some conservative subset of this pull request that can land and have the more focused discussions under smaller, incremental additions? That would allow @bassiounix to declare some level of progress and success as part of his work.

We talked about this PR at my office hours yesterday. The plan is to do another round of reviews, we'll likely add FIXME comments to various places for things where there are open questions about handling __attribute__ or other stuff, we might end up producing some national body comments for WG14 to consider changing the design, etc. But the hope is to get this landed relatively soon (but not until after the branch date, these changes require more bake time before going in a full release).

However, I think @bassiounix can declare progress and success on the work already today -- he's been actively engaged with the community on addressing feedback and each time we get a bit closer to it being ready to land.

@AaronBallman AaronBallman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect there will be some follow-up work and we may discover some surprises once users throw real world code at us, but I think this is now ready to go. LGTM with a couple of minor nits addressed.

def warn_c2y_compat_generic_with_type_arg : Warning<
"passing a type argument as the first operand to '_Generic' is incompatible "
"with C standards before C2y">, InGroup<CPre2yCompat>, DefaultIgnore;
def warn_c2y_compat_decl_statement : Warning<

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up work to switch to this whenever it lands: #209241

Comment thread clang/lib/Parse/ParseExprCXX.cpp Outdated
Comment thread clang/test/C/C2y/n3267.c
@bassiounix

Copy link
Copy Markdown
Member Author

I'm merging this later today if there isn't any more comments.
Thanks!

@bassiounix bassiounix force-pushed the users/bassiounix/clang/c2y/if-decl branch from 993ddea to 0ea1906 Compare July 14, 2026 17:56
@bassiounix bassiounix merged commit 226b0b5 into main Jul 14, 2026
19 of 21 checks passed
@bassiounix bassiounix deleted the users/bassiounix/clang/c2y/if-decl branch July 14, 2026 19:16
pedroMVicente pushed a commit to pedroMVicente/llvm-project that referenced this pull request Jul 15, 2026
Add support for _if declarations_ in `C2y` mode by exploiting existing
C++ code.

The only new changes to the code is narrowing the code path to `C` mode
and introduce new errors and warnings for some pitfalls with the syntax
and what is expected from the standard and old language modes.

It should be noted that the first clause in the standard paper can only
be declaration. This means we can't allow expression statement in the
first clause of the condition, and that's the goal of the new
warnings/errors. Also this is different from C++ grammar.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c2y clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants