[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
Wed Nov 26 09:46:36 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-codegen
Author: None (Sirraide)
<details>
<summary>Changes</summary>
---
Patch is 35.53 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/169686.diff
4 Files Affected:
- (modified) clang/lib/AST/ExprConstant.cpp (+40)
- (modified) clang/lib/CodeGen/CGDecl.cpp (+7)
- (modified) clang/lib/Sema/SemaDeclCXX.cpp (+3)
- (added) clang/test/SemaCXX/cxx2c-expansion-stmts.cpp (+1042)
``````````diff
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d93f87a27e68d..2de1641f6b46a 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -6037,6 +6037,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) &&
@@ -6309,6 +6315,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/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 8b1cd83af2396..678d2e9fa743a 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -143,6 +143,13 @@ void CodeGenFunction::EmitDecl(const Decl &D, bool EvaluateConditionDecl) {
// None of these decls require codegen support.
return;
+ case Decl::CXXExpansionStmt: {
+ const auto *ESD = cast<CXXExpansionStmtDecl>(&D);
+ assert(ESD->getInstantiations() && "expansion statement not expanded?");
+ EmitStmt(ESD->getInstantiations());
+ return;
+ }
+
case Decl::NamespaceAlias:
if (CGDebugInfo *DI = getDebugInfo())
DI->EmitNamespaceAlias(cast<NamespaceAliasDecl>(D));
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 8030aac3d8771..e398710ace63b 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 <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 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 <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}}
+}
+
+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 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<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 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 { r...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/169686
More information about the llvm-branch-commits
mailing list