[llvm-branch-commits] [clang] [Clang] [C++26] Expansion Statements (Part 7: Constexpr support and tests) (PR #169686)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Mar 24 08:29:50 PDT 2026


https://github.com/Sirraide updated https://github.com/llvm/llvm-project/pull/169686

>From f364692d8d1d7f307520e79a33ff767808e911c8 Mon Sep 17 00:00:00 2001
From: Sirraide <aeternalmail at gmail.com>
Date: Wed, 26 Nov 2025 17:09:56 +0100
Subject: [PATCH] [Clang] [C++26] Expansion Statements (Part 7)

---
 clang/lib/AST/ByteCode/Compiler.cpp          |   42 +
 clang/lib/AST/ByteCode/Compiler.h            |    3 +-
 clang/lib/AST/ExprConstant.cpp               |   40 +
 clang/lib/Sema/SemaDeclCXX.cpp               |    3 +
 clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 1472 ++++++++++++++++++
 5 files changed, 1559 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/SemaCXX/cxx2c-expansion-stmts.cpp

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index fc2da6362d0bb..b87aab56cf371 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -5883,6 +5883,9 @@ template <class Emitter> bool Compiler<Emitter>::visitStmt(const Stmt *S) {
     return this->emitInvalid(S);
   case Stmt::LabelStmtClass:
     return this->visitStmt(cast<LabelStmt>(S)->getSubStmt());
+  case Stmt::CXXExpansionStmtInstantiationClass:
+    return this->visitCXXExpansionStmtInstantiation(
+        cast<CXXExpansionStmtInstantiation>(S));
   default: {
     if (const auto *E = dyn_cast<Expr>(S))
       return this->discard(E);
@@ -5967,6 +5970,13 @@ bool Compiler<Emitter>::visitDeclStmt(const DeclStmt *DS,
             FunctionDecl, NamespaceAliasDecl, UsingDirectiveDecl>(D))
       continue;
 
+    if (const auto *ESD = dyn_cast<CXXExpansionStmtDecl>(D)) {
+      assert(ESD->getInstantiations() && "not expanded?");
+      if (!this->visitStmt(ESD->getInstantiations()))
+        return false;
+      continue;
+    }
+
     const auto *VD = dyn_cast<VarDecl>(D);
     if (!VD)
       return false;
@@ -6553,6 +6563,38 @@ bool Compiler<Emitter>::visitCXXTryStmt(const CXXTryStmt *S) {
   return this->visitStmt(S->getTryBlock());
 }
 
+/// template for (auto x : {1, 2}) {}
+///
+/// This is not a loop from an AST perspective at all since it has already
+/// been instantiated to a list of compound statements.
+///
+/// Since we can have control flow in those compound statements, we need to
+/// handle it mostly like a loop though.
+template <class Emitter>
+bool Compiler<Emitter>::visitCXXExpansionStmtInstantiation(
+    const CXXExpansionStmtInstantiation *S) {
+  LocalScope<Emitter> WholeLoopScope(this, ScopeKind::Block);
+
+  for (const Stmt *Shared : S->getSharedStmts()) {
+    if (!this->visitDeclStmt(cast<DeclStmt>(Shared), true))
+      return false;
+  }
+
+  LabelTy EndLabel = this->getLabel();
+  for (const Stmt *Instantiation : S->getInstantiations()) {
+    LabelTy ContinueLabel = this->getLabel();
+    LoopScope<Emitter> LS(this, S, EndLabel, ContinueLabel);
+
+    if (!this->visitStmt(Instantiation))
+      return false;
+    this->emitLabel(ContinueLabel);
+  }
+
+  this->emitLabel(EndLabel);
+
+  return WholeLoopScope.destroyLocals();
+}
+
 template <class Emitter>
 bool Compiler<Emitter>::emitLambdaStaticInvokerBody(const CXXMethodDecl *MD) {
   assert(MD->isLambdaStaticInvoker());
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 717928dc1fbbd..ac9889f8f7672 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -247,7 +247,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
   bool visitDefaultStmt(const DefaultStmt *S);
   bool visitAttributedStmt(const AttributedStmt *S);
   bool visitCXXTryStmt(const CXXTryStmt *S);
-
+  bool
+  visitCXXExpansionStmtInstantiation(const CXXExpansionStmtInstantiation *S);
 protected:
   bool visitStmt(const Stmt *S);
   bool visitExpr(const Expr *E, bool DestroyToplevelScope) override;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 86ff5f05b75f1..b98c1e9fc00c9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -5966,6 +5966,12 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
       const VarDecl *VD = dyn_cast_or_null<VarDecl>(D);
       if (VD && !CheckLocalVariableDeclaration(Info, VD))
         return ESR_Failed;
+
+      if (const auto *ESD = dyn_cast<CXXExpansionStmtDecl>(D)) {
+        assert(ESD->getInstantiations() && "not expanded?");
+        return EvaluateStmt(Result, Info, ESD->getInstantiations(), Case);
+      }
+
       // Each declaration initialization is its own full-expression.
       FullExpressionRAII Scope(Info);
       if (!EvaluateDecl(Info, D, /*EvaluateConditionDecl=*/true) &&
@@ -6238,6 +6244,40 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
     return Scope.destroy() ? ESR_Succeeded : ESR_Failed;
   }
 
+  case Stmt::CXXExpansionStmtInstantiationClass: {
+    BlockScopeRAII Scope(Info);
+    const auto *Expansion = cast<CXXExpansionStmtInstantiation>(S);
+    for (const Stmt *Shared : Expansion->getSharedStmts()) {
+      EvalStmtResult ESR = EvaluateStmt(Result, Info, Shared);
+      if (ESR != ESR_Succeeded) {
+        if (ESR != ESR_Failed && !Scope.destroy())
+          return ESR_Failed;
+        return ESR;
+      }
+    }
+
+    // No need to push an extra scope for these since they're already
+    // CompoundStmts.
+    EvalStmtResult ESR = ESR_Succeeded;
+    for (const Stmt *Instantiation : Expansion->getInstantiations()) {
+      ESR = EvaluateStmt(Result, Info, Instantiation);
+      if (ESR == ESR_Failed ||
+          ShouldPropagateBreakContinue(Info, Expansion, &Scope, ESR))
+        return ESR;
+      if (ESR != ESR_Continue) {
+        // Succeeded here actually means we encountered a 'break'.
+        assert(ESR == ESR_Succeeded || ESR == ESR_Returned);
+        break;
+      }
+    }
+
+    // Map Continue back to Succeeded if we fell off the end of the loop.
+    if (ESR == ESR_Continue)
+      ESR = ESR_Succeeded;
+
+    return Scope.destroy() ? ESR : ESR_Failed;
+  }
+
   case Stmt::SwitchStmtClass:
     return EvaluateSwitch(Result, Info, cast<SwitchStmt>(S));
 
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2a24ee42bcb4d..38f4a6fad3b31 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -2027,6 +2027,9 @@ static bool CheckConstexprDeclStmt(Sema &SemaRef, const FunctionDecl *Dcl,
       //   - using-enum-declaration
       continue;
 
+    case Decl::CXXExpansionStmt:
+      continue;
+
     case Decl::Typedef:
     case Decl::TypeAlias: {
       //   - typedef declarations and alias-declarations that do not define
diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
new file mode 100644
index 0000000000000..d1a822871e8c1
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp
@@ -0,0 +1,1472 @@
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -Wno-vla-cxx-extension -fconstexpr-steps=10000 -verify=expected,old-interp
+// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -Wno-vla-cxx-extension -fconstexpr-steps=10000 -verify=expected,new-interp -fexperimental-new-constant-interpreter
+namespace std {
+template <typename T>
+struct initializer_list {
+  const T* a;
+  const T* b;
+  initializer_list(T* a, T* b): a{a}, b{b} {}
+};
+}
+
+struct S {
+  int x;
+  constexpr S(int x) : x{x} {}
+};
+
+void g(int); // #g
+template <int n> constexpr int tg() { return n; }
+
+void f1() {
+  template for (auto x : {}) static_assert(false, "discarded");
+  template for (constexpr auto x : {}) static_assert(false, "discarded");
+  template for (auto x : {1}) g(x);
+  template for (auto x : {1, 2, 3}) g(x);
+  template for (constexpr auto x : {1}) g(x);
+  template for (constexpr auto x : {1, 2, 3}) g(x);
+  template for (constexpr auto x : {1}) tg<x>();
+  template for (constexpr auto x : {1, 2, 3})
+    static_assert(tg<x>());
+
+  template for (int x : {1, 2, 3}) g(x);
+  template for (S x : {1, 2, 3}) g(x.x);
+  template for (constexpr S x : {1, 2, 3}) tg<x.x>();
+
+  template for (int x : {"1", S(1), {1, 2}}) { // expected-error {{cannot initialize a variable of type 'int' with an lvalue of type 'const char[2]'}} \
+                                                  expected-error {{no viable conversion from 'S' to 'int'}} \
+                                                  expected-error {{excess elements in scalar initializer}} \
+                                                  expected-note 3 {{in instantiation of expansion statement requested here}}
+    g(x);
+  }
+
+  template for (constexpr auto x : {1, 2, 3, 4}) { // expected-note 3 {{in instantiation of expansion statement requested here}}
+    static_assert(tg<x>() == 4); // expected-error 3 {{static assertion failed due to requirement 'tg<x>() == 4'}} \
+                                    expected-note {{expression evaluates to '1 == 4'}} \
+                                    expected-note {{expression evaluates to '2 == 4'}} \
+                                    expected-note {{expression evaluates to '3 == 4'}}
+  }
+
+
+  template for (constexpr auto x : {1, 2}) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+    static_assert(false, "not discarded"); // expected-error 2 {{static assertion failed: not discarded}}
+  }
+}
+
+template <typename T>
+void t1() {
+  template for (T x : {}) g(x);
+  template for (constexpr T x : {}) g(x);
+  template for (auto x : {}) g(x);
+  template for (constexpr auto x : {}) g(x);
+  template for (T x : {1, 2}) g(x);
+  template for (T x : {T(1), T(2)}) g(x);
+  template for (auto x : {T(1), T(2)}) g(x);
+  template for (constexpr T x : {T(1), T(2)}) static_assert(tg<x>());
+  template for (constexpr auto x : {T(1), T(2)}) static_assert(tg<x>());
+}
+
+template <typename U>
+struct s1 {
+  template <typename T>
+  void tf() {
+      template for (T x : {}) g(x);
+      template for (constexpr T x : {}) g(x);
+      template for (U x : {}) g(x);
+      template for (constexpr U x : {}) g(x);
+      template for (auto x : {}) g(x);
+      template for (constexpr auto x : {}) g(x);
+      template for (T x : {1, 2}) g(x);
+      template for (U x : {1, 2}) g(x);
+      template for (U x : {T(1), T(2)}) g(x);
+      template for (T x : {U(1), U(2)}) g(x);
+      template for (auto x : {T(1), T(2)}) g(x);
+      template for (auto x : {U(1), T(2)}) g(x);
+      template for (constexpr U x : {T(1), T(2)}) static_assert(tg<x>());
+      template for (constexpr T x : {U(1), U(2)}) static_assert(tg<x>());
+      template for (constexpr auto x : {T(1), U(2)}) static_assert(tg<x>());
+    }
+};
+
+template <typename T>
+void t2() {
+  template for (T x : {}) g(x);
+}
+
+void f2() {
+  t1<int>();
+  t1<long>();
+  s1<long>().tf<long>();
+  s1<int>().tf<int>();
+  s1<int>().tf<long>();
+  s1<long>().tf<int>();
+  t2<S>();
+  t2<S[1231]>();
+  t2<S***>();
+}
+
+template <__SIZE_TYPE__ size>
+struct String {
+  char data[size];
+
+  template <__SIZE_TYPE__ n>
+  constexpr String(const char (&str)[n]) { __builtin_memcpy(data, str, n); }
+
+  constexpr const char* begin() const { return data; }
+  constexpr const char* end() const { return data + size - 1; }
+};
+
+template <__SIZE_TYPE__ n>
+String(const char (&str)[n]) -> String<n>;
+
+constexpr int f3() {
+  static constexpr String s{"abcd"};
+  int count = 0;
+  template for (constexpr auto x : s) count++;
+  return count;
+}
+
+template <String s>
+constexpr int tf3() {
+  int count = 0;
+  template for (constexpr auto x : s) count++;
+  return count;
+}
+
+static_assert(f3() == 4);
+static_assert(tf3<"1">() == 1);
+static_assert(tf3<"12">() == 2);
+static_assert(tf3<"123">() == 3);
+static_assert(tf3<"1234">() == 4);
+
+void f4() {
+  static constexpr String empty{""};
+  static constexpr String s{"abcd"};
+  template for (auto x : empty) static_assert(false, "not expanded");
+  template for (constexpr auto x : s) g(x);
+  template for (auto x : s) g(x);
+}
+
+struct NegativeSize {
+  static constexpr const char* str = "123";
+  constexpr const char* begin() const { return str + 3; }
+  constexpr const char* end() const { return str; }
+};
+
+void negative_size() {
+  static constexpr NegativeSize n;
+  template for (auto x : n) g(x); // expected-error {{expansion size is not a constant expression}} \
+                                     old-interp-note {{constexpr evaluation hit maximum step limit}} \
+                                     new-interp-note {{cannot refer to element 5 of array of 4 elements in a constant expression}} \
+                                     expected-note {{in call to}}
+  template for (constexpr auto x : n) g(x); // expected-error {{expansion size is not a constant expression}} \
+                                               old-interp-note {{constexpr evaluation hit maximum step limit}} \
+                                               new-interp-note {{cannot refer to element 5 of array of 4 elements in a constant expression}} \
+                                               expected-note {{in call to}}
+}
+
+template <typename T, __SIZE_TYPE__ size>
+struct Array {
+  T data[size]{};
+  constexpr const T* begin() const { return data; }
+  constexpr const T* end() const { return data + size; }
+};
+
+struct NotInt {
+  struct iterator {};
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+void not_int() {
+  static constexpr NotInt ni;
+  template for (auto x : ni) g(x); // expected-error {{invalid operands to binary expression}} \
+                                      expected-note {{while attempting to construct 'begin - begin' with iterator type 'iterator'}}
+}
+
+static constexpr Array<int, 3> integers{1, 2, 3};
+
+constexpr int friend_func();
+
+struct Private {
+  friend constexpr int friend_func();
+
+private:
+  constexpr const int* begin() const { return integers.begin(); } // expected-note 3 {{declared private here}}
+  constexpr const int* end() const { return integers.end(); } // expected-note 3 {{declared private here}}
+
+public:
+  static constexpr int member_func() {
+    int sum = 0;
+    static constexpr Private p1;
+    template for (auto x : p1) sum += x;
+    return sum;
+  }
+};
+
+struct Protected {
+  friend constexpr int friend_func();
+
+protected:
+  constexpr const int* begin() const { return integers.begin(); } // expected-note 3 {{declared protected here}}
+  constexpr const int* end() const { return integers.end(); } // expected-note 3 {{declared protected here}}
+
+public:
+  static constexpr int member_func() {
+    int sum = 0;
+    static constexpr Protected p1;
+    template for (auto x : p1) sum += x;
+    return sum;
+  }
+};
+
+void access_control() {
+  static constexpr Private p1;
+  template for (auto x : p1) g(x); // expected-error 3 {{'begin' is a private member of 'Private'}} expected-error 3 {{'end' is a private member of 'Private'}}
+
+  static constexpr Protected p2;
+  template for (auto x : p2) g(x); // expected-error 3 {{'begin' is a protected member of 'Protected'}} expected-error 3 {{'end' is a protected member of 'Protected'}}
+}
+
+constexpr int friend_func() {
+  int sum = 0;
+  static constexpr Private p1;
+  template for (auto x : p1) sum += x;
+
+  static constexpr Protected p2;
+  template for (auto x : p2) sum += x;
+  return sum;
+}
+
+static_assert(friend_func() == 12);
+static_assert(Private::member_func() == 6);
+static_assert(Protected::member_func() == 6);
+
+struct SizeNotICE {
+  struct iterator {
+    friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; }
+    friend constexpr __PTRDIFF_TYPE__ operator-(iterator, iterator) { return 7; }
+    constexpr iterator operator++() { return *this; }
+    int constexpr operator*() const { return 7; }
+
+    // NOT constexpr!
+    friend int operator!=(iterator, iterator) { return 7; } // expected-note {{declared here}}
+  };
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+struct PlusMissing {
+  struct iterator {
+    friend constexpr __PTRDIFF_TYPE__ operator-(iterator, iterator) { return 7; }
+    constexpr iterator operator++() { return *this; }
+    int constexpr operator*() const { return 7; }
+  };
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+struct DerefMissing {
+  struct iterator {
+    friend constexpr __PTRDIFF_TYPE__ operator-(iterator, iterator) { return 7; }
+    friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; }
+  };
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+struct MinusMissing {
+  struct iterator {};
+  constexpr iterator begin() const { return {}; }
+  constexpr iterator end() const { return {}; }
+};
+
+void missing_funcs() {
+  static constexpr SizeNotICE s1;
+  static constexpr PlusMissing s2;
+  static constexpr DerefMissing s3;
+  static constexpr MinusMissing s4;
+
+  template for (auto x : s1) g(x); // expected-error {{expansion size is not a constant expression}} \
+                                      expected-note {{non-constexpr function 'operator!=' cannot be used in a constant expression}} \
+                                      expected-note {{in call to}}
+
+  template for (auto x : s2) g(x); // expected-error {{invalid operands to binary expression}}
+  template for (auto x : s3) g(x); // expected-error {{indirection requires pointer operand ('iterator' invalid)}}
+  template for (auto x : s4) g(x); // expected-error {{invalid operands to binary expression ('iterator' and 'iterator')}} \
+                                      expected-note {{while attempting to construct 'begin - begin' with iterator type 'iterator'}}
+}
+
+namespace adl {
+struct ADL {
+
+};
+
+constexpr const int* begin(const ADL&) { return integers.begin(); }
+constexpr const int* end(const ADL&) { return integers.end(); }
+}
+
+namespace adl_error {
+struct ADLError1 {
+  constexpr const int* begin() const { return integers.begin(); }
+};
+
+struct ADLError2 {
+  constexpr const int* end() const { return integers.end(); }
+};
+
+constexpr const int* begin(const ADLError2&) { return integers.begin(); }
+constexpr const int* end(const ADLError1&) { return integers.end(); }
+}
+
+namespace adl_both {
+static constexpr Array<int, 5> integers2{1, 2, 3, 4, 5};
+struct ADLBoth {
+  // Test that member begin/end are preferred over ADl begin/end. These return
+  // pointers to a different array.
+  constexpr const int* begin() const { return integers2.begin(); }
+  constexpr const int* end() const { return integers2.end(); }
+};
+
+constexpr const int* begin(const ADLBoth&) { return integers.begin(); }
+constexpr const int* end(const ADLBoth&) { return integers.end(); }
+}
+
+constexpr int adl_begin_end() {
+  static constexpr adl::ADL a;
+  int sum = 0;
+  template for (auto x : a) sum += x;
+  template for (constexpr auto x : a) sum += x;
+  return sum;
+}
+
+static_assert(adl_begin_end() == 12);
+
+void adl_mixed() {
+  static constexpr adl_error::ADLError1 a1;
+  static constexpr adl_error::ADLError2 a2;
+
+  // These are actually destructuring because there is no
+  // valid begin/end pair.
+  template for (auto x : a1) g(x);
+  template for (auto x : a2) g(x);
+}
+
+constexpr int adl_both_test() {
+  static constexpr adl_both::ADLBoth a;
+  int sum = 0;
+  template for (auto x : a) sum += x;
+  return sum;
+}
+
+static_assert(adl_both_test() == 15);
+
+struct A {};
+struct B { int x = 1; };
+struct C { int a = 1, b = 2, c = 3; };
+struct D {
+  int a = 1;
+  int* b = nullptr;
+  const char* c = "3";
+};
+
+struct Nested {
+  A a;
+  B b;
+  C c;
+};
+
+struct PrivateDestructurable {
+  friend void destructurable_friend();
+private:
+  int a, b; // expected-note 4 {{declared private here}}
+};
+
+struct ProtectedDestructurable {
+  friend void destructurable_friend();
+protected:
+  int a, b; // expected-note 4 {{declared protected here}}
+};
+
+void destructuring() {
+  static constexpr A a;
+  static constexpr B b;
+  static constexpr C c;
+  static constexpr D d;
+
+  template for (auto x : a) static_assert(false, "not expanded");
+  template for (constexpr auto x : a) static_assert(false, "not expanded");
+
+  template for (auto x : b) g(x);
+  template for (constexpr auto x : b) g(x);
+
+  template for (auto x : c) g(x);
+  template for (constexpr auto x : c) g(x);
+
+  template for (auto x : d) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'int *' to 'int' for 1st argument}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'const char *' to 'int' for 1st argument}}
+    g(x); // expected-error 2 {{no matching function for call to 'g'}}
+
+  }
+
+  template for (constexpr auto x : d) { // expected-note 2 {{in instantiation of expansion statement requested here}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'int *const' to 'int' for 1st argument}}
+    // expected-note@#g {{candidate function not viable: no known conversion from 'const char *const' to 'int' for 1st argument}}
+    g(x); // expected-error 2 {{no matching function for call to 'g'}}
+  }
+}
+
+constexpr int array() {
+  static constexpr int x[4]{1, 2, 3, 4};
+  int sum = 0;
+  template for (auto y : x) sum += y;
+  template for (constexpr auto y : x) sum += y;
+  return sum;
+}
+
+static_assert(array() == 20);
+
+template <auto v>
+constexpr int destructure() {
+  int sum = 0;
+  template for (auto x : v) sum += x;
+  template for (constexpr auto x : v) sum += x;
+  return sum;
+}
+
+static_assert(destructure<B{10}>() == 20);
+static_assert(destructure<C{}>() == 12);
+static_assert(destructure<C{3, 4, 5}>() == 24);
+
+constexpr int nested() {
+  static constexpr Nested n;
+  int sum = 0;
+  template for (constexpr auto x : n) {
+    static constexpr auto val = x;
+    template for (auto y : val) {
+      sum += y;
+    }
+  }
+  template for (constexpr auto x : n) {
+    static constexpr auto val = x;
+    template for (constexpr auto y : val) {
+      sum += y;
+    }
+  }
+  return sum;
+}
+
+static_assert(nested() == 14);
+
+void access_control_destructurable() {
+  template for (auto x : PrivateDestructurable()) {} // expected-error 2 {{cannot bind private member 'a' of 'PrivateDestructurable'}} \
+                                                        expected-error 2 {{cannot bind private member 'b' of 'PrivateDestructurable'}}
+
+  template for (auto x : ProtectedDestructurable()) {} // expected-error 2 {{cannot bind protected member 'a' of 'ProtectedDestructurable'}} \
+                                                          expected-error 2 {{cannot bind protected member 'b' of 'ProtectedDestructurable'}}
+}
+
+void destructurable_friend() {
+  template for (auto x : PrivateDestructurable()) {}
+  template for (auto x : ProtectedDestructurable()) {}
+}
+
+struct Placeholder {
+  A get_value() const { return {}; }
+  __declspec(property(get = get_value)) A a;
+};
+
+void placeholder() {
+  template for (auto x: Placeholder().a) {}
+}
+
+union Union { int a; long b;};
+
+struct MemberPtr {
+  void f() {}
+};
+
+void overload_set(int); // expected-note 2 {{possible target for call}}
+void overload_set(long); // expected-note 2 {{possible target for call}}
+
+void invalid_types() {
+  template for (auto x : void()) {} // expected-error {{cannot expand expression of incomplete type 'void'}}
+  template for (auto x : 1) {} // expected-error {{cannot expand expression of type 'int'}}
+  template for (auto x : 1.f) {} // expected-error {{cannot expand expression of type 'float'}}
+  template for (auto x : 'c') {} // expected-error {{cannot expand expression of type 'char'}}
+  template for (auto x : invalid_types) {} // expected-error {{cannot expand expression of type 'void ()'}}
+  template for (auto x : &invalid_types) {} // expected-error {{cannot expand expression of type 'void (*)()'}}
+  template for (auto x : &MemberPtr::f) {} // expected-error {{cannot expand expression of type 'void (MemberPtr::*)()'}}
+  template for (auto x : overload_set) {} // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}}
+  template for (auto x : &overload_set) {} // expected-error{{reference to overloaded function could not be resolved; did you mean to call it?}}
+  template for (auto x : nullptr) {} // expected-error {{cannot expand expression of type 'std::nullptr_t'}}
+  template for (auto x : __builtin_strlen) {} // expected-error {{builtin functions must be directly called}}
+  template for (auto x : Union()) {} // expected-error {{cannot expand expression of type 'Union'}}
+  template for (auto x : (char*)nullptr) {} // expected-error {{cannot expand expression of type 'char *'}}
+  template for (auto x : []{}) {} // expected-error {{cannot expand lambda closure type}}
+  template for (auto x : [x=3]{}) {} // expected-error {{cannot expand lambda closure type}}
+}
+
+struct BeginOnly {
+  int x{1};
+  constexpr const int* begin() const { return nullptr; }
+};
+
+struct EndOnly {
+  int x{2};
+  constexpr const int* end() const { return nullptr; }
+};
+
+namespace adl1 {
+struct BeginOnly {
+  int x{3};
+};
+constexpr const int* begin(const BeginOnly&) { return nullptr; }
+}
+
+namespace adl2 {
+struct EndOnly {
+  int x{4};
+};
+constexpr const int* end(const EndOnly&) { return nullptr; }
+}
+
+namespace adl3 {
+struct BeginOnlyDeleted {
+  int x{4};
+};
+constexpr const int* begin(const BeginOnlyDeleted&) = delete;
+}
+
+namespace adl4 {
+struct EndOnlyDeleted {
+  int x{4};
+};
+constexpr const int* end(const EndOnlyDeleted&) = delete;
+}
+
+namespace adl5 {
+struct BothDeleted {
+  int x{4};
+};
+constexpr const int* begin(const BothDeleted&) = delete; // expected-note {{candidate function has been explicitly deleted}}
+constexpr const int* end(const BothDeleted&) = delete;
+}
+
+namespace adl6 {
+struct BeginNotViable {
+  int x{4};
+};
+constexpr const int* begin(int) { return nullptr; }
+}
+
+namespace adl7 {
+struct EndNotViable {
+  int x{4};
+};
+constexpr const int* end(int) { return nullptr; }
+}
+
+namespace adl8 {
+struct BothNotViable {
+  int x{4};
+};
+constexpr const int* begin(int) { return nullptr; }
+constexpr const int* end(int) { return nullptr; }
+}
+
+namespace adl9 {
+struct BeginDeleted {
+  int x{4};
+};
+constexpr const int* begin(const BeginDeleted&) = delete; // expected-note {{candidate function has been explicitly deleted}}
+constexpr const int* end(const BeginDeleted&) { return nullptr; }
+}
+
+namespace adl10 {
+struct EndDeleted {
+  int x{4};
+};
+constexpr const int* begin(const EndDeleted&) { return nullptr; }
+constexpr const int* end(const EndDeleted&) = delete; // expected-note {{candidate function has been explicitly deleted}}
+}
+
+void unpaired_begin_end() {
+  static constexpr adl1::BeginOnly begin_only;
+  static constexpr adl2::EndOnly end_only;
+  static constexpr adl3::BeginOnlyDeleted begin_only_deleted;
+  static constexpr adl4::EndOnlyDeleted end_only_deleted;
+  static constexpr adl5::BothDeleted both_deleted;
+  static constexpr adl6::BeginNotViable begin_not_viable;
+  static constexpr adl7::EndNotViable end_not_viable;
+  static constexpr adl8::BothNotViable both_not_viable;
+  static constexpr adl9::BeginDeleted begin_deleted;
+  static constexpr adl10::EndDeleted end_deleted;
+
+  // Ok, these are destructuring because there is no valid pair.
+  template for (auto x : begin_only) {}
+  template for (auto x : begin_only_deleted) {}
+  template for (auto x : begin_not_viable) {}
+  template for (auto x : end_only) {}
+  template for (auto x : end_only_deleted) {}
+  template for (auto x : end_not_viable) {}
+
+  // This is also ok because overload resolution fails.
+  template for (auto x : both_not_viable) {}
+
+  // These are invalid because overload resolution succeeds (even though
+  // there is no usable begin() and/or end()).
+  template for (auto x : both_deleted) {} // expected-error {{call to deleted function 'begin'}} \
+                                             expected-note {{when looking up 'begin' function for range expression of type 'const adl5::BothDeleted'}}~
+
+  template for (auto x : begin_deleted) {} // expected-error {{call to deleted function 'begin'}} \
+                                              expected-note {{when looking up 'begin' function for range expression of type 'const adl9::BeginDeleted'}}
+
+  template for (auto x : end_deleted) {} // expected-error {{call to deleted function 'end'}} \
+                                            expected-note {{when looking up 'end' function for range expression of type 'const adl10::EndDeleted'}}
+}
+
+// Examples taken from [stmt.expand].
+namespace stmt_expand_examples {
+consteval int f(auto const&... Containers) {
+  int result = 0;
+  template for (auto const& c : {Containers...}) {      // OK, enumerating expansion statement
+    result += c[0];
+  }
+  return result;
+}
+constexpr int c1[] = {1, 2, 3};
+constexpr int c2[] = {4, 3, 2, 1};
+static_assert(f(c1, c2) == 5);
+
+// TODO: This entire example should work without issuing any diagnostics once
+// we have full support for references to constexpr variables (P2686).
+consteval int f() {
+  constexpr Array<int, 3> arr {1, 2, 3}; // expected-note{{add 'static' to give it a constant address}} \
+                                            new-interp-note 5 {{add 'static' to give it a constant address}}
+
+  int result = 0;
+
+  // expected-error@#invalid-ref {{constexpr variable '__range1' must be initialized by a constant expression}}
+  // expected-error@#invalid-ref {{constexpr variable '__begin1' must be initialized by a constant expression}}
+  // expected-error@#invalid-ref {{constexpr variable '__end1' must be initialized by a constant expression}}
+  // expected-note@#invalid-ref {{reference to 'arr' is not a constant expression}}
+  // old-interp-error@#invalid-ref {{expansion size is not a constant expression}}
+  // old-interp-note@#invalid-ref {{in call to}}
+  // old-interp-note@#invalid-ref 3 {{member call on variable '__range1' whose value is not known}}
+  // old-interp-note@#invalid-ref 3 {{declared here}}
+  // new-interp-error@#invalid-ref 3 {{constexpr variable '__iter1' must be initialized by a constant expression}}
+  // new-interp-note@#invalid-ref 5 {{pointer to subobject of 'arr' is not a constant expression}}
+  // new-interp-note@#invalid-ref 3 {{in instantiation of expansion statement}}
+  template for (constexpr int s : arr) { // #invalid-ref                // OK, iterating expansion statement
+    result += sizeof(char[s]);
+  }
+  return result;
+}
+static_assert(f() == 6); // old-interp-error {{static assertion failed due to requirement 'f() == 6'}} old-interp-note {{expression evaluates to '0 == 6'}}
+
+struct S {
+  int i;
+  short s;
+};
+
+consteval long f(S s) {
+  long result = 0;
+  template for (auto x : s) {                           // OK, destructuring expansion statement
+    result += sizeof(x);
+  }
+  return result;
+}
+static_assert(f(S{}) == sizeof(int) + sizeof(short));
+}
+
+void not_constant_expression() {
+  template for (constexpr auto x : B()) { // expected-error {{constexpr variable '[__u0]' must be initialized by a constant expression}} \
+                                             expected-note {{reference to temporary is not a constant expression}} \
+                                             expected-note {{temporary created here}} \
+                                             expected-error {{constexpr variable 'x' must be initialized by a constant expression}} \
+                                             expected-note {{in instantiation of expansion statement requested here}} \
+                                             old-interp-note {{read of variable '[__u0]' whose value is not known}} \
+                                             old-interp-note {{declared here}} \
+                                             new-interp-note {{cannot access field of null pointer}}
+    g(x);
+  }
+}
+
+constexpr int references_enumerating() {
+  int x = 1, y = 2, z = 3;
+  template for (auto& x : {x, y, z}) { ++x; }
+  template for (auto&& x : {x, y, z}) { ++x; }
+  return x + y + z;
+}
+
+static_assert(references_enumerating() == 12);
+
+constexpr int references_destructuring() {
+  C c;
+  template for (auto& x : c) { ++x; }
+  template for (auto&& x : c) { ++x; }
+  return c.a + c.b + c.c;
+}
+
+static_assert(references_destructuring() == 12);
+
+constexpr int break_continue() {
+  int sum = 0;
+  template for (auto x : {1, 2}) {
+    break;
+    sum += x;
+  }
+
+  template for (auto x : {3, 4}) {
+    continue;
+    sum += x;
+  }
+
+  template for (auto x : {5, 6}) {
+    if (x == 6) break;
+    sum += x;
+  }
+
+  template for (auto x : {7, 8, 9}) {
+    if (x == 8) continue;
+    sum += x;
+  }
+
+  return sum;
+}
+
+static_assert(break_continue() == 21);
+
+constexpr int break_continue_nested() {
+  int sum = 0;
+
+  template for (auto x : {1, 2}) {
+    template for (auto y : {3, 4}) {
+      if (x == 2) break;
+      sum += y;
+    }
+    sum += x;
+  }
+
+  template for (auto x : {5, 6}) {
+    template for (auto y : {7, 8}) {
+      if (x == 6) continue;
+      sum += y;
+    }
+    sum += x;
+  }
+
+  return sum;
+}
+
+static_assert(break_continue_nested() == 36);
+
+template <typename ...Ts>
+void unexpanded_pack_bad(Ts ...ts) {
+  template for (auto x : ts) {} // expected-error {{expression contains unexpanded parameter pack 'ts'}}
+  template for (Ts x : {1, 2}) {} // expected-error {{declaration type contains unexpanded parameter pack 'Ts'}}
+  template for (auto x : {ts}) {} // expected-error {{initializer contains unexpanded parameter pack}} \
+  // expected-note {{in instantiation of expansion statement requested here}}
+}
+
+struct E { int x, y; constexpr E(int x, int y) : x{x}, y{y} {}};
+
+template <typename ...Es>
+constexpr int unexpanded_pack_good(Es ...es) {
+  int sum = 0;
+  ([&] {
+    template for (auto x : es) sum += x;
+    template for (Es e : {{5, 6}, {7, 8}}) sum += e.x + e.y;
+  }(), ...);
+  return sum;
+}
+
+static_assert(unexpanded_pack_good(E{1, 2}, E{3, 4}) == 62);
+
+// Ensure that the expansion-initializer is evaluated even if it expands
+// to nothing.
+//
+// This is related to CWG 3048. Note that we currently still model this as
+// a DecompositionDecl w/ zero bindings.
+constexpr bool empty_side_effect() {
+  struct A {
+    constexpr A(bool& b) {
+      b = true;
+    }
+  };
+
+  bool constructed = false;
+  template for (auto x : A(constructed)) static_assert(false);
+  return constructed;
+}
+
+static_assert(empty_side_effect());
+
+namespace apply_lifetime_extension {
+struct T {
+  int& x;
+  constexpr T(int& x) noexcept : x(x) {}
+  constexpr ~T() noexcept { x = 42; }
+};
+
+constexpr const T& f(const T& t) noexcept { return t; }
+constexpr T g(int& x) noexcept { return T(x); }
+
+// CWG 3043:
+//
+// Lifetime extension only applies to destructuring expansion statements
+// (enumerating statements don't have a range variable, and the range variable
+// of iterating statements is constexpr).
+constexpr int lifetime_extension() {
+  int x = 5;
+  int sum  = 0;
+  template for (auto e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename T>
+constexpr int lifetime_extension_instantiate_expansions() {
+  int x = 5;
+  int sum  = 0;
+  template for (T e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename T>
+constexpr int lifetime_extension_dependent_expansion_stmt() {
+  int x = 5;
+  int sum  = 0;
+  template for (int e : f(g((T&)x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename U>
+struct foo {
+  template <typename T>
+  constexpr int lifetime_extension_multiple_instantiations() {
+      int x = 5;
+      int sum  = 0;
+      template for (T e : f(g((U&)x))) {
+        sum += x;
+      }
+      return sum + x;
+  }
+};
+
+static_assert(lifetime_extension() == 47);
+static_assert(lifetime_extension_instantiate_expansions<int>() == 47);
+static_assert(lifetime_extension_dependent_expansion_stmt<int>() == 47);
+static_assert(foo<int>().lifetime_extension_multiple_instantiations<int>() == 47);
+}
+
+template <typename... Ts>
+constexpr int return_from_expansion(Ts... ts) {
+  template for (int i : {1, 2, 3}) {
+    return (ts + ...);
+  }
+  __builtin_unreachable();
+}
+
+static_assert(return_from_expansion(4, 5, 6) == 15);
+
+void not_constexpr();
+
+constexpr int empty_expansion_consteval() {
+  template for (auto _ : {}) {
+    not_constexpr();
+  }
+  return 3;
+}
+
+static_assert(empty_expansion_consteval() == 3);
+
+void nested_empty_expansion() {
+  template for (auto x1 : {})
+    template for (auto x2 : {1})
+      static_assert(false);
+
+  template for (auto x1 : {1})
+    template for (auto x2 : {})
+      template for (auto x3 : {1})
+        static_assert(false);
+
+  template for (auto x1 : {})
+    template for (auto x2 : {})
+      template for (auto x3 : {})
+        template for (auto x4 : {1})
+          static_assert(false);
+
+  template for (auto x1 : {})
+    template for (auto x2 : {1})
+      template for (auto x3 : {})
+        template for (auto x4 : {1})
+          static_assert(false);
+
+  template for (auto x1 : {})
+    template for (auto x2 : {1})
+      template for (auto x4 : {1})
+        static_assert(false);
+}
+
+struct Empty {};
+
+template <typename T>
+void nested_empty_expansion_dependent() {
+  template for (auto x1 : T())
+    template for (auto x2 : {1})
+      static_assert(false);
+
+  template for (auto x1 : {1})
+    template for (auto x2 : T())
+      template for (auto x3 : {1})
+        static_assert(false);
+
+  template for (auto x1 : T())
+    template for (auto x2 : T())
+      template for (auto x3 : T())
+        template for (auto x4 : {1})
+          static_assert(false);
+
+  template for (auto x1 : T())
+    template for (auto x2 : {1})
+      template for (auto x3 : T())
+        template for (auto x4 : {1})
+          static_assert(false);
+
+  template for (auto x1 : T())
+    template for (auto x2 : {1})
+      template for (auto x4 : {1})
+        static_assert(false);
+}
+
+void nested_empty_expansion_dependent_instantiate() {
+  nested_empty_expansion_dependent<Empty>();
+}
+
+// Destructuring expansion statements using tuple_size/tuple_element/get.
+namespace std {
+template <typename>
+struct tuple_size;
+
+template <__SIZE_TYPE__, typename>
+struct tuple_element; // expected-note {{template is declared here}}
+
+namespace get_decomposition {
+struct MemberGet {
+  int x[6]{};
+
+  template <__SIZE_TYPE__ I>
+  constexpr int& get() { return x[I * 2]; }
+};
+
+struct ADLGet {
+  long x[8]{};
+};
+
+template <__SIZE_TYPE__ I>
+constexpr long& get(ADLGet& a) { return a.x[I * 2]; }
+} // namespace get_decomposition
+
+template <>
+struct tuple_size<get_decomposition::MemberGet> {
+  static constexpr __SIZE_TYPE__ value = 3;
+};
+
+template <__SIZE_TYPE__ I>
+struct tuple_element<I, get_decomposition::MemberGet> {
+  using type = int;
+};
+
+template <>
+struct tuple_size<get_decomposition::ADLGet> {
+  static constexpr __SIZE_TYPE__ value = 4;
+};
+
+template <__SIZE_TYPE__ I>
+struct tuple_element<I, get_decomposition::ADLGet> {
+  using type = long;
+};
+
+constexpr int member() {
+  get_decomposition::MemberGet m;
+  int v = 1;
+  template for (int& i : m) {
+    i = v;
+    v++;
+  }
+  return m.x[0] + m.x[2] + m.x[4];
+}
+
+constexpr long adl() {
+  get_decomposition::ADLGet m;
+  long v = 1;
+  template for (long& i : m) {
+    i = v;
+    v++;
+  }
+  return m.x[0] + m.x[2] + m.x[4] + m.x[6];
+}
+
+static_assert(member() == 6);
+static_assert(adl() == 10);
+
+struct TupleSizeOnly {};
+
+template <>
+struct tuple_size<TupleSizeOnly> {
+  static constexpr __SIZE_TYPE__ value = 3;
+};
+
+struct TupleSizeAndGet {
+  template <__SIZE_TYPE__>
+  constexpr int get() { return 1; }
+};
+
+template <>
+struct tuple_size<TupleSizeAndGet> {
+  static constexpr __SIZE_TYPE__ value = 3;
+};
+
+void invalid() {
+  template for (auto x : TupleSizeOnly()) {} // expected-error {{use of undeclared identifier 'get'}} \
+                                                expected-note {{in implicit initialization of binding declaration}}
+
+  template for (auto x : TupleSizeAndGet()) {} // expected-error {{implicit instantiation of undefined template 'std::tuple_element<0, std::TupleSizeAndGet>'}} \
+                                                  expected-note {{in implicit initialization of binding declaration}}
+}
+} // namespace std
+
+constexpr int generic_lambda() {
+  static constexpr int arr[]{1, 2, 3};
+  int sum = 0;
+  [n = 5, &sum]<class = void>() {
+    template for (constexpr auto x : arr) {
+      sum += n + x;
+    }
+  }();
+  return sum;
+}
+
+static_assert(generic_lambda() == 21);
+
+void for_range_decl_must_be_var() {
+  template for (void q() : "error") // expected-error {{expansion statement declaration must declare a variable}}
+    ;
+}
+
+void init_list_bad() {
+  template for (auto y : {{1}, {2}, {3, {4}}, {{{5}}}}); // expected-error {{cannot deduce actual type for variable 'y' with type 'auto' from initializer list}} \
+                                                            expected-note {{in instantiation of expansion statement requested here}}
+}
+
+// Test that the init statement is evaluated even if the expansion statement
+// expands to nothing.
+constexpr int init_stmt_empty_expansion() {
+  static constexpr String empty{""};
+  int x = 0;
+  template for (int _ = x += 1; auto i : {}) {}
+  template for (int _ = x += 2; auto i : empty) {}
+  template for (int _ = x += 3; auto i : Empty()) {}
+  return x;
+}
+
+static_assert(init_stmt_empty_expansion() == 6);
+
+void vla(int n) {
+  int a[n];
+  template for (int x : a) {} // expected-error {{cannot expand variable length array type 'int[n]'}}
+}
+
+template <typename T>
+void template_vla(T& a) { // expected-note {{variably modified type 'int[n]' cannot be used as a template argument}}
+  template for (int x : a) {}
+}
+
+void instantiate_template_vla(int n) {
+  int a[n];
+  template_vla(a); // expected-error {{no matching function for call to 'template_vla'}}
+}
+
+struct Incomplete; // expected-note 2 {{forward declaration of 'Incomplete'}}
+void incomplete_type(Incomplete& s) {
+  template for (int x : s) {} // expected-error {{cannot expand expression of incomplete type 'Incomplete'}}
+}
+
+template <typename T>
+void dependent_incomplete_type(T& s) {
+  template for (int x : s) {} // expected-error {{cannot expand expression of incomplete type 'Incomplete'}}
+}
+
+template void dependent_incomplete_type<Incomplete>(Incomplete&); // expected-note {{in instantiation of function template specialization 'dependent_incomplete_type<Incomplete>' requested here}}
+
+template <typename T>
+void lambda_template(T a) {
+  template for (auto x : a) {} // expected-error {{cannot expand lambda closure type}}
+}
+
+void lambda_template_call() {
+  lambda_template([]{}); // expected-note {{in instantiation of function template specialization}}
+}
+
+// CWG 3131 makes it possible to expand over non-constexpr ranges.
+namespace cwg3131 {
+constexpr int f1() {
+  int j = 0;
+  template for (auto i : Array<int, 3>{1, 2, 3}) j +=i;
+  return j;
+}
+
+constexpr int f2() {
+  Array<int, 3> a{1, 2, 3};
+  int j = 0;
+  template for (auto i : a) j +=i; // new-interp-error {{expansion size is not a constant expression}} \
+                                      new-interp-note {{initializer of '__range1' is not a constant expression}} \
+                                      new-interp-note {{declared here}}
+  return j;
+}
+
+static_assert(f1() == 6);
+static_assert(f2() == 6); // new-interp-error {{static assertion failed due to requirement 'f2() == 6'}} \
+                             new-interp-note {{expression evaluates to '0 == 6'}}
+
+template <typename T>
+struct Span {
+  T* data;
+  __SIZE_TYPE__ size;
+
+  template <__SIZE_TYPE__ N>
+  constexpr Span(T(&a)[N]) : data{a}, size{N} {}
+
+  constexpr auto begin() const -> T* { return data; }
+  constexpr auto end() const -> T* { return data + size; }
+};
+
+constexpr int arr[3] = { 1, 2, 3 };
+consteval Span<const int> foo() {
+  return Span<const int>(arr);
+}
+
+constexpr int f3() {
+  int r = 0;
+  template for (constexpr auto m : foo())
+    r += m;
+  return r;
+}
+
+static_assert(f3() == 6);
+}
+
+// Test that we actually do 'begin + decltype(begin - begin){i}'.
+namespace cwg3044 {
+struct DifferenceType {
+  int v;
+  explicit constexpr DifferenceType(__PTRDIFF_TYPE__ v) : v(int(v)) {}
+};
+
+struct Range {
+  struct iterator {
+    DifferenceType v;
+
+    constexpr iterator& operator++() {
+      v.v++;
+      return *this;
+    }
+
+    constexpr int operator*() const { return int(v.v); }
+    friend constexpr bool operator!=(iterator a, iterator b) { return a.v.v != b.v.v; }
+    friend constexpr iterator operator+(iterator a, DifferenceType b) {
+      return iterator{DifferenceType{int(a.v.v) + int(b.v)}};
+    }
+    friend constexpr DifferenceType operator-(iterator a, iterator b) {
+      return DifferenceType{int(a.v.v) - int(b.v.v)};
+    }
+  };
+
+  constexpr auto begin() const { return iterator{DifferenceType{1}}; }
+  constexpr auto end() const { return iterator{DifferenceType{5}}; }
+};
+
+constexpr int f() {
+  int val = 0;
+  template for (auto v : Range()) val += v;
+  template for (constexpr auto v : Range()) val += v;
+  return val;
+}
+
+static_assert(f() == 20);
+}
+
+// Test that 'iter' is an lvalue, because we used to not actually create a
+// variable for it.
+namespace cwg3140 {
+struct IterLValue {
+  struct iterator {
+    int v;
+
+    constexpr iterator& operator++() {
+      v++;
+      return *this;
+    }
+
+    constexpr int operator*() const & { return int(v); }
+    constexpr int operator*() const && = delete("'iter' must be an lvalue");
+    friend constexpr bool operator!=(iterator a, iterator b) { return a.v != b.v; }
+    friend constexpr iterator operator+(iterator a, int b) { return iterator{a.v + b}; }
+    friend constexpr int operator-(iterator a, iterator b) { return a.v - b.v; }
+  };
+
+  constexpr auto begin() const { return iterator{1}; }
+  constexpr auto end() const { return iterator{5}; }
+};
+
+constexpr int f() {
+  int val = 0;
+  template for (auto v : IterLValue()) val += v;
+  template for (constexpr auto v : IterLValue()) val += v;
+  return val;
+}
+
+static_assert(f() == 20);
+}
+
+namespace cwg3149 {
+struct NotCopyable {
+  int x;
+  constexpr NotCopyable(int x) : x{x} {}
+  constexpr NotCopyable(NotCopyable&& o) : x{o.x} {}
+  NotCopyable(const NotCopyable&) = delete; // expected-note 2 {{explicitly marked deleted here}}
+};
+
+struct NotMovable {
+  int x;
+  constexpr NotMovable(int x) : x{x} {}
+  constexpr NotMovable(const NotMovable& o) : x{o.x} {}
+  NotMovable(NotMovable&&) = delete; // expected-note 2 {{explicitly marked deleted here}}
+};
+
+template <typename T>
+struct Wrapper {
+  T a;
+  T b;
+};
+
+constexpr int f() {
+  Wrapper<NotMovable> nm{{3},{4}};
+  Wrapper<NotCopyable> nc{{5}, {6}};
+  int sum = 0;
+  template for (auto x : Wrapper<NotCopyable>{{1}, {2}}) sum += x.x;
+  template for (auto& x : nc) sum += x.x;
+  template for (auto x : static_cast<Wrapper<NotCopyable>&&>(nc)) sum += x.x;
+  template for (auto&& x : static_cast<Wrapper<NotMovable>&&>(nm)) sum += x.x;
+  return sum;
+}
+
+static_assert(f() == 32);
+
+int err() {
+  Wrapper<NotCopyable> nc{{3},{4}};
+  int sum = 0;
+
+  // expected-error at +2 2 {{call to deleted constructor of 'cwg3149::NotMovable'}}
+  // expected-note at +1 2 {{in instantiation of expansion statement requested here}}
+  template for (auto x : Wrapper<NotMovable>{{1}, {2}}) sum += x.x;
+
+  // expected-error at +2 2 {{call to deleted constructor of 'cwg3149::NotCopyable'}}
+  // expected-note at +1 2 {{in instantiation of expansion statement requested here}}
+  template for (auto x : nc) sum += x.x;
+  return sum;
+}
+}
+
+namespace cwg3045 {
+void f1() {
+  int x;
+  int y;
+
+  template for (auto x : {1}) { // expected-note {{previous definition is here}}
+    int x{}; // expected-error {{redefinition of 'x'}}
+  }
+
+  { int x; }
+
+  template for (auto x : {1}) {
+    { int x{}; }
+    {
+      int x{};
+      { int x{}; }
+    }
+  }
+
+  template for (auto x : {1}) {
+    template for (auto y : {1}) { // expected-note {{previous definition is here}}
+      int x{};
+      int y{}; // expected-error {{redefinition of 'y'}}
+    }
+  }
+}
+
+void f2(int q) {
+  switch (q) {
+    case 1: template for (auto x : {1}) [[fallthrough]]; // expected-error {{fallthrough annotation does not directly precede switch label}}
+  }
+  switch (q) {
+    case 1: template for (auto x : {1}) [[fallthrough]]; // expected-error {{fallthrough annotation does not directly precede switch label}}
+    case 2:;
+  }
+
+  switch (q) {
+    case 1: template for (auto x : {1}) { [[fallthrough]]; } // expected-error {{fallthrough annotation does not directly precede switch label}}
+  }
+
+  switch (q) {
+    case 1: template for (auto x : {1}) {
+      switch (q) {
+        case 1: [[fallthrough]];
+        case 2:;
+      }
+    }
+  }
+}
+}
+
+// Check that we apply lifetime extension to iterating expansions statements.
+//
+// The new constant interpreter erroring on this is likely https://github.com/llvm/llvm-project/issues/187775.
+namespace cwg3140 {
+constexpr const char* arr = "1";
+struct T {
+  int& x;
+  constexpr T(int& x) noexcept : x(x) {}
+  constexpr ~T() noexcept { x = 42; }
+  constexpr const char* begin() const { return arr; }
+  constexpr const char* end() const { return arr + 1; }
+};
+
+constexpr const T& f(const T& t) noexcept { return t; }
+constexpr T g(int& x) noexcept { return T(x); }
+
+constexpr int lifetime_extension_iterating() {
+  int x = 5;
+  int sum  = 0;
+  // new-interp-error at +3 {{expansion size is not a constant expression}}
+  // new-interp-note at +2 {{initializer of '__range1' is not a constant expression}}
+  // new-interp-note at +1 {{declared here}}
+  template for (auto e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename T>
+constexpr int lifetime_extension_iterating_dependent() {
+  int x = 5;
+  int sum  = 0;
+  // new-interp-error at +3 {{expansion size is not a constant expression}}
+  // new-interp-note at +2 {{initializer of '__range0' is not a constant expression}}
+  // new-interp-note at +1 {{declared here}}
+  template for (auto e : T(f(g(x)))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+template <typename T>
+constexpr int lifetime_extension_iterating_instantiation() {
+  int x = 5;
+  int sum  = 0;
+  // new-interp-error at +3 {{expansion size is not a constant expression}}
+  // new-interp-note at +2 {{initializer of '__range2' is not a constant expression}}
+  // new-interp-note at +1 {{declared here}}
+  template for (auto e : f(g(x))) {
+    sum += x;
+  }
+  return sum + x;
+}
+
+static_assert(lifetime_extension_iterating() == 47); // new-interp-error {{static assertion failed}} new-interp-note {{expression evaluates to}}
+static_assert(lifetime_extension_iterating_dependent<const T&>() == 47); // new-interp-error {{static assertion expression is not an integral constant expression}} new-interp-note {{in instantiation of}}
+static_assert(lifetime_extension_iterating_instantiation<void>() == 47); // new-interp-error {{static assertion expression is not an integral constant expression}} new-interp-note {{in instantiation of}}
+}
+
+// Tests that make sure that certain parts of an expansion statement end up in
+// the right DeclContext.
+namespace decl_context {
+struct S { int x{}, y{}; };
+
+void dependent_context() {
+  // The init-statement should never be in a dependent context.
+  template for (int _ = ({ static_assert(false); 4; }); auto x : {}) {} // expected-error {{static assertion failed}}
+  template for (({ static_assert(false); }); auto x : {}) {} // expected-error {{static assertion failed}}
+
+  // Likewise, the expansion-initializer is not dependent.
+  template for (auto x : { ({ static_assert(false); 4; }) }) {} // expected-error {{static assertion failed}}
+  template for (auto x : ({ static_assert(false); S(); })) {} // expected-error {{static assertion failed}}
+
+  // The for-range-declaration *is* dependent because it only appears within the
+  // expansion(s), which means that it is discarded entirely if the expansion
+  // size is 0.
+  template for (decltype(({ static_assert(false); 42; })) _ : {}) {}
+  template for (decltype(({ static_assert(false); 42; })) _ : {1}) {} // expected-error {{static assertion failed}} expected-note {{in instantiation of expansion statement}}
+}
+
+template <typename>
+void not_instantiated() {
+  template for (int _ = ({ static_assert(false); 4; }); auto x : {}) {}
+  template for (({ static_assert(false); }); auto x : {}) {}
+  template for (auto x : { ({ static_assert(false); 4; }) }) {}
+  template for (auto x : ({ static_assert(false); S(); })) {}
+  template for (decltype(({ static_assert(false); 42; })) _ : {}) {}
+  template for (decltype(({ static_assert(false); 42; })) _ : {1}) {}
+}
+
+template <typename>
+void instantiated() {
+  template for (int _ = ({ static_assert(false); 4; }); auto x : {}) {} // expected-error {{static assertion failed}}
+  template for (({ static_assert(false); }); auto x : {}) {} // expected-error {{static assertion failed}}
+  template for (auto x : { ({ static_assert(false); 4; }) }) {} // expected-error {{static assertion failed}}
+  template for (auto x : ({ static_assert(false); S(); })) {} // expected-error {{static assertion failed}}
+  template for (decltype(({ static_assert(false); 42; })) _ : {}) {}
+  template for (decltype(({ static_assert(false); 42; })) _ : {1}) {} // expected-error {{static assertion failed}}
+}
+
+template <typename>
+struct Template {
+  template <typename>
+  void not_fully_instantiated() {
+    template for (int _ = ({ static_assert(false); 4; }); auto x : {}) {}
+    template for (({ static_assert(false); }); auto x : {}) {}
+    template for (auto x : { ({ static_assert(false); 4; }) }) {}
+    template for (auto x : ({ static_assert(false); S(); })) {}
+    template for (decltype(({ static_assert(false); 42; })) _ : {}) {}
+    template for (decltype(({ static_assert(false); 42; })) _ : {1}) {}
+  }
+
+  template <typename>
+  void instantiated() {
+    template for (int _ = ({ static_assert(false); 4; }); auto x : {}) {} // expected-error {{static assertion failed}}
+    template for (({ static_assert(false); }); auto x : {}) {} // expected-error {{static assertion failed}}
+    template for (auto x : { ({ static_assert(false); 4; }) }) {} // expected-error {{static assertion failed}}
+    template for (auto x : ({ static_assert(false); S(); })) {} // expected-error {{static assertion failed}}
+    template for (decltype(({ static_assert(false); 42; })) _ : {}) {}
+    template for (decltype(({ static_assert(false); 42; })) _ : {1}) {} // expected-error {{static assertion failed}}
+  }
+};
+
+void f() {
+  instantiated<void>(); // expected-note {{in instantiation of function template specialization 'decl_context::instantiated<void>'}}
+
+  // This should *not* produce any diagnostics.
+  Template<int>();
+
+  // Rather, the diagnostics are only emitted if we actually fully instantiate
+  // the function template.
+  Template<void>().instantiated<void>(); // expected-note {{in instantiation of function template specialization 'decl_context::Template<void>::instantiated<void>'}}
+}
+}



More information about the llvm-branch-commits mailing list