From a5f947d411b5e19ce7efbb4d766a2792b02c9626 Mon Sep 17 00:00:00 2001
From: Waffl3x <waffl3x@protonmail.com>
Date: Sun, 7 Jan 2024 15:02:57 -0700
Subject: [PATCH] C++23 P0847R7 (deducing this) - CWG2586. [PR102609]

This adds support for defaulted comparison operators and copy/move assignment
operators, as well as allowing user defined xobj copy/move assignment
operators. It turns out defaulted comparison operators already worked though,
so this just adds a test for them. Defaulted comparison operators were not so
nice and required a bit of a hack. Should work fine though!

The diagnostics leave something to be desired, and there are some things that
could be improved with more extensive design changes. There are a few notes
left indicating where I think we could make improvements.

Aside from some small bugs, with this commit xobj member functions should be
feature complete.

	PR c++/102609

gcc/cp/ChangeLog:

	PR c++/102609
	C++23 P0847R7 (deducing this) - CWG2586.
	* decl.cc (copy_fn_p): Accept xobj copy assignment functions.
	(move_signature_fn_p): Accept xobj move assignment functions.
	* method.cc (do_build_copy_assign): Handle defaulted xobj member
	functions.
	(defaulted_late_check): Comment.
	(defaultable_fn_check): Comment.

gcc/testsuite/ChangeLog:

	PR c++/102609
	C++23 P0847R7 (deducing this) - CWG2586.
	* g++.dg/cpp23/explicit-obj-basic6.C: New test.
	* g++.dg/cpp23/explicit-obj-default1.C: New test.
	* g++.dg/cpp23/explicit-obj-default2.C: New test.

Signed-off-by: Waffl3x <waffl3x@protonmail.com>
---
 gcc/cp/decl.cc                                | 28 +++++++-
 gcc/cp/method.cc                              | 55 ++++++++++++++--
 .../g++.dg/cpp23/explicit-obj-basic6.C        | 51 +++++++++++++++
 .../g++.dg/cpp23/explicit-obj-default1.C      | 57 ++++++++++++++++
 .../g++.dg/cpp23/explicit-obj-default2.C      | 65 +++++++++++++++++++
 5 files changed, 248 insertions(+), 8 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C

diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 7f267055c29..b10a72a87bf 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -15663,7 +15663,19 @@ copy_fn_p (const_tree d)
       && DECL_NAME (d) != assign_op_identifier)
     return 0;
 
-  args = FUNCTION_FIRST_USER_PARMTYPE (d);
+  if (DECL_XOBJ_MEMBER_FUNCTION_P (d))
+    {
+      tree object_param = TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (d)));
+      if (!TYPE_REF_P (object_param)
+	  || TYPE_REF_IS_RVALUE (object_param)
+	  /* Reject unrelated object parameters. */
+	  || TYPE_MAIN_VARIANT (TREE_TYPE (object_param)) != DECL_CONTEXT (d)
+	  || CP_TYPE_CONST_P (TREE_TYPE (object_param)))
+	return 0;
+      args = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (d)));
+    }
+  else
+    args = FUNCTION_FIRST_USER_PARMTYPE (d);
   if (!args)
     return 0;
 
@@ -15738,7 +15750,19 @@ move_signature_fn_p (const_tree d)
       && DECL_NAME (d) != assign_op_identifier)
     return 0;
 
-  args = FUNCTION_FIRST_USER_PARMTYPE (d);
+  if (DECL_XOBJ_MEMBER_FUNCTION_P (d))
+    {
+      tree object_param = TREE_VALUE (TYPE_ARG_TYPES (TREE_TYPE (d)));
+      if (!TYPE_REF_P (object_param)
+	  || TYPE_REF_IS_RVALUE (object_param)
+	  /* Reject unrelated object parameters. */
+	  || TYPE_MAIN_VARIANT (TREE_TYPE (object_param)) != DECL_CONTEXT (d)
+	  || CP_TYPE_CONST_P (TREE_TYPE (object_param)))
+	return 0;
+      args = TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (d)));
+    }
+  else
+    args = FUNCTION_FIRST_USER_PARMTYPE (d);
   if (!args)
     return 0;
 
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index aa5a044883e..da6a08a0304 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -795,13 +795,19 @@ do_build_copy_assign (tree fndecl)
   compound_stmt = begin_compound_stmt (0);
   parm = convert_from_reference (parm);
 
