https://github.com/akash-manna-sky created https://github.com/llvm/llvm-project/pull/218145
Fixes #211926 When a range-based for loop variable is declared `constexpr`, its initializer is the compiler-built `*__begin1`, which normally can't be a constant expression. The constant evaluator reports what it sees at the leaf — `read of non-constexpr variable '__begin1' is not allowed in a constant expression`, with a `declared here` note pointing at the loop's colon — leaking a synthesized name the user never wrote and explaining nothing about the actual problem. Sema now classifies this failure itself in `CheckCompleteVariableDeclaration`: if the failed variable is a for-range declaration and its initializer reads one of the loop's implicit non-constexpr iterator variables, it emits a dedicated error explaining that the loop variable is initialized on each iteration from the loop's iterator, drops the evaluator's notes, and marks the variable invalid. The check can't be an eager reject at loop-build time because a constexpr loop variable is sometimes valid (CWG1204, when `operator*` never reads the iterator's value) — those still compile, and expansion statement (`template for`) variables never match the condition since their synthesized iterators are themselves `constexpr`, so they keep their existing diagnostics. LLM tools were used for this contribution. I've reviewed, built, and tested the change myself before pushing to GitHub. >From cd6278b17945cd354399067f58bc9884726fefc9 Mon Sep 17 00:00:00 2001 From: Akash Manna <[email protected]> Date: Sat, 22 Aug 2026 21:51:41 +0530 Subject: [PATCH] [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 3c6694f510952..4131ebc3c692f 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -416,6 +416,10 @@ features cannot lower the translation-unit ABI level; - `-Wc++98-compat` now diagnoses explicit conversion functions in C++20 and later, matching the behavior in C++11 through C++17. (#GH161689) +- 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 3a910c9c3f2b9..7961197e47cbb 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 5055ef3d1cbb1..107d01336f5cf 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;) { + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
