https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/218145
>From d568c91eb8e069749353ecac6f5f7d1fd0701372 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Sat, 22 Aug 2026 21:51:41 +0530 Subject: [PATCH 1/4] [clang][Sema] Don't blame '__begin1' when a constexpr range-for loop variable fails constant initialization A constexpr loop variable in a range-based for statement is initialized from '*__begin1', so when constant initialization fails, the evaluator's notes name the compiler-synthesized '__begin1' variable, which appears nowhere in the source. Emit a dedicated error in CheckCompleteVariableDeclaration instead, gated on the initializer actually reading a non-constexpr implicit iterator variable, so valid constexpr loop variables (CWG1204) and expansion statements keep working unchanged. Fixes #211926 --- clang/docs/ReleaseNotes.md | 4 + .../clang/Basic/DiagnosticSemaKinds.td | 4 + clang/lib/Sema/SemaDecl.cpp | 45 +++++++--- clang/test/SemaCXX/GH211926.cpp | 87 +++++++++++++++++++ 4 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 clang/test/SemaCXX/GH211926.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 332e0bfdb3a8b..cb80dcb8fea56 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -418,6 +418,10 @@ features cannot lower the translation-unit ABI level; - Clang now diagnoses more details when a constraint evaluates to false. +- Clang now emits a clearer diagnostic when a `constexpr` range-based for + loop variable cannot be initialized by a constant expression, instead of + a note about the compiler-synthesized `__begin` variable. (#GH211926) + ### Improvements to Clang's time-trace ### Improvements to Coverage Mapping diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index cfb2ee3368201..910b7e209ed25 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2961,6 +2961,10 @@ def err_for_range_decl_must_be_var : Error< def err_for_range_storage_class : Error< "%select{loop|expansion}0 variable %1 may not be declared %select{'extern'|'static'|" "'__private_extern__'|'auto'|'register'|'constexpr'|'thread_local'}2">; +def err_for_range_constexpr_loop_var : Error< + "constexpr variable %0 must be initialized by a constant expression; " + "a range-based for loop variable is initialized on each iteration from the " + "loop's iterator, whose value is not known at compile time">; def err_type_defined_in_for_range : Error< "types may not be defined in a for range declaration">; def err_for_range_deduction_failure : Error< diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index a99fcb56d1138..a9144c4a06822 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -15056,6 +15056,23 @@ void Sema::addLifetimeBoundToImplicitThis(CXXMethodDecl *MD) { MD->setTypeSourceInfo(TLB.getTypeSourceInfo(Context, AttributedType)); } +/// Determine whether the initializer of a range-based for loop variable reads +/// one of the loop's implicit non-constexpr iterator variables ('__begin1'). +static bool initReadsForRangeImplicitVar(const Expr *Init) { + SmallVector<const Stmt *, 8> Worklist = {Init}; + while (!Worklist.empty()) { + const Stmt *S = Worklist.pop_back_val(); + if (!S) + continue; + if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) + if (VD->isCXXForRangeImplicitVar() && !VD->isConstexpr()) + return true; + Worklist.append(S->child_begin(), S->child_end()); + } + return false; +} + void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { if (var->isInvalidDecl()) return; @@ -15268,18 +15285,24 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { if (HasConstInit) { // FIXME: Consider replacing the initializer with a ConstantExpr. } else if (var->isConstexpr()) { - SourceLocation DiagLoc = var->getLocation(); - // If the note doesn't add any useful information other than a source - // location, fold it into the primary diagnostic. - if (Notes.size() == 1 && Notes[0].second.getDiagID() == - diag::note_invalid_subexpr_in_const_expr) { - DiagLoc = Notes[0].first; - Notes.clear(); + if (var->isCXXForRangeDecl() && initReadsForRangeImplicitVar(Init)) { + Diag(var->getLocation(), diag::err_for_range_constexpr_loop_var) + << var << Init->getSourceRange(); + var->setInvalidDecl(); + } else { + SourceLocation DiagLoc = var->getLocation(); + // If the note doesn't add any useful information other than a source + // location, fold it into the primary diagnostic. + if (Notes.size() == 1 && Notes[0].second.getDiagID() == + diag::note_invalid_subexpr_in_const_expr) { + DiagLoc = Notes[0].first; + Notes.clear(); + } + Diag(DiagLoc, diag::err_constexpr_var_requires_const_init) + << var << Init->getSourceRange(); + for (unsigned I = 0, N = Notes.size(); I != N; ++I) + Diag(Notes[I].first, Notes[I].second); } - Diag(DiagLoc, diag::err_constexpr_var_requires_const_init) - << var << Init->getSourceRange(); - for (unsigned I = 0, N = Notes.size(); I != N; ++I) - Diag(Notes[I].first, Notes[I].second); } else if (GlobalStorage && var->hasAttr<ConstInitAttr>()) { auto *Attr = var->getAttr<ConstInitAttr>(); Diag(var->getLocation(), diag::err_require_constant_init_failed) diff --git a/clang/test/SemaCXX/GH211926.cpp b/clang/test/SemaCXX/GH211926.cpp new file mode 100644 index 0000000000000..77e2938f0368b --- /dev/null +++ b/clang/test/SemaCXX/GH211926.cpp @@ -0,0 +1,87 @@ +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s + +// GH211926: constexpr range-based for loop variables used to be diagnosed +// with a note about the compiler-synthesized '__begin1' variable. + +namespace std { +typedef decltype(sizeof(int)) size_t; +template <typename T> struct initializer_list { + const T *p; + size_t n; + initializer_list(const T *p, size_t n); + const T *begin() const; // expected-note {{selected 'begin' function with iterator type 'const int *'}} + const T *end() const; +}; +} // namespace std + +void init_list() { + for (constexpr auto x : {1, 2, 3}) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} + } +} + +void c_array() { + int arr[3] = {1, 2, 3}; + for (constexpr int x : arr) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration}} + } +} + +struct Vec { + int data[3]; + const int *begin() const; // expected-note {{selected 'begin' function with iterator type 'const int *'}} + const int *end() const; +}; + +void container() { + Vec v = {{1, 2, 3}}; + for (constexpr int x : v) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration}} + } +} + +template <typename T> void dependent(T &range) { + for (constexpr auto x : range) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration}} + } +} + +void instantiate() { + int arr[3] = {1, 2, 3}; + dependent(arr); // expected-note {{in instantiation of function template specialization}} +} + +void plain_loop_var() { + for (auto x : {1, 2, 3}) + (void)x; + int arr[3] = {1, 2, 3}; + for (int &x : arr) + x = 0; +} + +// Still valid when the initializer is a constant expression (CWG1204). +struct StatelessIter { + struct It { + int pos; + It &operator++(); + bool operator!=(const It &other) const; + }; + It begin(); + It end(); +}; +constexpr int operator*(const StatelessIter::It &) { return 7; } + +void stateless_iterator() { + for (constexpr int x : StatelessIter()) { + static_assert(x == 7, ""); + } +} + +void ordinary_constexpr() { + constexpr int ok = 42; + static_assert(ok == 42, ""); + + int runtime = 0; // expected-note {{declared here}} + constexpr int bad = runtime; // expected-error {{constexpr variable 'bad' must be initialized by a constant expression}} expected-note {{read of non-const variable 'runtime' is not allowed in a constant expression}} + + for (constexpr int i = 0; i != 0;) { + } +} >From a8e330e18de882ed03ad7d296ca610d862f11e33 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Tue, 25 Aug 2026 12:01:44 +0530 Subject: [PATCH 2/4] [clang][Sema] Don't blame '__begin1' when a constexpr range-for loop variable fails constant initialization A constexpr loop variable in a range-based for statement is initialized from '*__begin1', so when constant initialization fails the evaluator notes a read of the compiler-synthesized '__begin1', which appears nowhere in the source. Emit a dedicated note from both constant evaluators when they encounter a read of a for-range implicit variable, explaining that the loop variable is initialized on each iteration from the loop's iterator. Fixes #211926 --- clang/docs/ReleaseNotes.md | 5 ++ .../include/clang/Basic/DiagnosticASTKinds.td | 3 + .../clang/Basic/DiagnosticSemaKinds.td | 4 - clang/lib/AST/ByteCode/Interp.cpp | 6 ++ clang/lib/AST/ExprConstant.cpp | 5 ++ clang/lib/Sema/SemaDecl.cpp | 45 +++------- clang/test/AST/ByteCode/cxx11.cpp | 8 ++ .../stmt.stmt/stmt.iter/stmt.ranged/p1.cpp | 3 + clang/test/SemaCXX/GH211926.cpp | 87 ------------------- .../SemaCXX/constant-expression-cxx11.cpp | 4 + 10 files changed, 45 insertions(+), 125 deletions(-) delete mode 100644 clang/test/SemaCXX/GH211926.cpp diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index cb80dcb8fea56..2d4c2cbd277ee 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -422,6 +422,11 @@ features cannot lower the translation-unit ABI level; loop variable cannot be initialized by a constant expression, instead of a note about the compiler-synthesized `__begin` variable. (#GH211926) +- When a `constexpr` range-based for loop variable cannot be initialized by a + constant expression, Clang now explains in a note that the loop variable is + initialized from the loop's iterator, instead of referring to the + compiler-synthesized `__begin` variable. (#GH211926) + ### Improvements to Clang's time-trace ### Improvements to Coverage Mapping diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 3c39ccf51ab67..492def1ca2145 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -222,6 +222,9 @@ def note_constexpr_ltor_non_integral : Note< "is not allowed in a constant expression">; def note_constexpr_ltor_non_constexpr : Note< "read of non-constexpr variable %0 is not allowed in a constant expression">; +def note_constexpr_ltor_for_range_var : Note< + "range-based for loop variable is initialized on each iteration from the " + "loop's iterator, whose value is not known at compile time">; def note_constexpr_ltor_incomplete_type : Note< "read of incomplete type %0 is not allowed in a constant expression">; def note_constexpr_access_null : Note< diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 910b7e209ed25..cfb2ee3368201 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2961,10 +2961,6 @@ def err_for_range_decl_must_be_var : Error< def err_for_range_storage_class : Error< "%select{loop|expansion}0 variable %1 may not be declared %select{'extern'|'static'|" "'__private_extern__'|'auto'|'register'|'constexpr'|'thread_local'}2">; -def err_for_range_constexpr_loop_var : Error< - "constexpr variable %0 must be initialized by a constant expression; " - "a range-based for loop variable is initialized on each iteration from the " - "loop's iterator, whose value is not known at compile time">; def err_type_defined_in_for_range : Error< "types may not be defined in a for range declaration">; def err_for_range_deduction_failure : Error< diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 896b2ab4494e7..589ef62650223 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -161,6 +161,12 @@ static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC, return; } + if (const auto *VarD = dyn_cast<VarDecl>(VD); + VarD && VarD->isCXXForRangeImplicitVar()) { + S.FFDiag(Loc, diag::note_constexpr_ltor_for_range_var); + return; + } + if (const auto *VarD = dyn_cast<VarDecl>(VD); VarD && VarD->getType().isConstQualified() && (VarD->isConstexpr() || !VarD->getType()->isArrayType()) && diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 9702105951b7b..90543e6e31c74 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -4773,6 +4773,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } else if (Info.getLangOpts().C23 && ConstexprVar) { Info.FFDiag(E); return CompleteObject(); + } else if (VD->isCXXForRangeImplicitVar()) { + if (!IsAccess) + return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); + Info.FFDiag(E, diag::note_constexpr_ltor_for_range_var); + return CompleteObject(); } else if (BaseType->isIntegralOrEnumerationType()) { if (!IsConstant) { if (!IsAccess) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index a9144c4a06822..a99fcb56d1138 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -15056,23 +15056,6 @@ void Sema::addLifetimeBoundToImplicitThis(CXXMethodDecl *MD) { MD->setTypeSourceInfo(TLB.getTypeSourceInfo(Context, AttributedType)); } -/// Determine whether the initializer of a range-based for loop variable reads -/// one of the loop's implicit non-constexpr iterator variables ('__begin1'). -static bool initReadsForRangeImplicitVar(const Expr *Init) { - SmallVector<const Stmt *, 8> Worklist = {Init}; - while (!Worklist.empty()) { - const Stmt *S = Worklist.pop_back_val(); - if (!S) - continue; - if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) - if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) - if (VD->isCXXForRangeImplicitVar() && !VD->isConstexpr()) - return true; - Worklist.append(S->child_begin(), S->child_end()); - } - return false; -} - void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { if (var->isInvalidDecl()) return; @@ -15285,24 +15268,18 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { if (HasConstInit) { // FIXME: Consider replacing the initializer with a ConstantExpr. } else if (var->isConstexpr()) { - if (var->isCXXForRangeDecl() && initReadsForRangeImplicitVar(Init)) { - Diag(var->getLocation(), diag::err_for_range_constexpr_loop_var) - << var << Init->getSourceRange(); - var->setInvalidDecl(); - } else { - SourceLocation DiagLoc = var->getLocation(); - // If the note doesn't add any useful information other than a source - // location, fold it into the primary diagnostic. - if (Notes.size() == 1 && Notes[0].second.getDiagID() == - diag::note_invalid_subexpr_in_const_expr) { - DiagLoc = Notes[0].first; - Notes.clear(); - } - Diag(DiagLoc, diag::err_constexpr_var_requires_const_init) - << var << Init->getSourceRange(); - for (unsigned I = 0, N = Notes.size(); I != N; ++I) - Diag(Notes[I].first, Notes[I].second); + SourceLocation DiagLoc = var->getLocation(); + // If the note doesn't add any useful information other than a source + // location, fold it into the primary diagnostic. + if (Notes.size() == 1 && Notes[0].second.getDiagID() == + diag::note_invalid_subexpr_in_const_expr) { + DiagLoc = Notes[0].first; + Notes.clear(); } + Diag(DiagLoc, diag::err_constexpr_var_requires_const_init) + << var << Init->getSourceRange(); + for (unsigned I = 0, N = Notes.size(); I != N; ++I) + Diag(Notes[I].first, Notes[I].second); } else if (GlobalStorage && var->hasAttr<ConstInitAttr>()) { auto *Attr = var->getAttr<ConstInitAttr>(); Diag(var->getLocation(), diag::err_require_constant_init_failed) diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp index d920f0d4eb7f9..bdd613511f2b3 100644 --- a/clang/test/AST/ByteCode/cxx11.cpp +++ b/clang/test/AST/ByteCode/cxx11.cpp @@ -501,3 +501,11 @@ namespace SubPtr { // both-note {{subtracted pointers are not elements of the same array}} constexpr auto diff8 = &a[1][2].n - (&a[1][2].n + 1); } + +namespace ConstexprForRangeVar { + void f() { + int arr[] = {1, 2, 3}; + for (constexpr int a : arr) {} // both-error {{constexpr variable 'a' must be initialized by a constant expression}} \ + // both-note {{range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} + } +} diff --git a/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp b/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp index 45d02d4272d22..50b722a9bec20 100644 --- a/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp +++ b/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp @@ -154,6 +154,9 @@ void g() { for (thread_local int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'thread_local'}} for (register int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'register'}} expected-warning 0-1{{register}} expected-error 0-1{{register}} for (constexpr int a : X::C()) {} // OK per CWG issue #1204. + for (constexpr int a : A()) {} // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} expected-note {{range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} + int arr[] = {1, 2, 3}; + for (constexpr int a : arr) {} // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} expected-note {{range-based for loop variable is initialized on each iteration}} for (auto u : X::NoBeginADL()) { // expected-error {{invalid range expression of type 'X::NoBeginADL'; no viable 'begin' function available}} } diff --git a/clang/test/SemaCXX/GH211926.cpp b/clang/test/SemaCXX/GH211926.cpp deleted file mode 100644 index 77e2938f0368b..0000000000000 --- a/clang/test/SemaCXX/GH211926.cpp +++ /dev/null @@ -1,87 +0,0 @@ -// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s -// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s -// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s - -// GH211926: constexpr range-based for loop variables used to be diagnosed -// with a note about the compiler-synthesized '__begin1' variable. - -namespace std { -typedef decltype(sizeof(int)) size_t; -template <typename T> struct initializer_list { - const T *p; - size_t n; - initializer_list(const T *p, size_t n); - const T *begin() const; // expected-note {{selected 'begin' function with iterator type 'const int *'}} - const T *end() const; -}; -} // namespace std - -void init_list() { - for (constexpr auto x : {1, 2, 3}) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} - } -} - -void c_array() { - int arr[3] = {1, 2, 3}; - for (constexpr int x : arr) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration}} - } -} - -struct Vec { - int data[3]; - const int *begin() const; // expected-note {{selected 'begin' function with iterator type 'const int *'}} - const int *end() const; -}; - -void container() { - Vec v = {{1, 2, 3}}; - for (constexpr int x : v) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration}} - } -} - -template <typename T> void dependent(T &range) { - for (constexpr auto x : range) { // expected-error {{constexpr variable 'x' must be initialized by a constant expression; a range-based for loop variable is initialized on each iteration}} - } -} - -void instantiate() { - int arr[3] = {1, 2, 3}; - dependent(arr); // expected-note {{in instantiation of function template specialization}} -} - -void plain_loop_var() { - for (auto x : {1, 2, 3}) - (void)x; - int arr[3] = {1, 2, 3}; - for (int &x : arr) - x = 0; -} - -// Still valid when the initializer is a constant expression (CWG1204). -struct StatelessIter { - struct It { - int pos; - It &operator++(); - bool operator!=(const It &other) const; - }; - It begin(); - It end(); -}; -constexpr int operator*(const StatelessIter::It &) { return 7; } - -void stateless_iterator() { - for (constexpr int x : StatelessIter()) { - static_assert(x == 7, ""); - } -} - -void ordinary_constexpr() { - constexpr int ok = 42; - static_assert(ok == 42, ""); - - int runtime = 0; // expected-note {{declared here}} - constexpr int bad = runtime; // expected-error {{constexpr variable 'bad' must be initialized by a constant expression}} expected-note {{read of non-const variable 'runtime' is not allowed in a constant expression}} - - for (constexpr int i = 0; i != 0;) { - } -} diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index 47a064c4026b4..0219461f05ae8 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -1943,6 +1943,10 @@ namespace InitializerList { constexpr std::initializer_list<float> il = {1.0, 2.0, 3.0}; static_assert(il.begin()[1] == 2.0, ""); } + + void constexpr_loop_var() { + for (constexpr auto x : {1, 2, 3}) {} // expected-error {{constexpr variable 'x' must be initialized by a constant expression}} expected-note {{range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} + } } namespace StmtExpr { >From d103b9256b6aaa77bcf26856d3e08615e4e70db3 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Wed, 26 Aug 2026 20:01:25 +0530 Subject: [PATCH 3/4] [clang] Name the range in the constexpr range-for iterator note Reword note_constexpr_ltor_for_range_var to state the actual cause: the loop's iterator is not a constant expression, which holds even when the range itself is constexpr. Name the range variable when there is one and point the note at the range expression, in both constant evaluators. --- clang/docs/ReleaseNotes.md | 6 +-- .../include/clang/Basic/DiagnosticASTKinds.td | 4 +- clang/lib/AST/ByteCode/Interp.cpp | 9 ++++- clang/lib/AST/ExprConstShared.h | 9 +++++ clang/lib/AST/ExprConstant.cpp | 38 ++++++++++++++++++- clang/test/AST/ByteCode/cxx11.cpp | 2 +- .../stmt.stmt/stmt.iter/stmt.ranged/p1.cpp | 6 ++- .../SemaCXX/constant-expression-cxx11.cpp | 2 +- 8 files changed, 65 insertions(+), 11 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 2d4c2cbd277ee..e523926fc2c40 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -423,9 +423,9 @@ features cannot lower the translation-unit ABI level; a note about the compiler-synthesized `__begin` variable. (#GH211926) - When a `constexpr` range-based for loop variable cannot be initialized by a - constant expression, Clang now explains in a note that the loop variable is - initialized from the loop's iterator, instead of referring to the - compiler-synthesized `__begin` variable. (#GH211926) + constant expression, Clang now notes that the loop's iterator is not a + constant expression, naming the range being iterated, instead of referring + to the compiler-synthesized `__begin` variable. (#GH211926) ### Improvements to Clang's time-trace diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 492def1ca2145..647d8d405c818 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -223,8 +223,8 @@ def note_constexpr_ltor_non_integral : Note< def note_constexpr_ltor_non_constexpr : Note< "read of non-constexpr variable %0 is not allowed in a constant expression">; def note_constexpr_ltor_for_range_var : Note< - "range-based for loop variable is initialized on each iteration from the " - "loop's iterator, whose value is not known at compile time">; + "iterator of the range-based for loop%select{| over %1}0 is not a constant " + "expression">; def note_constexpr_ltor_incomplete_type : Note< "read of incomplete type %0 is not allowed in a constant expression">; def note_constexpr_access_null : Note< diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 589ef62650223..b9467eaccc64d 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -163,7 +163,14 @@ static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC, if (const auto *VarD = dyn_cast<VarDecl>(VD); VarD && VarD->isCXXForRangeImplicitVar()) { - S.FFDiag(Loc, diag::note_constexpr_ltor_for_range_var); + auto [Range, RangeDecl] = GetCXXForRangeRange(VarD); + OptionalDiagnostic Diag = + Range ? S.FFDiag(Range, diag::note_constexpr_ltor_for_range_var) + : S.FFDiag(Loc, diag::note_constexpr_ltor_for_range_var); + if (RangeDecl) + Diag << 1 << RangeDecl; + else + Diag << 0; return; } diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index cdf2a5697528e..4da24ab12649e 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -17,6 +17,7 @@ #include "clang/Basic/BuiltinTraits.h" #include <cstdint> #include <optional> +#include <utility> namespace llvm { class APFloat; @@ -30,6 +31,8 @@ class ASTContext; class CharUnits; class Expr; class CallExpr; +class NamedDecl; +class VarDecl; } // namespace clang using namespace clang; /// Values returned by __builtin_classify_type, chosen to match the values @@ -79,6 +82,12 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat B, llvm::APFloat C, CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E, UnaryExprOrTypeTrait ExprKind); +/// Given an implicit variable of a range-based for statement ('__range', +/// '__begin' or '__end'), return the range expression the loop iterates over +/// and, if that expression names a variable, the variable. +std::pair<const Expr *, const NamedDecl *> +GetCXXForRangeRange(const VarDecl *ImplicitVar); + /// Convert a builtin ID to the canonical x86 builtin ID the constant evaluators /// dispatch on in their x86 target-specific cases. /// diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 90543e6e31c74..b06b4130c39dc 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -4776,7 +4776,13 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } else if (VD->isCXXForRangeImplicitVar()) { if (!IsAccess) return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); - Info.FFDiag(E, diag::note_constexpr_ltor_for_range_var); + auto [Range, RangeDecl] = GetCXXForRangeRange(VD); + OptionalDiagnostic Diag = Info.FFDiag( + Range ? Range : E, diag::note_constexpr_ltor_for_range_var); + if (RangeDecl) + Diag << 1 << RangeDecl; + else + Diag << 0; return CompleteObject(); } else if (BaseType->isIntegralOrEnumerationType()) { if (!IsConstant) { @@ -10564,6 +10570,36 @@ CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E, return GetAlignOfType(Ctx, E->getType(), ExprKind); } +std::pair<const Expr *, const NamedDecl *> +GetCXXForRangeRange(const VarDecl *ImplicitVar) { + const Expr *Range = ImplicitVar->getInit(); + if (!Range) + return {nullptr, nullptr}; + + // '__begin' and '__end' are initialized from '__range', whose initializer + // is the range expression. + SmallVector<const Stmt *, 8> Worklist = {Range}; + while (!Worklist.empty()) { + const Stmt *S = Worklist.pop_back_val(); + if (!S) + continue; + if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) { + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()); + VD && VD->isCXXForRangeImplicitVar()) { + Range = VD->getInit(); + break; + } + } + Worklist.append(S->child_begin(), S->child_end()); + } + + const NamedDecl *RangeDecl = nullptr; + if (Range) + if (const auto *DRE = dyn_cast<DeclRefExpr>(Range->IgnoreParenImpCasts())) + RangeDecl = DRE->getDecl(); + return {Range, RangeDecl}; +} + static CharUnits getBaseAlignment(EvalInfo &Info, const LValue &Value) { if (const auto *VD = Value.Base.dyn_cast<const ValueDecl *>()) return Info.Ctx.getDeclAlign(VD); diff --git a/clang/test/AST/ByteCode/cxx11.cpp b/clang/test/AST/ByteCode/cxx11.cpp index bdd613511f2b3..685847887ee84 100644 --- a/clang/test/AST/ByteCode/cxx11.cpp +++ b/clang/test/AST/ByteCode/cxx11.cpp @@ -506,6 +506,6 @@ namespace ConstexprForRangeVar { void f() { int arr[] = {1, 2, 3}; for (constexpr int a : arr) {} // both-error {{constexpr variable 'a' must be initialized by a constant expression}} \ - // both-note {{range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} + // both-note {{iterator of the range-based for loop over 'arr' is not a constant expression}} } } diff --git a/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp b/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp index 50b722a9bec20..efdf90d4118a2 100644 --- a/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp +++ b/clang/test/CXX/stmt.stmt/stmt.iter/stmt.ranged/p1.cpp @@ -154,9 +154,11 @@ void g() { for (thread_local int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'thread_local'}} for (register int a : A()) {} // expected-error {{loop variable 'a' may not be declared 'register'}} expected-warning 0-1{{register}} expected-error 0-1{{register}} for (constexpr int a : X::C()) {} // OK per CWG issue #1204. - for (constexpr int a : A()) {} // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} expected-note {{range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} + for (constexpr int a : A()) {} // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} expected-note {{iterator of the range-based for loop is not a constant expression}} int arr[] = {1, 2, 3}; - for (constexpr int a : arr) {} // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} expected-note {{range-based for loop variable is initialized on each iteration}} + for (constexpr int a : arr) {} // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} expected-note {{iterator of the range-based for loop over 'arr' is not a constant expression}} + constexpr int carr[] = {1, 2, 3}; + for (constexpr int a : carr) {} // expected-error {{constexpr variable 'a' must be initialized by a constant expression}} expected-note {{iterator of the range-based for loop over 'carr' is not a constant expression}} for (auto u : X::NoBeginADL()) { // expected-error {{invalid range expression of type 'X::NoBeginADL'; no viable 'begin' function available}} } diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index 0219461f05ae8..197b38f65dac2 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -1945,7 +1945,7 @@ namespace InitializerList { } void constexpr_loop_var() { - for (constexpr auto x : {1, 2, 3}) {} // expected-error {{constexpr variable 'x' must be initialized by a constant expression}} expected-note {{range-based for loop variable is initialized on each iteration from the loop's iterator, whose value is not known at compile time}} + for (constexpr auto x : {1, 2, 3}) {} // expected-error {{constexpr variable 'x' must be initialized by a constant expression}} expected-note {{iterator of the range-based for loop is not a constant expression}} } } >From aa661bfa5883ad885a511b6d1bd5e7ff170abc93 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Wed, 26 Aug 2026 20:25:05 +0530 Subject: [PATCH 4/4] [clang] Improve diagnostic for constexpr range-based for loop variable initialization --- clang/docs/ReleaseNotes.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index e523926fc2c40..4ca9886d0bf12 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -418,10 +418,6 @@ features cannot lower the translation-unit ABI level; - Clang now diagnoses more details when a constraint evaluates to false. -- Clang now emits a clearer diagnostic when a `constexpr` range-based for - loop variable cannot be initialized by a constant expression, instead of - a note about the compiler-synthesized `__begin` variable. (#GH211926) - - When a `constexpr` range-based for loop variable cannot be initialized by a constant expression, Clang now notes that the loop's iterator is not a constant expression, naming the range being iterated, instead of referring _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
