diff options
author | Jason Merrill <jason@gcc.gnu.org> | 2019-11-05 18:56:18 -0500 |
---|---|---|
committer | Jason Merrill <jason@gcc.gnu.org> | 2019-11-05 18:56:18 -0500 |
commit | b7689b962dd6536bbb2567bfdec52e35109af7ab (patch) | |
tree | 6a00e1710db7a55cbcfec28cfbaf837950a9c70b /gcc/cp/method.c | |
parent | b63566a4045e9cc27f739c89f863dbfb9dbe7860 (diff) | |
download | gcc-b7689b962dd6536bbb2567bfdec52e35109af7ab.zip gcc-b7689b962dd6536bbb2567bfdec52e35109af7ab.tar.gz gcc-b7689b962dd6536bbb2567bfdec52e35109af7ab.tar.bz2 |
Implement C++20 operator<=>.
There are three major pieces to this support: scalar operator<=>,
synthesis of comparison operators, and rewritten/reversed overload
resolution (e.g. a < b becomes 0 > b <=> a).
Unlike other defaulted functions, where we use synthesized_method_walk to
semi-simulate what the definition of the function will be like, this patch
determines the characteristics of a comparison operator by trying to define
it.
My handling of non-dependent rewritten operators in templates can still use
some work: build_min_non_dep_op_overload can't understand the rewrites and
crashes, so I'm avoiding it for now by clearing *overload. This means we'll
do name lookup again at instantiation time, which can incorrectly mean a
different result. I'll poke at this more in stage 3.
I'm leaving out a fourth section ("strong structural equality") even though
I've implemented it, because it seems likely to change radically tomorrow.
Thanks to Tim van Deurzen and Jakub for implementing lexing of the <=>
operator, and Jonathan for the initial <compare> header.
gcc/cp/
* cp-tree.h (struct lang_decl_fn): Add maybe_deleted bitfield.
(DECL_MAYBE_DELETED): New.
(enum special_function_kind): Add sfk_comparison.
(LOOKUP_REWRITTEN, LOOKUP_REVERSED): New.
* call.c (struct z_candidate): Add rewritten and reversed methods.
(add_builtin_candidate): Handle SPACESHIP_EXPR.
(add_builtin_candidates): Likewise.
(add_candidates): Don't add a reversed candidate if the parms are
the same.
(add_operator_candidates): Split out from build_new_op_1. Handle
rewritten and reversed candidates.
(add_candidate): Swap conversions of reversed candidate.
(build_new_op_1): Swap them back. Build a second operation for
rewritten candidates.
(extract_call_expr): Handle rewritten calls.
(same_fn_or_template): New.
(joust): Handle rewritten and reversed candidates.
* class.c (add_implicitly_declared_members): Add implicit op==.
(classtype_has_op, classtype_has_defaulted_op): New.
* constexpr.c (cxx_eval_binary_expression): Handle SPACESHIP_EXPR.
(cxx_eval_constant_expression, potential_constant_expression_1):
Likewise.
* cp-gimplify.c (genericize_spaceship): New.
(cp_genericize_r): Use it.
* cp-objcp-common.c (cp_common_init_ts): Handle SPACESHIP_EXPR.
* decl.c (finish_function): Handle deleted function.
* decl2.c (grokfield): SET_DECL_FRIEND_CONTEXT on defaulted friend.
(mark_used): Check DECL_MAYBE_DELETED. Remove assumption that
defaulted functions are non-static members.
* error.c (dump_expr): Handle SPACESHIP_EXPR.
* method.c (type_has_trivial_fn): False for sfk_comparison.
(enum comp_cat_tag, struct comp_cat_info_t): New types.
(comp_cat_cache): New array variable.
(lookup_comparison_result, lookup_comparison_category)
(is_cat, cat_tag_for, spaceship_comp_cat)
(spaceship_type, genericize_spaceship)
(common_comparison_type, early_check_defaulted_comparison)
(comp_info, build_comparison_op): New.
(synthesize_method): Handle sfk_comparison. Handle deleted.
(get_defaulted_eh_spec, maybe_explain_implicit_delete)
(explain_implicit_non_constexpr, implicitly_declare_fn)
(defaulted_late_check, defaultable_fn_check): Handle sfk_comparison.
* name-lookup.c (get_std_name_hint): Add comparison categories.
* tree.c (special_function_p): Add sfk_comparison.
* typeck.c (cp_build_binary_op): Handle SPACESHIP_EXPR.
2019-11-05 Tim van Deurzen <tim@kompiler.org>
Add new tree code for the spaceship operator.
gcc/cp/
* cp-tree.def: Add new tree code.
* operators.def: New binary operator.
* parser.c: Add new token and tree code.
libcpp/
* cpplib.h: Add spaceship operator for C++.
* lex.c: Implement conditional lexing of spaceship operator for C++20.
2019-11-05 Jonathan Wakely <jwakely@redhat.com>
libstdc++-v3/
* libsupc++/compare: New header.
* libsupc++/Makefile.am (std_HEADERS): Add compare.
* include/std/version: Define __cpp_lib_three_way_comparison.
* include/std/functional: #include <compare>.
From-SVN: r277865
Diffstat (limited to 'gcc/cp/method.c')
-rw-r--r-- | gcc/cp/method.c | 735 |
1 files changed, 713 insertions, 22 deletions
diff --git a/gcc/cp/method.c b/gcc/cp/method.c index 09e9c73..c9dd90f 100644 --- a/gcc/cp/method.c +++ b/gcc/cp/method.c @@ -406,6 +406,7 @@ type_has_trivial_fn (tree ctype, special_function_kind sfk) case sfk_virtual_destructor: return !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (ctype); case sfk_inheriting_constructor: + case sfk_comparison: return false; default: gcc_unreachable (); @@ -877,6 +878,588 @@ do_build_copy_assign (tree fndecl) finish_compound_stmt (compound_stmt); } +/* C++20 <compare> comparison category types. */ + +enum comp_cat_tag +{ + cc_weak_equality, + cc_strong_equality, + cc_partial_ordering, + cc_weak_ordering, + cc_strong_ordering, + cc_last +}; + +/* Names of the comparison categories and their value members, to be indexed by + comp_cat_tag enumerators. genericize_spaceship below relies on the ordering + of the members. */ + +struct comp_cat_info_t +{ + const char *name; + const char *members[4]; +}; +static const comp_cat_info_t comp_cat_info[cc_last] += { + { "weak_equality", "equivalent", "nonequivalent" }, + { "strong_equality", "equal", "nonequal" }, + { "partial_ordering", "equivalent", "greater", "less", "unordered" }, + { "weak_ordering", "equivalent", "greater", "less" }, + { "strong_ordering", "equal", "greater", "less" } +}; + +/* A cache of the category types to speed repeated lookups. */ + +static GTY((deletable)) tree comp_cat_cache[cc_last]; + +/* Look up one of the result variables in the comparison category type. */ + +static tree +lookup_comparison_result (tree type, const char *name_str, + tsubst_flags_t complain = tf_warning_or_error) +{ + tree name = get_identifier (name_str); + tree decl = lookup_qualified_name (type, name); + if (TREE_CODE (decl) != VAR_DECL) + { + if (complain & tf_error) + { + auto_diagnostic_group d; + if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST) + qualified_name_lookup_error (type, name, decl, input_location); + else + error ("%<%T::%D%> is not a static data member", type, decl); + inform (input_location, "determining value of %qs", "operator<=>"); + } + return error_mark_node; + } + return decl; +} + +/* Look up a <compare> comparison category type in std. */ + +static tree +lookup_comparison_category (comp_cat_tag tag, + tsubst_flags_t complain = tf_warning_or_error) +{ + if (tree cached = comp_cat_cache[tag]) + return cached; + + tree name = get_identifier (comp_cat_info[tag].name); + tree decl = lookup_qualified_name (std_node, name); + if (TREE_CODE (decl) != TYPE_DECL) + { + if (complain & tf_error) + { + auto_diagnostic_group d; + if (decl == error_mark_node || TREE_CODE (decl) == TREE_LIST) + qualified_name_lookup_error (std_node, name, decl, input_location); + else + error ("%<std::%D%> is not a type", decl); + inform (input_location, "forming type of %qs", "operator<=>"); + } + return error_mark_node; + } + /* Also make sure we can look up the value members now, since we won't + really use them until genericize time. */ + tree type = TREE_TYPE (decl); + for (int i = 0; i < 4; ++i) + { + const char *p = comp_cat_info[tag].members[i]; + if (!p) break; + if (lookup_comparison_result (type, p, complain) + == error_mark_node) + return error_mark_node; + } + return comp_cat_cache[tag] = type; +} + +/* Wrapper that takes the tag rather than the type. */ + +static tree +lookup_comparison_result (comp_cat_tag tag, const char *name_str, + tsubst_flags_t complain = tf_warning_or_error) +{ + tree type = lookup_comparison_category (tag, complain); + return lookup_comparison_result (type, name_str, complain); +} + +/* Wrapper that takes the index into the members array instead of the name. */ + +static tree +lookup_comparison_result (comp_cat_tag tag, tree type, int idx) +{ + const char *name_str = comp_cat_info[tag].members[idx]; + if (!name_str) + return NULL_TREE; + return lookup_comparison_result (type, name_str); +} + +/* Does TYPE correspond to TAG? */ + +static bool +is_cat (tree type, comp_cat_tag tag) +{ + tree name = TYPE_LINKAGE_IDENTIFIER (type); + return id_equal (name, comp_cat_info[tag].name); +} + +/* Return the comp_cat_tag for TYPE. */ + +static comp_cat_tag +cat_tag_for (tree type) +{ + for (int i = 0; i < cc_last; ++i) + { + comp_cat_tag tag = (comp_cat_tag)i; + if (is_cat (type, tag)) + return tag; + } + return cc_last; +} + +/* Return the comparison category tag of a <=> expression with non-class type + OPTYPE. */ + +static comp_cat_tag +spaceship_comp_cat (tree optype) +{ + if (INTEGRAL_OR_ENUMERATION_TYPE_P (optype) || TYPE_PTROBV_P (optype)) + return cc_strong_ordering; + else if (TREE_CODE (optype) == REAL_TYPE) + return cc_partial_ordering; + else if (TYPE_PTRFN_P (optype) || TYPE_PTRMEM_P (optype) + || NULLPTR_TYPE_P (optype)) + return cc_strong_equality; + else if (TREE_CODE (optype) == COMPLEX_TYPE) + { + tree intype = optype; + while (TREE_CODE (intype) == COMPLEX_TYPE) + intype = TREE_TYPE (intype); + if (TREE_CODE (intype) == REAL_TYPE) + return cc_weak_equality; + else + return cc_strong_equality; + } + + /* FIXME should vector <=> produce a vector of one of the above? */ + gcc_unreachable (); +} + +/* Return the comparison category type of a <=> expression with non-class type + OPTYPE. */ + +tree +spaceship_type (tree optype, tsubst_flags_t complain) +{ + comp_cat_tag tag = spaceship_comp_cat (optype); + return lookup_comparison_category (tag, complain); +} + +/* Turn <=> with type TYPE and operands OP0 and OP1 into GENERIC. */ + +tree +genericize_spaceship (tree type, tree op0, tree op1) +{ + /* ??? maybe optimize based on knowledge of representation? */ + comp_cat_tag tag = cat_tag_for (type); + gcc_checking_assert (tag < cc_last); + + tree eq = lookup_comparison_result (tag, type, 0); + tree negt = lookup_comparison_result (tag, type, 1); + + if (tag == cc_strong_equality || tag == cc_weak_equality) + { + tree comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1); + return fold_build3 (COND_EXPR, type, comp, eq, negt); + } + + tree r; + op0 = save_expr (op0); + op1 = save_expr (op1); + + if (tag == cc_partial_ordering) + { + /* op0 == op1 ? equivalent : op0 < op1 ? less : + op0 > op1 ? greater : unordered */ + tree uo = lookup_comparison_result (tag, type, 3); + tree comp = fold_build2 (GT_EXPR, boolean_type_node, op0, op1); + r = fold_build3 (COND_EXPR, type, comp, negt, uo); + } + else + /* op0 == op1 ? equal : op0 < op1 ? less : greater */ + r = negt; + + tree lt = lookup_comparison_result (tag, type, 2); + tree comp = fold_build2 (LT_EXPR, boolean_type_node, op0, op1); + r = fold_build3 (COND_EXPR, type, comp, lt, r); + + comp = fold_build2 (EQ_EXPR, boolean_type_node, op0, op1); + r = fold_build3 (COND_EXPR, type, comp, eq, r); + + return r; +} + +/* Check that the signature of a defaulted comparison operator is + well-formed. */ + +static bool +early_check_defaulted_comparison (tree fn) +{ + location_t loc = DECL_SOURCE_LOCATION (fn); + tree ctx; + if (DECL_CLASS_SCOPE_P (fn)) + ctx = DECL_CONTEXT (fn); + else + ctx = DECL_FRIEND_CONTEXT (fn); + bool ok = true; + + if (!DECL_OVERLOADED_OPERATOR_IS (fn, SPACESHIP_EXPR) + && !same_type_p (TREE_TYPE (TREE_TYPE (fn)), boolean_type_node)) + { + error_at (loc, "defaulted %qD must return %<bool%>", fn); + ok = false; + } + + int i = DECL_NONSTATIC_MEMBER_FUNCTION_P (fn); + if (i && type_memfn_quals (TREE_TYPE (fn)) != TYPE_QUAL_CONST) + { + error_at (loc, "defaulted %qD must be %<const%>", fn); + ok = false; + } + tree parmnode = FUNCTION_FIRST_USER_PARMTYPE (fn); + for (; parmnode != void_list_node; parmnode = TREE_CHAIN (parmnode)) + { + ++i; + tree parmtype = TREE_VALUE (parmnode); + diagnostic_t kind = DK_UNSPECIFIED; + int opt = 0; + if (same_type_p (parmtype, ctx)) + /* The draft specifies const reference, but let's also allow by-value + unless -Wpedantic, hopefully it will be added soon. */ + kind = DK_PEDWARN, + opt = OPT_Wpedantic; + else if (TREE_CODE (parmtype) != REFERENCE_TYPE + || TYPE_QUALS (TREE_TYPE (parmtype)) != TYPE_QUAL_CONST + || !(same_type_ignoring_top_level_qualifiers_p + (TREE_TYPE (parmtype), ctx))) + kind = DK_ERROR; + if (kind) + emit_diagnostic (kind, loc, opt, "defaulted %qD must have " + "parameter type %<const %T&%>", fn, ctx); + if (kind == DK_ERROR) + ok = false; + } + + /* We still need to deduce deleted/constexpr/noexcept and maybe return. */ + DECL_MAYBE_DELETED (fn) = true; + + return ok; +} + +/* Subroutine of build_comparison_op. Given the vec of memberwise + comparisons COMPS, calculate the overall comparison category for + operator<=>. */ + +static tree +common_comparison_type (vec<tree> &comps) +{ + tree seen[cc_last] = {}; + + for (unsigned i = 0; i < comps.length(); ++i) + { + tree comp = comps[i]; + tree ctype = TREE_TYPE (comp); + comp_cat_tag tag = cat_tag_for (ctype); + if (tag < cc_last) + seen[tag] = ctype; + else + /* If any Ti is not a comparison category type, U is void. */ + return void_type_node; + } + + /* Otherwise, if at least one T i is std::weak_equality, or at least one T i + is std::strong_equality and at least one T j is std::partial_ordering or + std::weak_ordering, U is std::weak_equality. */ + if (tree t = seen[cc_weak_equality]) return t; + if (seen[cc_strong_equality] + && (seen[cc_partial_ordering] || seen[cc_weak_ordering])) + return lookup_comparison_category (cc_weak_equality); + + /* Otherwise, if at least one T i is std::strong_equality, U is + std::strong_equality. */ + if (tree t = seen[cc_strong_equality]) return t; + + /* Otherwise, if at least one T i is std::partial_ordering, U is + std::partial_ordering. */ + if (tree t = seen[cc_partial_ordering]) return t; + + /* Otherwise, if at least one T i is std::weak_ordering, U is + std::weak_ordering. */ + if (tree t = seen[cc_weak_ordering]) return t; + + /* Otherwise, U is std::strong_ordering. */ + if (tree t = seen[cc_strong_ordering]) return t; + return lookup_comparison_category (cc_strong_ordering); +} + +/* Data structure for build_comparison_op. */ + +struct comp_info +{ + tree fndecl; + location_t loc; + bool defining; + bool first_time; + bool constexp; + bool was_constexp; + bool noex; + + comp_info (tree fndecl, tsubst_flags_t &complain) + : fndecl (fndecl) + { + loc = DECL_SOURCE_LOCATION (fndecl); + + /* We only have tf_error set when we're called from + explain_invalid_constexpr_fn or maybe_explain_implicit_delete. */ + defining = !(complain & tf_error); + + first_time = DECL_MAYBE_DELETED (fndecl); + DECL_MAYBE_DELETED (fndecl) = false; + + /* Do we want to try to set constexpr? */ + was_constexp = DECL_DECLARED_CONSTEXPR_P (fndecl); + constexp = first_time; + if (constexp) + /* Set this for var_in_constexpr_fn. */ + DECL_DECLARED_CONSTEXPR_P (fndecl) = true; + + /* Do we want to try to set noexcept? */ + noex = first_time; + if (noex) + { + tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl)); + if (raises && !UNEVALUATED_NOEXCEPT_SPEC_P (raises)) + /* There was an explicit exception-specification. */ + noex = false; + } + } + + /* EXPR is an expression built as part of the function body. + Adjust the properties appropriately. */ + void check (tree expr) + { + if (expr == error_mark_node) + DECL_DELETED_FN (fndecl) = true; + if ((constexp || was_constexp) + && !potential_rvalue_constant_expression (expr)) + { + if (was_constexp) + require_potential_rvalue_constant_expression (expr); + else + constexp = false; + } + if (noex && !expr_noexcept_p (expr, tf_none)) + noex = false; + } +}; + +/* Build up the definition of a defaulted comparison operator. Unlike other + defaulted functions that use synthesized_method_walk to determine whether + the function is e.g. deleted, for comparisons we use the same code. We try + to use synthesize_method at the earliest opportunity and bail out if the + function ends up being deleted. */ + +static void +build_comparison_op (tree fndecl, tsubst_flags_t complain) +{ + comp_info info (fndecl, complain); + + if (!info.defining && !(complain & tf_error) && !DECL_MAYBE_DELETED (fndecl)) + return; + + int flags = LOOKUP_NORMAL | LOOKUP_NONVIRTUAL | LOOKUP_DEFAULTED; + const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (DECL_NAME (fndecl)); + tree_code code = op->tree_code; + + tree lhs = DECL_ARGUMENTS (fndecl); + tree rhs = DECL_CHAIN (lhs); + if (is_this_parameter (lhs)) + lhs = cp_build_fold_indirect_ref (lhs); + else + lhs = convert_from_reference (lhs); + rhs = convert_from_reference (rhs); + tree ctype = TYPE_MAIN_VARIANT (TREE_TYPE (lhs)); + + iloc_sentinel ils (info.loc); + + /* A defaulted comparison operator function for class C is defined as + deleted if ... C is a union-like class. */ + if (TREE_CODE (ctype) == UNION_TYPE) + { + if (complain & tf_error) + inform (info.loc, "cannot default compare union %qT", ctype); + DECL_DELETED_FN (fndecl) = true; + } + + tree compound_stmt = NULL_TREE; + if (info.defining) + compound_stmt = begin_compound_stmt (0); + else + ++cp_unevaluated_operand; + + tree rettype = TREE_TYPE (TREE_TYPE (fndecl)); + if (code != SPACESHIP_EXPR && is_auto (rettype)) + { + rettype = boolean_type_node; + apply_deduced_return_type (fndecl, rettype); + } + + if (code == EQ_EXPR || code == SPACESHIP_EXPR) + { + auto_vec<tree> comps; + + /* Compare each of the subobjects. Note that we get bases from + next_initializable_field because we're past C++17. */ + for (tree field = next_initializable_field (TYPE_FIELDS (ctype)); + field; + field = next_initializable_field (DECL_CHAIN (field))) + { + tree expr_type = TREE_TYPE (field); + + /* A defaulted comparison operator function for class C is defined as + deleted if any non-static data member of C is of reference type or + C is a union-like class. */ + if (TREE_CODE (expr_type) == REFERENCE_TYPE) + { + if (complain & tf_error) + inform (DECL_SOURCE_LOCATION (field), "cannot default compare " + "reference member %qD", field); + DECL_DELETED_FN (fndecl) = true; + continue; + } + else if (ANON_UNION_TYPE_P (expr_type)) + { + if (complain & tf_error) + inform (DECL_SOURCE_LOCATION (field), "cannot default compare " + "anonymous union member"); + DECL_DELETED_FN (fndecl) = true; + continue; + } + + tree lhs_mem = build3 (COMPONENT_REF, expr_type, lhs, field, + NULL_TREE); + tree rhs_mem = build3 (COMPONENT_REF, expr_type, rhs, field, + NULL_TREE); + tree comp = build_new_op (info.loc, code, flags, lhs_mem, rhs_mem, + NULL_TREE, NULL, complain); + comps.safe_push (comp); + } + if (code == SPACESHIP_EXPR && is_auto (rettype)) + { + rettype = common_comparison_type (comps); + apply_deduced_return_type (fndecl, rettype); + } + for (unsigned i = 0; i < comps.length(); ++i) + { + tree comp = comps[i]; + tree eq, retval = NULL_TREE, if_ = NULL_TREE; + if (info.defining) + if_ = begin_if_stmt (); + /* Spaceship is specified to use !=, but for the comparison category + types, != is equivalent to !(==), so let's use == directly. */ + if (code == EQ_EXPR) + { + /* if (x==y); else return false; */ + eq = comp; + retval = boolean_false_node; + } + else + { + /* if (auto v = x<=>y, v == 0); else return v; */ + if (TREE_CODE (comp) == SPACESHIP_EXPR) + TREE_TYPE (comp) = rettype; + else + comp = build_static_cast (rettype, comp, complain); + info.check (comp); + if (info.defining) + { + tree var = create_temporary_var (rettype); + pushdecl (var); + cp_finish_decl (var, comp, false, NULL_TREE, flags); + comp = retval = var; + } + eq = build_new_op (info.loc, EQ_EXPR, flags, comp, + integer_zero_node, NULL_TREE, NULL, + complain); + } + tree ceq = contextual_conv_bool (eq, complain); + info.check (ceq); + if (info.defining) + { + finish_if_stmt_cond (ceq, if_); + finish_then_clause (if_); + begin_else_clause (if_); + finish_return_stmt (retval); + finish_else_clause (if_); + finish_if_stmt (if_); + } + } + if (info.defining) + { + tree val; + if (code == EQ_EXPR) + val = boolean_true_node; + else + { + tree seql = lookup_comparison_result (cc_strong_ordering, + "equal", complain); + val = build_static_cast (rettype, seql, complain); + } + finish_return_stmt (val); + } + } + else if (code == NE_EXPR) + { + tree comp = build_new_op (info.loc, EQ_EXPR, flags, lhs, rhs, + NULL_TREE, NULL, complain); + comp = contextual_conv_bool (comp, complain); + info.check (comp); + if (info.defining) + { + tree neg = build1 (TRUTH_NOT_EXPR, boolean_type_node, comp); + finish_return_stmt (neg); + } + } + else + { + tree comp = build_new_op (info.loc, SPACESHIP_EXPR, flags, lhs, rhs, + NULL_TREE, NULL, complain); + tree comp2 = build_new_op (info.loc, code, flags, comp, integer_zero_node, + NULL_TREE, NULL, complain); + info.check (comp2); + if (info.defining) + finish_return_stmt (comp2); + } + + if (info.defining) + finish_compound_stmt (compound_stmt); + else + --cp_unevaluated_operand; + + if (info.first_time) + { + DECL_DECLARED_CONSTEXPR_P (fndecl) = info.constexp || info.was_constexp; + tree raises = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (fndecl)); + if (!raises || UNEVALUATED_NOEXCEPT_SPEC_P (raises)) + { + raises = info.noex ? noexcept_true_spec : noexcept_false_spec; + TREE_TYPE (fndecl) = build_exception_variant (TREE_TYPE (fndecl), + raises); + } + } +} + /* Synthesize FNDECL, a non-static member function. */ void @@ -889,6 +1472,7 @@ synthesize_method (tree fndecl) location_t save_input_location = input_location; int error_count = errorcount; int warning_count = warningcount + werrorcount; + special_function_kind sfk = special_function_p (fndecl); /* Reset the source location, we might have been previously deferred, and thus have saved where we were first needed. */ @@ -930,6 +1514,12 @@ synthesize_method (tree fndecl) else finish_mem_initializers (NULL_TREE); } + else if (sfk == sfk_comparison) + { + /* Pass tf_none so the function is just deleted if there's a problem. */ + build_comparison_op (fndecl, tf_none); + need_body = false; + } /* If we haven't yet generated the body of the function, just generate an empty compound statement. */ @@ -941,7 +1531,10 @@ synthesize_method (tree fndecl) } finish_function_body (stmt); - expand_or_defer_fn (finish_function (/*inline_p=*/false)); + finish_function (/*inline_p=*/false); + + if (!DECL_DELETED_FN (fndecl)) + expand_or_defer_fn (fndecl); input_location = save_input_location; @@ -1753,6 +2346,13 @@ get_defaulted_eh_spec (tree decl, tsubst_flags_t complain) if (DECL_CLONED_FUNCTION_P (decl)) decl = DECL_CLONED_FUNCTION (decl); special_function_kind sfk = special_function_p (decl); + if (sfk == sfk_comparison) + { + /* We're in synthesize_method. Start with NULL_TREE, build_comparison_op + will adjust as needed. */ + gcc_assert (decl == current_function_decl); + return NULL_TREE; + } tree ctype = DECL_CONTEXT (decl); tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl); tree parm_type = TREE_VALUE (parms); @@ -1836,7 +2436,14 @@ maybe_explain_implicit_delete (tree decl) informed = true; } } - if (!informed) + if (!informed && sfk == sfk_comparison) + { + inform (DECL_SOURCE_LOCATION (decl), + "%q#D is implicitly deleted because the default " + "definition would be ill-formed:", decl); + build_comparison_op (decl, tf_warning_or_error); + } + else if (!informed) { tree parms = FUNCTION_FIRST_USER_PARMTYPE (decl); bool const_p = false; @@ -1891,10 +2498,18 @@ explain_implicit_non_constexpr (tree decl) bool const_p = CP_TYPE_CONST_P (non_reference (TREE_VALUE (parms))); tree inh = DECL_INHERITED_CTOR (decl); bool dummy; - synthesized_method_walk (DECL_CLASS_CONTEXT (decl), - special_function_p (decl), const_p, - NULL, NULL, NULL, &dummy, true, - &inh, parms); + special_function_kind sfk = special_function_p (decl); + if (sfk == sfk_comparison) + { + DECL_DECLARED_CONSTEXPR_P (decl) = true; + build_comparison_op (decl, tf_warning_or_error); + DECL_DECLARED_CONSTEXPR_P (decl) = false; + } + else + synthesized_method_walk (DECL_CLASS_CONTEXT (decl), + sfk, const_p, + NULL, NULL, NULL, &dummy, true, + &inh, parms); } /* DECL is an instantiation of an inheriting constructor template. Deduce @@ -1933,12 +2548,12 @@ deduce_inheriting_ctor (tree decl) /* Implicitly declare the special function indicated by KIND, as a member of TYPE. For copy constructors and assignment operators, CONST_P indicates whether these functions should take a const - reference argument or a non-const reference. Returns the - FUNCTION_DECL for the implicitly declared function. */ + reference argument or a non-const reference. + Returns the FUNCTION_DECL for the implicitly declared function. */ tree implicitly_declare_fn (special_function_kind kind, tree type, - bool const_p, tree inherited_ctor, + bool const_p, tree pattern_fn, tree inherited_parms) { tree fn; @@ -1950,8 +2565,11 @@ implicitly_declare_fn (special_function_kind kind, tree type, tree this_parm; tree name; HOST_WIDE_INT saved_processing_template_decl; - bool deleted_p; - bool constexpr_p; + bool deleted_p = false; + bool constexpr_p = false; + bool friend_p = (kind == sfk_comparison && DECL_FRIEND_P (pattern_fn)); + tree inherited_ctor = (kind == sfk_inheriting_constructor + ? pattern_fn : NULL_TREE); /* Because we create declarations for implicitly declared functions lazily, we may be creating the declaration for a member of TYPE @@ -1978,6 +2596,7 @@ implicitly_declare_fn (special_function_kind kind, tree type, else return_type = void_type_node; + int this_quals = TYPE_UNQUALIFIED; switch (kind) { case sfk_destructor: @@ -2021,6 +2640,36 @@ implicitly_declare_fn (special_function_kind kind, tree type, } break; } + + case sfk_comparison: + /* If the class definition does not explicitly declare an == operator + function, but declares a defaulted three-way comparison operator + function, an == operator function is declared implicitly with the same + access as the three-way comparison operator function. + + The implicitly-declared == operator for a class X is an inline member + and is defined as defaulted in the definition of X. + + If the three-way comparison operator function is declared as a + non-static const member, the implicitly-declared == operator function + is a member of the form + + bool X::operator==(const X&) const; + + Otherwise, the implicitly-declared == operator function is of the form + + friend bool operator==(const X&, const X&); */ + /* No other comparison operator is implicitly declared. */ + name = ovl_op_identifier (false, EQ_EXPR); + return_type = boolean_type_node; + rhs_parm_type = cp_build_qualified_type (type, TYPE_QUAL_CONST); + rhs_parm_type = cp_build_reference_type (rhs_parm_type, false); + parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types); + if (friend_p) + parameter_types = tree_cons (NULL_TREE, rhs_parm_type, parameter_types); + this_quals = TYPE_QUAL_CONST; + break; + default: gcc_unreachable (); } @@ -2038,9 +2687,10 @@ implicitly_declare_fn (special_function_kind kind, tree type, else if (cxx_dialect >= cxx11) { raises = noexcept_deferred_spec; - synthesized_method_walk (type, kind, const_p, NULL, &trivial_p, - &deleted_p, &constexpr_p, false, - &inherited_ctor, inherited_parms); + if (kind != sfk_comparison) + synthesized_method_walk (type, kind, const_p, NULL, &trivial_p, + &deleted_p, &constexpr_p, false, + &inherited_ctor, inherited_parms); } else synthesized_method_walk (type, kind, const_p, &raises, &trivial_p, @@ -2062,7 +2712,9 @@ implicitly_declare_fn (special_function_kind kind, tree type, type_set_nontrivial_flag (type, kind); /* Create the function. */ - fn_type = build_method_type_directly (type, return_type, parameter_types); + tree this_type = cp_build_qualified_type (type, this_quals); + fn_type = build_method_type_directly (this_type, return_type, + parameter_types); if (raises) { if (raises != error_mark_node) @@ -2073,16 +2725,25 @@ implicitly_declare_fn (special_function_kind kind, tree type, gcc_assert (seen_error ()); } fn = build_lang_decl (FUNCTION_DECL, name, fn_type); - if (kind != sfk_inheriting_constructor) + if (kind == sfk_comparison) + { + DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (pattern_fn); + DECL_MAYBE_DELETED (fn) = true; + } + else if (kind != sfk_inheriting_constructor) DECL_SOURCE_LOCATION (fn) = DECL_SOURCE_LOCATION (TYPE_NAME (type)); - if (!IDENTIFIER_CDTOR_P (name)) - /* Assignment operator. */ - DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = OVL_OP_NOP_EXPR; + if (IDENTIFIER_OVL_OP_P (name)) + { + const ovl_op_info_t *op = IDENTIFIER_OVL_OP_INFO (name); + DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) = op->ovl_op_code; + } else if (IDENTIFIER_CTOR_P (name)) DECL_CXX_CONSTRUCTOR_P (fn) = true; - else + else if (IDENTIFIER_DTOR_P (name)) DECL_CXX_DESTRUCTOR_P (fn) = true; + else + gcc_unreachable (); SET_DECL_ALIGN (fn, MINIMUM_METHOD_BOUNDARY); @@ -2097,6 +2758,13 @@ implicitly_declare_fn (special_function_kind kind, tree type, retrofit_lang_decl (decl); DECL_PARM_INDEX (decl) = DECL_PARM_LEVEL (decl) = 1; DECL_ARGUMENTS (fn) = decl; + if (friend_p) + { + /* The second parm of friend op==. */ + tree decl2 = copy_decl (decl); + DECL_CHAIN (decl) = decl2; + DECL_PARM_INDEX (decl2) = 2; + } } else if (kind == sfk_inheriting_constructor) { @@ -2122,7 +2790,7 @@ implicitly_declare_fn (special_function_kind kind, tree type, constexpr_p = DECL_DECLARED_CONSTEXPR_P (inherited_ctor); } /* Add the "this" parameter. */ - this_parm = build_this_parm (fn, fn_type, TYPE_UNQUALIFIED); + this_parm = build_this_parm (fn, fn_type, this_quals); DECL_CHAIN (this_parm) = DECL_ARGUMENTS (fn); DECL_ARGUMENTS (fn) = this_parm; @@ -2141,6 +2809,12 @@ implicitly_declare_fn (special_function_kind kind, tree type, set_linkage_according_to_type (type, fn); if (TREE_PUBLIC (fn)) DECL_COMDAT (fn) = 1; + if (kind == sfk_comparison && !friend_p) + { + /* The implicit op== has the same access as the op<=>. */ + TREE_PRIVATE (fn) = TREE_PRIVATE (pattern_fn); + TREE_PROTECTED (fn) = TREE_PROTECTED (pattern_fn); + } rest_of_decl_compilation (fn, namespace_bindings_p (), at_eof); gcc_assert (!TREE_USED (fn)); @@ -2182,6 +2856,16 @@ defaulted_late_check (tree fn) /* Complain about invalid signature for defaulted fn. */ tree ctx = DECL_CONTEXT (fn); special_function_kind kind = special_function_p (fn); + + if (kind == sfk_comparison) + { + /* If the function was declared constexpr, check that the definition + qualifies. Otherwise we can define the function lazily. */ + if (DECL_DECLARED_CONSTEXPR_P (fn)) + synthesize_method (fn); + return; + } + bool fn_const_p = (copy_fn_p (fn) == 2); tree implicit_fn = implicitly_declare_fn (kind, ctx, fn_const_p, NULL, NULL); @@ -2272,6 +2956,13 @@ defaultable_fn_check (tree fn) else if (move_fn_p (fn)) kind = sfk_move_assignment; } + else if (DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) >= OVL_OP_EQ_EXPR + && DECL_OVERLOADED_OPERATOR_CODE_RAW (fn) <= OVL_OP_SPACESHIP_EXPR) + { + kind = sfk_comparison; + if (!early_check_defaulted_comparison (fn)) + return false; + } if (kind == sfk_none) { @@ -2293,7 +2984,7 @@ defaultable_fn_check (tree fn) if (DECL_NAME (p)) TREE_NO_WARNING (p) = 1; - if (TYPE_BEING_DEFINED (DECL_CONTEXT (fn))) + if (current_class_type && TYPE_BEING_DEFINED (current_class_type)) /* Defer checking. */; else if (!processing_template_decl) defaulted_late_check (fn); |