+  /* If we are building a defaulted xobj copy/move assignment operator then
+     current_class_ref will not have been set up.
+     Kind of an icky hack, but what can ya do?  */
+  tree const class_ref = DECL_XOBJ_MEMBER_FUNCTION_P (fndecl)
+    ? cp_build_fold_indirect_ref (DECL_ARGUMENTS (fndecl)) : current_class_ref;
+
   if (trivial
       && is_empty_class (current_class_type))
     /* Don't copy the padding byte; it might not have been allocated
        if *this is a base subobject.  */;
   else if (trivial)
     {
-      tree t = build2 (MODIFY_EXPR, void_type_node, current_class_ref, parm);
+      tree t = build2 (MODIFY_EXPR, void_type_node, class_ref, parm);
       finish_expr_stmt (t);
     }
   else
@@ -826,7 +832,7 @@ do_build_copy_assign (tree fndecl)
 	  /* Call the base class assignment operator.  */
 	  releasing_vec parmvec (make_tree_vector_single (converted_parm));
 	  finish_expr_stmt
-	    (build_special_member_call (current_class_ref,
+	    (build_special_member_call (class_ref,
 					assign_op_identifier,
 					&parmvec,
 					base_binfo,
@@ -839,7 +845,7 @@ do_build_copy_assign (tree fndecl)
 	   fields;
 	   fields = DECL_CHAIN (fields))
 	{
-	  tree comp = current_class_ref;
+	  tree comp = class_ref;
 	  tree init = parm;
 	  tree field = fields;
 	  tree expr_type;
@@ -898,7 +904,7 @@ do_build_copy_assign (tree fndecl)
 	  finish_expr_stmt (init);
 	}
     }
-  finish_return_stmt (current_class_ref);
+  finish_return_stmt (class_ref);
   finish_compound_stmt (compound_stmt);
 }
 
@@ -3380,13 +3386,46 @@ defaulted_late_check (tree fn)
 					    NULL, NULL);
   tree eh_spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (implicit_fn));
 
+  /* Includes special handling for a default xobj operator.  */
+  auto compare_fn_params = [](tree fn, tree implicit_fn){
+    tree fn_parms = TYPE_ARG_TYPES (TREE_TYPE (fn));
+    tree implicit_fn_parms = TYPE_ARG_TYPES (TREE_TYPE (implicit_fn));
+
+    if (DECL_XOBJ_MEMBER_FUNCTION_P (fn))
+      {
+	tree fn_obj_ref_type = TREE_VALUE (fn_parms);
+	/* We can't default xobj operators with an xobj parameter that is not
+	   an lvalue reference.  */
+	if (!TYPE_REF_P (fn_obj_ref_type)
+	    || TYPE_REF_IS_RVALUE (fn_obj_ref_type))
+	  return false;
+	/* If implicit_fn's object parameter is not a pointer, something is not
+	   right.  (Or we have finally changed the type of the iobj parameter
+	   in iobj member functions.)  */
+	gcc_assert (TYPE_PTR_P (TREE_VALUE (implicit_fn_parms)));
+	/* Strip the reference/pointer off each object parameter before
+	   comparing them.  */
+	if (!same_type_p (TREE_TYPE (fn_obj_ref_type),
+			  TREE_TYPE (TREE_VALUE (implicit_fn_parms))))
+	  return false;
+	/* We just compared the object parameters, skip over them before
+	   passing to compparms.  */
+	fn_parms = TREE_CHAIN (fn_parms);
+	implicit_fn_parms = TREE_CHAIN (implicit_fn_parms);
+      }
+    return compparms(fn_parms, implicit_fn_parms);
+  };
+
   if (!same_type_p (TREE_TYPE (TREE_TYPE (fn)),
 		    TREE_TYPE (TREE_TYPE (implicit_fn)))
-      || !compparms (TYPE_ARG_TYPES (TREE_TYPE (fn)),
-		     TYPE_ARG_TYPES (TREE_TYPE (implicit_fn))))
+      || !compare_fn_params (fn, implicit_fn))
     {
       error ("defaulted declaration %q+D does not match the "
 	     "expected signature", fn);
+      /* FIXME: If the user is defaulting an xobj member function we should
+	 emit an xobj member function for a signature.  When we do this, maybe
+	 we can just synthesize implicit_fn as an xobj member function and
+	 avoid the dance in compare_fn_parms.  */
       inform (DECL_SOURCE_LOCATION (fn),
 	      "expected signature: %qD", implicit_fn);
     }
@@ -3473,6 +3512,10 @@ defaultable_fn_check (tree fn)
 	return false;
     }
 
