On Tue, 5 May 2026, Jason Merrill wrote:

> On 5/5/26 5:24 PM, Patrick Palka wrote:
> > On Tue, 5 May 2026, Jason Merrill wrote:
> > 
> > > On 5/5/26 4:34 PM, Patrick Palka wrote:
> > > > On Tue, 5 May 2026, Jason Merrill wrote:
> > > > 
> > > > > On 5/5/26 3:53 PM, Patrick Palka wrote:
> > > > > > On Tue, 5 May 2026, Jason Merrill wrote:
> > > > > > > On 5/5/26 1:20 PM, Patrick Palka wrote:
> > > > > > > > +/* Recursive workhorse of consteval_only_p.  Returns true if T
> > > > > > > > is
> > > > > > > > definitely
> > > > > > > > +   consteval-only, false if it's definitely not, and unknown if
> > > > > > > > we
> > > > > > > > saw
> > > > > > > > an
> > > > > > > > +   incomplete type and therefore don't know.  */
> > > > > > > > +
> > > > > > > > +tristate
> > > > > > > > +consteval_only_p_walker::walk (tree t)
> > > > > > > > +{
> > > > > > > > +  t = TYPE_MAIN_VARIANT (t);
> > > > > > > > +
> > > > > > > > +  if (REFLECTION_TYPE_P (t))
> > > > > > > > +    return true;
> > > > > > > > +  else if (INDIRECT_TYPE_P (t))
> > > > > > > > +    return walk (TREE_TYPE (t));
> > > > > > > > +  else if (TREE_CODE (t) == ARRAY_TYPE)
> > > > > > > > +    return walk (TREE_TYPE (t));
> > > > > > > > +  else if (FUNC_OR_METHOD_TYPE_P (t))
> > > > > > > > +    {
> > > > > > > > +      tristate r = walk (TREE_TYPE (t));
> > > > > > > > +      for (tree parm = TYPE_ARG_TYPES (t);
> > > > > > > > +          parm != NULL_TREE && parm != void_list_node;
> > > > > > > > +          parm = TREE_CHAIN (parm))
> > > > > > > > +       {
> > > > > > > > +         if (r.is_true ())
> > > > > > > > +           break;
> > > > > > > > +         r = r || walk (TREE_VALUE (parm));
> > > > > > > > +       }
> > > > > > > > +      return r;
> > > > > > > > +    }
> > > > > > > > +  else if (RECORD_OR_UNION_TYPE_P (t))
> > > > > > > > +    {
> > > > > > > > +      if (tree *slot = hash_map_safe_get
> > > > > > > > (consteval_only_class_cache,
> > > > > > > > t))
> > > > > > > > +       return *slot == boolean_true_node;
> > > > > > > > +
> > > > > > > > +      if (class_seen.add (t))
> > > > > > > > +       {
> > > > > > > > +         /* Optimistically assume this already seen 
> > > > > > > > consteval-unknown
> > > > > > > > class
> > > > > > > > is
> > > > > > > > +            not consteval only, for sake of mutually recursive
> > > > > > > > classes.  */
> > > > > > > > +         optimistic_p = true;
> > > > > > > > +         return false;
> > > > > > > > +       }
> > > > > > > > +      class_stack.safe_push (t);
> > > > > > > > +
> > > > > > > > +      tristate r = COMPLETE_TYPE_P (t) ? false :
> > > > > > > > tristate::unknown
> > > > > > > > ();
> > > > > > > > +      for (tree member = TYPE_FIELDS (t); member; member =
> > > > > > > > DECL_CHAIN
> > > > > > > > (member))
> > > > > > > > +       if (TREE_CODE (member) == FIELD_DECL)
> > > > > > > > +         {
> > > > > > > > +           r = r || walk (TREE_TYPE (member));
> > > > > > > > +           if (r.is_true ())
> > > > > > > > +             break;
> > > > > > > > +         }
> > > > > > > > +
> > > > > > > > +      if (!COMPLETE_TYPE_P (t))
> > > > > > > > +       /* Until the type is laid out, we can't trust that its
> > > > > > > > TYPE_FIELDS
> > > > > > > > +          won't change.  */;
> > > > > > > 
> > > > > > > We might move the COMPLETE_TYPE check...
> > > > > > > 
> > > > > > > > +      else if (r.is_true ())
> > > > > > > > +       hash_map_safe_put<hm_ggc> (consteval_only_class_cache,
> > > > > > > > +                                  t, boolean_true_node);
> > > > > > > > +      else if (r.is_false ()
> > > > > > > 
> > > > > > > ...into another exception for the is_false case, since I don't
> > > > > > > think
> > > > > > > the
> > > > > > > TYPE_FIELDS can change in a way that would invalidate is_true().
> > > > > > 
> > > > > > Sadly, it can! prune_lambda_captures can remove consteval-only
> > > > > > TYPE_FIELDS (corresponding to folded-away captures) before the
> > > > > > closure
> > > > > > type has been laid out, so if we'd cache the result now we'd get the
> > > > > > wrong answer after pruning.
> > > > > > 
> > > > > > Without this second COMPLETE_TYPE_P check we'd get a bogus error in
> > > > > > reflect/reflect_constant_array6.C due to treating the lambda as
> > > > > > consteval even after pruning.
> > > > > 
> > > > > Ah, lambdas, always keeping us on our toes.  But then we could skip
> > > > > walking
> > > > > the fields at all if we're going to return unknown no matter what we
> > > > > see.
> > > > 
> > > > In that lambda case, where TYPE_FIELDS is set but TYPE_COMPLETE_P is
> > > > false, we won't always return unknown, we could return true if one of
> > > > its TYPE_FIELDS is consteval-only.
> > > 
> > > Hmm, we currently return true but don't cache it?  So we could be
> > > returning
> > > consteval-only about a type that later turns out not to be. That doesn't
> > > seem
> > > better to me than just returning unknown.
> > 
> > Always returning unknown for !COMPLETE_TYPE_P class types seems to break
> > <meta>:
> > 
> > /scratchpad/gcc-master-build/x86_64-pc-linux-gnu/libstdc++-v3/include/meta:117:7:
> > error: ‘consteval’ function ‘virtual consteval const char*
> > std::meta::exception::what() const’ overriding non-‘consteval’ function
> >    117 |       what() const noexcept override
> >        |       ^~~~
> > In file included from
> > /home/patrick/code/gcc-master/libstdc++-v3/libsupc++/new:43,
> >                   from
> > /scratchpad/gcc-master-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/stl_construct.h:59,
> >                   from
> > /scratchpad/gcc-master-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/char_traits.h:59,
> >                   from
> > /scratchpad/gcc-master-build/x86_64-pc-linux-gnu/libstdc++-v3/include/string:45,
> >                   from
> > gcc/testsuite/g++.dg/reflect/reflect_constant_array6.C:5:
> > /scratchpad/gcc-master-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/exception.h:83:5:
> > note: overridden function is ‘virtual constexpr const char*
> > std::exception::what() const’
> >     83 |     what() const _GLIBCXX_TXN_SAFE_DYN noexcept { return
> > "std::exception"; }
> >        |     ^~~~
> > 
> > Reduced:
> > 
> > struct exception {
> >    virtual void foo();
> > };
> > 
> > struct meta_exception : exception {
> >    decltype(^^int) *p;
> >    consteval virtual void foo() { }
> > };
> > 
> > because check_for_override is called before the class is fully laid out
> > and COMPLETE_TYPE_P is not yet set, but we still need consteval_only_p
> > to return true at this point.  That doesn't seem ideal :/
> 
> How about always returning unknown for closures, but returning/caching true
> for other classes?

Makes sense, like so? Passes dg.exp=*reflect* so far.

-- >8 --

        PR c++/125179

gcc/cp/ChangeLog:

        * reflect.cc: (consteval_only_type_r): Remove this cp_walk_tree
        callback and replace with ...
        (consteval_only_walker): ... this recursive memoized
        implementation.
        (consteval_only_p): Define in terms of consteval_only_walker.
---
 gcc/cp/reflect.cc | 135 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 94 insertions(+), 41 deletions(-)

diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
index 3b9f56ea5484..f9e3f81f0acd 100644
--- a/gcc/cp/reflect.cc
+++ b/gcc/cp/reflect.cc
@@ -8561,47 +8561,26 @@ splice (tree refl)
   return refl;
 }
 
-/* A walker for consteval_only_p.  It cannot be a lambda, because we
-   have to call this recursively, sigh.  */
+/* A cache of the known boolean result of consteval_only_p_walker::walk
+   for class types.  */
 
-static tree
-consteval_only_type_r (tree *tp, int *walk_subtrees, void *data)
-{
-  tree t = *tp;
-  /* Types can contain themselves recursively, hence this.  */
-  auto visited = static_cast<hash_set<tree> *>(data);
-
-  if (!TYPE_P (t))
-    return NULL_TREE;
+static GTY((cache)) type_tree_cache_map *consteval_only_class_cache;
 
-  if (REFLECTION_TYPE_P (t))
-    return t;
-
-  if (typedef_variant_p (t))
-    /* Tell cp_walk_subtrees to look through typedefs.  */
-    *walk_subtrees = 2;
-
-  if (RECORD_OR_UNION_TYPE_P (t))
-    {
-      /* Don't walk template arguments; A<info>::type isn't a consteval-only
-        type.  */
-      *walk_subtrees = 0;
-      /* So we have to walk the fields manually.  */
-      for (tree member = TYPE_FIELDS (t);
-          member; member = DECL_CHAIN (member))
-       if (TREE_CODE (member) == FIELD_DECL)
-         if (tree r = cp_walk_tree (&TREE_TYPE (member),
-                                    consteval_only_type_r, visited, visited))
-           return r;
-    }
+struct consteval_only_p_walker
+{
+  /* The stack of class types we're recursively inside.  */
+  auto_vec<tree> class_stack;
+  /* The set of class types we've seen.  */
+  hash_set<tree> class_seen;
+  /* True if we've optimistically assumed an already-seen
+     consteval-unknown class type is not consteval.  */
+  bool optimistic_p = false;
 
-  return NULL_TREE;
-}
+  tristate walk (tree);
+};
 
-/* True if T is a consteval-only type as per [basic.types.general]:
-   "A type is consteval-only if it is either std::meta::info or a type
-   compounded from a consteval-only type", or something that has
-   a consteval-only type.  */
+/* True if T is a consteval-only type as per [basic.types.general], or
+   is a declaration with such a type, or a TREE_VEC thereof.  */
 
 bool
 consteval_only_p (tree t)
@@ -8612,7 +8591,7 @@ consteval_only_p (tree t)
   if (!TYPE_P (t))
     t = TREE_TYPE (t);
 
-  if (!t)
+  if (!t || t == error_mark_node)
     return false;
 
   if (TREE_CODE (t) == TREE_VEC)
@@ -8634,9 +8613,83 @@ consteval_only_p (tree t)
      which could be consteval-only, depending on T.  */
   t = complete_type (t);
 
-  /* Classes with std::meta::info members are also consteval-only.  */
-  hash_set<tree> visited;
-  return !!cp_walk_tree (&t, consteval_only_type_r, &visited, &visited);
+  consteval_only_p_walker walker;
+  return walker.walk (t).is_true ();
+}
+
+/* Recursive workhorse of consteval_only_p.  Returns true if T is definitely
+   consteval-only, false if it's definitely not, and unknown if we saw an
+   incomplete type and therefore don't know.  */
+
+tristate
+consteval_only_p_walker::walk (tree t)
+{
+  t = TYPE_MAIN_VARIANT (t);
+
+  if (REFLECTION_TYPE_P (t))
+    return true;
+  else if (INDIRECT_TYPE_P (t))
+    return walk (TREE_TYPE (t));
+  else if (TREE_CODE (t) == ARRAY_TYPE)
+    return walk (TREE_TYPE (t));
+  else if (FUNC_OR_METHOD_TYPE_P (t))
+    {
+      tristate r = walk (TREE_TYPE (t));
+      for (tree parm = TYPE_ARG_TYPES (t);
+          parm != NULL_TREE && parm != void_list_node;
+          parm = TREE_CHAIN (parm))
+       {
+         if (r.is_true ())
+           break;
+         r = r || walk (TREE_VALUE (parm));
+       }
+      return r;
+    }
+  else if (RECORD_OR_UNION_TYPE_P (t))
+    {
+      if (tree *slot = hash_map_safe_get (consteval_only_class_cache, t))
+       return *slot == boolean_true_node;
+
+      if (!COMPLETE_TYPE_P (t) && LAMBDA_TYPE_P (t))
+       /* Defer until we've definitely gone through prune_lambda_captures.  */
+       return tristate::unknown ();
+
+      if (class_seen.add (t))
+       {
+         /* Optimistically assume this already seen consteval-unknown class is
+            not consteval-only, for sake of mutually recursive classes.  */
+         optimistic_p = true;
+         return false;
+       }
+      class_stack.safe_push (t);
+
+      tristate r = COMPLETE_TYPE_P (t) ? false : tristate::unknown ();
+      for (tree member = TYPE_FIELDS (t); member; member = DECL_CHAIN (member))
+       if (TREE_CODE (member) == FIELD_DECL)
+         {
+           r = r || walk (TREE_TYPE (member));
+           if (r.is_true ())
+             break;
+         }
+
+      if (r.is_true ())
+       hash_map_safe_put<hm_ggc> (consteval_only_class_cache,
+                                  t, boolean_true_node);
+      else if (r.is_false ()
+              /* The optimistic assumption above is at odds with caching
+                 'false' results for a nested class type.  */
+              && (class_stack.length () == 1 || !optimistic_p))
+       hash_map_safe_put<hm_ggc> (consteval_only_class_cache,
+                                  t, boolean_false_node);
+
+      class_stack.pop ();
+      return r;
+    }
+  else if (TYPE_PTRMEM_P (t))
+    return (walk (TYPE_PTRMEM_CLASS_TYPE (t))
+           || walk (TYPE_PTRMEM_POINTED_TO_TYPE (t)));
+  else
+    return false;
 }
 
 /* A walker for check_out_of_consteval_use_r.  It cannot be a lambda, because
-- 
2.54.0.rc1.54.g60f07c4f5c

Reply via email to