From b4329a40fcbe6102d4995055f2918e0e8ad7c6bf Mon Sep 17 00:00:00 2001 From: Bakiev Damir Date: Thu, 8 Oct 2026 00:11:16 +0300 Subject: [PATCH 1/5] Exempt members designated by splice-expressions from the protected access check [] A class member designated by a splice-expression is accessible from any point ([class.access.base]), and, as clarified by CWG 3109, the additional protected member access check of [class.protected] does not apply to it either. The front end already skipped the ordinary access check for a splice (is_splicer in the symbol locator), but still did the [class.protected] check when forming a pointer to member with &[:r:] and when naming the member with obj.[:r:] or p->[:r:], also in template instantiations. An operand made by scan_expr_splicer for a nonstatic member now has the new designated_by_splice flag, which suppresses the check in conv_sym_for_member_operand_to_ptr_to_member, and a member selection through a splice no longer asks cast_pointer_for_field_selection for the check. The conversion of the object expression to the designating class is still checked. Also: new tests reflections/splice_protected_access.sft.cpp and reflections/splice_protected_access_neg.sft.cpp, and a src/Changes entry. --- src/Changes | 27 +++++ src/expr.c | 8 +- src/exprutil.c | 9 +- src/exprutil.h | 9 ++ .../edg_x86_64_cp/reflections.log | 1 + .../default.1.1.txt | 1 + .../default.1.2.txt | 5 + .../edg_x86_64_cp.1.1.txt | 104 +++++++++++++++++ .../default.1.1.txt | 18 +++ .../splice_protected_access.sft.cpp | 107 ++++++++++++++++++ .../splice_protected_access_neg.sft.cpp | 42 +++++++ 11 files changed, 327 insertions(+), 4 deletions(-) create mode 100644 tests/tests/reflections/.splice_protected_access.rto/default.1.1.txt create mode 100644 tests/tests/reflections/.splice_protected_access.rto/default.1.2.txt create mode 100644 tests/tests/reflections/.splice_protected_access.rto/edg_x86_64_cp.1.1.txt create mode 100644 tests/tests/reflections/.splice_protected_access_neg.rto/default.1.1.txt create mode 100644 tests/tests/reflections/splice_protected_access.sft.cpp create mode 100644 tests/tests/reflections/splice_protected_access_neg.sft.cpp diff --git a/src/Changes b/src/Changes index 8e798977ad..77124d2043 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,32 @@ Version 7.1 +10/8/26 +Protected members designated by splice-expressions + +A class member designated by a splice-expression is accessible from any +point ([class.access.base]), and, as clarified by CWG 3109, the additional +access check for protected members ([class.protected]) does not apply to it. +The front end skipped the ordinary access check for such a member but still +did the protected member check, so forming a pointer to a protected member +with &[:r:], or naming it with obj.[:r:] or p->[:r:], was usually rejected +outside of the members and friends of the class of the member. For example, +with --c++26 --set_flag=reflection: + + #include + class C { + protected: + int prot = 1; + }; + constexpr std::meta::info r = std::meta::nonstatic_data_members_of( + ^^C, std::meta::access_context::unchecked())[0]; + int C::*pm = &[:r:]; // Previously an error + int f(C &c) { + return c.[:r:]; // Previously an error + } + +This is now fixed. + + 10/7/26 [GH #176] C-generating back end: always_inline attribute and inlined setjmp calls diff --git a/src/expr.c b/src/expr.c index fe8a6d57cd..a17bba1cd4 100644 --- a/src/expr.c +++ b/src/expr.c @@ -11465,7 +11465,8 @@ case). operand_1, is_arrow_operator, member_sym, projection_member_sym, (a_boolean)locator.access_control_error_reported, - /*do_protected_member_check=*/TRUE, &member_position); + /*do_protected_member_check=*/!locator.is_splicer, + &member_position); do_field_selection_operation(operand_1, orig_class_struct_union_type, is_arrow_operator, /*compiler_generated=*/FALSE, @@ -11553,7 +11554,8 @@ case). projection_member_sym, (a_boolean)locator. access_control_error_reported, - /*do_protected_member_check=*/TRUE, + /*do_protected_member_check=*/ + !locator.is_splicer, &member_position); } /* if */ /* Make an operand for the function with the selector bound @@ -41763,6 +41765,7 @@ FIXME: This is currently incomplete. (a_ref_entry*)NULL, result); result->is_id_expression = TRUE; + result->designated_by_splice = TRUE; } else if (iek == iek_variable) { vp = (a_variable*)rvp->entity.ptr; variable_case: @@ -41785,6 +41788,7 @@ FIXME: This is currently incomplete. (a_ref_entry*)NULL, result); result->is_id_expression = TRUE; + result->designated_by_splice = TRUE; } else { make_function_designator_operand(symbol_for(rp), /*is_qualified_name=*/FALSE, diff --git a/src/exprutil.c b/src/exprutil.c index db22dd4b7b..7baf03c975 100644 --- a/src/exprutil.c +++ b/src/exprutil.c @@ -3486,6 +3486,7 @@ values. operand->is_qualified_name = FALSE; operand->is_microsoft_deferred_name = FALSE; operand->access_control_error_reported = FALSE; + operand->designated_by_splice = FALSE; operand->is_operand_of_address_of = FALSE; operand->has_required_ptr_to_member_form = FALSE; operand->is_template_id = FALSE; @@ -23550,7 +23551,7 @@ by an "&" in the source, and *ampersand_position gives its position. { an_operand orig_operand; a_symbol_ptr member_sym, fund_sym; - a_boolean has_required_ampersand; + a_boolean has_required_ampersand, check_protected_access; a_boolean allow_addr_of_managed_member = FALSE, force_node = FALSE; has_required_ampersand = (ampersand_position != NULL && @@ -23578,11 +23579,15 @@ by an "&" in the source, and *ampersand_position gives its position. #if MICROSOFT_EXTENSIONS_ALLOWED allow_addr_of_managed_member = operand->allow_addr_of_managed_member; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ + /* The protected member access check does not apply to a member designated + by a splice-expression ([class.protected]). */ + check_protected_access = !operand->access_control_error_reported && + !operand->designated_by_splice; /* Make an operand for a pointer-to-member constant. */ make_ptr_to_member_constant_operand(member_sym, member_sym, &orig_operand.position, end_position_of_operand(&orig_operand), - !operand->access_control_error_reported, + check_protected_access, (a_boolean)operand->is_qualified_name, has_required_ampersand, allow_addr_of_managed_member, diff --git a/src/exprutil.h b/src/exprutil.h index fd7441f146..75fef003b6 100644 --- a/src/exprutil.h +++ b/src/exprutil.h @@ -364,6 +364,15 @@ typedef struct an_operand { essentially still just a representation for an identifier, e.g., ok_indefinite_function and ok_sym_for_member. */ + a_bit_field designated_by_splice:1; + /* TRUE if this operand was generated from a + splice-expression designating a class member. + Such a member is accessible from any point, and + the additional protected member access check + ([class.protected]) does not apply to it. Like + access_control_error_reported, this remains + meaningful only for ok_sym_for_member + operands. */ a_bit_field is_operand_of_address_of:1; /* TRUE if this operand is the immediate operand of an "&" address-of operator. This is diff --git a/tests/expectations/edg_x86_64_cp/reflections.log b/tests/expectations/edg_x86_64_cp/reflections.log index aa2a434ab6..ac2b16356e 100644 --- a/tests/expectations/edg_x86_64_cp/reflections.log +++ b/tests/expectations/edg_x86_64_cp/reflections.log @@ -1 +1,2 @@ demo1.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): +splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): diff --git a/tests/tests/reflections/.splice_protected_access.rto/default.1.1.txt b/tests/tests/reflections/.splice_protected_access.rto/default.1.1.txt new file mode 100644 index 0000000000..30032fc283 --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access.rto/default.1.1.txt @@ -0,0 +1 @@ +eccp -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c diff --git a/tests/tests/reflections/.splice_protected_access.rto/default.1.2.txt b/tests/tests/reflections/.splice_protected_access.rto/default.1.2.txt new file mode 100644 index 0000000000..a8994540de --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access.rto/default.1.2.txt @@ -0,0 +1,5 @@ +./a.out +1 2 3 4 5 +6 7 +8 9 10 +11 12 diff --git a/tests/tests/reflections/.splice_protected_access.rto/edg_x86_64_cp.1.1.txt b/tests/tests/reflections/.splice_protected_access.rto/edg_x86_64_cp.1.1.txt new file mode 100644 index 0000000000..51bf7fb6b5 --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access.rto/edg_x86_64_cp.1.1.txt @@ -0,0 +1,104 @@ +eccp -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"Test_name.c", line 77: error: expression must have a constant value + constexpr std::meta::info prot = member_named((decltype(^^0){}), "prot"); + ^ +"Test_name.c", line 29: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 29: note: called from: + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ + +"Test_name.c", line 78: error: expression must have a constant value + constexpr std::meta::info get = member_named((decltype(^^0){}), "get"); + ^ +"Test_name.c", line 29: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 29: note: called from: + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ + +"Test_name.c", line 79: error: expression must have a constant value + constexpr std::meta::info set = member_named((decltype(^^0){}), "set"); + ^ +"Test_name.c", line 29: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 29: note: called from: + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ + +"Test_name.c", line 80: error: expression must have a constant value + constexpr std::meta::info priv = member_named((decltype(^^0){}), "priv"); + ^ +"Test_name.c", line 29: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 29: note: called from: + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ + +"Test_name.c", line 85: error: member "C::prot" (declared at line 37) is inaccessible + int (C::*pm) = (&C::prot); + ^ + +"Test_name.c", line 86: error: function "C::get" (declared at line 38) is inaccessible + int (C::*pf)(void) const = (&C::get); + ^ + +"Test_name.c", line 87: error: function "C::set" (declared at line 39) is inaccessible + void (C::*ps)(int) noexcept = (&C::set); + ^ + +"Test_name.c", line 90: error: function "C::get" (declared at line 38) is inaccessible + int e = c.get() + 1; + ^ + +"Test_name.c", line 92: error: member "C::prot" (declared at line 37) is inaccessible + int f = pc->prot; + ^ + +"Test_name.c", line 93: error: function "C::set" (declared at line 39) is inaccessible + d.set(5); + ^ + +"Test_name.c", line 94: error: member "C::prot" (declared at line 37) is inaccessible + int g = d.prot; + ^ + +"Test_name.c", line 100: error: no instance of overloaded function "read" matches the argument list + argument types are: (D *) + printf("%d %d %d\n", read< prot> (&d) + 3, call< get> (d) + 7, read< priv> (&c) + 4); + ^ +"Test_name.c", line 49: note: substituting explicit template arguments "<>" for function template "read(T *)" failed + template < info M, typename T > int read ( T * p ) { + ^ + +"Test_name.c", line 100: error: no instance of function template "call" matches the argument list + argument types are: (D) + printf("%d %d %d\n", read< prot> (&d) + 3, call< get> (d) + 7, read< priv> (&c) + 4); + ^ +"Test_name.c", line 53: note: substituting explicit template arguments "<>" for function template "call" failed + template < info M, typename T > int call ( T & obj ) { + ^ + +"Test_name.c", line 100: error: no instance of overloaded function "read" matches the argument list + argument types are: (C *) + printf("%d %d %d\n", read< prot> (&d) + 3, call< get> (d) + 7, read< priv> (&c) + 4); + ^ +"Test_name.c", line 49: note: substituting explicit template arguments "<>" for function template "read(T *)" failed + template < info M, typename T > int read ( T * p ) { + ^ + +"Test_name.c", line 104: error: expression must have a constant value + printf("%d %d\n", w.sum() - 1, w.*member_pointer< member_named((decltype(^^0){}), "value")> + 8); + ^ +"Test_name.c", line 29: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 29: note: called from: + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ + +15 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/reflections/.splice_protected_access_neg.rto/default.1.1.txt b/tests/tests/reflections/.splice_protected_access_neg.rto/default.1.1.txt new file mode 100644 index 0000000000..2241f8d4d1 --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access_neg.rto/default.1.1.txt @@ -0,0 +1,18 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"Test_name.c", line 37: error: conversion to inaccessible base class "C" is not allowed + int a = e.[:^^C::pub:]; // error: inaccessible base + ^ + +"Test_name.c", line 38: error: conversion to inaccessible base class "C" is not allowed + int b = f.[:member_named(^^C, "prot"):]; // error: inaccessible base + ^ + +"Test_name.c", line 39: error: conversion to inaccessible base class "C" is not allowed + int c = (&f)->[:member_named(^^C, "pub"):]; // error: inaccessible base + ^ + +"Test_name.c", line 40: error: member "C::prot" (declared at line 28) is inaccessible + static_assert(^^C::prot != info{}); // error: inaccessible + ^ + +4 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/reflections/splice_protected_access.sft.cpp b/tests/tests/reflections/splice_protected_access.sft.cpp new file mode 100644 index 0000000000..b41368e049 --- /dev/null +++ b/tests/tests/reflections/splice_protected_access.sft.cpp @@ -0,0 +1,107 @@ +//type:rp +//use_system_includes: true +//edg_header_pack: exp_meta + +/* +A class member designated by a splice-expression is accessible from any +point ([class.access.base]), and the additional access check for protected +members ([class.protected], CWG 3109) does not apply to it. So a pointer to a +protected member can be formed with &[:r:], and a protected member can be +named with obj.[:r:] or p->[:r:] where obj or *p is of the designating class +or of any class derived from it, outside of any member or friend of the class. +The same holds when the splice is template-dependent. + +Output should be: +$ ./a.out +1 2 3 4 5 +6 7 +8 9 10 +11 12 +*/ + +#include +#include +#include + +using namespace std::meta; + +consteval info member_named(info cls, std::string_view name) { + for (info m : members_of(cls, access_context::unchecked())) { + if (has_identifier(m) && identifier_of(m) == name) return m; + } + return info{}; +} + +class C { +protected: + int prot = 1; + int get() const { return 2; } + void set(int v) noexcept { prot = v; } +private: + int priv = 6; +}; + +struct D : C {}; + +// Template-dependent splices. +template constexpr auto member_pointer = &[:M:]; + +template int read(T *p) { + return p->[:M:]; +} + +template int call(T &obj) { + return obj.[:M:](); +} + +// The pattern of a CRTP base that reaches the non-public members of the +// class derived from it. +template struct Base { + int sum() { + Derived &d = static_cast(*this); + constexpr auto pm = &[:member_named(^^Derived, "value"):]; + constexpr auto pf = &[:member_named(^^Derived, "twice"):]; + return d.*pm + (d.*pf)(); + } +}; + +class Widget : public Base { + friend struct Base; // For the static_cast only. +protected: + int value = 4; +private: + int twice() const { return 2 * value; } +}; + +int main() { + constexpr info prot = member_named(^^C, "prot"); + constexpr info get = member_named(^^C, "get"); + constexpr info set = member_named(^^C, "set"); + constexpr info priv = member_named(^^C, "priv"); + C c; + D d; + C *pc = &c; + + int C::*pm = &[:prot:]; + int (C::*pf)() const = &[:get:]; + void (C::*ps)(int) noexcept = &[:set:]; + int a = c.*pm; + int b = (c.*pf)(); + int e = c.[:get:]() + 1; + (c.*ps)(4); + int f = pc->[:prot:]; + d.[:set:](5); + int g = d.[:prot:]; + printf("%d %d %d %d %d\n", a, b, e, f, g); + + printf("%d %d\n", c.*member_pointer, + d.*member_pointer + 2); + + printf("%d %d %d\n", read(&d) + 3, call(d) + 7, + read(&c) + 4); + + Widget w; + printf("%d %d\n", w.sum() - 1, + w.*member_pointer + 8); + return 0; +} diff --git a/tests/tests/reflections/splice_protected_access_neg.sft.cpp b/tests/tests/reflections/splice_protected_access_neg.sft.cpp new file mode 100644 index 0000000000..607701390e --- /dev/null +++ b/tests/tests/reflections/splice_protected_access_neg.sft.cpp @@ -0,0 +1,42 @@ +//type:fn +//use_system_includes: true +//edg_header_pack: exp_meta + +/* +A member designated by a splice-expression is accessible from any point, but +a class member access whose right operand is such a splice is still +ill-formed if the left operand (considered as a pointer) cannot be implicitly +converted to a pointer to the designating class ([class.access.base]). A +reflect-expression naming a member is subject to the usual access checks. +*/ + +#include +#include + +using namespace std::meta; + +consteval info member_named(info cls, std::string_view name) { + for (info m : members_of(cls, access_context::unchecked())) { + if (has_identifier(m) && identifier_of(m) == name) return m; + } + return info{}; +} + +struct C { + int pub = 1; +protected: + int prot = 2; +}; + +struct E : private C {}; +struct F : protected C {}; + +int main() { + E e; + F f; + int a = e.[:^^C::pub:]; // error: inaccessible base + int b = f.[:member_named(^^C, "prot"):]; // error: inaccessible base + int c = (&f)->[:member_named(^^C, "pub"):]; // error: inaccessible base + static_assert(^^C::prot != info{}); // error: inaccessible + return a + b + c; +} From e960ef14f317c556dddf0c9abb0507173aefe1dc Mon Sep 17 00:00:00 2001 From: Bakiev Damir Date: Thu, 8 Oct 2026 00:31:12 +0300 Subject: [PATCH 2/5] Return the members of a class or namespace in declaration order from std::meta::members_of [] std::meta::members_of collected the nonstatic data members of a class first and then the entities listed in the class or namespace scope kind by kind (functions, types, variables, templates, namespaces), so the members came out grouped by kind. P2996R13 ([meta.reflection.member.queries]) requires the members of a class to appear in the order in which they are declared, with the implicitly-declared special members after the user-declared ones. The collected reflections are now sorted by the positions of the declarations of the members (sort_reflections_in_declaration_order, using compare_member_declaration_order), with compiler-generated functions last. Members with the same position, such as those of a class defined by std::meta::define_aggregate, keep their relative order. The members of a namespace are sorted the same way. Also: a new test (reflections/members_of_order.sft.cpp) and a src/Changes entry. --- src/Changes | 23 ++++ src/interpret.c | 66 +++++++++++ .../edg_x86_64_cp/reflections.log | 1 + .../.members_of_order.rto/default.1.1.txt | 1 + .../reflections/members_of_order.sft.cpp | 111 ++++++++++++++++++ 5 files changed, 202 insertions(+) create mode 100644 tests/tests/reflections/.members_of_order.rto/default.1.1.txt create mode 100644 tests/tests/reflections/members_of_order.sft.cpp diff --git a/src/Changes b/src/Changes index 77124d2043..0605f30e45 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,28 @@ Version 7.1 +10/8/26 +std::meta::members_of: declaration order + +std::meta::members_of returned the members of a class grouped by kind: the +nonstatic data members first, then the member functions, the member types +and type aliases, the static data members, and the member templates. The +members of a namespace were grouped by kind in the same way. As specified in +P2996R13 ([meta.reflection.member.queries]), the members of a class are now +returned in the order in which they are declared, with the +implicitly-declared special members after all the others. The members of a +namespace are now also returned in declaration order. For example: + + #include + struct S { + int a; + void f(); + int b; + }; + // Previously a, b, f; now a, f, b (then the implicit members). + constexpr auto members = std::define_static_array( + std::meta::members_of(^^S, std::meta::access_context::unchecked())); + + 10/8/26 Protected members designated by splice-expressions diff --git a/src/interpret.c b/src/interpret.c index b9b7e62341..485bf4c004 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -16784,6 +16784,70 @@ not the scope). } /* collect_scoped_reflections */ +static int compare_member_declaration_order(a_reflection_value rv1, + a_reflection_value rv2) +/* +Compare the reflections rv1 and rv2 of two members of a class or namespace by +the order in which the members are declared. Return a negative value if the +member reflected by rv1 comes first, a positive value if that reflected by +rv2 does, and 0 if their positions are the same. Implicitly-declared members +(compiler-generated functions, whose positions are those of their classes) +come after all the others. +*/ +{ + int result; + a_boolean is_implicit1, is_implicit2; + a_source_correspondence *scp1, *scp2; + + is_implicit1 = (rv1.entity.kind == iek_routine && + ((a_routine*)rv1.entity.ptr)->compiler_generated); + is_implicit2 = (rv2.entity.kind == iek_routine && + ((a_routine*)rv2.entity.ptr)->compiler_generated); + if (is_implicit1 != is_implicit2) { + result = is_implicit1 ? 1 : -1; + } else { + scp1 = source_corresp_for_reflection(&rv1); + scp2 = source_corresp_for_reflection(&rv2); + result = compare_source_positions( + scp1 != NULL ? &scp1->decl_position : &null_source_position, + scp2 != NULL ? &scp2->decl_position : &null_source_position); + } /* if */ + return result; +} /* compare_member_declaration_order */ + + +static void sort_reflections_in_declaration_order( + Dyn_array *reflections) +/* +Sort the reflections of the members of a class or namespace in *reflections +into the order in which the members are declared, as std::meta::members_of +requires ([meta.reflection.member.queries]); see +compare_member_declaration_order. Members with the same position, e.g., +those of a class defined by std::meta::define_aggregate (which have no +position at all), keep their relative order. +*/ +{ + size_t n = reflections->length(), i; + Dyn_array order(n); + Dyn_array sorted(n); + + for (i = 0; i < n; ++i) { + order.push_back(i); + } /* for */ + sort(&order, [reflections](size_t i1, size_t i2) { + int cmp = compare_member_declaration_order((*reflections)[i1], + (*reflections)[i2]); + return cmp != 0 ? cmp < 0 : i1 < i2; + }); + for (i = 0; i < n; ++i) { + sorted.push_back((*reflections)[order[i]]); + } /* for */ + for (i = 0; i < n; ++i) { + (*reflections)[i] = sorted[i]; + } /* for */ +} /* sort_reflections_in_declaration_order */ + + static a_constant_ptr info_array_element_pointer( an_interpreter_state *ips, a_variable_ptr vp, @@ -17184,6 +17248,8 @@ See do_constexpr_intrinsic_call for the meaning of the parameters. ips); goto done; } /* if */ + /* The members were collected by kind; put them in declaration order. */ + sort_reflections_in_declaration_order(&all_reflections); /* Keep only members that are accessible from the given access_context. */ keep_accessible_reflections(&all_reflections, &scope_rv, &dc_rv); result = make_info_vector(ips, callee, call_node, &all_reflections, diff --git a/tests/expectations/edg_x86_64_cp/reflections.log b/tests/expectations/edg_x86_64_cp/reflections.log index ac2b16356e..32987394d4 100644 --- a/tests/expectations/edg_x86_64_cp/reflections.log +++ b/tests/expectations/edg_x86_64_cp/reflections.log @@ -1,2 +1,3 @@ demo1.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): +members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): diff --git a/tests/tests/reflections/.members_of_order.rto/default.1.1.txt b/tests/tests/reflections/.members_of_order.rto/default.1.1.txt new file mode 100644 index 0000000000..44c36760c6 --- /dev/null +++ b/tests/tests/reflections/.members_of_order.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c diff --git a/tests/tests/reflections/members_of_order.sft.cpp b/tests/tests/reflections/members_of_order.sft.cpp new file mode 100644 index 0000000000..d930e98052 --- /dev/null +++ b/tests/tests/reflections/members_of_order.sft.cpp @@ -0,0 +1,111 @@ +//type:fp +//use_system_includes: true +//edg_header_pack: exp_meta +//require:DO_IL_LOWERING 1 + +/* +std::meta::members_of returns the members of a class in the order in which +they are declared, whatever their kinds ([meta.reflection.member.queries]); +implicitly-declared special members come after the user-declared members. +Members of a namespace are also returned in declaration order. +*/ + +#include +#include +#include + +using namespace std::meta; + +// The names of the members of r in the order of members_of, except for +// implicitly-declared special members (how many of them are declared is not +// checked here). A member that follows an implicitly-declared one is marked. +consteval std::string names(info r, access_context ctx) { + std::string out; + bool after_implicit = false; + for (info m : members_of(r, ctx)) { + if (is_special_member_function(m) && !is_user_declared(m)) { + after_implicit = true; + continue; + } /* if */ + if (after_implicit) out += ""; + out += has_identifier(m) ? identifier_of(m) : ""; + out += ' '; + } /* for */ + return out; +} + +consteval int implicit_count(info r) { + int n = 0; + for (info m : members_of(r, access_context::unchecked())) { + n += is_special_member_function(m) && !is_user_declared(m); + } /* for */ + return n; +} + +consteval std::string names(info r) { + return names(r, access_context::unchecked()); +} + +struct S { + int a; + void f(); + int b; + static void g(); + using T = int; + int c; + struct N {}; + static int sv; + enum E { e }; + int : 3; + S() = default; + int d; +}; + +static_assert(names(^^S) == "a f b g T c N sv E S d "); +static_assert(implicit_count(^^S) != 0); + +class P { + int p1; +public: + int q1; + void q2(); +private: + int p2; +public: + using q3 = int; +}; + +static_assert(names(^^P, access_context::current()) == "q1 q2 q3 "); + +// Members of an instantiation follow the declarations in the template. +template struct TS { + U x; void h(); U y; + static U z; +}; + +static_assert(names(^^TS) == "x h y z "); +static_assert(implicit_count(^^TS) != 0); + +// Members of a class defined by define_aggregate. +struct Agg; +consteval { + define_aggregate(^^Agg, {data_member_spec(^^int, {.name = "m1"}), + data_member_spec(^^char, {.name = "m2"})}); +} + +static_assert(names(^^Agg) == "m1 m2 "); +static_assert(implicit_count(^^Agg) != 0); + +namespace ns { + void f(); + int v; + struct C; + namespace inner {} + using A = int; + namespace alias = inner; +} +namespace ns { + void g(); +} + +static_assert(names(^^ns) == "f v C inner A alias g "); From f6c1757d49ad0dc5791d55cdb3baa93ccc079064 Mon Sep 17 00:00:00 2001 From: Bakiev Damir Date: Thu, 8 Oct 2026 00:33:37 +0300 Subject: [PATCH 3/5] Make std::meta::source_location_of a constant for every reflection [] As specified in P2996R13 ([meta.reflection.names]), source_location_of has no "Constant When" clause. For a reflection of a value, of a type other than a class or enumeration type (that is not a type alias), of the global namespace, or of a data member description, it now returns a value-initialized std::source_location instead of failing; so does any other reflection without a declaration position. A cv-qualified class or enumeration type uses the position of the class or enumeration. build_source_location_value accepts a null position to build a value-initialized std::source_location (a null __impl pointer). Also: a new test (reflections/source_location_of.sft.cpp) and a src/Changes entry. --- src/Changes | 18 +++ src/interpret.c | 104 ++++++++++++------ .../edg_x86_64_cp/reflections.log | 1 + .../.source_location_of.rto/default.1.1.txt | 1 + .../reflections/source_location_of.sft.cpp | 50 +++++++++ 5 files changed, 139 insertions(+), 35 deletions(-) create mode 100644 tests/tests/reflections/.source_location_of.rto/default.1.1.txt create mode 100644 tests/tests/reflections/source_location_of.sft.cpp diff --git a/src/Changes b/src/Changes index 0605f30e45..50056e4c54 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,23 @@ Version 7.1 +10/8/26 +std::meta::source_location_of for reflections without a declaration + +As specified in P2996R13 ([meta.reflection.names]), +std::meta::source_location_of is now a constant expression for every +reflection. For a reflection of a value, of a type other than a class or +enumeration type, of the global namespace, or of a data member description, +the result is a value-initialized std::source_location; the same is true for +other reflections that have no declaration position. Previously, those calls +were not constant expressions. For example: + + static_assert(std::meta::source_location_of(^^int).line() == 0); + // Previously an error. + static_assert(std::meta::source_location_of(^^const S).line() != 0); + // Previously an error (S is a + // class). + + 10/8/26 std::meta::members_of: declaration order diff --git a/src/interpret.c b/src/interpret.c index 485bf4c004..d44876e463 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -11091,15 +11091,20 @@ member and the object are marked initialized as subobjects of complete_obj the function-name field is set as for __builtin_source_location (the enclosing function's __func__ string); otherwise it is set to the empty string, which is what source_location_of uses since an entity's function field is -implementation-defined. On any construction failure *p_result is set to FALSE -(any needed diagnostic having been issued). Return TRUE if the source-location -type was valid (so the builtin/intrinsic was handled), FALSE if it was an error -type (which has already been diagnosed). +implementation-defined. If use_pos is NULL, the result is a value-initialized +std::source_location (whose pointer member is null, so that all its queries +return zero or the empty string); that is only possible for a class result. +On any construction failure *p_result is set to FALSE (any needed diagnostic +having been issued). Return TRUE if the source-location type was valid (so +the builtin/intrinsic was handled), FALSE if it was an error type (which has +already been diagnosed). */ { a_boolean handled = TRUE; a_gnu_source_location_type_info interp_inf; + check_assertion(use_pos != NULL || result_class_type != NULL); + /* Load the type information; if the type is invalid, silently fail (this has already been diagnosed). */ interp_inf = gnu_source_location_impl(); @@ -11108,11 +11113,13 @@ type (which has already been diagnosed). do_constexpr_fail(*p_result); handled = FALSE; } else { - a_byte *obj_storage; - /* Allocate the source location __impl object. */ - alloc_storage_promotable_object(ips, interp_inf.impl_type, &obj_storage, - p_result); - if (*p_result) { + a_byte *obj_storage = NULL; + if (use_pos != NULL) { + /* Allocate the source location __impl object. */ + alloc_storage_promotable_object(ips, interp_inf.impl_type, &obj_storage, + p_result); + } /* if */ + if (*p_result && obj_storage != NULL) { /* Populate the source location __impl object. The fields (and their associated types) are guaranteed to have been validated when the source-location type was first used. */ @@ -11153,14 +11160,16 @@ type (which has already been diagnosed). do_constexpr_write_source_column(ips, use_pos, column_fp->type, column_f_bytes, p_result); mark_subobject_initialized(column_f_bytes, obj_storage); - + } /* if */ + if (*p_result) { if (result_class_type == NULL) { /* Scalar (pointer) result: result_storage is the __impl pointer. */ clear_address(result_storage, obj_storage); mark_complete_object_initialized(result_storage); } else { - /* Class result: store the __impl address in the source_location - object's single pointer member and mark the member and the object + /* Class result: store the __impl address (null for a + value-initialized source_location) in the source_location object's + single pointer member and mark the member and the object initialized as subobjects of complete_obj, mirroring make_reflective_string_view. */ a_field_ptr slfp = next_alloc_field(fields_of(result_class_type)); @@ -16696,19 +16705,22 @@ See do_constexpr_intrinsic_call for the meaning of the parameters. static a_boolean do_constexpr_std_meta_source_location_of( - an_interpreter_state *ips, - a_routine_ptr callee, - an_expr_node_ptr call_node, - a_byte **p_arg_bytes, - a_byte *result_storage, - a_byte *complete_obj) + an_interpreter_state *ips, + a_routine_ptr callee, + ARG_UNUSED an_expr_node_ptr call_node, + a_byte **p_arg_bytes, + a_byte *result_storage, + a_byte *complete_obj) /* Implement std::meta::source_location_of(). It returns a std::source_location describing the declaration of the reflected entity. The file/line/column come from the entity's declaration position; the function-name -field is left empty (it is implementation-defined for an entity). The call -fails to be a constant expression for a reflection that has no associated -declaration position. +field is left empty (it is implementation-defined for an entity). As specified +in [meta.reflection.names], the call is always a constant expression: a +reflection of a value, of a type other than a class or enumeration type (that +is not a type alias), of the global namespace, or of a data member description +yields a value-initialized std::source_location, as does any other reflection +with no associated declaration position. See do_constexpr_intrinsic_call for the meaning of the parameters. */ @@ -16717,26 +16729,48 @@ See do_constexpr_intrinsic_call for the meaning of the parameters. a_reflection_value *rvp = (a_reflection_value*)p_arg_bytes[0]; a_type_ptr rtp = skip_typerefs(callee->type), sl_type; a_source_correspondence_ptr - scp; + scp = NULL; a_source_position *use_pos = NULL; strip_template_arg(rvp); - scp = source_corresp_for_reflection(rvp); + switch (rvp->entity.kind) { + case iek_constant: + /* A value has no source location; a named constant (an enumerator) is + an entity, which does. */ + scp = source_corresp_for_reflection(rvp); + if (scp != NULL && scp->name == NULL) scp = NULL; + break; + case iek_type: + { a_type_ptr tp = (a_type_ptr)rvp->entity.ptr; + if (type_is_typedef(tp)) { + /* A type alias. */ + scp = source_corresp_for_reflection(rvp); + } else { + tp = skip_typerefs(tp); + if (is_class_struct_union_type(tp) || is_enum_type(tp)) { + scp = &tp->source_corresp; + } /* if */ + } /* if */ + } + break; + case iek_none: + case iek_data_member_spec: + /* No source location. */ + break; + default: + /* The global namespace has no source correspondence. */ + scp = source_corresp_for_reflection(rvp); + break; + } /* switch */ if (scp != NULL && scp->decl_position.seq != 0) { use_pos = &scp->decl_position; } /* if */ - if (use_pos == NULL) { - do_constexpr_fail(result); - info_with_pos(ec_invalid_reflection_for_intrinsic, &call_node->position, - ips); - } else { - check_assertion(type_is(rtp, tk_routine)); - sl_type = skip_typerefs(rtp->variant.routine.return_type); - (void)build_source_location_value(ips, use_pos, - /*use_current_function=*/FALSE, - sl_type, result_storage, complete_obj, - &result); - } /* if */ + check_assertion(type_is(rtp, tk_routine)); + sl_type = skip_typerefs(rtp->variant.routine.return_type); + (void)build_source_location_value(ips, use_pos, + /*use_current_function=*/FALSE, + sl_type, result_storage, complete_obj, + &result); return result; } /* do_constexpr_std_meta_source_location_of */ diff --git a/tests/expectations/edg_x86_64_cp/reflections.log b/tests/expectations/edg_x86_64_cp/reflections.log index 32987394d4..f9d22cb073 100644 --- a/tests/expectations/edg_x86_64_cp/reflections.log +++ b/tests/expectations/edg_x86_64_cp/reflections.log @@ -1,3 +1,4 @@ demo1.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: +source_location_of.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): diff --git a/tests/tests/reflections/.source_location_of.rto/default.1.1.txt b/tests/tests/reflections/.source_location_of.rto/default.1.1.txt new file mode 100644 index 0000000000..44c36760c6 --- /dev/null +++ b/tests/tests/reflections/.source_location_of.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c diff --git a/tests/tests/reflections/source_location_of.sft.cpp b/tests/tests/reflections/source_location_of.sft.cpp new file mode 100644 index 0000000000..b5372fe063 --- /dev/null +++ b/tests/tests/reflections/source_location_of.sft.cpp @@ -0,0 +1,50 @@ +//type:fp +//use_system_includes: true +//edg_header_pack: exp_meta +//require:DO_IL_LOWERING 1 + +/* +std::meta::source_location_of is a constant expression for every reflection +([meta.reflection.names] in P2996R13 has no "Constant When" clause for it). +For a value, a type other than a class or enumeration type, the global +namespace, or a data member description, the result is source_location{}; +for an entity, it is the location of a declaration. +*/ + +#include +#include + +using namespace std::meta; + +consteval bool is_empty_location(std::source_location l) { + return l.line() == 0 && l.column() == 0 && l.file_name()[0] == '\0' && + l.function_name()[0] == '\0'; +} + +// No source location. +static_assert(is_empty_location(source_location_of(^^int))); +static_assert(is_empty_location(source_location_of(^^int*))); +static_assert(is_empty_location(source_location_of(^^const int&))); +static_assert(is_empty_location(source_location_of(reflect_constant(42)))); +static_assert(is_empty_location(source_location_of(^^::))); +static_assert(is_empty_location( + source_location_of(data_member_spec(^^int, {.name = "x"})))); +static_assert(is_empty_location(source_location_of(info{}))); + +// Entities: the line of the declaration. +struct S { int m; }; +enum E { e1 }; +using Alias = int; +void f(); +namespace N {} +constexpr int line_S = __LINE__ - 5; + +static_assert(source_location_of(^^S).line() == line_S); +static_assert(source_location_of(^^const S).line() == line_S); +static_assert(source_location_of(^^S::m).line() == line_S); +static_assert(source_location_of(^^E).line() == line_S + 1); +static_assert(source_location_of(^^e1).line() == line_S + 1); +static_assert(source_location_of(^^Alias).line() == line_S + 2); +static_assert(source_location_of(^^f).line() == line_S + 3); +static_assert(source_location_of(^^N).line() == line_S + 4); +static_assert(!is_empty_location(source_location_of(^^S))); From 9bda256970543479729d18db00e40e4ed35afe91 Mon Sep 17 00:00:00 2001 From: Bakiev Damir Date: Thu, 8 Oct 2026 00:34:47 +0300 Subject: [PATCH 4/5] Accept namespaces and namespace aliases in std::meta::parent_of [] parent_of_reflection, which implements std::meta::parent_of and std::meta::has_parent, did not handle a reflection of a namespace (an iek_scope entry) or of a namespace alias (an iek_namespace entry), so parent_of(^^ns) was not a constant expression and has_parent(^^ns) was false. P2996R13 ([meta.reflection.scope]) allows any namespace or namespace alias other than the global namespace; the result is the enclosing namespace. The parent is now found from the source correspondence of the namespace (or alias), as for other namespace members. The global namespace has no source correspondence and so still has no parent. Also: a new test (reflections/parent_of_namespace.sft.cpp) and a src/Changes entry. --- src/Changes | 18 ++++++++ src/interpret.c | 17 +++++-- .../edg_x86_64_cp/reflections.log | 1 + .../.parent_of_namespace.rto/default.1.1.txt | 1 + .../reflections/parent_of_namespace.sft.cpp | 45 +++++++++++++++++++ 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 tests/tests/reflections/.parent_of_namespace.rto/default.1.1.txt create mode 100644 tests/tests/reflections/parent_of_namespace.sft.cpp diff --git a/src/Changes b/src/Changes index 50056e4c54..53865faeca 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,23 @@ Version 7.1 +10/8/26 +std::meta::parent_of for namespaces + +std::meta::parent_of and std::meta::has_parent did not accept a reflection +of a namespace or of a namespace alias: parent_of was not a constant +expression for one, and has_parent returned false. As specified in P2996R13 +([meta.reflection.scope]), parent_of now returns the enclosing namespace for +any namespace or namespace alias other than the global namespace. For +example: + + #include + namespace outer { namespace inner {} } + static_assert(std::meta::parent_of(^^outer::inner) == ^^outer); + // Previously an error. + static_assert(std::meta::parent_of(^^outer) == ^^::); + // Previously an error. + + 10/8/26 std::meta::source_location_of for reflections without a declaration diff --git a/src/interpret.c b/src/interpret.c index d44876e463..f22f7b0615 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -15842,10 +15842,11 @@ Set *parent_rvp to the reflection of the parent that std::meta::parent_of and std::meta::has_parent report for the entity reflected by rvp. That parent is the enumeration type of an enumerator, the class of which the entity is a member, or else the nearest namespace or file scope that encloses its -declaration. Return TRUE when there is such a parent, and FALSE (leaving +declaration (for a namespace or namespace alias, that is the enclosing +namespace). Return TRUE when there is such a parent, and FALSE (leaving *parent_rvp untouched) when the entity is of a kind that has none to report, -such as an unnamed constant or a type that is not a class, an enumeration, or -a typedef. rvp is modified in place: It is normalized by applying +such as an unnamed constant, the global namespace, or a type that is not a +class, an enumeration, or a typedef. rvp is modified in place: It is normalized by applying strip_template_arg and extract_reflected_entity to it (in that order). */ { @@ -15894,6 +15895,16 @@ strip_template_arg and extract_reflected_entity to it (in that order). case iek_variable: scp = &((a_variable*)rvp->entity.ptr)->source_corresp; break; + case iek_namespace: + /* A namespace alias. */ + scp = &((a_namespace*)rvp->entity.ptr)->source_corresp; + break; + case iek_scope: + /* A namespace is reflected as its scope; the correspondence is that of + the namespace. The global namespace (and any other scope) has + none, and therefore no parent. */ + scp = source_corresp_for_reflection(rvp); + break; default: break; } /* switch */ diff --git a/tests/expectations/edg_x86_64_cp/reflections.log b/tests/expectations/edg_x86_64_cp/reflections.log index f9d22cb073..fe4e3c98e7 100644 --- a/tests/expectations/edg_x86_64_cp/reflections.log +++ b/tests/expectations/edg_x86_64_cp/reflections.log @@ -1,4 +1,5 @@ demo1.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: +parent_of_namespace.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: source_location_of.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): diff --git a/tests/tests/reflections/.parent_of_namespace.rto/default.1.1.txt b/tests/tests/reflections/.parent_of_namespace.rto/default.1.1.txt new file mode 100644 index 0000000000..44c36760c6 --- /dev/null +++ b/tests/tests/reflections/.parent_of_namespace.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c diff --git a/tests/tests/reflections/parent_of_namespace.sft.cpp b/tests/tests/reflections/parent_of_namespace.sft.cpp new file mode 100644 index 0000000000..00422351b3 --- /dev/null +++ b/tests/tests/reflections/parent_of_namespace.sft.cpp @@ -0,0 +1,45 @@ +//type:fp +//use_system_includes: true +//edg_header_pack: exp_meta +//require:DO_IL_LOWERING 1 + +/* +std::meta::parent_of accepts a reflection of a namespace or namespace alias +other than the global namespace ([meta.reflection.scope]); the result is the +enclosing namespace. has_parent is true for them and false for ::. +*/ + +#include + +using namespace std::meta; + +namespace outer { + namespace inner { struct S {}; } + inline namespace v1 { int x; } + namespace { int y; } + namespace alias2 = inner; +} +namespace outer::inner { void f(); } +namespace alias1 = outer::inner; + +static_assert(parent_of(^^outer::inner::S) == ^^outer::inner); +static_assert(parent_of(^^outer::inner::f) == ^^outer::inner); +static_assert(parent_of(^^outer::inner) == ^^outer); +static_assert(parent_of(^^outer) == ^^::); +static_assert(parent_of(^^outer::v1) == ^^outer); +static_assert(parent_of(^^outer::x) == ^^outer::v1); +static_assert(parent_of(parent_of(^^outer::y)) == ^^outer); +static_assert(parent_of(^^alias1) == ^^::); +static_assert(parent_of(^^outer::alias2) == ^^outer); +static_assert(is_namespace(parent_of(^^outer::inner))); +static_assert(identifier_of(parent_of(^^outer::inner)) == "outer"); +static_assert(has_parent(^^outer) && has_parent(^^alias1)); +static_assert(!has_parent(^^::)); + +// Walking out to the global namespace. +consteval int depth(info r) { + int n = 0; + for (; r != ^^::; r = parent_of(r)) ++n; + return n; +} +static_assert(depth(^^outer::inner::S) == 3); From 63a6c8741cbb2e5af25085dff07ffe348cac0764 Mon Sep 17 00:00:00 2001 From: Bakiev Damir Date: Thu, 8 Oct 2026 00:36:31 +0300 Subject: [PATCH 5/5] Make std::meta::type_of of a nonstatic member function a plain function type [] For a reflection of a nonstatic member function, type_of returned the routine type of the function, whose this_class is set. That type is not identical to the corresponding function type, so type_of(^^S::f) did not compare equal to ^^int(int) and the spliced type did not match a partial specialization such as X. The type of a nonstatic member function is a plain function type ([dcl.fct]), including its cv-qualifiers and ref-qualifier. type_of now drops the this_class with routine_type_without_this_class (which keeps the qualifiers), building the new type in file scope memory so that the reflection outlives the evaluation. Also: a new test (reflections/type_of_member_function.sft.cpp) and a src/Changes entry. --- src/Changes | 22 +++++++ src/interpret.c | 14 ++++- .../edg_x86_64_cp/reflections.log | 1 + .../default.1.1.txt | 1 + .../type_of_member_function.sft.cpp | 63 +++++++++++++++++++ 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/tests/reflections/.type_of_member_function.rto/default.1.1.txt create mode 100644 tests/tests/reflections/type_of_member_function.sft.cpp diff --git a/src/Changes b/src/Changes index 53865faeca..1c0effb143 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,27 @@ Version 7.1 +10/8/26 +std::meta::type_of for nonstatic member functions + +For a reflection of a nonstatic member function, std::meta::type_of returned +a type that still carried the class of the function, so it did not compare +equal to the reflection of the corresponding function type, and the spliced +type did not match a partial specialization for function types. The type of +a nonstatic member function is a plain function type ([dcl.fct]), including +its cv-qualifiers and ref-qualifier; type_of now returns that type. For +example: + + #include + struct M { + int f(int, double); + void g() const noexcept; + }; + static_assert(type_of(^^M::f) == ^^int(int, double)); + // Previously failed. + static_assert(type_of(^^M::g) == ^^void() const noexcept); + // Previously failed. + + 10/8/26 std::meta::parent_of for namespaces diff --git a/src/interpret.c b/src/interpret.c index f22f7b0615..22fc779ba8 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -15743,7 +15743,10 @@ static a_boolean do_constexpr_std_meta_type_of( a_byte *result_storage, a_byte *complete_obj) /* -Implement std::meta::type_of(info). +Implement std::meta::type_of(info). The type of a nonstatic member function +is a plain function type ([dcl.fct]): the class of the implicit object +parameter is not part of it, but its cv-qualifiers and ref-qualifier are +(e.g., "void() const" for "void f() const"). See do_constexpr_intrinsic_call for the meaning of the parameters. */ @@ -15788,6 +15791,15 @@ See do_constexpr_intrinsic_call for the meaning of the parameters. default: break; } /* switch */ + if (tp != NULL && tp->kind == tk_routine && + tp->variant.routine.extra_info->this_class != NULL) { + /* Drop the class of a nonstatic member function from its type. The new + type must outlive the evaluation. */ + a_memory_region_number region_to_switch_back_to; + switch_to_file_scope_region(®ion_to_switch_back_to); + tp = routine_type_without_this_class(tp, /*copy_default_args=*/FALSE); + switch_back_to_original_region(region_to_switch_back_to); + } /* if */ if (tp == NULL) { info_with_pos(ec_invalid_reflection_for_intrinsic, &call_node->position, ips); diff --git a/tests/expectations/edg_x86_64_cp/reflections.log b/tests/expectations/edg_x86_64_cp/reflections.log index fe4e3c98e7..7c4864a0f9 100644 --- a/tests/expectations/edg_x86_64_cp/reflections.log +++ b/tests/expectations/edg_x86_64_cp/reflections.log @@ -3,3 +3,4 @@ members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: parent_of_namespace.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: source_location_of.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): +type_of_member_function.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: diff --git a/tests/tests/reflections/.type_of_member_function.rto/default.1.1.txt b/tests/tests/reflections/.type_of_member_function.rto/default.1.1.txt new file mode 100644 index 0000000000..44c36760c6 --- /dev/null +++ b/tests/tests/reflections/.type_of_member_function.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c diff --git a/tests/tests/reflections/type_of_member_function.sft.cpp b/tests/tests/reflections/type_of_member_function.sft.cpp new file mode 100644 index 0000000000..eee9642cde --- /dev/null +++ b/tests/tests/reflections/type_of_member_function.sft.cpp @@ -0,0 +1,63 @@ +//type:fp +//use_system_includes: true +//edg_header_pack: exp_meta +//require:DO_IL_LOWERING 1 + +/* +std::meta::type_of of a nonstatic member function is a plain function type +([dcl.fct]): the class is not part of it, but the cv-qualifiers, the +ref-qualifier, and the exception specification are. So it compares equal to +the reflection of that function type, and the spliced type matches partial +specializations for function types. +*/ + +#include + +using namespace std::meta; + +struct M { + int f(int, double); + void g() const noexcept; + void h() volatile &&; + static int s(int); + void d(int = 1); + void e(this M &, int); + virtual void v(); +}; + +static_assert(type_of(^^M::f) == ^^int(int, double)); +static_assert(type_of(^^M::g) == ^^void() const noexcept); +static_assert(type_of(^^M::h) == ^^void() volatile &&); +static_assert(type_of(^^M::s) == ^^int(int)); +static_assert(type_of(^^M::d) == ^^void(int)); +static_assert(type_of(^^M::e) == ^^void(M &, int)); +static_assert(type_of(^^M::v) == ^^void()); +static_assert(is_function_type(type_of(^^M::f))); + +template struct Kind { static constexpr int value = 0; }; +template struct Kind { + static constexpr int value = 1; +}; +template struct Kind { + static constexpr int value = 2; +}; +template struct Kind { + static constexpr int value = 3; +}; + +static_assert(Kind::value == 1); +static_assert(Kind::value == 2); +static_assert(Kind::value == 3); + +// The function type gives back the pointer-to-member type. +using F = [:type_of(^^M::f):]; +using G = [:type_of(^^M::g):]; +using PF = F M::*; +using PG = G M::*; +static_assert(dealias(^^PF) == ^^int (M::*)(int, double)); +static_assert(dealias(^^PG) == ^^void (M::*)() const noexcept); + +// The result does not depend on where type_of is evaluated. +consteval info type_of_g() { return type_of(^^M::g); } +constexpr info g_type = type_of_g(); +static_assert(g_type == type_of(^^M::g));