]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: Fix up consteval-only diagnostics with structured bindings [PR124012]
authorJakub Jelinek <jakub@redhat.com>
Fri, 13 Feb 2026 15:11:07 +0000 (16:11 +0100)
committerJakub Jelinek <jakub@gcc.gnu.org>
Fri, 13 Feb 2026 15:11:07 +0000 (16:11 +0100)
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  <jakub@redhat.com>

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.

gcc/cp/reflect.cc
gcc/testsuite/g++.dg/reflect/diag3.C
gcc/testsuite/g++.dg/reflect/init1.C
gcc/testsuite/g++.dg/reflect/init10.C
gcc/testsuite/g++.dg/reflect/init6.C
gcc/testsuite/g++.dg/reflect/init7.C
gcc/testsuite/g++.dg/reflect/pr124012.C [new file with mode: 0644]

index d04930d732cb2a593bbc00c429e8ce2638d092be..825f68e179f17180a979adcc5184a7d43a50c2dc 100644 (file)
@@ -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<tree> *) 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<tree> 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 %<constexpr%> "
                        "used outside a constant-evaluated context", t);
-             if (TREE_STATIC (t) || CP_DECL_THREAD_LOCAL_P (t))
-               inform (DECL_SOURCE_LOCATION (t), "add %<constexpr%> or "
-                       "%<constinit%>");
-             else
-               inform (DECL_SOURCE_LOCATION (t), "add %<constexpr%>");
+             inform (DECL_SOURCE_LOCATION (t), "add %<constexpr%>");
            }
          else
            error_at (cp_expr_loc_or_input_loc (t),
index 754a8b29b8764fc8fcdd7f6de2f07c26691c5840..49233ee354c5ed2db5ec9c564516fc77fceb0d7c 100644 (file)
@@ -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;
 }
index 7de12a4d1c2a6380358a9d92db2d1e1273dcfd34..17fcf5874c0e244d1be08cee486a7c805ea2d006 100644 (file)
@@ -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 ()
index 115c3eeac1226f245bb37bd9fd53b01217e7f7d6..df81b890fae9c68d112afdfd9da80bf90da4f500 100644 (file)
@@ -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;
index 3febb88cba55fafc991932393e27545b99073e5f..371d8cee314d400b51907079d22e3fe37932e739 100644 (file)
@@ -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<typename T>
@@ -25,7 +25,7 @@ struct X {
 };
 
 X<info> x1;  // { dg-error "consteval-only variable" }
-constinit X<info> x2{};
+constinit X<info> x2{};  // { dg-error "consteval-only variable" }
 constexpr X<info> x3{^^int};
 
 void
index 3f8405ba2bcef796a8219a98d185523895a1edba..c530776d74ba437d95e9ffca40b3ef41660a0115 100644 (file)
@@ -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 (file)
index 0000000..84783d1
--- /dev/null
@@ -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);
+}