https://github.com/bassiounix created https://github.com/llvm/llvm-project/pull/198244
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: - _if declaration_ standard paper https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3356.htm Resourses: - An old draft of `C2y` https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3356.htm --- _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 :) >From 4b23504a24dd2eed4681159da7d26681e2508443 Mon Sep 17 00:00:00 2001 From: bassiounix <[email protected]> Date: Mon, 18 May 2026 10:05:00 +0300 Subject: [PATCH] [Clang][C2y] Add support for if declaration --- .../clang/Basic/DiagnosticParseKinds.td | 2 + clang/include/clang/Parse/Parser.h | 10 ++-- clang/lib/Parse/ParseDecl.cpp | 5 +- clang/lib/Parse/ParseExprCXX.cpp | 47 +++++++++++---- clang/lib/Parse/ParseStmt.cpp | 2 +- clang/lib/Parse/ParseTentative.cpp | 4 +- clang/test/C/C2y/n3267.c | 59 +++++++++++++++++++ 7 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 clang/test/C/C2y/n3267.c 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'}} + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
