From: Jakub Jelinek Date: Fri, 13 Feb 2026 15:11:07 +0000 (+0100) Subject: c++: Fix up consteval-only diagnostics with structured bindings [PR124012] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c7c02eeae17ee3440a00ee232e3684ed06c76e30;p=thirdparty%2Fgcc.git c++: Fix up consteval-only diagnostics with structured bindings [PR124012] We ICE on the following testcase, because a constexpr structured binding with consteval-only initializer of the whole struct but not for this exact structured binding is used in a context where it is not constant evaluated and folded into a constant. The problem is that check_out_of_consteval_use during walking didn't walk DECL_VALUE_EXPR of vars it sees being used. So we haven't noticed invalid consteval-only use, the consteval-only base of the structured binding isn't gimplified but the structured binding referencing that in its DECL_VALUE_EXPR is and so we ICE during gimplification. In order to fix that, I had to move the lambda into a separate function (similarly to why consteval_only_type_r is not a lambda) so that it can recurse with that. That isn't the only problem though. DECL_VALUE_EXPR in this case is just COMPONENT_REF with the underlying VAR_DECL (which is consteval-only). But walker had: if (VAR_P (t) && (DECL_DECLARED_CONSTEXPR_P (t) || DECL_DECLARED_CONSTINIT_P (t))) /* This is fine, don't bother checking the type. */ return NULL_TREE; and so wouldn't report anything even after the fix. The reason why it works correctly in the constexpr auto a = A {}; foo (a.a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } case is that in that case (no DECL_VALUE_EXPR) there is COMPONENT_REF of VIEW_CONVERT_EXPR of the VAR_DECL (i.e. location wrapper) and so we diagnose it on the location wrapper. That is just weird, we really shouldn't depend on location wrappers being there for correct behavior (rather than just for better locations). That if is there because cp_finish_decl calls check_out_of_consteval_use on the whole VAR_DECL and in that case we want to avoid diagnosing anything if it is constexpr/constinit var. Maybe we just shouldn't call check_out_of_consteval_use at all, though this patch moves that check to an early out in that function rather than during the walk (similarly to early out for expr == NULL_TREE which also happens occassionally). If we see a constexpr/constinit VAR_DECL which is consteval-only nested somewhere deep inside of other expressions, we should diagnose that. On Wed, Feb 11, 2026 at 08:58:26PM +0900, Jason Merrill wrote: > I'd drop constinit, that doesn't seem to qualify under > [basic.types.general]/12.1. You're right. Seems constinit was still in P2996R9 but P2996R10 has removed it except for one occurrence in the revision history (that is ok, but surprisingly the P2996R10 change that removed it is not mentioned). So, this patch additionally stops special casing constinit for consteval-only checks anywhere, only constexpr is significant. There is an unresolved part of the PR, if there is constexpr auto a = A {}; int b = a.a; then we don't reject that (similarly after the patch with constexpr structured binding). The problem in that case is that we try to constant evaluate initializers of vars (and a few other spots) and if they fold to constants (like in this case to 0) even when it is not manifestly constant-evaluated, we just replace it with the constant and so don't see there was a consteval-only use that should have been reported. Of course, if it is something manifestly constant-evaluated and folds into constant, it shouldn't be rejected. So I wonder if we don't need to call check_out_of_consteval_use in further spots... 2026-02-13 Jakub Jelinek PR c++/124012 * reflect.cc (check_out_of_consteval_use_r): New function. (check_out_of_consteval_use): Use it instead of lambda. Don't ignore constexpr/constinit vars in the walker and walk DECL_VALUE_EXPR of vars which have it. Ignore expr equal to constexpr VAR_DECL. In diagnostics only complain about missing constexpr on VAR_DECLs without that flag and never suggest constinit. Remove constinit traces from function comment. * g++.dg/reflect/pr124012.C: New test. * g++.dg/reflect/init1.C (r): Change constinit to constexpr. (p): Change constinit to constexpr const. * g++.dg/reflect/init6.C: Expect diagnostics for constinit consteval-only vars. * g++.dg/reflect/init7.C: Likewise. * g++.dg/reflect/init10.C: Likewise. * g++.dg/reflect/diag3.C: Likewise. Don't expect suggestion to add constinit. --- diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc index d04930d732c..825f68e179f 100644 --- a/gcc/cp/reflect.cc +++ b/gcc/cp/reflect.cc @@ -8092,8 +8092,79 @@ consteval_only_p (tree t) return !!cp_walk_tree (&t, consteval_only_type_r, &visited, &visited); } +/* A walker for check_out_of_consteval_use_r. It cannot be a lambda, because + we have to call this recursively. */ + +static tree +check_out_of_consteval_use_r (tree *tp, int *walk_subtrees, void *pset) +{ + tree t = *tp; + + /* No need to look into types or unevaluated operands. */ + if (TYPE_P (t) + || unevaluated_p (TREE_CODE (t)) + /* Don't walk INIT_EXPRs, because we'd emit bogus errors about + member initializers. */ + || TREE_CODE (t) == INIT_EXPR + /* Don't walk BIND_EXPR_VARS. */ + || TREE_CODE (t) == BIND_EXPR + /* And don't recurse on DECL_EXPRs. */ + || TREE_CODE (t) == DECL_EXPR) + { + *walk_subtrees = false; + return NULL_TREE; + } + + /* A subexpression of a manifestly constant-evaluated expression is + an immediate function context. For example, + + consteval void foo (std::meta::info) { } + void g() { foo (^^void); } + + is all good. */ + if (tree decl = cp_get_callee_fndecl_nofold (t)) + if (immediate_invocation_p (decl)) + { + *walk_subtrees = false; + return NULL_TREE; + } + + if (VAR_P (t) && DECL_HAS_VALUE_EXPR_P (t)) + { + tree vexpr = DECL_VALUE_EXPR (t); + if (tree ret = cp_walk_tree (&vexpr, check_out_of_consteval_use_r, pset, + (hash_set *) pset)) + return ret; + } + + /* Now check the type to see if we are dealing with a consteval-only + expression. */ + if (!consteval_only_p (t)) + return NULL_TREE; + + /* Already escalated? */ + if (current_function_decl + && DECL_IMMEDIATE_FUNCTION_P (current_function_decl)) + { + *walk_subtrees = false; + return NULL_TREE; + } + + /* We might have to escalate if we are in an immediate-escalating + function. */ + if (immediate_escalating_function_p (current_function_decl)) + { + promote_function_to_consteval (current_function_decl); + *walk_subtrees = false; + return NULL_TREE; + } + + *walk_subtrees = false; + return t; +} + /* Detect if a consteval-only expression EXPR or a consteval-only - variable EXPR not declared constexpr/constinit is used outside + variable EXPR not declared constexpr is used outside a manifestly constant-evaluated context. E.g.: void f() { @@ -8115,88 +8186,24 @@ consteval_only_p (tree t) bool check_out_of_consteval_use (tree expr, bool complain/*=true*/) { - if (!flag_reflection || in_immediate_context ()) + if (!flag_reflection || in_immediate_context () || expr == NULL_TREE) return false; - auto walker = [](tree *tp, int *walk_subtrees, void *) -> tree - { - tree t = *tp; - - /* No need to look into types or unevaluated operands. */ - if (TYPE_P (t) - || unevaluated_p (TREE_CODE (t)) - /* Don't walk INIT_EXPRs, because we'd emit bogus errors about - member initializers. */ - || TREE_CODE (t) == INIT_EXPR - /* Don't walk BIND_EXPR_VARS. */ - || TREE_CODE (t) == BIND_EXPR - /* And don't recurse on DECL_EXPRs. */ - || TREE_CODE (t) == DECL_EXPR) - { - *walk_subtrees = false; - return NULL_TREE; - } - - /* A subexpression of a manifestly constant-evaluated expression is - an immediate function context. For example, - - consteval void foo (std::meta::info) { } - void g() { foo (^^void); } - - is all good. */ - if (tree decl = cp_get_callee_fndecl_nofold (t)) - if (immediate_invocation_p (decl)) - { - *walk_subtrees = false; - return NULL_TREE; - } - - if (VAR_P (t) - && (DECL_DECLARED_CONSTEXPR_P (t) || DECL_DECLARED_CONSTINIT_P (t))) - /* This is fine, don't bother checking the type. */ - return NULL_TREE; - - /* Now check the type to see if we are dealing with a consteval-only - expression. */ - if (!consteval_only_p (t)) - return NULL_TREE; - - /* Already escalated? */ - if (current_function_decl - && DECL_IMMEDIATE_FUNCTION_P (current_function_decl)) - { - *walk_subtrees = false; - return NULL_TREE; - } - - /* We might have to escalate if we are in an immediate-escalating - function. */ - if (immediate_escalating_function_p (current_function_decl)) - { - promote_function_to_consteval (current_function_decl); - *walk_subtrees = false; - return NULL_TREE; - } - - *walk_subtrees = false; - return t; - }; + if (VAR_P (expr) && DECL_DECLARED_CONSTEXPR_P (expr)) + return false; - if (tree t = cp_walk_tree_without_duplicates (&expr, walker, nullptr)) + hash_set pset; + if (tree t = cp_walk_tree (&expr, check_out_of_consteval_use_r, &pset, &pset)) { if (complain) { - if (VAR_P (t)) + if (VAR_P (t) && !DECL_DECLARED_CONSTEXPR_P (t)) { auto_diagnostic_group d; error_at (cp_expr_loc_or_input_loc (t), "consteval-only variable %qD not declared % " "used outside a constant-evaluated context", t); - if (TREE_STATIC (t) || CP_DECL_THREAD_LOCAL_P (t)) - inform (DECL_SOURCE_LOCATION (t), "add % or " - "%"); - else - inform (DECL_SOURCE_LOCATION (t), "add %"); + inform (DECL_SOURCE_LOCATION (t), "add %"); } else error_at (cp_expr_loc_or_input_loc (t), diff --git a/gcc/testsuite/g++.dg/reflect/diag3.C b/gcc/testsuite/g++.dg/reflect/diag3.C index 754a8b29b87..49233ee354c 100644 --- a/gcc/testsuite/g++.dg/reflect/diag3.C +++ b/gcc/testsuite/g++.dg/reflect/diag3.C @@ -1,14 +1,16 @@ // { dg-do compile { target c++26 } } // { dg-additional-options "-freflection" } -// Test that we suggest adding "constexpr" or "constinit" (where allowed). +// Test that we suggest adding "constexpr" (where allowed). auto foo = ^^int; // { dg-error "consteval-only variable .foo." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } -constinit auto foo_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } +constinit auto foo_ = ^^int; // { dg-error "consteval-only variable .foo_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } constexpr auto foo__ = ^^int; thread_local auto tfoo = ^^int; // { dg-error "consteval-only variable .tfoo." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } -thread_local constinit auto tfoo_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } +thread_local constinit auto tfoo_ = ^^int; // { dg-error "consteval-only variable .tfoo_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } thread_local constexpr auto tfoo__ = ^^int; void @@ -18,11 +20,13 @@ f () // { dg-message "add .constexpr." "" { target *-*-* } .-1 } constexpr auto ref_ = ^^int; static auto sref = ^^int; // { dg-error "consteval-only variable .sref." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } - static auto constinit sref_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } + static auto constinit sref_ = ^^int; // { dg-error "consteval-only variable .sref_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } static auto constexpr sref__ = ^^int; thread_local auto tref = ^^int; // { dg-error "consteval-only variable .tref." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } - thread_local constinit auto tref_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } + thread_local constinit auto tref_ = ^^int; // { dg-error "consteval-only variable .tref_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } thread_local constexpr auto tref__ = ^^int; } diff --git a/gcc/testsuite/g++.dg/reflect/init1.C b/gcc/testsuite/g++.dg/reflect/init1.C index 7de12a4d1c2..17fcf5874c0 100644 --- a/gcc/testsuite/g++.dg/reflect/init1.C +++ b/gcc/testsuite/g++.dg/reflect/init1.C @@ -22,8 +22,8 @@ struct W { decltype(^^::) i = ^^W::i; }; -constinit info r = ^^int; -constinit info *p = &r; +constexpr info r = ^^int; +constexpr const info *p = &r; consteval void f () diff --git a/gcc/testsuite/g++.dg/reflect/init10.C b/gcc/testsuite/g++.dg/reflect/init10.C index 115c3eeac12..df81b890fae 100644 --- a/gcc/testsuite/g++.dg/reflect/init10.C +++ b/gcc/testsuite/g++.dg/reflect/init10.C @@ -10,7 +10,7 @@ struct A { }; A a1; // { dg-error "consteval-only variable .a1." } -constinit A a2; +constinit A a2; // { dg-error "consteval-only variable .a2." } constexpr A a3; struct B { @@ -20,5 +20,5 @@ struct B { }; B b1; // { dg-error "consteval-only variable .b1." } -constinit B b2; +constinit B b2; // { dg-error "consteval-only variable .b2." } constexpr B b3; diff --git a/gcc/testsuite/g++.dg/reflect/init6.C b/gcc/testsuite/g++.dg/reflect/init6.C index 3febb88cba5..371d8cee314 100644 --- a/gcc/testsuite/g++.dg/reflect/init6.C +++ b/gcc/testsuite/g++.dg/reflect/init6.C @@ -12,11 +12,11 @@ struct N { }; S s1; // { dg-error "consteval-only variable" } -constinit S s2{}; +constinit S s2{}; // { dg-error "consteval-only variable" } constexpr S s3{^^int}; N n1; // { dg-error "consteval-only variable" } -constinit N n2; +constinit N n2; // { dg-error "consteval-only variable" } constexpr N n3; template @@ -25,7 +25,7 @@ struct X { }; X x1; // { dg-error "consteval-only variable" } -constinit X x2{}; +constinit X x2{}; // { dg-error "consteval-only variable" } constexpr X x3{^^int}; void diff --git a/gcc/testsuite/g++.dg/reflect/init7.C b/gcc/testsuite/g++.dg/reflect/init7.C index 3f8405ba2bc..c530776d74b 100644 --- a/gcc/testsuite/g++.dg/reflect/init7.C +++ b/gcc/testsuite/g++.dg/reflect/init7.C @@ -9,7 +9,7 @@ info r1 = ^^int; // { dg-error "consteval-only variable .r1. not declared .cons const info r2 = ^^int; // { dg-error "consteval-only variable .r2. not declared .constexpr. used outside a constant-evaluated context" } constexpr info r3 = ^^int; -constinit info r4 = ^^int; +constinit info r4 = ^^int; // { dg-error "consteval-only variable .r4. not declared .constexpr. used outside a constant-evaluated context" } const info *const p1 = &r3; // { dg-error "consteval-only variable .p1. not declared .constexpr. used outside a constant-evaluated context" } info *p2; // { dg-error "consteval-only variable .p2. not declared .constexpr. used outside a constant-evaluated context" } const info &q = r3; // { dg-error "consteval-only variable .q. not declared .constexpr. used outside a constant-evaluated context" } @@ -23,7 +23,7 @@ g () static info l4 = ^^int; // { dg-error "consteval-only variable .l4. not declared .constexpr. used outside a constant-evaluated context" } static const info l5 = ^^int; // { dg-error "consteval-only variable .l5. not declared .constexpr. used outside a constant-evaluated context" } static constexpr info l6 = ^^int; - static constinit info l7 = ^^int; + static constinit info l7 = ^^int; // { dg-error "consteval-only variable .l7. not declared .constexpr. used outside a constant-evaluated context" } } consteval void diff --git a/gcc/testsuite/g++.dg/reflect/pr124012.C b/gcc/testsuite/g++.dg/reflect/pr124012.C new file mode 100644 index 00000000000..84783d1a805 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/pr124012.C @@ -0,0 +1,44 @@ +// PR c++/124012 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } + +void foo (char); +void corge (const char *); +struct A { char a; decltype (^^::) b; }; + +void +bar () +{ + constexpr auto [a, b] = A {}; + foo (a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +baz () +{ + constexpr auto a = A {}; + foo (a.a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +qux () +{ + constexpr auto a = A {}; + corge (&a.a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +garply () +{ + constexpr auto [a, b] = A {}; + corge (&a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +fred () +{ + constexpr auto [a, b] = A {}; + constexpr auto c = a; + foo (c); + corge (&c); +}