diff --git a/src/Changes b/src/Changes index 8e798977ad..1c0effb143 100644 --- a/src/Changes +++ b/src/Changes @@ -1,5 +1,113 @@ 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 + +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 + +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 + +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 + +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/src/interpret.c b/src/interpret.c index b9b7e62341..22fc779ba8 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)); @@ -15734,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. */ @@ -15779,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); @@ -15833,10 +15854,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). */ { @@ -15885,6 +15907,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 */ @@ -16696,19 +16728,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 +16752,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 */ @@ -16784,6 +16841,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 +17305,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 aa2a434ab6..7c4864a0f9 100644 --- a/tests/expectations/edg_x86_64_cp/reflections.log +++ b/tests/expectations/edg_x86_64_cp/reflections.log @@ -1 +1,6 @@ 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): +type_of_member_function.sft.cpp:fp:-DTEST_NUMBER=1:REQUIREMENTS NOT MET: 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/.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/.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/.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/.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/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 "); 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); 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))); 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; +} 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));