+  /* FIXME: We need to check for xobj member functions here to give better
+     diagnostics for weird cases where unrelated xobj parameters are given.
+     We just want to do better than 'cannot be defaulted'.  */
+
   if (kind == sfk_none)
     {
       error ("%qD cannot be defaulted", fn);
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C
new file mode 100644
index 00000000000..6b8881ed07e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-basic6.C
@@ -0,0 +1,51 @@
+// P0847R7
+// { dg-do compile { target c++23 } }
+
+// user defined copy/move assignment operators
+
+inline constexpr int add_when_copy = 5;
+inline constexpr int add_when_move = 10;
+inline constexpr int poison = -1;
+
+struct S {
+  int _v;
+  S& operator=(this S& self, S const& rhs) {
+    self._v = rhs._v + add_when_copy;
+    return self;
+  };
+  S& operator=(this S& self, S&& rhs) {
+    self._v = rhs._v + add_when_move;
+    rhs._v = poison;
+    return self;
+  }
+};
+
+inline constexpr int init_val = 5;
+
+int main()
+{
+  S s0{0};
+  S s1{init_val};
+
+  // Sanity check.
+  if (s0._v != 0
+      || s1._v != init_val)
+    __builtin_abort ();
+
+  s0 = s1;
+  if (s0._v != init_val + add_when_copy)
+    __builtin_abort ();
+  if (s1._v != init_val)
+    __builtin_abort ();
+
+  s0 = S{init_val};
+  if (s0._v != init_val + add_when_move)
+    __builtin_abort ();
+
+  S s2{init_val};
+  s0 = static_cast<S&&>(s2);
+  if (s0._v != init_val + add_when_move)
+    __builtin_abort (); 
+  if (s2._v != poison)
+    __builtin_abort ();
+}
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C
new file mode 100644
index 00000000000..fac3c28904c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default1.C
@@ -0,0 +1,57 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// defaulted comparison operators
+
+#include <compare>
+
+struct S {
+  int _v;
+  bool operator==(this S const&, S const&) = default;
+  auto operator<=>(this S const&, S const&) = default;
+};
+
+int main()
+{
+  S const a_10{10};
+  S const b_10{10};
+  S const c_20{20};
+  S const d_5{5};
+
+  if (a_10 != b_10)
+    __builtin_abort ();
+  if (c_20 == a_10)
+    __builtin_abort ();
+  if (!(a_10 == b_10))
+    __builtin_abort ();
+  if (!(c_20 != a_10))
+    __builtin_abort ();
+
+  if (a_10 < b_10)
+    __builtin_abort ();
+  if (a_10 > b_10)
+    __builtin_abort ();
+  if (!(a_10 <= b_10))
+    __builtin_abort ();
+  if (!(a_10 >= b_10))
+    __builtin_abort ();
+
+  if (!(a_10 < c_20))
+    __builtin_abort ();
+  if (a_10 > c_20)
+    __builtin_abort ();
+  if (!(a_10 <= c_20))
+    __builtin_abort ();
+  if (a_10 >= c_20)
+    __builtin_abort ();
+
+  if (a_10 < d_5)
+    __builtin_abort ();
+  if (!(a_10 > d_5))
+    __builtin_abort ();
+  if (a_10 <= d_5)
+    __builtin_abort ();
+  if (!(a_10 >= d_5))
+    __builtin_abort ();
+}
+
diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C
new file mode 100644
index 00000000000..786f0c21c80
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-default2.C
@@ -0,0 +1,65 @@
+// P0847R7
+// { dg-do run { target c++23 } }
+
+// defaulted copy/move assignment operators
+
+inline constexpr int add_when_copy = 5;
+inline constexpr int add_when_move = 10;
+inline constexpr int poison = -1;
+
+struct A {
+  int _v;
+  A(int v) : _v(v) {}
+  A& operator=(A const& rhs) {
+    if (&rhs == this)
+      return *this;
+    _v = rhs._v + add_when_copy;
+    return *this;
+  }
+  A& operator=(A&& rhs) {
+    if (&rhs == this)
+      return *this;
+    _v = rhs._v + add_when_move;
+    rhs._v = poison;
+    return *this;
+  }
+};
+
+struct S {
+  A _a;
+  S& operator=(this S&, S const&) = default;
+  S& operator=(this S&, S&&) = default;
+
+  int v() const { return _a._v; }
+};
+
+inline constexpr int init_val = 5;
+
+int main()
+{
+  S s0{0};
+  S s1{init_val};
+
+  // Sanity check.
+  if (s0.v () != 0
+      || s1.v () != init_val)
+    __builtin_abort ();
+
+  s0 = s1;
+  if (s0.v () != init_val + add_when_copy)
+    __builtin_abort ();
+  if (s1.v () != init_val)
+    __builtin_abort ();
+
+  s0 = S{init_val};
+  if (s0.v () != init_val + add_when_move)
+    __builtin_abort ();
+
+  S s2{init_val};
+  s0 = static_cast<S&&>(s2);
+  if (s0.v () != init_val + add_when_move)
+    __builtin_abort (); 
+  if (s2.v () != poison)
+    __builtin_abort ();
+}
+
-- 
2.43.0

