From 08eaa56a2b0328744f89f45d4aa56aefff476868 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 26 Nov 2025 17:09:56 +0100 Subject: [PATCH 1/6] [Clang] [C++26] Expansion Statements (Part 7) --- clang/lib/AST/ExprConstant.cpp | 40 + clang/lib/Sema/SemaDeclCXX.cpp | 3 + clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 1042 ++++++++++++++++++ 3 files changed, 1085 insertions(+) create mode 100644 clang/test/SemaCXX/cxx2c-expansion-stmts.cpp diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index d09eff7b54b65..07e70b8a21b40 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6036,6 +6036,12 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info, const VarDecl *VD = dyn_cast_or_null(D); if (VD && !CheckLocalVariableDeclaration(Info, VD)) return ESR_Failed; + + if (const auto *ESD = dyn_cast(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) && @@ -6308,6 +6314,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(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(S)); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 3bc748969065a..c6541e453cfff 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..71ce71c4f69fe --- /dev/null +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -0,0 +1,1042 @@ +// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -verify +namespace std { +template +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 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(); + template for (constexpr auto x : {1, 2, 3}) + static_assert(tg()); + + 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(); + + 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() == 4); // expected-error 3 {{static assertion failed due to requirement 'tg() == 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 +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()); + template for (constexpr auto x : {T(1), T(2)}) static_assert(tg()); +} + +template +struct s1 { + template + 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()); + template for (constexpr T x : {U(1), U(2)}) static_assert(tg()); + template for (constexpr auto x : {T(1), U(2)}) static_assert(tg()); + } +}; + +template +void t2() { + template for (T x : {}) g(x); +} + +void f2() { + t1(); + t1(); + s1().tf(); + s1().tf(); + s1().tf(); + s1().tf(); + t2(); + t2(); + t2(); +} + +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; + +constexpr int f3() { + static constexpr String s{"abcd"}; + int count = 0; + template for (constexpr auto x : s) count++; + return count; +} + +template +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 must not be negative (was -3)}} + template for (constexpr auto x : n) g(x); // expected-error {{expansion size must not be negative (was -3)}} +} + +template +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}} +} + +static constexpr Array 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 2 {{declared private here}} + constexpr const int* end() const { return integers.end(); } // expected-note 2 {{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 2 {{declared protected here}} + constexpr const int* end() const { return integers.end(); } // expected-note 2 {{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 2 {{'begin' is a private member of 'Private'}} expected-error 2 {{'end' is a private member of 'Private'}} + + static constexpr Protected p2; + template for (auto x : p2) g(x); // expected-error 2 {{'begin' is a protected member of 'Protected'}} expected-error 2 {{'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; } + int constexpr operator*() const { return 7; } + + // NOT constexpr! + friend int operator-(iterator, iterator) { return 7; } // expected-note {{declared here}} + friend int operator!=(iterator, iterator) { return 7; } + }; + constexpr iterator begin() const { return {}; } + constexpr iterator end() const { return {}; } +}; + +struct PlusMissing { + struct iterator { + int constexpr operator*() const { return 7; } + }; + constexpr iterator begin() const { return {}; } + constexpr iterator end() const { return {}; } +}; + +struct DerefMissing { + struct iterator { + friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; } + }; + 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; + + // TODO: This message should start complaining about '!=' once we support the + // proper way of computing the size. + 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}} + + 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)}} +} + +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 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 +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() == 20); +static_assert(destructure() == 12); +static_assert(destructure() == 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 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 arr {1, 2, 3}; // expected-note{{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-error@#invalid-ref {{expansion size is not a constant expression}} + // expected-note@#invalid-ref 2 {{member call on variable '__range1' whose value is not known}} + // expected-note@#invalid-ref 1 {{initializer of '__end1' is not a constant expression}} + // expected-note@#invalid-ref 3 {{declared here}} + // expected-note@#invalid-ref {{reference to 'arr' is not a constant expression}} + template for (constexpr int s : arr) { // #invalid-ref // OK, iterating expansion statement + result += sizeof(char[s]); + } + return result; +} +static_assert(f() == 6); // expected-error {{static assertion failed due to requirement 'f() == 6'}} expected-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}} \ + expected-note {{read of variable '[__u0]' whose value is not known}} \ + expected-note {{declared here}} + 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 +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 +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 +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 +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 +struct foo { + template + 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() == 47); +static_assert(lifetime_extension_dependent_expansion_stmt() == 47); +static_assert(foo().lifetime_extension_multiple_instantiations() == 47); +} + +template +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 +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(); +} + +// Destructuring expansion statements using tuple_size/tuple_element/get. +namespace std { +template +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 { + static constexpr __SIZE_TYPE__ value = 3; +}; + +template <__SIZE_TYPE__ I> +struct tuple_element { + using type = int; +}; + +template <> +struct tuple_size { + static constexpr __SIZE_TYPE__ value = 4; +}; + +template <__SIZE_TYPE__ I> +struct tuple_element { + 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 { + static constexpr __SIZE_TYPE__ value = 3; +}; + +struct TupleSizeAndGet { + template <__SIZE_TYPE__> + constexpr int get() { return 1; } +}; + +template <> +struct tuple_size { + 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]() { + 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}} +} From 0bdd2b310830b718d39a7ea3aa42ba4f9e9309a3 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Mon, 1 Dec 2025 19:11:19 +0100 Subject: [PATCH 2/6] Add incomplete type and vla tests --- clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 44 +++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index 71ce71c4f69fe..9aa2bccc44b94 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -verify +// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -Wno-vla-cxx-extension -verify namespace std { template struct initializer_list { @@ -470,7 +470,7 @@ 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 type 'void'}} + 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'}} @@ -1040,3 +1040,43 @@ 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 +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 +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&); // expected-note {{in instantiation of function template specialization 'dependent_incomplete_type' requested here}} From d3a0f7e80ebc28341a3126b9d84974e0871d9fcc Mon Sep 17 00:00:00 2001 From: Sirraide Date: Tue, 2 Dec 2025 21:54:58 +0100 Subject: [PATCH 3/6] Add test for lambda in template --- clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index 9aa2bccc44b94..2d5313d259c40 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -1080,3 +1080,12 @@ void dependent_incomplete_type(T& s) { } template void dependent_incomplete_type(Incomplete&); // expected-note {{in instantiation of function template specialization 'dependent_incomplete_type' requested here}} + +template +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}} +} From 227c7b06e900166559d7033ba2943ec8ef4e7107 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 3 Dec 2025 01:48:02 +0100 Subject: [PATCH 4/6] Properly compute iterating expansion stmt size --- clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 23 ++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index 2d5313d259c40..8a5b2936505cd 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -Wno-vla-cxx-extension -verify +// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -Wno-vla-cxx-extension -fconstexpr-steps=1000 -verify namespace std { template struct initializer_list { @@ -153,8 +153,12 @@ struct NegativeSize { void negative_size() { static constexpr NegativeSize n; - template for (auto x : n) g(x); // expected-error {{expansion size must not be negative (was -3)}} - template for (constexpr auto x : n) g(x); // expected-error {{expansion size must not be negative (was -3)}} + template for (auto x : n) g(x); // expected-error {{expansion size is not a constant expression}} \ + expected-note {{constexpr evaluation hit maximum step limit}} \ + expected-note {{in call to}} + template for (constexpr auto x : n) g(x); // expected-error {{expansion size is not a constant expression}} \ + expected-note {{constexpr evaluation hit maximum step limit}} \ + expected-note {{in call to}} } template @@ -236,11 +240,11 @@ static_assert(Protected::member_func() == 6); struct SizeNotICE { struct iterator { friend constexpr iterator operator+(iterator a, __PTRDIFF_TYPE__) { return a; } + constexpr iterator operator++() { return *this; } int constexpr operator*() const { return 7; } // NOT constexpr! - friend int operator-(iterator, iterator) { return 7; } // expected-note {{declared here}} - friend int operator!=(iterator, iterator) { return 7; } + friend int operator!=(iterator, iterator) { return 7; } // expected-note {{declared here}} }; constexpr iterator begin() const { return {}; } constexpr iterator end() const { return {}; } @@ -248,6 +252,7 @@ struct SizeNotICE { struct PlusMissing { struct iterator { + constexpr iterator operator++() { return *this; } int constexpr operator*() const { return 7; } }; constexpr iterator begin() const { return {}; } @@ -267,10 +272,9 @@ void missing_funcs() { static constexpr PlusMissing s2; static constexpr DerefMissing s3; - // TODO: This message should start complaining about '!=' once we support the - // proper way of computing the size. 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 {{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)}} @@ -631,9 +635,10 @@ consteval int f() { // expected-error@#invalid-ref {{constexpr variable '__end1' must be initialized by a constant expression}} // expected-error@#invalid-ref {{expansion size is not a constant expression}} // expected-note@#invalid-ref 2 {{member call on variable '__range1' whose value is not known}} - // expected-note@#invalid-ref 1 {{initializer of '__end1' is not a constant expression}} + // expected-note@#invalid-ref {{initializer of '__begin1' is not a constant expression}} // expected-note@#invalid-ref 3 {{declared here}} // expected-note@#invalid-ref {{reference to 'arr' is not a constant expression}} + // expected-note@#invalid-ref {{in call to}} template for (constexpr int s : arr) { // #invalid-ref // OK, iterating expansion statement result += sizeof(char[s]); } From 8571f6e6db854e9d4fdbe96928cd80a2b0dbd60b Mon Sep 17 00:00:00 2001 From: Sirraide Date: Wed, 3 Dec 2025 03:16:46 +0100 Subject: [PATCH 5/6] CWG 3131 --- clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 60 +++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index 8a5b2936505cd..f26fac3b24dee 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -187,8 +187,8 @@ struct Private { friend constexpr int friend_func(); private: - constexpr const int* begin() const { return integers.begin(); } // expected-note 2 {{declared private here}} - constexpr const int* end() const { return integers.end(); } // expected-note 2 {{declared private here}} + 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() { @@ -203,8 +203,8 @@ struct Protected { friend constexpr int friend_func(); protected: - constexpr const int* begin() const { return integers.begin(); } // expected-note 2 {{declared protected here}} - constexpr const int* end() const { return integers.end(); } // expected-note 2 {{declared protected here}} + 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() { @@ -217,10 +217,10 @@ struct Protected { void access_control() { static constexpr Private p1; - template for (auto x : p1) g(x); // expected-error 2 {{'begin' is a private member of 'Private'}} expected-error 2 {{'end' is a private member of 'Private'}} + 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 2 {{'begin' is a protected member of 'Protected'}} expected-error 2 {{'end' is a protected member of 'Protected'}} + 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() { @@ -634,8 +634,7 @@ consteval int f() { // 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-error@#invalid-ref {{expansion size is not a constant expression}} - // expected-note@#invalid-ref 2 {{member call on variable '__range1' whose value is not known}} - // expected-note@#invalid-ref {{initializer of '__begin1' is not a constant expression}} + // expected-note@#invalid-ref 3 {{member call on variable '__range1' whose value is not known}} // expected-note@#invalid-ref 3 {{declared here}} // expected-note@#invalid-ref {{reference to 'arr' is not a constant expression}} // expected-note@#invalid-ref {{in call to}} @@ -1094,3 +1093,48 @@ void lambda_template(T a) { 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{1, 2, 3}) j +=i; + return j; +} + +constexpr int f2() { + Array a{1, 2, 3}; + int j = 0; + template for (auto i : a) j +=i; + return j; +} + +static_assert(f1() == 6); +static_assert(f2() == 6); + +template +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 foo() { + return Span(arr); +} + +constexpr int f3() { + int r = 0; + template for (constexpr auto m : foo()) + r += m; + return r; +} + +static_assert(f3() == 6); +} From 157da01b1ebf34246b3875b6e4b4ac92d472bed8 Mon Sep 17 00:00:00 2001 From: Sirraide Date: Fri, 5 Dec 2025 18:43:36 +0100 Subject: [PATCH 6/6] New constant interpreter support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timm Bäder --- clang/lib/AST/ByteCode/Compiler.cpp | 42 ++++++++++++++++++++ clang/lib/AST/ByteCode/Compiler.h | 3 +- clang/test/SemaCXX/cxx2c-expansion-stmts.cpp | 35 ++++++++++------ 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 14b6bb7bf61d5..7575f688935b4 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -5533,6 +5533,9 @@ template bool Compiler::visitStmt(const Stmt *S) { return this->emitInvalid(S); case Stmt::LabelStmtClass: return this->visitStmt(cast(S)->getSubStmt()); + case Stmt::CXXExpansionStmtInstantiationClass: + return this->visitCXXExpansionStmtInstantiation( + cast(S)); default: { if (const auto *E = dyn_cast(S)) return this->discard(E); @@ -5616,6 +5619,13 @@ bool Compiler::visitDeclStmt(const DeclStmt *DS, FunctionDecl, NamespaceAliasDecl, UsingDirectiveDecl>(D)) continue; + if (const auto *ESD = dyn_cast(D)) { + assert(ESD->getInstantiations() && "not expanded?"); + if (!this->visitStmt(ESD->getInstantiations())) + return false; + continue; + } + const auto *VD = dyn_cast(D); if (!VD) return false; @@ -6180,6 +6190,38 @@ bool Compiler::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 +bool Compiler::visitCXXExpansionStmtInstantiation( + const CXXExpansionStmtInstantiation *S) { + LocalScope WholeLoopScope(this, ScopeKind::Block); + + for (const Stmt *Shared : S->getSharedStmts()) { + if (!this->visitDeclStmt(cast(Shared), true)) + return false; + } + + LabelTy EndLabel = this->getLabel(); + for (const Stmt *Instantiation : S->getInstantiations()) { + LabelTy ContinueLabel = this->getLabel(); + LoopScope LS(this, S, EndLabel, ContinueLabel); + + if (!this->visitStmt(Instantiation)) + return false; + this->emitLabel(ContinueLabel); + } + + this->emitLabel(EndLabel); + + return WholeLoopScope.destroyLocals(); +} + template bool Compiler::emitLambdaStaticInvokerBody(const CXXMethodDecl *MD) { assert(MD->isLambdaStaticInvoker()); diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index c641af52c2811..2585b60df1890 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -247,7 +247,8 @@ class Compiler : public ConstStmtVisitor, 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/test/SemaCXX/cxx2c-expansion-stmts.cpp b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp index f26fac3b24dee..d3f9415200887 100644 --- a/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp +++ b/clang/test/SemaCXX/cxx2c-expansion-stmts.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_cc1 %s -std=c++2c -fsyntax-only -fdeclspec -fblocks -Wno-vla-cxx-extension -fconstexpr-steps=1000 -verify +// 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 struct initializer_list { @@ -154,10 +155,12 @@ struct NegativeSize { void negative_size() { static constexpr NegativeSize n; template for (auto x : n) g(x); // expected-error {{expansion size is not a constant expression}} \ - expected-note {{constexpr evaluation hit maximum step limit}} \ + 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}} \ - expected-note {{constexpr evaluation hit maximum step limit}} \ + 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}} } @@ -626,24 +629,26 @@ 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 arr {1, 2, 3}; // expected-note{{add 'static' to give it a constant address}} + constexpr Array arr {1, 2, 3}; // expected-note{{add 'static' to give it a constant address}} \ + new-interp-note 2 {{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-error@#invalid-ref {{expansion size is not a constant expression}} - // expected-note@#invalid-ref 3 {{member call on variable '__range1' whose value is not known}} - // expected-note@#invalid-ref 3 {{declared here}} // expected-note@#invalid-ref {{reference to 'arr' is not a constant expression}} - // expected-note@#invalid-ref {{in call to}} + // 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-note@#invalid-ref 2 {{pointer to subobject of 'arr' is not a constant expression}} template for (constexpr int s : arr) { // #invalid-ref // OK, iterating expansion statement result += sizeof(char[s]); } return result; } -static_assert(f() == 6); // expected-error {{static assertion failed due to requirement 'f() == 6'}} expected-note {{expression evaluates to '0 == 6'}} +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; @@ -666,8 +671,9 @@ void not_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}} \ - expected-note {{read of variable '[__u0]' whose value is not known}} \ - expected-note {{declared 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); } } @@ -1105,12 +1111,15 @@ constexpr int f1() { constexpr int f2() { Array a{1, 2, 3}; int j = 0; - template for (auto i : a) j +=i; + 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); +static_assert(f2() == 6); // new-interp-error {{static assertion failed due to requirement 'f2() == 6'}} \ + new-interp-note {{expression evaluates to '0 == 6'}} template struct Span {