From 810bcc00156cefce7ad40fc9d8de6e43c3a04450 Mon Sep 17 00:00:00 2001
From: Jason Merrill <jason@redhat.com>
Date: Thu, 17 Aug 2023 11:36:23 -0400
Subject: [PATCH] c++: constrained hidden friends [PR109751]

r13-4035 avoided a problem with overloading of constrained hidden friends by
checking satisfaction, but checking satisfaction early is inconsistent with
the usual late checking and can lead to hard errors, so let's not do that
after all.

We were wrongly treating the different instantiations of the same friend
template as the same function because maybe_substitute_reqs_for was failing
to actually substitute in the case of a non-template friend.  But we don't
actually need to do the substitution anyway, because [temp.friend] says that
such a friend can't be the same as any other declaration.

After fixing that, instead of a redefinition error we got an ambiguous
overload error, fixed by allowing constrained hidden friends to coexist
until overload resolution, at which point they probably won't be in the same
ADL overload set anyway.

And we avoid mangling collisions by following the proposed mangling for
these friends as a member function with an extra 'F' before the name.  I
demangle this by just adding [friend] to the name of the function because
it's not feasible to reconstruct the actual scope of the function since the
mangling ABI doesn't distinguish between class and namespace scopes.

	PR c++/109751

gcc/cp/ChangeLog:

	* cp-tree.h (member_like_constrained_friend_p): Declare.
	* decl.cc (member_like_constrained_friend_p): New.
	(function_requirements_equivalent_p): Check it.
	(duplicate_decls): Check it.
	(grokfndecl): Check friend template constraints.
	* mangle.cc (decl_mangling_context): Check it.
	(write_unqualified_name): Check it.
	* pt.cc (uses_outer_template_parms_in_constraints): Fix for friends.
	(tsubst_friend_function): Don't check satisfaction.

include/ChangeLog:

	* demangle.h (enum demangle_component_type): Add
	DEMANGLE_COMPONENT_FRIEND.

libiberty/ChangeLog:

	* cp-demangle.c (d_make_comp): Handle DEMANGLE_COMPONENT_FRIEND.
	(d_count_templates_scopes): Likewise.
	(d_print_comp_inner): Likewise.
	(d_unqualified_name): Handle member-like friend mangling.
	* testsuite/demangle-expected: Add test.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp2a/concepts-friend11.C: Now works.  Add template.
	* g++.dg/cpp2a/concepts-friend15.C: New test.
---
 gcc/cp/cp-tree.h                              |  3 +-
 gcc/cp/decl.cc                                | 49 ++++++++++++++++++-
 gcc/cp/mangle.cc                              | 10 ++++
 gcc/cp/pt.cc                                  | 14 ++++--
 .../g++.dg/cpp2a/concepts-friend11.C          | 26 ++++++----
 .../g++.dg/cpp2a/concepts-friend11a.C         | 15 ++++++
 .../g++.dg/cpp2a/concepts-friend15.C          | 22 +++++++++
 include/demangle.h                            |  2 +
 libiberty/cp-demangle.c                       | 17 +++++++
 libiberty/testsuite/demangle-expected         |  3 ++
 10 files changed, 145 insertions(+), 16 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-friend11a.C
 create mode 100644 gcc/testsuite/g++.dg/cpp2a/concepts-friend15.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index d051ee85f701..356d7ffb6d61 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6859,6 +6859,7 @@ extern void note_break_stmt			(void);
 extern bool note_iteration_stmt_body_start	(void);
 extern void note_iteration_stmt_body_end	(bool);
 extern void determine_local_discriminator	(tree);
+extern bool member_like_constrained_friend_p	(tree);
 extern bool fns_correspond			(tree, tree);
 extern int decls_match				(tree, tree, bool = true);
 extern bool maybe_version_functions		(tree, tree, bool);
