Implement std::meta::access_context.

This commit is contained in:
Jakub Jelinek
2025-10-23 16:01:22 +02:00
committed by Marek Polacek
parent 3abdff74a0
commit 09f2c4896f
8 changed files with 392 additions and 19 deletions

View File

@@ -1348,6 +1348,18 @@ cxx_constexpr_manifestly_const_eval (const constexpr_ctx *ctx)
return ctx->manifestly_const_eval;
}
/* Return ctx->call->fundef->decl or NULL_TREE. For use in
reflect.cc. */
tree
cxx_constexpr_caller (const constexpr_ctx *ctx)
{
if (ctx->call)
return ctx->call->fundef->decl;
else
return NULL_TREE;
}
/* Predicates for the meaning of *jump_target. */
static bool
@@ -10510,7 +10522,7 @@ find_heap_var_refs (tree *tp, int *walk_subtrees, void */*data*/)
/* Find immediate function decls in *TP if any. */
static tree
find_immediate_fndecl (tree *tp, int */*walk_subtrees*/, void */*data*/)
find_immediate_fndecl (tree *tp, int *walk_subtrees, void */*data*/)
{
if (TREE_CODE (*tp) == FUNCTION_DECL && DECL_IMMEDIATE_FUNCTION_P (*tp))
return *tp;
@@ -10518,6 +10530,8 @@ find_immediate_fndecl (tree *tp, int */*walk_subtrees*/, void */*data*/)
&& TREE_CODE (PTRMEM_CST_MEMBER (*tp)) == FUNCTION_DECL
&& DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (*tp)))
return PTRMEM_CST_MEMBER (*tp);
if (TREE_CODE (*tp) == REFLECT_EXPR)
*walk_subtrees = 0;
return NULL_TREE;
}

View File

