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

Reply via email to