From 07d09f0af100a9873982fba663800d87bfd73585 Mon Sep 17 00:00:00 2001 From: waffl3x Date: Sun, 7 Jan 2024 00:53:32 +0000 Subject: c++: P0847R7 (deducing this) - xobj lambdas. [PR102609] This implements support for xobj lambdas. There are extensive tests included, but not exhaustive. Dependent lambdas should work and have been tested lightly, but we need more exhaustive tests for them. PR c++/102609 gcc/cp/ChangeLog: PR c++/102609 C++23 P0847R7 (deducing this) - xobj lambdas. * lambda.cc (build_capture_proxy): Don't fold direct object types. * parser.cc (cp_parser_lambda_declarator_opt): Handle xobj lambdas, diagnostics. Comments also updated. * pt.cc (tsubst_function_decl): Handle xobj lambdas. Check object type of xobj lambda call operator, diagnose incorrect types. (tsubst_lambda_expr): Update comment. * semantics.cc (finish_decltype_type): Also consider by-value object parameter qualifications. gcc/testsuite/ChangeLog: PR c++/102609 C++23 P0847R7 (deducing this) - xobj lambdas. * g++.dg/cpp23/explicit-obj-diagnostics8.C: New test. * g++.dg/cpp23/explicit-obj-lambda1.C: New test. * g++.dg/cpp23/explicit-obj-lambda10.C: New test. * g++.dg/cpp23/explicit-obj-lambda11.C: New test. * g++.dg/cpp23/explicit-obj-lambda12.C: New test. * g++.dg/cpp23/explicit-obj-lambda13.C: New test. * g++.dg/cpp23/explicit-obj-lambda2.C: New test. * g++.dg/cpp23/explicit-obj-lambda3.C: New test. * g++.dg/cpp23/explicit-obj-lambda4.C: New test. * g++.dg/cpp23/explicit-obj-lambda5.C: New test. * g++.dg/cpp23/explicit-obj-lambda6.C: New test. * g++.dg/cpp23/explicit-obj-lambda7.C: New test. * g++.dg/cpp23/explicit-obj-lambda8.C: New test. * g++.dg/cpp23/explicit-obj-lambda9.C: New test. Signed-off-by: Waffl3x --- gcc/cp/lambda.cc | 4 ++- gcc/cp/parser.cc | 84 +++++++++++++++++++++++++++++++++++++++++++++++++---- gcc/cp/pt.cc | 74 ++++++++++++++++++++++++++++++++++++++++++---- gcc/cp/semantics.cc | 10 +++++-- 4 files changed, 158 insertions(+), 14 deletions(-) (limited to 'gcc/cp') diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc index 04f159fe8..1d37e5a 100644 --- a/gcc/cp/lambda.cc +++ b/gcc/cp/lambda.cc @@ -404,8 +404,10 @@ build_capture_proxy (tree member, tree init) fn = lambda_function (closure); lam = CLASSTYPE_LAMBDA_EXPR (closure); + object = DECL_ARGUMENTS (fn); /* The proxy variable forwards to the capture field. */ - object = build_fold_indirect_ref (DECL_ARGUMENTS (fn)); + if (INDIRECT_TYPE_P (TREE_TYPE (object))) + object = build_fold_indirect_ref (object); object = finish_non_static_data_member (member, object, NULL_TREE); if (REFERENCE_REF_P (object)) object = TREE_OPERAND (object, 0); diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index b0c08cf..d71522d 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -11874,8 +11874,12 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) else if (cxx_dialect < cxx23) omitted_parms_loc = cp_lexer_peek_token (parser->lexer)->location; - /* In the decl-specifier-seq of the lambda-declarator, each - decl-specifier shall either be mutable or constexpr. */ + /* [expr.prim.lambda.general] + lambda-specifier: + consteval, constexpr, mutable, static + [4] A lambda-specifier-seq shall contain at most one of each + lambda-specifier and shall not contain both constexpr and consteval. + The lambda-specifier-seq shall not contain both mutable and static. */ int declares_class_or_enum; if (cp_lexer_next_token_is_decl_specifier_keyword (parser->lexer)) cp_parser_decl_specifier_seq (parser, @@ -11890,13 +11894,83 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) "%<-std=gnu++2b%>"); omitted_parms_loc = UNKNOWN_LOCATION; } - - if (lambda_specs.storage_class == sc_mutable) + /* Peek at the params, see if we have an xobj parameter. */ + if (param_list && TREE_PURPOSE (param_list) == this_identifier) + { + quals = TYPE_UNQUALIFIED; + /* We still need grokdeclarator to see that this is an xobj function + and finish the rest of the work, don't mutate it. */ + tree const xobj_param = TREE_VALUE (param_list); + tree const param_type = TREE_TYPE (xobj_param); + /* [expr.prim.lambda.closure-5] + Given a lambda with a lambda-capture, the type of the explicit object + parameter, if any, of the lambda's function call operator (possibly + instantiated from a function call operator template) shall be either: + -- the closure type, + -- a class type derived from the closure type, or + -- a reference to a possibly cv-qualified such type. */ + bool const unrelated_with_captures + = (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE + || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr)) + /* Since a lambda's type is anonymous, we can assume an xobj + parameter is unrelated to the closure if it is non-dependent. + If it is dependent we handle it at instantiation time. */ + && !WILDCARD_TYPE_P (non_reference (param_type)); + if (unrelated_with_captures) + { + error_at (DECL_SOURCE_LOCATION (xobj_param), + "a lambda with captures may not have an explicit object " + "parameter of an unrelated type"); + LAMBDA_EXPR_CAPTURE_LIST (lambda_expr) = NULL_TREE; + } + + /* [expr.prim.lambda.general-4] + If the lambda-declarator contains an explicit object parameter + ([dcl.fct]), then no lambda-specifier in the lambda-specifier-seq + shall be mutable or static. */ + if (lambda_specs.storage_class == sc_mutable) + { + auto_diagnostic_group d; + error_at (lambda_specs.locations[ds_storage_class], + "% lambda specifier " + "with explicit object parameter"); + /* Tell the user how to do what they probably meant, maybe fixits + would be appropriate later? */ + if (unrelated_with_captures) + /* The following hints don't make sense when we already have an + unrelated type with captures, don't emit them. */; + else if (!TYPE_REF_P (param_type)) + inform (DECL_SOURCE_LOCATION (xobj_param), + "the passed in closure object will not be mutated because " + "it is taken by value"); + else if (TYPE_READONLY (TREE_TYPE (param_type))) + inform (DECL_SOURCE_LOCATION (xobj_param), + "declare the explicit object parameter as non-const " + "reference instead"); + else + inform (DECL_SOURCE_LOCATION (xobj_param), + "explicit object parameter is already a mutable " + "reference"); + } + else if (lambda_specs.storage_class == sc_static) + { + auto_diagnostic_group d; + error_at (lambda_specs.locations[ds_storage_class], + "% lambda specifier " + "with explicit object parameter"); + inform (DECL_SOURCE_LOCATION (xobj_param), + "explicit object parameter declared here"); + } + } + else if (lambda_specs.storage_class == sc_mutable) { quals = TYPE_UNQUALIFIED; } else if (lambda_specs.storage_class == sc_static) { + /* [expr.prim.lambda.general-4] + If the lambda-specifier-seq contains static, there shall be no + lambda-capture. */ if (LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr)) error_at (lambda_specs.locations[ds_storage_class], @@ -12021,7 +12095,7 @@ cp_parser_lambda_declarator_opt (cp_parser* parser, tree lambda_expr) { DECL_INITIALIZED_IN_CLASS_P (fco) = 1; DECL_ARTIFICIAL (fco) = 1; - if (!LAMBDA_EXPR_STATIC_P (lambda_expr)) + if (DECL_IOBJ_MEMBER_FUNCTION_P (fco)) /* Give the object parameter a different name. */ DECL_NAME (DECL_ARGUMENTS (fco)) = closure_identifier; DECL_SET_LAMBDA_FUNCTION (fco, true); diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index da4b7fa..6900d17 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -14490,9 +14490,9 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain, tree ctx = closure ? closure : DECL_CONTEXT (t); bool member = ctx && TYPE_P (ctx); - /* If this is a static lambda, remove the 'this' pointer added in + /* If this is a static or xobj lambda, remove the 'this' pointer added in tsubst_lambda_expr now that we know the closure type. */ - if (lambda_fntype && DECL_STATIC_FUNCTION_P (t)) + if (lambda_fntype && !DECL_IOBJ_MEMBER_FUNCTION_P (t)) lambda_fntype = static_fn_type (lambda_fntype); if (member && !closure) @@ -14567,12 +14567,12 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain, DECL_NAME (r) = make_conv_op_name (TREE_TYPE (type)); tree parms = DECL_ARGUMENTS (t); - if (closure && !DECL_STATIC_FUNCTION_P (t)) + if (closure && DECL_IOBJ_MEMBER_FUNCTION_P (t)) parms = DECL_CHAIN (parms); parms = tsubst (parms, args, complain, t); for (tree parm = parms; parm; parm = DECL_CHAIN (parm)) DECL_CONTEXT (parm) = r; - if (closure && !DECL_STATIC_FUNCTION_P (t)) + if (closure && DECL_IOBJ_MEMBER_FUNCTION_P (t)) { tree tparm = build_this_parm (r, closure, type_memfn_quals (type)); DECL_NAME (tparm) = closure_identifier; @@ -14608,6 +14608,66 @@ tsubst_function_decl (tree t, tree args, tsubst_flags_t complain, && !grok_op_properties (r, /*complain=*/false)) return error_mark_node; + /* If we are looking at an xobj lambda, we might need to check the type of + its xobj parameter. */ + if (LAMBDA_FUNCTION_P (r) && DECL_XOBJ_MEMBER_FUNCTION_P (r)) + { + tree closure_obj = DECL_CONTEXT (r); + tree lambda_expr = CLASSTYPE_LAMBDA_EXPR (closure_obj); + tree obj_param = TREE_TYPE (DECL_ARGUMENTS (r)); + + if (!(LAMBDA_EXPR_DEFAULT_CAPTURE_MODE (lambda_expr) != CPLD_NONE + || LAMBDA_EXPR_CAPTURE_LIST (lambda_expr))) + /* If a lambda has an empty capture clause, an xobj parameter of + unrelated type is not an error. */; + else if (dependent_type_p (obj_param)) + /* If we are coming from tsubst_lambda_expr we might not have + substituted into our xobj parameter yet. We can't error out until + we know what the type really is so do nothing... + ...but if we are instantiating the call op for real and we don't + have a real type then something has gone incredibly wrong. */ + gcc_assert (lambda_fntype); + else + { + /* We have a lambda with captures, and know the type of the xobj + parameter, time to check it. */ + tree obj_param_type = TYPE_MAIN_VARIANT (non_reference (obj_param)); + if (!same_or_base_type_p (closure_obj, obj_param_type)) + { + /* This error does not emit when the lambda's call operator + template is instantiated by taking its address, such as in + the following case: + + auto f = [x = 0](this auto&&){}; + int (*fp)(int&) = &decltype(f)::operator(); + + It only emits when explicitly calling the call operator with + an explicit template parameter: + + template + struct S : T { + using T::operator(); + operator int() const {return {};} + }; + + auto s = S{[x = 0](this auto&&) {}}; + s.operator()(); + + This is due to resolve_address_of_overloaded_function being + deficient at reporting candidates when overload resolution + fails. + + This diagnostic will be active in the first case if/when + resolve_address_of_overloaded_function is fixed to properly + emit candidates upon failure to resolve to an overload. */ + if (complain & tf_error) + error ("a lambda with captures may not have an explicit " + "object parameter of an unrelated type"); + return error_mark_node; + } + } + } + /* Associate the constraints directly with the instantiation. We don't substitute through the constraints; that's only done when they are checked. */ @@ -19595,7 +19655,11 @@ tsubst_lambda_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) which would be skipped if cp_unevaluated_operand. */ cp_evaluated ev; - /* Fix the type of 'this'. */ + /* Fix the type of 'this'. + For static and xobj member functions we use this to transport the + lambda's closure type. It appears that in the regular case the + object parameter is still pulled off, and then re-added again anyway. + So perhaps we could do something better here? */ fntype = build_memfn_type (fntype, type, type_memfn_quals (fntype), type_memfn_rqual (fntype)); diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 3253cee..86e1adf 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -11933,9 +11933,13 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p, if (WILDCARD_TYPE_P (non_reference (obtype))) /* We don't know what the eventual obtype quals will be. */ goto dependent; - int quals = cp_type_quals (type); - if (INDIRECT_TYPE_P (obtype)) - quals |= cp_type_quals (TREE_TYPE (obtype)); + auto direct_type = [](tree t){ + if (INDIRECT_TYPE_P (t)) + return TREE_TYPE (t); + return t; + }; + int const quals = cp_type_quals (type) + | cp_type_quals (direct_type (obtype)); type = cp_build_qualified_type (type, quals); type = build_reference_type (type); } -- cgit v1.1