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