On 5/5/26 5:48 PM, Patrick Palka wrote:
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.

OK.

-- >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

Reply via email to