@@ -7385,7 +7386,7 @@ extern tree lookup_template_function		(tree, tree);
 extern tree lookup_template_variable		(tree, tree, tsubst_flags_t);
 extern bool uses_template_parms			(tree);
 extern bool uses_template_parms_level		(tree, int);
-extern bool uses_outer_template_parms_in_constraints (tree);
+extern bool uses_outer_template_parms_in_constraints (tree, tree = NULL_TREE);
 extern bool need_generic_capture		(void);
 extern tree instantiate_class_template		(tree);
 extern tree instantiate_template		(tree, tree, tsubst_flags_t);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 62c34bf9abe8..bea0ee921068 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -951,6 +951,30 @@ determine_local_discriminator (tree decl)
 }
 
 
+/* True if DECL is a constrained hidden friend as per [temp.friend]/9:
+
+   A non-template friend declaration with a requires-clause shall be a
+   definition. A friend function template with a constraint that depends on a
+   template parameter from an enclosing template shall be a definition. Such a
+   constrained friend function or function template declaration does not
+   declare the same function or function template as a declaration in any other
+   scope.
+
+   The ABI calls this a "member-like constrained friend" and mangles it like a
+   member function to avoid collisions.  */
+
+bool
+member_like_constrained_friend_p (tree decl)
+{
+  return (TREE_CODE (decl) == FUNCTION_DECL
+	  && DECL_UNIQUE_FRIEND_P (decl)
+	  && DECL_FRIEND_CONTEXT (decl)
+	  && get_constraints (decl)
+	  && (!DECL_TEMPLATE_INFO (decl)
+	      || !PRIMARY_TEMPLATE_P (DECL_TI_TEMPLATE (decl))
+	      || (uses_outer_template_parms_in_constraints
+		  (most_general_template (decl)))));
+}
 
 /* Returns true if functions FN1 and FN2 have equivalent trailing
    requires clauses.  */
@@ -968,6 +992,13 @@ function_requirements_equivalent_p (tree newfn, tree oldfn)
       return cp_tree_equal (req1, req2);
     }
 
+  /* [temp.friend]/9 "Such a constrained friend function does not declare the
+     same function as a declaration in any other scope."  So no need to
+     actually compare the requirements.  */
+  if (member_like_constrained_friend_p (newfn)
+      || member_like_constrained_friend_p (oldfn))
+    return false;
+
   /* Compare only trailing requirements.  */
   tree reqs1 = get_trailing_function_requirements (newfn);
   tree reqs2 = get_trailing_function_requirements (oldfn);
@@ -1936,6 +1967,10 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
 	     are not ambiguous.  */
 	  else if ((!DECL_FUNCTION_VERSIONED (newdecl)
 		    && !DECL_FUNCTION_VERSIONED (olddecl))
+		   /* Let constrained hidden friends coexist for now, we'll
+		      check satisfaction later.  */
+		   && !member_like_constrained_friend_p (newdecl)
+		   && !member_like_constrained_friend_p (olddecl)
                    // The functions have the same parameter types.
 		   && compparms (TYPE_ARG_TYPES (TREE_TYPE (newdecl)),
 				 TYPE_ARG_TYPES (TREE_TYPE (olddecl)))
@@ -10305,16 +10340,28 @@ grokfndecl (tree ctype,
           ci = NULL_TREE;
         }
       /* C++20 CA378: Remove non-templated constrained functions.  */
+      /* [temp.friend]/9 A non-template friend declaration with a
+	 requires-clause shall be a definition. A friend function template with
+	 a constraint that depends on a template parameter from an enclosing
+	 template shall be a definition. */
       if (ci
 	  && (block_local
 	      || (!flag_concepts_ts
 		  && (!processing_template_decl
 		      || (friendp && !memtmpl && !funcdef_flag)))))
 	{
-	  error_at (location, "constraints on a non-templated function");
+	  if (!friendp || !processing_template_decl)
+	    error_at (location, "constraints on a non-templated function");
+	  else
+	    error_at (location, "constrained non-template friend declaration"
+		      " must be a definition");
 	  ci = NULL_TREE;
 	}
       set_constraints (decl, ci);
+      if (ci && friendp && memtmpl && !funcdef_flag
+	  && uses_outer_template_parms_in_constraints (decl, ctx))
+	error_at (location, "friend function template with constraints that "
+		  "depend on outer template parameters must be a definition");
     }
 
   if (TREE_CODE (type) == METHOD_TYPE)
diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
index bef0fda6d229..bb0e9d38203a 100644
--- a/gcc/cp/mangle.cc
+++ b/gcc/cp/mangle.cc
@@ -963,6 +963,9 @@ decl_mangling_context (tree decl)
 
   tcontext = CP_DECL_CONTEXT (decl);
 
+  if (member_like_constrained_friend_p (decl))
+    tcontext = DECL_FRIEND_CONTEXT (decl);
+
   /* Ignore the artificial declare reduction functions.  */
   if (tcontext
       && TREE_CODE (tcontext) == FUNCTION_DECL
@@ -1419,6 +1422,7 @@ anon_aggr_naming_decl (tree type)
 			::= [<module-name>] <source-name>
 			::= [<module-name>] <unnamed-type-name>
 			::= <local-source-name> 
+			::= F <source-name> # member-like constrained friend
 
     <local-source-name>	::= L <source-name> <discriminator> */
 
@@ -1476,6 +1480,12 @@ write_unqualified_name (tree decl)
   else if (DECL_DECLARES_FUNCTION_P (decl))
     {
       found = true;
+
+      /* A constrained hidden friend is mangled like a member function, with
+	 the name prefixed by 'F'.  */
+      if (member_like_constrained_friend_p (decl))
+	write_char ('F');
+
       if (DECL_CONSTRUCTOR_P (decl))
 	write_special_name_constructor (decl);
       else if (DECL_DESTRUCTOR_P (decl))
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index a4809f034dce..f4e77d172b9d 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11049,14 +11049,21 @@ uses_outer_template_parms (tree decl)
    from its enclosing scope.  */
 
 bool
-uses_outer_template_parms_in_constraints (tree decl)
+uses_outer_template_parms_in_constraints (tree decl, tree ctx/*=NULL_TREE*/)
 {
   tree ci = get_constraints (decl);
   if (ci)
     ci = CI_ASSOCIATED_CONSTRAINTS (ci);
   if (!ci)
     return false;
-  int depth = template_class_depth (CP_DECL_CONTEXT (decl));
+  if (!ctx)
+    {
+      if (tree fc = DECL_FRIEND_CONTEXT (decl))
+	ctx = fc;
+      else
+	ctx = CP_DECL_CONTEXT (decl);
+    }
+  int depth = template_class_depth (ctx);
   if (depth == 0)
     return false;
   return for_each_template_parm (ci, template_parm_outer_level,
@@ -11393,9 +11400,6 @@ tsubst_friend_function (tree decl, tree args)
 	  not_tmpl = DECL_TEMPLATE_RESULT (new_friend);
 	  new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
 	}
-      else if (!constraints_satisfied_p (new_friend))
-	/* Only define a constrained hidden friend when satisfied.  */
-	return error_mark_node;
 
       /* Inside pushdecl_namespace_level, we will push into the
 	 current namespace. However, the friend function should go
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-friend11.C b/gcc/testsuite/g++.dg/cpp2a/concepts-friend11.C
index 0350ac3553e1..93cb1f05ad0f 100644
--- a/gcc/testsuite/g++.dg/cpp2a/concepts-friend11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-friend11.C
@@ -1,21 +1,29 @@
 // CWG2596
 // { dg-do compile { target c++20 } }
+// { dg-additional-options -fno-implicit-constexpr }
 
 struct Base {};
 
-int foo(Base&) { return 0; } // #0
-
 template<int N>
 struct S : Base {
   friend int foo(Base&) requires (N == 1) { return 1; }  // #1
-  // friend int foo(Base&) requires (N == 2) { return 3; }  // #2
+  friend int foo(Base&) requires (N == 2) { return 3; }  // #2
+
+  template <class T>
+  friend int bar(Base&) requires (N == 1) { return 1; }
+  template <class T>
+  friend int bar(Base&) requires (N == 2) { return 3; }
 };
 
 S<1> s1;
-S<2> s2;          // OK, no conflict between #1 and #0
-int x = foo(s1);  // { dg-error "ambiguous" }
-int y = foo(s2);  // OK, selects #0
+S<2> s2;          // OK, no conflict between #1 and #2
+
+// { dg-final { scan-assembler "_ZN1SILi1EEF3fooER4Base" } }
+int x = foo(s1);  // OK, selects #1
+// { dg-final { scan-assembler "_ZN1SILi2EEF3fooER4Base" } }
+int y = foo(s2);  // OK, selects #2
 
-// ??? currently the foos all mangle the same, so comment out #2
-// and only test that #1 isn't multiply defined and overloads with #0.
-// The 2596 example does not include #0 and expects both calls to work.
+// { dg-final { scan-assembler "_ZN1SILi1EEF3barIiEEiR4Base" } }
+int x2 = bar<int>(s1);  // OK, selects #1
+// { dg-final { scan-assembler "_ZN1SILi2EEF3barIiEEiR4Base" } }
+int y2 = bar<int>(s2);  // OK, selects #2
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-friend11a.C b/gcc/testsuite/g++.dg/cpp2a/concepts-friend11a.C
new file mode 100644
index 000000000000..f3481b6731e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-friend11a.C
@@ -0,0 +1,15 @@
+// CWG2596
+// { dg-do compile { target c++20 } }
+
+struct Base {};
+
+template<int N>
+struct S : Base {
+  friend int foo(Base&) requires (N == 1); // { dg-error "must be a definition" }
+  friend int foo(Base&) requires (N == 2); // { dg-error "must be a definition" }
+
+  template <class T>
+  friend int bar(Base&) requires (N == 1); // { dg-error "must be a definition" }
+  template <class T>
+  friend int bar(Base&) requires (N == 2); // { dg-error "must be a definition" }
+};
diff --git a/gcc/testsuite/g++.dg/cpp2a/concepts-friend15.C b/gcc/testsuite/g++.dg/cpp2a/concepts-friend15.C
new file mode 100644
index 000000000000..c37d547bbdfa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/concepts-friend15.C
@@ -0,0 +1,22 @@
+// PR c++/109751
+// { dg-do compile { target c++20 } }
+
+template<typename _Tp> concept cmpeq
+  = requires(_Tp __t, _Tp __u) { { __u != __t } ; };
+
+template<typename D>
+struct iterator_interface
+{
+  friend constexpr bool operator>=(D lhs, D rhs)
+    requires cmpeq<D> { return true; }
+};
+
+template<typename T>
+struct iterator : iterator_interface<iterator<T>>
+{
+    bool operator==(iterator) const;
+    iterator &operator++();
+    iterator &operator++(int);
+};
+
+static_assert(cmpeq<iterator<int>>);
diff --git a/include/demangle.h b/include/demangle.h
index 769137e03e54..f062d7731c6b 100644
--- a/include/demangle.h
+++ b/include/demangle.h
@@ -448,6 +448,8 @@ enum demangle_component_type
   DEMANGLE_COMPONENT_TRANSACTION_SAFE,
   /* A cloned function.  */
   DEMANGLE_COMPONENT_CLONE,
+  /* A member-like friend function.  */
+  DEMANGLE_COMPONENT_FRIEND,
   DEMANGLE_COMPONENT_NOEXCEPT,
   DEMANGLE_COMPONENT_THROW_SPEC,
 
diff --git a/libiberty/cp-demangle.c b/libiberty/cp-demangle.c
index 3bd303a75445..2ce984f85cf4 100644
--- a/libiberty/cp-demangle.c
+++ b/libiberty/cp-demangle.c
@@ -1036,6 +1036,7 @@ d_make_comp (struct d_info *di, enum demangle_component_type type,
     case DEMANGLE_COMPONENT_TEMPLATE_NON_TYPE_PARM:
     case DEMANGLE_COMPONENT_TEMPLATE_TEMPLATE_PARM:
     case DEMANGLE_COMPONENT_TEMPLATE_PACK_PARM:
+    case DEMANGLE_COMPONENT_FRIEND:
       if (left == NULL)
 	return NULL;
       break;
@@ -1681,6 +1682,7 @@ d_maybe_module_name (struct d_info *di, struct demangle_component **name)
 /* <unqualified-name> ::= [<module-name>] <operator-name> [<abi-tags>]
                       ::= [<module-name>] <ctor-dtor-name> [<abi-tags>]
                       ::= [<module-name>] <source-name> [<abi-tags>]
+		      ::= [<module-name>] F <source-name> [<abi-tags>]
 		      ::= [<module-name>] <local-source-name>  [<abi-tags>]
                       ::= [<module-name>] DC <source-name>+ E [<abi-tags>]
     <local-source-name>	::= L <source-name> <discriminator> [<abi-tags>]
@@ -1692,11 +1694,18 @@ d_unqualified_name (struct d_info *di, struct demangle_component *scope,
 {
   struct demangle_component *ret;
   char peek;
+  int member_like_friend = 0;
 
   if (!d_maybe_module_name (di, &module))
     return NULL;
 
   peek = d_peek_char (di);
+  if (peek == 'F')
+    {
+      member_like_friend = 1;
+      d_advance (di, 1);
+      peek = d_peek_char (di);
+    }
   if (IS_DIGIT (peek))
     ret = d_source_name (di);
   else if (IS_LOWER (peek))
@@ -1773,6 +1782,8 @@ d_unqualified_name (struct d_info *di, struct demangle_component *scope,
     ret = d_make_comp (di, DEMANGLE_COMPONENT_MODULE_ENTITY, ret, module);
   if (d_peek_char (di) == 'B')
     ret = d_abi_tags (di, ret);
+  if (member_like_friend)
+    ret = d_make_comp (di, DEMANGLE_COMPONENT_FRIEND, ret, NULL);
   if (scope)
     ret = d_make_comp (di, DEMANGLE_COMPONENT_QUAL_NAME, scope, ret);
 
@@ -4459,6 +4470,7 @@ d_count_templates_scopes (struct d_print_info *dpi,
     case DEMANGLE_COMPONENT_GLOBAL_CONSTRUCTORS:
     case DEMANGLE_COMPONENT_GLOBAL_DESTRUCTORS:
     case DEMANGLE_COMPONENT_MODULE_ENTITY:
+    case DEMANGLE_COMPONENT_FRIEND:
       d_count_templates_scopes (dpi, d_left (dc));
       break;
 
@@ -6197,6 +6209,11 @@ d_print_comp_inner (struct d_print_info *dpi, int options,
       d_append_char (dpi, ']');
       return;
 
+    case DEMANGLE_COMPONENT_FRIEND:
+      d_print_comp (dpi, options, d_left (dc));
+      d_append_string (dpi, "[friend]");
+      return;
+
     case DEMANGLE_COMPONENT_TEMPLATE_HEAD:
       {
 	d_append_char (dpi, '<');
diff --git a/libiberty/testsuite/demangle-expected b/libiberty/testsuite/demangle-expected
index 0acd2d635db0..01ca22278cd3 100644
--- a/libiberty/testsuite/demangle-expected
+++ b/libiberty/testsuite/demangle-expected
@@ -1689,3 +1689,6 @@ X::operator Z<int><int>()::y
 
 _ZZN1XIfEcv1ZIT_EIiEEvE1y
 X<float>::operator Z<int><int>()::y
+
+_ZN1SILi1EEF3barIiEEiR4Base
+int S<1>::bar[friend]<int>(Base&)
-- 
GitLab