Hi!

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.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

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-11  Jakub Jelinek  <[email protected]>

        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/constinit
        VAR_DECL.  In diagnostics only complain about missing constexpr or
        constinit on VAR_DECLs without those flags.

        * g++.dg/reflect/pr124012.C: New test.

--- gcc/cp/reflect.cc.jj        2026-02-10 15:29:53.068028373 +0100
+++ gcc/cp/reflect.cc   2026-02-10 15:57:57.831402092 +0100
@@ -8092,6 +8092,77 @@ 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
    a manifestly constant-evaluated context.  E.g.:
@@ -8115,78 +8186,21 @@ 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) || DECL_DECLARED_CONSTINIT_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)
+             && !DECL_DECLARED_CONSTINIT_P (t))
            {
              auto_diagnostic_group d;
              error_at (cp_expr_loc_or_input_loc (t),
--- gcc/testsuite/g++.dg/reflect/pr124012.C.jj  2026-02-10 16:08:34.251590974 
+0100
+++ gcc/testsuite/g++.dg/reflect/pr124012.C     2026-02-10 16:07:59.915174262 
+0100
@@ -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);
+}

        Jakub

Reply via email to