Implement std::meta::define_aggregate.

Also, fix up type_of for bit-fields.  And add some tests to data_member_spec1
for invalid identifiers.
This commit is contained in:
Jakub Jelinek
2025-10-20 18:54:27 +02:00
committed by Marek Polacek
parent 6dd28aaa7c
commit 6032d7391c
8 changed files with 387 additions and 40 deletions

View File

@@ -1322,6 +1322,22 @@ struct constexpr_ctx {
mce_value manifestly_const_eval;
};
/* Return ctx->quiet. For use in reflect.cc. */
bool
cxx_constexpr_quiet_p (const constexpr_ctx *ctx)
{
return ctx->quiet;
}
/* Return ctx->manifestly_const_eval. For use in reflect.cc. */
mce_value
cxx_constexpr_manifestly_const_eval (const constexpr_ctx *ctx)
{
return ctx->manifestly_const_eval;
}
/* Predicates for the meaning of *jump_target. */
static bool

View File

@@ -9225,6 +9225,8 @@ enum value_cat {
extern tree cxx_eval_constant_expression (const constexpr_ctx *, tree,
value_cat, bool *, bool *,
tree *);
extern bool cxx_constexpr_quiet_p (const constexpr_ctx *);
extern mce_value cxx_constexpr_manifestly_const_eval (const constexpr_ctx *);
/* An RAII sentinel used to restrict constexpr evaluation so that it
doesn't do anything that causes extra DECL_UID generation. */

View File

@@ -509,8 +509,9 @@ fail_ret:
}
if (TREE_CODE (TREE_TYPE (deref)) != META_TYPE)
{
error_at (loc, "unexpected type %qT of iterator dereference",
TREE_TYPE (deref));
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "unexpected type %qT of iterator dereference",
TREE_TYPE (deref));
*non_constant_p = true;
return call;
}
@@ -1986,6 +1987,8 @@ type_of (tree r, reflect_kind kind)
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 if (TREE_CODE (r) == FIELD_DECL && DECL_BIT_FIELD_TYPE (r))
r = DECL_BIT_FIELD_TYPE (r);
else
r = TREE_TYPE (r);
return r;
@@ -4528,6 +4531,41 @@ eval_substitute (location_t loc, const constexpr_ctx *ctx,
return get_reflection_raw (loc, ret);
}
/* Process std::meta::tuple_size.
Returns: tuple_size_v<T>, where T is the type represented by
dealias(type). */
static tree
eval_tuple_size (location_t loc, const constexpr_ctx *ctx, tree type,
tree *jump_target)
{
if (eval_is_type (type) != boolean_true_node)
return throw_exception_nontype (loc, ctx, type, jump_target);
type = strip_typedefs (type);
/* It's UB to specialize tuple_size_v, so we can use this. */
return get_tuple_size (type);
}
/* Process std::meta::tuple_element.
Returns: A reflection representing the type denoted by
tuple_element_t<I, T>, where T is the type represented by dealias(type)
and I is a constant equal to index. */
static tree
eval_tuple_element (location_t loc, const constexpr_ctx *ctx, tree i,
tree type, tree *jump_target)
{
const unsigned HOST_WIDE_INT index = tree_to_uhwi (i);
if (eval_is_type (type) != boolean_true_node)
return throw_exception_nontype (loc, ctx, type, jump_target);
type = strip_typedefs (type);
type = get_tuple_element_type (type, index);
if (type == error_mark_node)
return error_mark_node;
type = strip_typedefs (type);
return get_reflection_raw (loc, type);
}
/* Process std::meta::data_member_spec.
Returns: A reflection of a data member description (T,N,A,W,NUA) where
-- T is the type represented by dealias(type),
@@ -4911,39 +4949,171 @@ eval_data_member_spec (location_t loc, const constexpr_ctx *ctx,
return get_reflection_raw (loc, ret, REFLECT_DATA_MEMBER_SPEC);
}
/* Process std::meta::tuple_size.
Returns: tuple_size_v<T>, where T is the type represented by
dealias(type). */
/* Process std::meta::define_aggregate.
Let C be the class represented by class_type and r_K be the Kth reflection
value in mdescrs.
For every r_K in mdescrs, let (T_K,N_K,A_K,W_K,NUA_K) be the corresponding
data member description represented by r_K.
Constant When:
-- C is incomplete from every point in the evaluation context;
-- is_data_member_spec(r_K) is true for every r_K;
-- is_complete_type(T_K) is true for every r_K; and
-- for every pair (r_K,r_L) where K<L, if N_K is not _|_ and N_L is not
_|_, then either:
-- N_K != N_L is true or
-- N_K == u8"_" is true.
Effects: Produces an injected declaration D that defines C and has
properties as follows:
-- The target scope of D is the scope to which C belongs.
-- The locus of D follows immediately after the core constant expression
currently under evaluation.
-- The characteristic sequence of D is the sequence of reflection values
r_K.
-- If C is a specialization of a templated class T, and C is not a local
class, then D is an explicit specialization of T.
-- For each r_K, there is a corresponding entity M_K belonging to the class
scope of D with the following properties:
-- If N_K is _|_, M_K is an unnamed bit-field.
Otherwise, M_K is a non-static data member whose name is the
determined by the character sequence encoded by N_K in UTF-8.
-- The type of M_K is T_K.
-- M_K is declared with the attribute [[no_unique_address]] if and only
if NUA_K is true.
-- If W_K is not _|_, M_K is a bit-field whose width is that value.
Otherwise, M_K is not a bit-field.
-- If A_K is not _|_, M_K has the alignment-specifier alignas(A_K).
Otherwise, M_K has no alignment-specifier.
-- For every r_L in mdescrs such that K<L, the declaration corresponding to
r_K precedes the declaration corresponding to r_L.
Returns: class_type. */
static tree
eval_tuple_size (location_t loc, const constexpr_ctx *ctx, tree type,
tree *jump_target)
eval_define_aggregate (location_t loc, const constexpr_ctx *ctx,
tree type, tree rvec, tree call, bool *non_constant_p)
{
if (eval_is_type (type) != boolean_true_node)
return throw_exception_nontype (loc, ctx, type, jump_target);
tree orig_type = type;
if (!CLASS_TYPE_P (type))
{
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "first %<define_aggregate%> argument is not a class "
"type reflection");
*non_constant_p = true;
return call;
}
if (COMPLETE_TYPE_P (type))
{
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "first %<define_aggregate%> argument is a complete "
"class type reflection");
*non_constant_p = true;
return call;
}
hash_set<tree> nameset;
for (int i = 0; i < TREE_VEC_LENGTH (rvec); ++i)
{
tree ra = TREE_VEC_ELT (rvec, i);
tree a = REFLECT_EXPR_HANDLE (ra);
if (REFLECT_EXPR_KIND (ra) != REFLECT_DATA_MEMBER_SPEC)
{
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "%<define_aggregate%> argument not a data member "
"description");
*non_constant_p = true;
return call;
}
if (eval_is_complete_type (TREE_VEC_ELT (a, 0)) != boolean_true_node)
{
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "%<define_aggregate%> argument data member "
"description without complete type");
*non_constant_p = true;
return call;
}
if (TREE_VEC_ELT (a, 1)
&& !id_equal (TREE_VEC_ELT (a, 1), "_")
&& nameset.add (TREE_VEC_ELT (a, 1)))
{
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "name %qD used in multiple data member "
"descriptions", TREE_VEC_ELT (a, 1));
*non_constant_p = true;
return call;
}
if (TYPE_WARN_IF_NOT_ALIGN (type)
&& TREE_VEC_ELT (a, 3))
{
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "cannot declare bit-field in "
"%<warn_if_not_aligned%> type");
*non_constant_p = true;
return call;
}
}
if (cxx_constexpr_manifestly_const_eval (ctx) != mce_true)
{
/* If define_aggregate is evaluated multiple times,
the second invocation with the same arguments will
necessarily fail. Limit those to manifestly
constant-evaluation. */
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "%<define_aggregate%> used outside of "
"manifestly constant-evaluation");
*non_constant_p = true;
return call;
}
iloc_sentinel ils = loc;
type = strip_typedefs (type);
/* It's UB to specialize tuple_size_v, so we can use this. */
return get_tuple_size (type);
}
/* Process std::meta::tuple_element.
Returns: A reflection representing the type denoted by
tuple_element_t<I, T>, where T is the type represented by dealias(type)
and I is a constant equal to index. */
static tree
eval_tuple_element (location_t loc, const constexpr_ctx *ctx, tree i,
tree type, tree *jump_target)
{
const unsigned HOST_WIDE_INT index = tree_to_uhwi (i);
if (eval_is_type (type) != boolean_true_node)
return throw_exception_nontype (loc, ctx, type, jump_target);
type = strip_typedefs (type);
type = get_tuple_element_type (type, index);
if (type == error_mark_node)
return error_mark_node;
type = strip_typedefs (type);
return get_reflection_raw (loc, type);
type = TYPE_MAIN_VARIANT (type);
if (primary_template_specialization_p (type))
{
type = maybe_process_partial_specialization (type);
if (type == error_mark_node)
{
*non_constant_p = true;
return call;
}
}
if (!TYPE_BINFO (type))
xref_basetypes (type, NULL_TREE);
pushclass (type);
gcc_assert (!TYPE_FIELDS (type));
tree fields = NULL_TREE;
for (int i = 0; i < TREE_VEC_LENGTH (rvec); ++i)
{
tree ra = TREE_VEC_ELT (rvec, i);
tree a = REFLECT_EXPR_HANDLE (ra);
tree f = build_decl (cp_expr_loc_or_input_loc (ra), FIELD_DECL,
TREE_VEC_ELT (a, 1), TREE_VEC_ELT (a, 0));
DECL_CHAIN (f) = fields;
DECL_IN_AGGR_P (f) = 1;
DECL_CONTEXT (f) = type;
TREE_PUBLIC (f) = 1;
if (TREE_VEC_ELT (a, 3))
{
/* Temporarily stash the width in DECL_BIT_FIELD_REPRESENTATIVE.
check_bitfield_decl picks it from there later and sets DECL_SIZE
accordingly. */
DECL_BIT_FIELD_REPRESENTATIVE (f) = TREE_VEC_ELT (a, 3);
SET_DECL_C_BIT_FIELD (f);
}
else if (TREE_VEC_ELT (a, 2))
{
SET_DECL_ALIGN (f, tree_to_uhwi (TREE_VEC_ELT (a, 2))
* BITS_PER_UNIT);
DECL_USER_ALIGN (f) = 1;
}
if (TREE_VEC_ELT (a, 4) == boolean_true_node)
{
tree attr = build_tree_list (NULL_TREE,
get_identifier ("no_unique_address"));
attr = build_tree_list (attr, NULL_TREE);
cplus_decl_attributes (&f, attr, 0);
}
fields = f;
}
TYPE_FIELDS (type) = fields;
finish_struct (type, NULL_TREE);
return get_reflection_raw (loc, orig_type);
}
/* Expand a call to a metafunction. CALL is the CALL_EXPR.
@@ -5602,6 +5772,20 @@ process_metafunction (const constexpr_ctx *ctx, tree call,
if (id_equal (name, "u8identifier_of"))
return eval_identifier_of (loc, ctx, h, kind, jump_target,
char8_type_node, TREE_TYPE (call));
if (id_equal (name, "tuple_size"))
{
tree tsize = eval_tuple_size (loc, ctx, h, jump_target);
if (*jump_target)
return NULL_TREE;
if (!tsize)
{
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "couldn%'t compute %qs of %qT", "tuple_size", h);
*non_constant_p = true;
return call;
}
return tsize;
}
if (id_equal (name, "can_substitute"))
{
tree hvec = get_info_vec (loc, ctx, call, 1, non_constant_p,
@@ -5635,18 +5819,15 @@ process_metafunction (const constexpr_ctx *ctx, tree call,
return eval_data_member_spec (loc, ctx, h, opts, call,
non_constant_p, overflow_p, jump_target);
}
if (id_equal (name, "tuple_size"))
if (id_equal (name, "define_aggregate"))
{
tree tsize = eval_tuple_size (loc, ctx, h, jump_target);
tree hvec = get_info_vec (loc, ctx, call, 1, non_constant_p,
overflow_p, jump_target);
if (*jump_target)
return NULL_TREE;
if (!tsize)
{
error_at (loc, "couldn%'t compute %qs of %qT", "tuple_size", h);
*non_constant_p = true;
return call;
}
return tsize;
if (*non_constant_p)
return call;
return eval_define_aggregate (loc, ctx, h, hvec, call, non_constant_p);
}
not_found:

View File

@@ -94,6 +94,10 @@ static_assert (!valid_data_member_spec (^^int, { .name = "static_assert" }));
static_assert (!valid_data_member_spec (^^int, { .name = "__is_convertible" }));
static_assert (!valid_data_member_spec (^^int, { .name = "__builtin_is_nothrow_relocatable" }));
static_assert (!valid_data_member_spec (^^int, { .name = "007" }));
static_assert (!valid_data_member_spec (^^int, { .name = "operator++" }));
static_assert (!valid_data_member_spec (^^int, { .name = "operator ++" }));
static_assert (!valid_data_member_spec (^^int, { .name = "foo\\u00AA" }));
static_assert (!valid_data_member_spec (^^int, { .name = "+ -" }));
static_assert (valid_data_member_spec (^^int, { .name = u8"\u00AA" }));
static_assert (!valid_data_member_spec (^^int, { .name = u8"\u00AB" }));
static_assert (!valid_data_member_spec (^^int, { .name = u8"\u00B6" }));

View File

@@ -0,0 +1,108 @@
// { dg-do compile { target c++26 } }
// { dg-additional-options "-freflection" }
// Test std::meta::define_aggregate.
#include <cstddef>
#include <meta>
using namespace std::meta;
enum E { E0, E1 };
struct S0 {};
struct S1;
struct S2;
struct S3;
struct S4;
struct S5;
union U1;
template <int N>
struct S6;
template <int N>
struct S7 { long e; short f; };
struct S8;
struct S9;
using A9 = S9;
consteval {
if (define_aggregate (^^S1, {}) != ^^S1)
throw 1;
if (define_aggregate (^^S2, { data_member_spec (^^S1, { .name = "bar" }),
data_member_spec (^^long, { .name = u8"baz",
.alignment = 2 * alignof (long),
.no_unique_address = true }),
data_member_spec (^^unsigned int, { .bit_width = 7 }),
data_member_spec (^^E, { .name = "extremely_long_identifier1",
.bit_width = 6 }),
data_member_spec (^^int, { .bit_width = 0 }),
data_member_spec (^^const E *, { .name = u8"extremely_long_identifier2",
.alignment = 2 * alignof (E *) }) }) != ^^S2)
throw 2;
if (define_aggregate (^^S3, { data_member_spec (^^S0, { .name = "a",
.no_unique_address = true }),
data_member_spec (^^S1, { .name = "b",
.no_unique_address = true }) }) != ^^S3)
throw 3;
if (define_aggregate (^^S4, { data_member_spec (^^S0, { .name = "c" }),
data_member_spec (^^S1, { .name = "d" }) }) != ^^S4)
throw 4;
if (define_aggregate (^^S5, { data_member_spec (^^const E &, { .name = u8"qu\N{LATIN SMALL LETTER AE}" }),
data_member_spec (^^const E &, { .name = u8"foo" }) }) != ^^S5)
throw 5;
if (define_aggregate (^^U1, { data_member_spec (^^int, { .name = u8"_" }),
data_member_spec (^^long long, { .name = "abc" }) }) != ^^U1)
throw 6;
if (define_aggregate (^^S6 <42>, { data_member_spec (^^int, { .name = "a" }),
data_member_spec (^^long, { .name = "b" }) }) != ^^S6 <42>)
throw 7;
if (define_aggregate (substitute (^^S6, { reflect_constant (43) }),
{ data_member_spec (^^long, { .name = "c" }),
data_member_spec (^^unsigned int, { .name = "d", .bit_width = 3 }) }) != ^^S6 <43>)
throw 8;
if (define_aggregate (^^S7 <42>, { data_member_spec (^^short, { .name = "g" }),
data_member_spec (^^float, { .name = "h" }) }) != ^^S7 <42>)
throw 9;
if (define_aggregate (^^const S8, { data_member_spec (^^U1, { .name = "u" }) }) != ^^const S8)
throw 10;
if (define_aggregate (^^A9, { data_member_spec (^^U1, { .name = "u" }) }) != ^^A9)
throw 10;
}
constexpr E e0 = E0, e1 = E1;
S2 s2 = { .bar = {}, .baz = 42LL, .extremely_long_identifier1 = E1, .extremely_long_identifier2 = &e0 };
S3 s3 = { .a = {}, .b = {} };
S4 s4 = { .c = {}, .d = {} };
constexpr S5 s5 = { e0, e1 };
U1 u1 = { ._ = 5 }, u1a = { .abc = 42LL };
S6 <42> s642 = { .a = 1, .b = 2 };
S6 <43> s643 = { .c = 6, .d = 7 };
S7 <42> s742 = { .g = 5, .h = 6.0f };
S7 <43> s743 = { .e = 8, .f = 9 };
S8 s8 = { .u = { .abc = 2LL } };
S9 s9 = { .u = { ._ = 3 } };
consteval {
S6 <43> x;
x.c = -1;
x.d = 7;
++x.d;
if (x.d != 0)
throw 11;
--x.d;
if (x.d != 7)
throw 12;
}
static_assert (type_of (^^S2::bar) == ^^S1);
static_assert (type_of (^^S2::baz) == ^^long);
static_assert (type_of (^^S2::extremely_long_identifier1) == ^^E);
static_assert (type_of (^^S2::extremely_long_identifier2) == ^^const E *);
static_assert (offsetof (S2, bar) == 0);
static_assert (offsetof (S2, baz) >= 2 * alignof (long) && offsetof (S2, baz) % (2 * alignof (long)) == 0);
static_assert (offsetof (S2, extremely_long_identifier2) > offsetof (S2, baz));
static_assert (offsetof (S2, extremely_long_identifier2) % (2 * alignof (E)) == 0);
static_assert (type_of (^^S3::a) == ^^S0);
static_assert (type_of (^^S3::b) == ^^S1);
static_assert (sizeof (S3) == sizeof (S0) && sizeof (S4) == 2 * sizeof (S0));
static_assert (type_of (^^S4::c) == ^^S0);
static_assert (type_of (^^S4::d) == ^^S1);
static_assert (s5.qu\u00E6 == E0 && s5.foo == E1);
static_assert (type_of (^^S5::qu\u00E6) == ^^const E &);
static_assert (type_of (^^S5::foo) == ^^const E &);

View File

@@ -0,0 +1,32 @@
// { dg-do compile { target c++26 } }
// { dg-additional-options "-freflection" }
// Test std::meta::define_aggregate.
#include <cstddef>
#include <meta>
using namespace std::meta;
enum E { E0, E1 };
struct S0 {};
struct S1;
struct S2;
struct S3;
struct S4;
struct S5;
template <int N>
struct S7 { long e; short f; };
S7 <15> s715;
consteval { define_aggregate (^^::, {}); } // { dg-error "first 'define_aggregate' argument is not a class type reflection" }
consteval { define_aggregate (^^int, {}); } // { dg-error "first 'define_aggregate' argument is not a class type reflection" }
consteval { define_aggregate (^^E, {}); } // { dg-error "first 'define_aggregate' argument is not a class type reflection" }
consteval { define_aggregate (^^S0, {}); } // { dg-error "first 'define_aggregate' argument is a complete class type reflection" }
consteval { define_aggregate (^^S7 <15>, {}); } // { dg-error "first 'define_aggregate' argument is a complete class type reflection" }
consteval { define_aggregate (^^S1, { ^^int }); } // { dg-error "'define_aggregate' argument not a data member description" }
consteval { define_aggregate (^^S2, { data_member_spec (^^S5, { .name = "a" }) }); } // { dg-error "'define_aggregate' argument data member description without complete type" }
consteval { define_aggregate (^^S3, { data_member_spec (^^int, { .name = "a" }), // { dg-error "name 'a' used in multiple data member descriptions" }
data_member_spec (^^long, { .name = "a" }) }); }
consteval { define_aggregate (^^S4, { data_member_spec (^^int, { .name = u8"_" }),
data_member_spec (^^long, { .name = u8"_" }) }); }
constexpr S4 s4 = { 1, 2 };
consteval { auto a = s4._; } // { dg-error "request for member '_' is ambiguous" }

View File

@@ -24,6 +24,7 @@ struct T {
};
struct U {
int u;
int v : 5;
};
template<auto> struct TCls {};
template<auto> void TFn();
@@ -113,6 +114,7 @@ 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 (^^U::v) == ^^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" })) == ^^int);

View File

@@ -328,6 +328,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
};
consteval info data_member_spec(info, data_member_options);
consteval bool is_data_member_spec(info);
template<reflection_range _R = initializer_list<info>>
consteval info define_aggregate(info, _R&&);
// associated with [meta.unary.cat], primary type categories
consteval bool is_void_type(info);