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

Reply via email to