@@ -7950,6 +7950,7 @@ extern void remove_dummy_lambda_op (tree, tree);
extern tree canonical_type_parameter (tree);
extern void push_access_scope (tree);
extern void pop_access_scope (tree);
extern tree current_function_decl_without_access_scope ();
extern bool check_template_shadow (tree);
extern tree get_innermost_template_args (tree, int);
extern void maybe_begin_member_template_processing (tree);
@@ -9229,6 +9230,7 @@ extern tree cxx_eval_constant_expression (const constexpr_ctx *, tree,
tree *);
extern bool cxx_constexpr_quiet_p (const constexpr_ctx *);
extern mce_value cxx_constexpr_manifestly_const_eval (const constexpr_ctx *);
extern tree cxx_constexpr_caller (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

@@ -277,6 +277,18 @@ pop_access_scope (tree t)
pop_from_top_level ();
}
/* Return current function, ignoring temporary overrides
of current_function_decl by push_access_scope. */
tree
current_function_decl_without_access_scope ()
{
if (vec_safe_length (saved_access_scope))
return (*saved_access_scope)[0];
else
return current_function_decl;
}
/* Do any processing required when DECL (a member template
declaration) is finished. Returns the TEMPLATE_DECL corresponding
to DECL, unless it is a specialization, in which case the DECL

View File

@@ -263,8 +263,18 @@ metafunction_p (tree fndecl)
return false;
/* Is the call from std::meta? */
fndecl = decl_namespace_context (fndecl);
return DECL_NAMESPACE_STD_META_P (fndecl);
tree ctx = decl_namespace_context (fndecl);
if (!DECL_NAMESPACE_STD_META_P (ctx))
return false;
/* They should be user provided and not defined. */
if (!user_provided_p (fndecl)
|| (DECL_NAMESPACE_SCOPE_P (fndecl) && DECL_DELETED_FN (fndecl)))
return false;
if (DECL_INITIAL (fndecl))
return false;
return true;
}
/* Extract the N-th reflection argument from a metafunction call CALL. */
@@ -5733,6 +5743,65 @@ eval_is_implicit_lifetime_type (location_t loc, const constexpr_ctx *ctx,
return boolean_false_node;
}
/* Process std::meta::access_context::current. */
static tree
eval_access_context_current (location_t loc, const constexpr_ctx *ctx,
tree call, bool *non_constant_p)
{
tree scope = cxx_constexpr_caller (ctx);
/* Ignore temporary current_function_decl changes caused by
push_access_scope. */
if (scope == NULL_TREE && current_function_decl)
scope = current_function_decl_without_access_scope ();
if (scope && DECL_INHERITED_CTOR (scope))
scope = DECL_CONTEXT (scope);
if (scope == NULL_TREE)
{
if (cxx_constexpr_manifestly_const_eval (ctx) != mce_true)
{
/* Outside of functions limit this to manifestly constant-evaluation
so that we don't fold it prematurely. */
if (!cxx_constexpr_quiet_p (ctx))
error_at (loc, "%<access_context::current%> used outside of "
"manifestly constant-evaluation");
*non_constant_p = true;
return call;
}
if (current_class_type)
scope = current_class_type;
else if (current_namespace)
scope = current_namespace;
else
scope = global_namespace;
}
tree lam;
while (LAMBDA_FUNCTION_P (scope)
&& (lam = CLASSTYPE_LAMBDA_EXPR (CP_DECL_CONTEXT (scope)))
&& LAMBDA_EXPR_CONSTEVAL_BLOCK_P (lam))
scope = CP_TYPE_CONTEXT (CP_DECL_CONTEXT (scope));
tree access_context = TREE_TYPE (call);
if (TREE_CODE (access_context) != RECORD_TYPE)
{
fail:
error_at (loc, "unexpected return type of %qs",
"std::meta::access_context::current");
return build_zero_cst (access_context);
}
tree scopef = next_aggregate_field (TYPE_FIELDS (access_context));
if (!scopef || !REFLECTION_TYPE_P (TREE_TYPE (scopef)))
goto fail;
tree classf = next_aggregate_field (DECL_CHAIN (scopef));
if (!classf || !REFLECTION_TYPE_P (TREE_TYPE (classf)))
goto fail;
if (next_aggregate_field (DECL_CHAIN (classf)))
goto fail;
vec<constructor_elt, va_gc> *elts = nullptr;
CONSTRUCTOR_APPEND_ELT (elts, scopef, get_reflection_raw (loc, scope));
CONSTRUCTOR_APPEND_ELT (elts, classf, get_null_reflection ());
return build_constructor (access_context, elts);
}
/* Expand a call to a metafunction FUN. CALL is the CALL_EXPR.
JUMP_TARGET is set if we are throwing std::meta::exception. */
@@ -5823,6 +5892,14 @@ process_metafunction (const constexpr_ctx *ctx, tree fun, tree call,
if (id_equal (name, "reflect_constant_array"))
return eval_reflect_constant_array (loc, ctx, call, non_constant_p,
overflow_p, jump_target);
if (id_equal (name, "current")
&& DECL_CLASS_SCOPE_P (fun)
&& TYPE_NAME (DECL_CONTEXT (fun))
&& TREE_CODE (TYPE_NAME (DECL_CONTEXT (fun))) == TYPE_DECL
&& DECL_NAME (TYPE_NAME (DECL_CONTEXT (fun)))
&& id_equal (DECL_NAME (TYPE_NAME (DECL_CONTEXT (fun))),
"access_context"))
return eval_access_context_current (loc, ctx, call, non_constant_p);
tree info = get_info (ctx, call, 0, non_constant_p, overflow_p, jump_target);
if (*jump_target)

View File

@@ -0,0 +1,175 @@
// { dg-do compile { target c++26 } }
// { dg-additional-options "-freflection" }
// Test std::meta::access_context.
#include <meta>
using namespace std::meta;
constexpr info null_reflection;
struct S {};
constexpr access_context a = access_context::unprivileged ();
static_assert (a.scope () == ^^:: && a.designating_class () == null_reflection);
constexpr access_context b = access_context::unchecked ();
static_assert (b.scope () == null_reflection && b.designating_class () == null_reflection);
constexpr access_context c = access_context::current ();
static_assert (c.scope () == ^^:: && c.designating_class () == null_reflection);
constexpr access_context d = access_context::unprivileged ().via (^^S);
static_assert (d.scope () == ^^:: && d.designating_class () == ^^S);
constexpr access_context e = access_context::unchecked ().via (^^const S);
static_assert (e.scope () == null_reflection && e.designating_class () == ^^const S);
constexpr access_context f = access_context::current ().via (^^volatile S);
static_assert (f.scope () == ^^:: && f.designating_class () == ^^volatile S);
consteval info
foo (info x = access_context::current ().scope ())
{
static_assert (access_context::current ().scope () == ^^foo);
return x;
}
static_assert (foo (^^std) == ^^std);
static_assert (foo () == ^^::);
namespace N
{
struct S {};
constexpr access_context a = access_context::current ().via (^^::S).via (^^S);
static_assert (a.scope () == ^^N && a.designating_class () == ^^S);
static_assert (access_context::unprivileged ().scope () == ^^::);
static_assert (access_context::unchecked ().scope () == null_reflection);
namespace M
{
constexpr access_context a = access_context::current ().via (^^S).via (^^::S);
static_assert (a.scope () == ^^M && a.designating_class () == ^^::S);
static_assert (foo () == ^^M);
consteval {
static_assert (access_context::current ().scope () == ^^M);
consteval {
static_assert (access_context::current ().scope () == ^^M);
}
}
}
}
struct T
{
static_assert (access_context::unprivileged ().scope () == ^^::);
static_assert (access_context::unchecked ().scope () == null_reflection);
static_assert (access_context::current ().scope () == ^^T);
static_assert (access_context::unprivileged ().designating_class () == null_reflection);
static_assert (access_context::unchecked ().designating_class () == null_reflection);
static_assert (access_context::current ().designating_class () == null_reflection);
static_assert (foo () == ^^T);
info t = access_context::current ().scope ();
};
struct U
{
consteval U () {}
info u = access_context::current ().scope ();
};
struct V : U
{
using U::U;
info v = access_context::current ().scope ();
consteval {
static_assert (access_context::current ().scope () == ^^V);
consteval {
static_assert (access_context::current ().scope () == ^^V);
}
}
};
void
bar ()
{
static constexpr access_context a = access_context::current ();
static_assert (a.scope () == ^^bar && a.designating_class () == null_reflection);
static_assert (foo (^^foo) == ^^foo);
static_assert (foo () == ^^bar);
static_assert (T {}.t == ^^bar);
consteval {
static_assert (access_context::current ().scope () == ^^bar);
consteval {
static_assert (access_context::current ().scope () == ^^bar);
}
}
auto l = [] () {
int v = 0;
static_assert (access_context::current ().scope () == parent_of (^^v));
static_assert (parent_of (parent_of (access_context::current ().scope ())) == ^^bar);
};
auto l2 = [] () -> int (&) [access_context::current ().scope () == ^^bar ? 2 : 3] {
static int a[2];
return a;
};
}
consteval {
static_assert (access_context::current ().scope () == ^^::);
consteval {
static_assert (access_context::current ().scope () == ^^::);
}
static_assert (access_context::current ().via (^^S).via (null_reflection).designating_class () == null_reflection);
}
consteval bool
baz ()
{
constexpr U u;
static_assert (is_constructor (u.u) && parent_of (u.u) == ^^U);
constexpr V v;
static_assert (is_constructor (v.u) && parent_of (v.u) == ^^U);
static_assert (is_constructor (v.v) && parent_of (v.v) == ^^V);
return true;
}
static_assert (baz ());
consteval info
qux ()
{
return ^^qux;
}
constexpr info q = qux ();
static_assert (q == ^^qux);
namespace O
{
int a[2];
auto
foo () -> int (&) [access_context::current ().scope () == ^^O ? 2 : 3]
{
return a;
}
}
consteval bool
can_do_via (info r)
{
try { access_context::current ().via (r); }
catch (std::meta::exception &) { return false; }
return true;
}
struct W;
static_assert (can_do_via (^^S));
static_assert (can_do_via (^^T));
static_assert (can_do_via (^^U));
static_assert (can_do_via (^^V));
static_assert (can_do_via (^^N::S));
static_assert (can_do_via (null_reflection));
static_assert (!can_do_via (^^W));
static_assert (!can_do_via (^^::));
static_assert (!can_do_via (^^O));
static_assert (!can_do_via (^^bar));
static_assert (!can_do_via (^^int));
enum E { E0, E1 };
static_assert (!can_do_via (^^E));
static_assert (!can_do_via (^^E0));
static_assert (!can_do_via (reflect_constant (42)));

View File

@@ -0,0 +1,47 @@
// { dg-do compile { target c++26 } }
// { dg-additional-options "-freflection" }
// Test from [meta.reflection.access.context].
#include <meta>
using namespace std::meta;
struct A {
int a = 0;
consteval A(int p) : a(p) {}
};
struct B : A {
using A::A;
consteval B(int p, int q) : A(p * q) {}
info s = access_context::current().scope();
};
struct C : B { using B::B; };
struct Agg {
consteval bool eq(info rhs = access_context::current().scope()) {
return s == rhs;
}
info s = access_context::current().scope();
};
namespace NS {
static_assert(Agg{}.s == access_context::current().scope()); // OK
static_assert(Agg{}.eq()); // OK
static_assert(B(1).s == ^^B); // OK
static_assert(is_constructor(B{1, 2}.s) && parent_of(B{1, 2}.s) == ^^B); // OK
static_assert(is_constructor(C{1, 2}.s) && parent_of(C{1, 2}.s) == ^^B); // OK
auto fn() -> [:is_namespace(access_context::current().scope()) ? ^^int : ^^bool:];
static_assert(return_type_of(type_of(^^fn)) == ^^int);
// TODO: This doesn't work yet.
// static_assert(type_of(^^fn) == ^^auto()->int); // OK
template<auto R>
struct TCls {
consteval bool fn()
requires (is_type(access_context::current().scope())) {
return true; // OK, scope is TCls<R>.
}
};
static_assert(TCls<0>{}.fn()); // OK
}

View File

@@ -1,6 +1,6 @@
// { dg-do compile { target c++26 } }
// { dg-additional-options "-freflection" }
// Test std::meta::has_parent.
// Test std::meta::parent_of.
#include <meta>

View File

@@ -87,20 +87,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// [meta.reflection.exception], class exception
class exception : public std::exception {
private:
//optional<string> what_; // exposition only
std::string_view what_;
u8string u8what_; // exposition only
info from_; // exposition only
source_location where_; // exposition only
//optional<string> _M_what;
std::string_view _M_what;
u8string _M_u8what;
info _M_from;
source_location _M_where;
public:
consteval exception(u8string_view what, info from,
source_location where = source_location::current()) noexcept
: u8what_{what}, from_{from}, where_{where} { }
consteval exception(u8string_view __what, info __from,
source_location __where
= source_location::current()) noexcept
: _M_u8what{__what}, _M_from{__from}, _M_where{__where} { }
consteval exception(string_view what, info from,
source_location where = source_location::current()) noexcept
: what_{what}, from_{from}, where_{where} { }
consteval exception(string_view __what, info __from,
source_location __where
= source_location::current()) noexcept
: _M_what{__what}, _M_from{__from}, _M_where{__where} { }
exception(const exception&) = default;
exception(exception&&) = default;
@@ -108,10 +110,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
exception& operator=(const exception&) = default;
exception& operator=(exception&&) = default;
constexpr const char *what() const noexcept override { return what_.data (); }
consteval u8string_view u8what() const noexcept { return u8what_; }
consteval info from() const noexcept { return from_; }
consteval source_location where() const noexcept { return where_; }
constexpr const char *what() const noexcept override
{ return _M_what.data (); }
consteval u8string_view u8what() const noexcept { return _M_u8what; }
consteval info from() const noexcept { return _M_from; }
consteval source_location where() const noexcept { return _M_where; }
};
// [meta.reflection.operators], operator representations
@@ -266,6 +269,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
consteval info variable_of(info);
consteval info return_type_of(info);
// [meta.reflection.access.context], access control context
struct access_context {
private:
consteval access_context(info __scope, info __designating_class) noexcept
: _M_scope{__scope}, _M_designating_class{__designating_class} { }
public:
access_context() = delete;
consteval access_context(const access_context &) = default;
consteval access_context(access_context &&) = default;
consteval info scope() const { return _M_scope; }
consteval info designating_class() const { return _M_designating_class; }
static consteval access_context current() noexcept;
static consteval access_context unprivileged() noexcept
{ return access_context { ^^::, info {} }; }
static consteval access_context unchecked() noexcept
{ return access_context { info {}, info {} }; }
consteval access_context via(info) const;
info _M_scope;
info _M_designating_class;
};
// [meta.reflection.member.queries], reflection member queries
consteval vector<info> enumerators_of(info);
@@ -501,6 +528,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// [meta.reflection.annotation], annotation reflection
consteval vector<info> annotations_of(info);
consteval vector<info> annotations_of_with_type(info, info);
consteval access_context
access_context::via(info __cls) const
{
if (__cls != info {}
&& (!std::meta::is_class_type(__cls)
|| !std::meta::is_complete_type(__cls)))
{
#if __cpp_exceptions
throw std::meta::exception(u8"via argument other than null "
"or complete class type reflection",
^^access_context::via);
#else
return *this;
#endif
}
return access_context { _M_scope, __cls };
}
} // namespace meta
_GLIBCXX_END_NAMESPACE_VERSION