From 5862553af66ddb096a2d3b47f7af8c0fdc297f5a 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 | 28 +++++ src/expr.c | 8 +- src/exprutil.c | 9 +- src/exprutil.h | 9 ++ tests/expectations/edg_x86_32/reflections.log | 1 + .../edg_x86_32_cp/reflections.log | 1 + .../edg_x86_64_cp/reflections.log | 1 + .../default.1.1.txt | 1 + .../default.1.2.txt | 5 + .../edg_x86_32.1.1.txt | 34 ++++++ .../edg_x86_32_cp.1.1.txt | 34 ++++++ .../edg_x86_64_cp.1.1.txt | 104 +++++++++++++++++ .../default.1.1.txt | 18 +++ .../edg_x86_32.1.1.txt | 50 ++++++++ .../edg_x86_32_cp.1.1.txt | 50 ++++++++ .../splice_protected_access.sft.cpp | 108 ++++++++++++++++++ .../splice_protected_access_neg.sft.cpp | 42 +++++++ 17 files changed, 499 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_32.1.1.txt create mode 100644 tests/tests/reflections/.splice_protected_access.rto/edg_x86_32_cp.1.1.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_neg.rto/edg_x86_32.1.1.txt create mode 100644 tests/tests/reflections/.splice_protected_access_neg.rto/edg_x86_32_cp.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 de5c529b76..fda6227147 100644 --- a/src/Changes +++ b/src/Changes @@ -32,6 +32,34 @@ the latter has not yet been entered. For example, with --c++17 and Constructor calls had the same problem. This is now fixed. +10/8/26 +Protected members designated by splice-expressions + +A class member designated by a splice-expression is accessible from any +point (N5046 [class.access.base]), and, as clarified by CWG 3109, the +additional access check for protected members (N5046 [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..4d3b968941 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 (N5046 [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..34611cc699 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 + (N5046 [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_32/reflections.log b/tests/expectations/edg_x86_32/reflections.log index aa2a434ab6..ac2b16356e 100644 --- a/tests/expectations/edg_x86_32/reflections.log +++ b/tests/expectations/edg_x86_32/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/expectations/edg_x86_32_cp/reflections.log b/tests/expectations/edg_x86_32_cp/reflections.log index aa2a434ab6..ac2b16356e 100644 --- a/tests/expectations/edg_x86_32_cp/reflections.log +++ b/tests/expectations/edg_x86_32_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/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_32.1.1.txt b/tests/tests/reflections/.splice_protected_access.rto/edg_x86_32.1.1.txt new file mode 100644 index 0000000000..52299eafc6 --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access.rto/edg_x86_32.1.1.txt @@ -0,0 +1,34 @@ +eccp -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +8 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/reflections/.splice_protected_access.rto/edg_x86_32_cp.1.1.txt b/tests/tests/reflections/.splice_protected_access.rto/edg_x86_32_cp.1.1.txt new file mode 100644 index 0000000000..52299eafc6 --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access.rto/edg_x86_32_cp.1.1.txt @@ -0,0 +1,34 @@ +eccp -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +8 errors detected in the compilation of "Test_name.c". 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..17f4112f74 --- /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 78: error: expression must have a constant value + constexpr std::meta::info prot = member_named((decltype(^^0){}), "prot"); + ^ +"Test_name.c", line 30: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 30: 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 get = member_named((decltype(^^0){}), "get"); + ^ +"Test_name.c", line 30: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 30: 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 set = member_named((decltype(^^0){}), "set"); + ^ +"Test_name.c", line 30: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 30: note: called from: + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ + +"Test_name.c", line 81: error: expression must have a constant value + constexpr std::meta::info priv = member_named((decltype(^^0){}), "priv"); + ^ +"Test_name.c", line 30: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 30: note: called from: + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ + +"Test_name.c", line 86: error: member "C::prot" (declared at line 38) is inaccessible + int (C::*pm) = (&C::prot); + ^ + +"Test_name.c", line 87: error: function "C::get" (declared at line 39) is inaccessible + int (C::*pf)(void) const = (&C::get); + ^ + +"Test_name.c", line 88: error: function "C::set" (declared at line 40) is inaccessible + void (C::*ps)(int) noexcept = (&C::set); + ^ + +"Test_name.c", line 91: error: function "C::get" (declared at line 39) is inaccessible + int e = c.get() + 1; + ^ + +"Test_name.c", line 93: error: member "C::prot" (declared at line 38) is inaccessible + int f = pc->prot; + ^ + +"Test_name.c", line 94: error: function "C::set" (declared at line 40) is inaccessible + d.set(5); + ^ + +"Test_name.c", line 95: error: member "C::prot" (declared at line 38) is inaccessible + int g = d.prot; + ^ + +"Test_name.c", line 101: 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 50: note: substituting explicit template arguments "<>" for function template "read(T *)" failed + template < info M, typename T > int read ( T * p ) { + ^ + +"Test_name.c", line 101: 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 54: note: substituting explicit template arguments "<>" for function template "call" failed + template < info M, typename T > int call ( T & obj ) { + ^ + +"Test_name.c", line 101: 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 50: note: substituting explicit template arguments "<>" for function template "read(T *)" failed + template < info M, typename T > int read ( T * p ) { + ^ + +"Test_name.c", line 105: 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 30: note: invalid reflection for intrinsic metafunction + for (std::meta::info m : members_of(cls, access_context::unchecked())) { + ^ +"Test_name.c", line 30: 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_neg.rto/edg_x86_32.1.1.txt b/tests/tests/reflections/.splice_protected_access_neg.rto/edg_x86_32.1.1.txt new file mode 100644 index 0000000000..83113c180f --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access_neg.rto/edg_x86_32.1.1.txt @@ -0,0 +1,50 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +"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 + ^ + +12 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/reflections/.splice_protected_access_neg.rto/edg_x86_32_cp.1.1.txt b/tests/tests/reflections/.splice_protected_access_neg.rto/edg_x86_32_cp.1.1.txt new file mode 100644 index 0000000000..83113c180f --- /dev/null +++ b/tests/tests/reflections/.splice_protected_access_neg.rto/edg_x86_32_cp.1.1.txt @@ -0,0 +1,50 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +"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 + ^ + +12 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..15c22f99d8 --- /dev/null +++ b/tests/tests/reflections/splice_protected_access.sft.cpp @@ -0,0 +1,108 @@ +//type:rp +//use_system_includes: true +//edg_header_pack: exp_meta + +/* +A class member designated by a splice-expression is accessible from any +point (N5046 [class.access.base]), and the additional access check for +protected members (N5046 [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..f4374d770f --- /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 (N5046 [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 84d461b6e836009c914d21349c748208277d1399 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 | 26 +++ src/interpret.c | 160 +++++++++++++++++ tests/expectations/edg_x86_32/reflections.log | 1 + .../edg_x86_32_cp/reflections.log | 1 + .../edg_x86_64_cp/reflections.log | 1 + .../.members_of_order.rto/default.1.1.txt | 1 + .../.members_of_order.rto/edg_x86_32.1.1.txt | 34 ++++ .../reflections/members_of_order.sft.cpp | 165 ++++++++++++++++++ 8 files changed, 389 insertions(+) create mode 100644 tests/tests/reflections/.members_of_order.rto/default.1.1.txt create mode 100644 tests/tests/reflections/.members_of_order.rto/edg_x86_32.1.1.txt create mode 100644 tests/tests/reflections/members_of_order.sft.cpp diff --git a/src/Changes b/src/Changes index fda6227147..4db1ef280e 100644 --- a/src/Changes +++ b/src/Changes @@ -32,6 +32,32 @@ the latter has not yet been entered. For example, with --c++17 and Constructor calls had the same problem. This is now fixed. +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 +N5046 [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. The order is that of +the first declarations of the members, so a member that is defined outside +its class or namespace, or declared by a macro expansion, is placed where it +is declared. 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 1ebcfc1719..d1dbdd757a 100644 --- a/src/interpret.c +++ b/src/interpret.c @@ -16784,6 +16784,164 @@ not the scope). } /* collect_scoped_reflections */ +typedef struct a_member_order_key { + /* The sort key of a member in sort_reflections_in_declaration_order. */ + a_boolean is_implicit; + /* TRUE for an implicitly-declared member, i.e., a + compiler-generated function. */ + a_decl_sequence_number + seq; + /* The declaration sequence number of the symbol of + the member, or, for a member that has none, that + of the member after which it is placed. */ + a_boolean follows; + /* TRUE if the member has no sequence number of its + own and seq is that of its predecessor. */ +} a_member_order_key; + +namespace detail { + +template<> +struct Is_trivially_copyable_edg_impl : + Integral_constant { +}; /* Is_trivially_copyable_edg_impl */ + +template<> +struct Is_trivially_destructible_edg_impl : + Integral_constant { +}; /* Is_trivially_destructible_edg_impl */ + +} /* namespace detail */ + + +static a_decl_sequence_number decl_sequence_for_reflection( + a_reflection_value *rvp) +/* +Return the declaration sequence number (see a_symbol::decl_seq) of the symbol +of the entity reflected by rvp, or NO_DECL_SEQUENCE_NUMBER if the entity has +no symbol or its symbol has no sequence number. The latter is the case for +compiler-generated special member functions and for unnamed bit-fields, which +all share one symbol. +*/ +{ + a_decl_sequence_number seq = NO_DECL_SEQUENCE_NUMBER; + a_source_correspondence *scp = source_corresp_for_reflection(rvp); + + if (scp != NULL && scp->assoc_info != NULL) { + seq = ((a_symbol_ptr)scp->assoc_info)->decl_seq; + } /* if */ + return seq; +} /* decl_sequence_for_reflection */ + + +static int compare_member_order_keys(const a_member_order_key *k1, + const a_member_order_key *k2) +/* +Compare two member sort keys (see sort_reflections_in_declaration_order). +Return a negative value if the member with key k1 comes first, a positive +value if the member with key k2 does, and 0 if the keys are the same. +Implicitly-declared members come after all the others; among the rest, the +members are ordered by their declaration sequence numbers, and a member +placed after its predecessor follows the predecessor itself. +*/ +{ + int result; + + if (k1->is_implicit != k2->is_implicit) { + result = k1->is_implicit ? 1 : -1; + } else if (k1->seq != k2->seq) { + result = k1->seq < k2->seq ? -1 : 1; + } else if (k1->follows != k2->follows) { + result = k1->follows ? 1 : -1; + } else { + result = 0; + } /* if */ + return result; +} /* compare_member_order_keys */ + + +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 (N5046 [meta.reflection.member.queries]). The order is that of the +declaration sequence numbers of the symbols of the members (a_symbol::decl_seq; +see decl_sequence_for_reflection). A sequence number is assigned at the first +declaration of a member, so, unlike the declaration positions, the numbers are +not disturbed by macro expansions, and they do not move to the definition of a +member that is defined outside its class or namespace. Implicitly-declared +members (compiler-generated functions) have no sequence number and come after +all the others. An unnamed bit-field has no sequence number either; it is +placed after the member whose declaration position precedes its own most +closely or, if there are no positions to compare, after the member collected +before it. Members whose keys are still the same keep their relative order. +*/ +{ + size_t n = reflections->length(), i, j; + Dyn_array keys(n); + Dyn_array order(n); + Dyn_array sorted(n); + + for (i = 0; i < n; ++i) { + a_reflection_value *rvp = &(*reflections)[i]; + a_member_order_key key; + key.is_implicit = (rvp->entity.kind == iek_routine && + ((a_routine*)rvp->entity.ptr)->compiler_generated); + key.seq = key.is_implicit ? NO_DECL_SEQUENCE_NUMBER + : decl_sequence_for_reflection(rvp); + key.follows = FALSE; + keys.push_back(key); + } /* for */ + /* Give the members without a sequence number (unnamed bit-fields) the + number of a predecessor. Only members whose declaration position is + before that of the bit-field are candidates, and the one declared last + (with the largest number) is the closest. */ + for (i = 0; i < n; ++i) { + if (!keys[i].is_implicit && keys[i].seq == NO_DECL_SEQUENCE_NUMBER) { + a_source_correspondence *scp; + a_boolean found = FALSE; + scp = source_corresp_for_reflection(&(*reflections)[i]); + if (scp != NULL && scp->decl_position.seq != 0) { + for (j = 0; j < n; ++j) { + a_source_correspondence *scp2; + if (keys[j].is_implicit || + keys[j].seq == NO_DECL_SEQUENCE_NUMBER || + keys[j].follows) { + continue; + } /* if */ + scp2 = source_corresp_for_reflection(&(*reflections)[j]); + if (scp2 != NULL && scp2->decl_position.seq != 0 && + compare_source_positions(&scp2->decl_position, + &scp->decl_position) < 0 && + (!found || keys[j].seq > keys[i].seq)) { + keys[i].seq = keys[j].seq; + found = TRUE; + } /* if */ + } /* for */ + } /* if */ + if (!found && i > 0) { + keys[i].seq = keys[i-1].seq; + } /* if */ + keys[i].follows = TRUE; + } /* if */ + } /* for */ + for (i = 0; i < n; ++i) { + order.push_back(i); + } /* for */ + sort(&order, [&keys](size_t i1, size_t i2) { + int cmp = compare_member_order_keys(&keys[i1], &keys[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 +17342,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_32/reflections.log b/tests/expectations/edg_x86_32/reflections.log index ac2b16356e..be20a9de15 100644 --- a/tests/expectations/edg_x86_32/reflections.log +++ b/tests/expectations/edg_x86_32/reflections.log @@ -1,2 +1,3 @@ demo1.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): +members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): diff --git a/tests/expectations/edg_x86_32_cp/reflections.log b/tests/expectations/edg_x86_32_cp/reflections.log index ac2b16356e..32987394d4 100644 --- a/tests/expectations/edg_x86_32_cp/reflections.log +++ b/tests/expectations/edg_x86_32_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/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.rto/edg_x86_32.1.1.txt b/tests/tests/reflections/.members_of_order.rto/edg_x86_32.1.1.txt new file mode 100644 index 0000000000..c53979935c --- /dev/null +++ b/tests/tests/reflections/.members_of_order.rto/edg_x86_32.1.1.txt @@ -0,0 +1,34 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +8 errors detected in the compilation of "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..a2fbf5ea59 --- /dev/null +++ b/tests/tests/reflections/members_of_order.sft.cpp @@ -0,0 +1,165 @@ +//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 (N5046 +[meta.reflection.member.queries]); implicitly-declared special members come +after the user-declared members. Members of a namespace are also returned in +declaration order. The order is that of the first declarations: a member +defined outside its class or namespace, or declared by a macro expansion, is +placed where it is declared. +*/ + +#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); + +// Members declared in the class and defined outside it are placed where they +// are declared, not where they are defined. +struct O { + void f(); + int x; + static int s; + struct N; + int y; + O(); + int z; +}; +void O::f() {} +int O::s; +struct O::N {}; +O::O() {} + +static_assert(names(^^O) == "f x s N y O z "); + +// Members declared by one macro expansion are in the order of the expansion, +// although they all have the position of the macro invocation. +#define MEMBERS(n) int m1##n; void g##n(); int m2##n; using T##n = int; +struct M { + int a; + MEMBERS(1) MEMBERS(2) + int b; +}; + +static_assert(names(^^M) == "a m11 g1 m21 T1 m12 g2 m22 T2 b "); + +// An unnamed bit-field is placed after the member declared just before it, +// whatever the kind of that member. +struct B { + int a : 2; + void h(); + int : 3; + int b : 2; + using U = int; + int : 1; + int c : 2; +}; + +static_assert(names(^^B) == "a h b U c "); + +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, also +// when a member is defined outside the template. +template struct TS { + U x; void h(); U y; + static U z; + void k(); + U w; +}; +template void TS::k() {} + +static_assert(names(^^TS) == "x h y z k w "); +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; +} +void ns::f() {} +struct ns::C {}; +namespace ns { + void g(); + int w; +} +void ns::g() {} + +static_assert(names(^^ns) == "f v C inner A alias g w "); From 6e6eb029e421945ba3a48160927176002dcbaad1 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 | 105 ++++++++++++------ tests/expectations/edg_x86_32/reflections.log | 1 + .../edg_x86_32_cp/reflections.log | 1 + .../edg_x86_64_cp/reflections.log | 1 + .../.source_location_of.rto/default.1.1.txt | 1 + .../edg_x86_32.1.1.txt | 34 ++++++ .../reflections/source_location_of.sft.cpp | 51 +++++++++ 8 files changed, 177 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.rto/edg_x86_32.1.1.txt create mode 100644 tests/tests/reflections/source_location_of.sft.cpp diff --git a/src/Changes b/src/Changes index 4db1ef280e..6511cabdd3 100644 --- a/src/Changes +++ b/src/Changes @@ -32,6 +32,24 @@ the latter has not yet been entered. For example, with --c++17 and Constructor calls had the same problem. This is now fixed. +10/8/26 +std::meta::source_location_of for reflections without a declaration + +As specified in N5046 [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 d1dbdd757a..bf9af5dfb3 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,23 @@ 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). N5046 +[meta.reflection.names] specifies the function without a Throws clause (in +P2996R13, without a "Constant When" clause), so 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 +16730,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_immediate_class_type(tp) || is_immediate_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_32/reflections.log b/tests/expectations/edg_x86_32/reflections.log index be20a9de15..e2f6379ff7 100644 --- a/tests/expectations/edg_x86_32/reflections.log +++ b/tests/expectations/edg_x86_32/reflections.log @@ -1,3 +1,4 @@ demo1.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: +source_location_of.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): diff --git a/tests/expectations/edg_x86_32_cp/reflections.log b/tests/expectations/edg_x86_32_cp/reflections.log index 32987394d4..f9d22cb073 100644 --- a/tests/expectations/edg_x86_32_cp/reflections.log +++ b/tests/expectations/edg_x86_32_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/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.rto/edg_x86_32.1.1.txt b/tests/tests/reflections/.source_location_of.rto/edg_x86_32.1.1.txt new file mode 100644 index 0000000000..c53979935c --- /dev/null +++ b/tests/tests/reflections/.source_location_of.rto/edg_x86_32.1.1.txt @@ -0,0 +1,34 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +8 errors detected in the compilation of "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..6a0ebaa92c --- /dev/null +++ b/tests/tests/reflections/source_location_of.sft.cpp @@ -0,0 +1,51 @@ +//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 +(N5046 [meta.reflection.names] specifies it without a Throws clause, and +P2996R13 without a "Constant When" clause). +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 1714a36adcd7e29f859759fd54c5a377274b602d 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 +++++-- tests/expectations/edg_x86_32/reflections.log | 1 + .../edg_x86_32_cp/reflections.log | 1 + .../edg_x86_64_cp/reflections.log | 1 + .../.parent_of_namespace.rto/default.1.1.txt | 1 + .../edg_x86_32.1.1.txt | 34 ++++++++++++++ .../reflections/parent_of_namespace.sft.cpp | 45 +++++++++++++++++++ 8 files changed, 115 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.rto/edg_x86_32.1.1.txt create mode 100644 tests/tests/reflections/parent_of_namespace.sft.cpp diff --git a/src/Changes b/src/Changes index 6511cabdd3..6020d88d92 100644 --- a/src/Changes +++ b/src/Changes @@ -32,6 +32,24 @@ the latter has not yet been entered. For example, with --c++17 and Constructor calls had the same problem. This is now fixed. +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 N5046 +[meta.reflection.queries], 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 bf9af5dfb3..3011465051 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_32/reflections.log b/tests/expectations/edg_x86_32/reflections.log index e2f6379ff7..9fcd8cd644 100644 --- a/tests/expectations/edg_x86_32/reflections.log +++ b/tests/expectations/edg_x86_32/reflections.log @@ -1,4 +1,5 @@ demo1.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: +parent_of_namespace.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: source_location_of.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): diff --git a/tests/expectations/edg_x86_32_cp/reflections.log b/tests/expectations/edg_x86_32_cp/reflections.log index f9d22cb073..fe4e3c98e7 100644 --- a/tests/expectations/edg_x86_32_cp/reflections.log +++ b/tests/expectations/edg_x86_32_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/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.rto/edg_x86_32.1.1.txt b/tests/tests/reflections/.parent_of_namespace.rto/edg_x86_32.1.1.txt new file mode 100644 index 0000000000..c53979935c --- /dev/null +++ b/tests/tests/reflections/.parent_of_namespace.rto/edg_x86_32.1.1.txt @@ -0,0 +1,34 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +8 errors detected in the compilation of "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..5156e73db5 --- /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 (N5046 [meta.reflection.queries]); 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 f45007796fad480df5059755534749b7819c2535 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 ++++- tests/expectations/edg_x86_32/reflections.log | 1 + .../edg_x86_32_cp/reflections.log | 1 + .../edg_x86_64_cp/reflections.log | 1 + .../default.1.1.txt | 1 + .../edg_x86_32.1.1.txt | 34 ++++++++++ .../type_of_member_function.sft.cpp | 63 +++++++++++++++++++ 8 files changed, 136 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.rto/edg_x86_32.1.1.txt create mode 100644 tests/tests/reflections/type_of_member_function.sft.cpp diff --git a/src/Changes b/src/Changes index 6020d88d92..2031de4169 100644 --- a/src/Changes +++ b/src/Changes @@ -32,6 +32,28 @@ the latter has not yet been entered. For example, with --c++17 and Constructor calls had the same problem. This is now fixed. +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 (N5046 [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 3011465051..1bec6853db 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 (N5046 [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_32/reflections.log b/tests/expectations/edg_x86_32/reflections.log index 9fcd8cd644..1ea0902b19 100644 --- a/tests/expectations/edg_x86_32/reflections.log +++ b/tests/expectations/edg_x86_32/reflections.log @@ -3,3 +3,4 @@ members_of_order.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: parent_of_namespace.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: source_location_of.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: splice_protected_access.sft.cpp:rp:-DTEST_NUMBER=1:FAIL(COMPILE): +type_of_member_function.sft.cpp:fp:-DTEST_NUMBER=1:FAIL: diff --git a/tests/expectations/edg_x86_32_cp/reflections.log b/tests/expectations/edg_x86_32_cp/reflections.log index fe4e3c98e7..7c4864a0f9 100644 --- a/tests/expectations/edg_x86_32_cp/reflections.log +++ b/tests/expectations/edg_x86_32_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/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.rto/edg_x86_32.1.1.txt b/tests/tests/reflections/.type_of_member_function.rto/edg_x86_32.1.1.txt new file mode 100644 index 0000000000..c53979935c --- /dev/null +++ b/tests/tests/reflections/.type_of_member_function.rto/edg_x86_32.1.1.txt @@ -0,0 +1,34 @@ +fe_only -DTEST_NUMBER=1 --c++26 --set_flag=reflection Test_name.c +"/usr/include/c++/15/type_traits", line 514: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating_point_helper<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/bits/cpp_type_traits.h", line 348: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __is_floating<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 224: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + struct __promote<__gnu_cxx::__bfloat16_t> + ^ + +"/usr/include/c++/15/ext/type_traits.h", line 225: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { typedef __gnu_cxx::__bfloat16_t __type; }; + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 135: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + constexpr __gnu_cxx::__bfloat16_t + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 136: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + abs(__gnu_cxx::__bfloat16_t __x) + ^ + +"/usr/include/c++/15/bits/std_abs.h", line 137: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + { return __gnu_cxx::__bfloat16_t(__builtin_fabsf(__x)); } + ^ + +"/usr/include/c++/15/bits/ostream.h", line 286: error: namespace "__gnu_cxx" has no member "__bfloat16_t" + operator<<(__gnu_cxx::__bfloat16_t __f) + ^ + +8 errors detected in the compilation of "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..b76b64e29a --- /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 +(N5046 [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));