Implement std::meta::type_of (and has-type exposition only predicate).

Some cases aren't done yet because they depend on the implementation
of other metafunctions and decision how those reflections will be
represented.

I had to change process_metafunction/get_info, because some tests
in the new testcase were failing otherwise.  cxx_constant_value
is a wrong way to evaluate arguments of metafunction, they need to
be evaluated in the same constant expression context as the rest,
they can also throw, break, continue, return (the latter three only
when using GNU statement expressions), and e.g. could perform heap
allocation if something during the same evaluation later on deletes
those etc.
In particular, e.g. type_of (reflect_constant (42)) didn't work,
because it attempted to constant evaluate reflect_constant separately.
This commit is contained in:
Jakub Jelinek
2025-09-26 00:08:01 +02:00
committed by Marek Polacek
parent 13a53fb942
commit 0510df7079
5 changed files with 307 additions and 27 deletions

View File

@@ -1589,17 +1589,6 @@ save_fundef_copy (tree fun, tree copy)
*slot = copy;
}
/* Whether our evaluation wants a prvalue (e.g. CONSTRUCTOR or _CST),
a glvalue (e.g. VAR_DECL or _REF), or nothing. */
enum value_cat {
vc_prvalue = 0,
vc_glvalue = 1,
vc_discard = 2
};
static tree cxx_eval_constant_expression (const constexpr_ctx *, tree,
value_cat, bool *, bool *, tree *);
static tree cxx_eval_bare_aggregate (const constexpr_ctx *, tree,
value_cat, bool *, bool *, tree *);
static tree cxx_fold_indirect_ref (const constexpr_ctx *, location_t, tree, tree,
@@ -3805,8 +3794,9 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
*non_constant_p = true;
return t;
}
tree e = process_metafunction (ctx, t, jump_target);
if (throws (jump_target))
tree e = process_metafunction (ctx, t, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
e = cxx_eval_constant_expression (ctx, e, vc_prvalue,
non_constant_p, overflow_p,
@@ -8886,7 +8876,7 @@ merge_jump_target (location_t loc, const constexpr_ctx *ctx, tree r,
/* FIXME unify with c_fully_fold */
/* FIXME overflow_p is too global */
static tree
tree
cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
value_cat lval,
bool *non_constant_p, bool *overflow_p,

View File

@@ -9163,6 +9163,18 @@ extern bool replace_decl (tree *, tree, tree);
extern tree cxa_allocate_and_throw_exception (location_t, const constexpr_ctx *,
tree);
/* Whether our evaluation wants a prvalue (e.g. CONSTRUCTOR or _CST),
a glvalue (e.g. VAR_DECL or _REF), or nothing. */
enum value_cat {
vc_prvalue = 0,
vc_glvalue = 1,
vc_discard = 2
};
extern tree cxx_eval_constant_expression (const constexpr_ctx *, tree,
value_cat, bool *, bool *,
tree *);
/* An RAII sentinel used to restrict constexpr evaluation so that it
doesn't do anything that causes extra DECL_UID generation. */
@@ -9212,7 +9224,8 @@ extern void coro_set_ramp_function (tree, tree);
/* In reflect.cc */
extern void init_reflection ();
extern bool metafunction_p (tree) ATTRIBUTE_PURE;
extern tree process_metafunction (const constexpr_ctx *, tree, tree *);
extern tree process_metafunction (const constexpr_ctx *, tree,
bool *, bool *, tree *);
extern tree get_reflection (location_t, tree) ATTRIBUTE_PURE;
extern tree get_null_reflection () ATTRIBUTE_PURE;
extern tree splice (tree);

View File

@@ -237,12 +237,17 @@ metafunction_p (tree fndecl)
/* Extract the N-th reflection argument from a metafunction call CALL. */
static tree
get_info (tree call, int n)
get_info (const constexpr_ctx *ctx, tree call, int n, bool *non_constant_p,
bool *overflow_p, tree *jump_target)
{
gcc_checking_assert (call_expr_nargs (call) > n);
tree info = get_nth_callarg (call, n);
gcc_checking_assert (REFLECTION_TYPE_P (TREE_TYPE (info)));
info = cxx_constant_value (info);
info = cxx_eval_constant_expression (ctx, info, vc_prvalue,
non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
return info;
}
@@ -732,6 +737,83 @@ eval_is_conversion_function_template (const_tree)
gcc_assert (!"TODO");
}
/* has-type (exposition only).
Returns: true if r represents a value, annotation, object, variable,
function whose type does not contain an undeduced placeholder type and
that is not a constructor or destructor, enumerator, non-static data
member, unnamed bit-field, direct base class relationship, data member
description, or function parameter. Otherwise, false. */
static bool
has_type (tree r)
{
r = MAYBE_BASELINK_FUNCTIONS (r);
if (TREE_CODE (r) == FUNCTION_DECL)
{
if (DECL_CONSTRUCTOR_P (r) || DECL_DESTRUCTOR_P (r))
return false;
if (undeduced_auto_decl (r))
return false;
return true;
}
if (CONSTANT_CLASS_P (r)
|| eval_is_variable (r) == boolean_true_node
|| eval_is_enumerator (r) == boolean_true_node
|| TREE_CODE (r) == FIELD_DECL
|| eval_is_annotation (r) == boolean_true_node)
return true;
// TODO: object, direct base class relationship, data member description.
return false;
}
/* Process std::meta::type_of. Returns:
-- If r represents the ith parameter of a function F, then the ith type
in the parameter-type-list of F.
-- Otherwise, if r represents a value, object, variable, function,
non-static data member, or unnamed bit-field, then the type of what is
represented by r.
-- Otherwise, if r represents an annotation, then type_of(constant_of(r)).
-- Otherwise, if r represents an enumerator N of an enumeration E, then:
-- If E is defined by a declaration D that precedes a point P in the
evaluation context and P does not occur within an enum-specifier of
D, then a reflection of E.
-- Otherwise, a reflection of the type of N prior to the closing brace
of the enum-specifier as specified in [dcl.enum].
-- Otherwise, if r represents a direct base class relationship (D,B), then
a reflection of B.
-- Otherwise, for a data member description (T,N,A,W,NUA), a reflection of
the type T. */
static tree
eval_type_of (location_t loc, const constexpr_ctx *ctx, tree r,
tree *jump_target)
{
if (!has_type (r))
return throw_exception (loc, ctx, N_("reflection does not have a type"),
r, jump_target);
r = MAYBE_BASELINK_FUNCTIONS (r);
if (TREE_CODE (r) == PARM_DECL)
{
tree fn = DECL_CONTEXT (r);
tree args = FUNCTION_FIRST_USER_PARM (fn);
tree type = FUNCTION_FIRST_USER_PARMTYPE (fn);
while (r != args)
{
args = DECL_CHAIN (args);
type = TREE_CHAIN (type);
}
r = TREE_VALUE (type);
}
else if (TREE_CODE (r) == FUNCTION_DECL)
r = TREE_TYPE (TREE_TYPE (r));
else if (eval_is_annotation (r) == boolean_true_node)
// TODO: or do we need to reflect_constant and get type of that?
r = TREE_TYPE (TREE_VALUE (TREE_VALUE (r)));
else
r = TREE_TYPE (r);
return get_reflection_raw (loc, r);
}
/* Process std::meta::dealias.
Returns: A reflection representing the underlying entity of what r
represents.
@@ -1437,7 +1519,9 @@ eval_add_cv (location_t loc, const constexpr_ctx *ctx, tree type,
// TODO Use gperf?
tree
process_metafunction (const constexpr_ctx *ctx, tree call, tree *jump_target)
process_metafunction (const constexpr_ctx *ctx, tree call,
bool *non_constant_p, bool *overflow_p,
tree *jump_target)
{
tree name = DECL_NAME (cp_get_callee_fndecl_nofold (call));
const char *ident = IDENTIFIER_POINTER (name);
@@ -1446,10 +1530,17 @@ process_metafunction (const constexpr_ctx *ctx, tree call, tree *jump_target)
{
tree expr = get_nth_callarg (call, 0);
location_t loc = cp_expr_loc_or_input_loc (expr);
expr = cxx_eval_constant_expression (ctx, expr, vc_prvalue,
non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
return eval_reflect_constant (loc, ctx, expr, jump_target);
}
tree info = get_info (call, 0);
tree info = get_info (ctx, call, 0, non_constant_p, overflow_p, jump_target);
if (*jump_target)
return NULL_TREE;
tree h = REFLECT_EXPR_HANDLE (info);
const location_t loc = cp_expr_loc_or_input_loc (info);
@@ -1542,38 +1633,66 @@ process_metafunction (const constexpr_ctx *ctx, tree call, tree *jump_target)
return eval_is_arithmetic_type (loc, ctx, h, jump_target);
if (!strcmp (ident, "same_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_is_same_type (loc, ctx, h, h1, jump_target);
}
if (!strcmp (ident, "base_of_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_is_base_of_type (loc, ctx, h, h1, jump_target);
}
if (!strcmp (ident, "virtual_base_of_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_is_virtual_base_of_type (loc, ctx, h, h1, jump_target);
}
if (!strcmp (ident, "convertible_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_is_convertible_type (loc, ctx, h, h1, jump_target);
}
if (!strcmp (ident, "nothrow_convertible_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_is_nothrow_convertible_type (loc, ctx, h, h1,
jump_target);
}
if (!strcmp (ident, "layout_compatible_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_is_layout_compatible_type (loc, ctx, h, h1, jump_target);
}
if (!strcmp (ident, "pointer_interconvertible_base_of_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_is_pointer_interconvertible_base_of_type (loc, ctx, h, h1,
jump_target);
}
@@ -1615,9 +1734,15 @@ process_metafunction (const constexpr_ctx *ctx, tree call, tree *jump_target)
return eval_annotations_of (loc, ctx, h, NULL_TREE, jump_target);
if (id_equal (name, "annotations_of_with_type"))
{
tree h1 = REFLECT_EXPR_HANDLE (get_info (call, 1));
tree i1 = get_info (ctx, call, 1, non_constant_p, overflow_p,
jump_target);
if (*jump_target)
return NULL_TREE;
tree h1 = REFLECT_EXPR_HANDLE (i1);
return eval_annotations_of (loc, ctx, h, h1, jump_target);
}
if (id_equal (name, "type_of"))
return eval_type_of (loc, ctx, h, jump_target);
not_found:
sorry ("%qE", name);

View File

@@ -0,0 +1,151 @@
// { dg-do compile { target c++26 } }
// { dg-additional-options "-freflection" }
// Test std::meta::type_of.
#include <meta>
using namespace std::meta;
int arr[] = {1, 2, 3};
auto [a1, a2, a3] = arr;
void fn();
auto &fn2();
enum Enum { A };
using Alias = int;
struct B {};
struct S : B {
int mem;
int : 0;
};
struct T {
T ();
T (const T &);
~T ();
};
struct U {
int u;
};
template<auto> struct TCls {};
template<auto> void TFn();
template<auto> int TVar;
template<auto> concept Concept = requires { true; };
namespace NS {};
namespace NSAlias = NS;
// constexpr auto ctx = std::meta::access_context::current ();
consteval bool
has_type (info r)
{
try { type_of (r); }
catch (std::meta::exception &) { return false; }
return true;
}
static_assert (has_type (std::meta::reflect_constant (42)));
//static_assert (has_type (std::meta::reflect_object (arr[1])));
static_assert (has_type (^^arr));
static_assert (!has_type (^^a3));
static_assert (has_type (^^fn));
static_assert (!has_type (^^fn2));
static_assert (has_type (^^Enum::A));
static_assert (!has_type (^^Alias));
static_assert (!has_type (^^S));
static_assert (has_type (^^S::mem));
//static_assert (has_type (std::meta::members_of (^^S, ctx)[1]));
static_assert (!has_type (^^TCls));
static_assert (!has_type (^^TFn));
static_assert (!has_type (^^TVar));
static_assert (!has_type (^^Concept));
static_assert (!has_type (^^NSAlias));
static_assert (!has_type (^^NS));
//static_assert (has_type (std::meta::bases_of (^^S, ctx)[0]));
//static_assert (has_type (std::meta::data_member_spec (^^int, {.name="member"})));
int
foo (int a, const long b, T c, int d[4], T &e)
{
static_assert (has_type (^^a));
static_assert (has_type (^^b));
static_assert (has_type (^^c));
static_assert (has_type (^^d));
static_assert (has_type (^^e));
static_assert (type_of (^^a) == ^^int);
static_assert (type_of (^^b) == ^^long);
static_assert (type_of (^^c) == ^^T);
using ptr = int *;
static_assert (type_of (^^d) == dealias (^^ptr));
using ref = T &;
static_assert (type_of (^^e) == dealias (^^ref));
return 0;
}
static_assert (type_of (std::meta::reflect_constant (42)) == ^^int);
static_assert (type_of (std::meta::reflect_constant (42.0)) == ^^double);
//static_assert (type_of (std::meta::reflect_constant (U { 42 })) == ^^U);
//static_assert (type_of (std::meta::reflect_object (arr[1])) == ^int);
using int3 = int[3];
static_assert (type_of (^^arr) == dealias (^^int3));
static_assert (type_of (^^fn) == ^^void);
static_assert (type_of (^^Enum::A) == ^^Enum);
static_assert (type_of (^^A) == ^^Enum);
static_assert (type_of (^^S::mem) == ^^int);
//static_assert (type_of (std::meta::members_of (^^S, ctx)[1]) == ??);
//static_assert (type_of (std::meta::bases_of (^^S, ctx)[0]) == ??);
//static_assert (type_of (std::meta::data_member_spec (^^int, {.name="member"})) == ??);
consteval int
test (info x, info y)
{
if (x == y)
return 0;
throw 1;
}
using ull = unsigned long long;
enum Enum2 {
E21,
E22,
E23 = 3L,
E24,
E25 = 5ULL,
E26,
E27 = 7 + test (type_of (^^E21), ^^int)
+ test (type_of (^^E22), ^^int)
+ test (type_of (^^E23), ^^long)
+ test (type_of (^^E24), ^^long)
+ test (type_of (^^E25), dealias (^^ull))
+ test (type_of (^^E26), dealias (^^ull))
};
static_assert (type_of (^^E21) == ^^Enum2);
static_assert (type_of (^^E22) == ^^Enum2);
static_assert (type_of (^^E23) == ^^Enum2);
static_assert (type_of (^^E24) == ^^Enum2);
static_assert (type_of (^^E25) == ^^Enum2);
static_assert (type_of (^^E26) == ^^Enum2);
static_assert (type_of (^^E27) == ^^Enum2);
enum Enum3 : long {
E31,
E32,
E33 = 3L,
E34,
E35 = 5ULL,
E36,
E37 = 7 + test (type_of (^^E31), ^^long)
+ test (type_of (^^E32), ^^long)
+ test (type_of (^^E33), ^^long)
+ test (type_of (^^E34), ^^long)
+ test (type_of (^^E35), ^^long)
+ test (type_of (^^E36), ^^long)
};
static_assert (type_of (^^E31) == ^^Enum3);
static_assert (type_of (^^E32) == ^^Enum3);
static_assert (type_of (^^E33) == ^^Enum3);
static_assert (type_of (^^E34) == ^^Enum3);
static_assert (type_of (^^E35) == ^^Enum3);
static_assert (type_of (^^E36) == ^^Enum3);
static_assert (type_of (^^E37) == ^^Enum3);

View File

@@ -89,6 +89,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
consteval bool has_identifier(info);
// [meta.reflection.queries], reflection queries
consteval info type_of(info);
consteval bool is_enumerator(info);
consteval bool is_annotation(info);