public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] c++/modules: Fix instantiation of imported temploid friends [PR114275]
@ 2024-03-25  9:17 Nathaniel Shead
  2024-04-15  4:49 ` [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare Nathaniel Shead
  2024-04-15  4:53 ` [PATCH v2 2/2] c++/modules: Fix instantiation of imported temploid friends [PR114275] Nathaniel Shead
  0 siblings, 2 replies; 12+ messages in thread
From: Nathaniel Shead @ 2024-03-25  9:17 UTC (permalink / raw)
  To: gcc-patches; +Cc: Jason Merrill, Nathan Sidwell, Patrick Palka

Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

I'm not 100% sure I've covered all places where this needs to be handled
but so far this passes all the testcases I have.

-- >8 --

This patch fixes a number of issues with the handling of temploid friend
declarations.

The primary issue is that instantiations of friend declarations should
attach the declaration to the same module as the befriending class, by
[module.unit] p7.1 and [temp.friend] p2; this could be a different
module from the current TU, and so needs special handling.

This patch only implements this for class declarations so far, as
function declarations don't seem to be causing any issues with this
currently, but probably should revisit for GCC 15.

The other main issue here is that we can't assume that just because name
lookup didn't find a definition for a hidden template class, it doesn't
mean that it doesn't exist: it could be a non-exported entity that we've
nevertheless streamed in from an imported module. We need to ensure that
when instantiating friend classes that we return the same TYPE_DECL that
we got from our imports, otherwise we will get later issues with
'duplicate_decls' (rightfully) complaining that they're different.

	PR c++/105320
	PR c++/114275

gcc/cp/ChangeLog:

	* cp-tree.h (module_may_redeclare_friend_class): New.
	(propagate_defining_module): New.
	(lookup_imported_hidden_friend): New.
	* module.cc (imported_temploid_friends): New.
	(trees_out::decl_value): Write imported_temploid_friends.
	(trees_in::decl_value): Read it.
	(get_originating_module_decl): Handle instantiated friend
	classes being attached to a different module.
	(module_may_redeclare_friend_class): New.
	(propagate_defining_module): New.
	(init_modules): Initialize imported_temploid_friends.
	* name-lookup.cc (lookup_imported_hidden_friend): New.
	* pt.cc (tsubst_friend_class): Lookup imported hidden friends.
	Error when redeclaring the type in the wrong module. Propagate
	the defining module to the instantiated type.

gcc/testsuite/ChangeLog:

	* g++.dg/modules/tpl-friend-10_a.C: New test.
	* g++.dg/modules/tpl-friend-10_b.C: New test.
	* g++.dg/modules/tpl-friend-10_c.C: New test.
	* g++.dg/modules/tpl-friend-11_a.C: New test.
	* g++.dg/modules/tpl-friend-11_b.C: New test.
	* g++.dg/modules/tpl-friend-12_a.C: New test.
	* g++.dg/modules/tpl-friend-12_b.C: New test.
	* g++.dg/modules/tpl-friend-12_c.C: New test.
	* g++.dg/modules/tpl-friend-12_d.C: New test.
	* g++.dg/modules/tpl-friend-12_e.C: New test.
	* g++.dg/modules/tpl-friend-12_f.C: New test.
	* g++.dg/modules/tpl-friend-13_a.C: New test.
	* g++.dg/modules/tpl-friend-13_b.C: New test.
	* g++.dg/modules/tpl-friend-13_c.C: New test.
	* g++.dg/modules/tpl-friend-13_d.C: New test.
	* g++.dg/modules/tpl-friend-13_e.C: New test.
	* g++.dg/modules/tpl-friend-9.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                              |  3 +
 gcc/cp/module.cc                              | 96 +++++++++++++++++++
 gcc/cp/name-lookup.cc                         | 42 ++++++++
 gcc/cp/pt.cc                                  | 20 ++++
 gcc/testsuite/g++.dg/modules/tpl-friend-10.h  |  0
 .../g++.dg/modules/tpl-friend-10_a.C          | 15 +++
 .../g++.dg/modules/tpl-friend-10_b.C          |  5 +
 .../g++.dg/modules/tpl-friend-10_c.C          |  7 ++
 .../g++.dg/modules/tpl-friend-11_a.C          | 14 +++
 .../g++.dg/modules/tpl-friend-11_b.C          |  5 +
 .../g++.dg/modules/tpl-friend-12_a.C          | 10 ++
 .../g++.dg/modules/tpl-friend-12_b.C          |  9 ++
 .../g++.dg/modules/tpl-friend-12_c.C          | 10 ++
 .../g++.dg/modules/tpl-friend-12_d.C          |  8 ++
 .../g++.dg/modules/tpl-friend-12_e.C          |  7 ++
 .../g++.dg/modules/tpl-friend-12_f.C          |  8 ++
 .../g++.dg/modules/tpl-friend-13_a.C          |  7 ++
 .../g++.dg/modules/tpl-friend-13_b.C          |  5 +
 .../g++.dg/modules/tpl-friend-13_c.C          |  7 ++
 .../g++.dg/modules/tpl-friend-13_d.C          |  6 ++
 .../g++.dg/modules/tpl-friend-13_e.C          |  8 ++
 gcc/testsuite/g++.dg/modules/tpl-friend-9.C   | 11 +++
 22 files changed, 303 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10.h
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-9.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 52d53589e51..b782385c574 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7397,6 +7397,7 @@ inline bool module_exporting_p ()
 extern module_state *get_module (tree name, module_state *parent = NULL,
 				 bool partition = false);
 extern bool module_may_redeclare (tree decl);
+extern bool module_may_redeclare_friend_class (tree decl, tree friend_tmpl);
 
 extern bool module_global_init_needed ();
 extern bool module_determine_import_inits ();
@@ -7412,6 +7413,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
 extern void set_instantiating_module (tree);
 extern void set_defining_module (tree);
 extern void maybe_key_decl (tree ctx, tree decl);
+extern void propagate_defining_module (tree decl, tree tmpl);
 
 extern void mangle_module (int m, bool include_partition);
 extern void mangle_module_fini ();
@@ -7644,6 +7646,7 @@ extern bool template_guide_p			(const_tree);
 extern bool builtin_guide_p			(const_tree);
 extern void store_explicit_specifier		(tree, tree);
 extern tree lookup_explicit_specifier		(tree);
+extern tree lookup_imported_hidden_friend	(tree);
 extern void walk_specializations		(bool,
 						 void (*)(bool, spec_entry *,
 							  void *),
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 8aab9ea0bae..cd3acca5e52 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2693,6 +2693,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
 typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
 static keyed_map_t *keyed_table;
 
+/* Instantiations of temploid friends imported from another module
+   need to be owned by the same module as their instantiating template.
+   This maps these to the template that instantiated them.  */
+static hash_map<tree, tree> *imported_temploid_friends;
+
 /********************************************************************/
 /* Tree streaming.   The tree streaming is very specific to the tree
    structures themselves.  A tag indicates the kind of tree being
@@ -7884,6 +7889,14 @@ trees_out::decl_value (tree decl, depset *dep)
 	}
     }
 
+  if (TREE_CODE (decl) == TEMPLATE_DECL)
+    {
+      /* Write imported temploid friends so that importers can reconstruct
+	 this information on stream-in.  */
+      tree* slot = imported_temploid_friends->get (decl);
+      tree_node (slot ? *slot : NULL_TREE);
+    }
+
   bool is_typedef = false;
   if (!type && TREE_CODE (inner) == TYPE_DECL)
     {
@@ -8190,6 +8203,13 @@ trees_in::decl_value ()
 	}
     }
 
+  if (TREE_CODE (decl) == TEMPLATE_DECL)
+    if (tree owner = tree_node ())
+      {
+	bool exists = imported_temploid_friends->put (decl, owner);
+	gcc_assert (exists == !is_new);
+      }
+
   /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
   unsigned tdef_flags = 0;
   bool is_typedef = false;
@@ -18717,6 +18737,12 @@ get_originating_module_decl (tree decl)
 	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
 	decl = TYPE_NAME (DECL_CHAIN (decl));
 
+      /* An imported temploid friend is attached to the same module the
+	 befriending class was.  */
+      if (TREE_CODE (decl) == TEMPLATE_DECL)
+	if (tree *slot = imported_temploid_friends->get (decl))
+	  decl = *slot;
+
       int use;
       if (tree ti = node_template_info (decl, use))
 	{
@@ -18851,6 +18877,46 @@ module_may_redeclare (tree decl)
   return me && get_primary (them) == get_primary (me);
 }
 
+/* Like module_may_redeclare, but compares the attachment and module
+   with FRIEND_TMPL rather than the current scope.  */
+
+bool
+module_may_redeclare_friend_class (tree tmpl, tree friend_tmpl)
+{
+  tree inner = DECL_TEMPLATE_RESULT (tmpl);
+  tree friend_inner = DECL_TEMPLATE_RESULT (friend_tmpl);
+
+  bool tmpl_attached_p = (DECL_LANG_SPECIFIC (inner)
+			  && DECL_MODULE_ATTACH_P (inner));
+  bool friend_attached_p = (DECL_LANG_SPECIFIC (friend_inner)
+			    && DECL_MODULE_ATTACH_P (friend_inner));
+
+  if (!friend_attached_p)
+    /* We should be attached to the GMF if the befriending class is.  */
+    return !tmpl_attached_p;
+
+  if (!tmpl_attached_p)
+    /* The befriending class is attached to a named module, so should we. */
+    return false;
+
+  module_state *me = (*modules)[0];
+  module_state *them = me;
+
+  if (DECL_LANG_SPECIFIC (inner)
+      && DECL_MODULE_IMPORT_P (inner))
+    if (tree* slot = imported_temploid_friends->get (tmpl))
+      me = import_entity_module (import_entity_index (*slot));
+
+  if (DECL_LANG_SPECIFIC (friend_inner)
+      && DECL_MODULE_IMPORT_P (friend_inner))
+    them = import_entity_module (import_entity_index (friend_tmpl));
+
+  if (me == them)
+    return true;
+
+  return me && get_primary (me) == get_primary (them);
+}
+
 /* DECL is being created by this TU.  Record it came from here.  We
    record module purview, so we can see if partial or explicit
    specialization needs to be written out, even though its purviewness
@@ -18985,6 +19051,34 @@ maybe_key_decl (tree ctx, tree decl)
   vec.safe_push (decl);
 }
 
+/* DECL is an instantiated friend that should be attached to the same
+   module that TMPL is.  */
+
+void
+propagate_defining_module (tree decl, tree tmpl)
+{
+  if (!modules_p ())
+    return;
+
+  tree not_tmpl = DECL_TEMPLATE_RESULT (tmpl);
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
+    {
+      tree inner = DECL_TEMPLATE_RESULT (decl);
+      retrofit_lang_decl (inner);
+      DECL_MODULE_ATTACH_P (inner) = true;
+    }
+
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
+    {
+      bool exists = imported_temploid_friends->put (decl, tmpl);
+
+      /* We should only be called if lookup for an imported decl
+	 failed in tsubst_friend_class, in which case there shouldn't
+	 already be an entry in the map.  */
+      gcc_assert (!exists);
+    }
+}
+
 /* Create the flat name string.  It is simplest to have it handy.  */
 
 void
@@ -20200,6 +20294,8 @@ init_modules (cpp_reader *reader)
       vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
     }
 
+  imported_temploid_friends = new hash_map<tree,tree> (EXPERIMENT (1, 400));
+
 #if CHECKING_P
   note_defs = note_defs_table_t::create_ggc (1000);
 #endif
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index dce4caf8981..4e79ee84aa2 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
   add_decl_to_level (b, decl);
 }
 
+/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
+   instantiations of temploid hidden friends from imported modules.  */
+
+tree
+lookup_imported_hidden_friend (tree friend_tmpl)
+{
+  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
+  if (!DECL_LANG_SPECIFIC (inner)
+      || !DECL_MODULE_IMPORT_P (inner))
+    return NULL_TREE;
+
+  tree name = DECL_NAME (inner);
+  tree *slot = find_namespace_slot (current_namespace, name);
+  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
+    return NULL_TREE;
+
+  /* Look in the appropriate slot, as with check_module_override.  */
+  binding_slot mslot;
+  if (named_module_p ())
+    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
+				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
+      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
+  else
+    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
+  gcc_assert (!mslot.is_lazy ());
+
+  tree ovl = mslot;
+  if (!ovl)
+    return NULL_TREE;
+
+  /* We're only interested in declarations coming from the same module
+     of the friend class we're attempting to instantiate.  */
+  int m = get_originating_module (friend_tmpl);
+  gcc_assert (m != 0);
+
+  for (ovl_iterator iter (ovl); iter; ++iter)
+    if (get_originating_module (*iter) == m)
+      return *iter;
+
+  return NULL_TREE;
+}
+
 \f
 /* true means unconditionally make a BLOCK for the next level pushed.  */
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 8cf0d5b7a8d..1e0d3efe5d2 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11693,6 +11693,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
   tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
 		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
 
+  if (!tmpl)
+    /* If we didn't find by name lookup, the type may still exist but as a
+       'hidden' import; we should check for this too to avoid accidentally
+       instantiating a duplicate.  */
+    tmpl = lookup_imported_hidden_friend (friend_tmpl);
+
   if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
     {
       /* The friend template has already been declared.  Just
@@ -11701,6 +11707,16 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 	 of course.  We only need the innermost template parameters
 	 because that is all that redeclare_class_template will look
 	 at.  */
+
+      if (modules_p ()
+	  && !module_may_redeclare_friend_class (tmpl, friend_tmpl))
+	{
+	  auto_diagnostic_group d;
+	  error_at (DECL_SOURCE_LOCATION (friend_tmpl),
+		    "cannot declare %qD in different module", tmpl);
+	  inform (DECL_SOURCE_LOCATION (tmpl), "previously declared here");
+	}
+
       if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
 	  > TMPL_ARGS_DEPTH (args))
 	{
@@ -11750,6 +11766,10 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 						     args, tf_warning_or_error);
 	    }
 
+	  /* We need to propagate the attachment of the original template to the
+	     newly instantiated template type.  */
+	  propagate_defining_module (tmpl, friend_tmpl);
+
 	  /* Inject this template into the enclosing namspace scope.  */
 	  tmpl = pushdecl_namespace_level (tmpl, /*hiding=*/true);
 	}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10.h b/gcc/testsuite/g++.dg/modules/tpl-friend-10.h
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
new file mode 100644
index 00000000000..7547326e554
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
@@ -0,0 +1,15 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi test_support }
+
+module;
+template<class> struct _Sp_atomic;
+template<class> struct shared_ptr {
+  template<class> friend struct _Sp_atomic;
+  using atomic_type = _Sp_atomic<int>;
+};
+export module test_support;
+export
+template<class T> struct A {
+   shared_ptr<T> data;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
new file mode 100644
index 00000000000..6b88ee4258b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
@@ -0,0 +1,5 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+
+import test_support;
+A<int> a;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
new file mode 100644
index 00000000000..789bdeb64d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
@@ -0,0 +1,7 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi user }
+
+export module user;
+import test_support; 
+A<int> b;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
new file mode 100644
index 00000000000..f29eebd1a7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
@@ -0,0 +1,14 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+
+template <typename... _Elements> struct T;
+
+template <typename H> struct T<H> {
+  template <typename...> friend struct T;
+};
+
+export module M;
+export template <typename=void> void fun() { T<int> t; }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
new file mode 100644
index 00000000000..5bf79998139
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
@@ -0,0 +1,5 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+int main() { fun(); }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
new file mode 100644
index 00000000000..216dbf62c71
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:A }
+
+module M:A;
+
+template <typename T> struct A {
+  template <typename U> friend struct B;
+private:
+  int x = 42;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
new file mode 100644
index 00000000000..26e1c38b518
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:B }
+
+export module M:B;
+import :A;
+
+export template <typename U> struct B {
+  int foo(A<U> a) { return a.x; }
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
new file mode 100644
index 00000000000..e44c2819cfd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:C }
+
+export module M:C;
+import :A;
+
+template <typename T> struct B;
+export template <typename T, typename U> int bar(B<T> t, U u) {
+  return t.foo(u);
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
new file mode 100644
index 00000000000..9a575ad5046
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export import :B;
+export import :C;
+
+export int go_in_module();
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
new file mode 100644
index 00000000000..329d1e8b263
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+
+module M;
+
+int go_in_module() {
+  return bar(B<int>{}, A<int>{});
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
new file mode 100644
index 00000000000..c9855663fbd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+int main() {
+  B<double> b{};
+  go_in_module();
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
new file mode 100644
index 00000000000..dda5c544564
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export template <typename> struct A {
+  template <typename> friend struct B;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
new file mode 100644
index 00000000000..1fa5cc7b57b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
@@ -0,0 +1,5 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+A<int> a;
+template <typename> struct B {};  // { dg-error "different module" }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
new file mode 100644
index 00000000000..1f293fe0785
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+template <typename> struct B {};  // { dg-message "previously declared here" }
+A<int> a;  // { dg-message "required from here" }
+
+// { dg-error "different module" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
new file mode 100644
index 00000000000..34eb3249186
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
@@ -0,0 +1,6 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi O }
+
+export module O;
+export import M;
+A<int> a;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
new file mode 100644
index 00000000000..6d50bc487b3
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+
+template <typename> struct B {};  // { dg-message "previous declaration" }
+import O;
+A<double> b;  // { dg-message "required from here" }
+
+// specifically, B is defined in M, not O, despite the instantiation being in O
+// { dg-error "conflicting declaration \[^\n\r\]* B@M" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-9.C b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
new file mode 100644
index 00000000000..487b44327b0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
@@ -0,0 +1,11 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+template<class> struct A {
+  template<class> friend struct B;
+};
+A<int> a;
+template<class> struct B { };
-- 
2.43.2


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare
  2024-03-25  9:17 [PATCH] c++/modules: Fix instantiation of imported temploid friends [PR114275] Nathaniel Shead
@ 2024-04-15  4:49 ` Nathaniel Shead
  2024-04-17 15:13   ` Patrick Palka
  2024-04-19 16:18   ` Nathaniel Shead
  2024-04-15  4:53 ` [PATCH v2 2/2] c++/modules: Fix instantiation of imported temploid friends [PR114275] Nathaniel Shead
  1 sibling, 2 replies; 12+ messages in thread
From: Nathaniel Shead @ 2024-04-15  4:49 UTC (permalink / raw)
  To: gcc-patches; +Cc: Jason Merrill, Nathan Sidwell, Patrick Palka

I took another look at this patch and have split it into two, one (this
one) to standardise the error messages used and prepare
'module_may_redeclare' for use with temploid friends, and another
followup patch to actually handle them correctly.

Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

-- >8 --

Currently different places calling 'module_may_redeclare' all emit very
similar but slightly different error messages, and handle different
kinds of declarations differently.  This patch makes the function
perform its own error messages so that they're all in one place, and
prepares it for use with temploid friends (PR c++/114275).

gcc/cp/ChangeLog:

	* cp-tree.h (module_may_redeclare): Add default parameter.
	* decl.cc (duplicate_decls): Don't emit errors for failed
	module_may_redeclare.
	(xref_tag): Likewise.
	(start_enum): Likewise.
	* semantics.cc (begin_class_definition): Likewise.
	* module.cc (module_may_redeclare): Clean up logic. Emit error
	messages on failure.

gcc/testsuite/ChangeLog:

	* g++.dg/modules/enum-12.C: Update error message.
	* g++.dg/modules/friend-5_b.C: Likewise.
	* g++.dg/modules/shadow-1_b.C: Likewise.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                          |   2 +-
 gcc/cp/decl.cc                            |  28 +----
 gcc/cp/module.cc                          | 120 ++++++++++++++--------
 gcc/cp/semantics.cc                       |   6 +-
 gcc/testsuite/g++.dg/modules/enum-12.C    |   2 +-
 gcc/testsuite/g++.dg/modules/friend-5_b.C |   2 +-
 gcc/testsuite/g++.dg/modules/shadow-1_b.C |   5 +-
 7 files changed, 89 insertions(+), 76 deletions(-)

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 1dbb577a38d..faa7a0052a5 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7401,7 +7401,7 @@ inline bool module_exporting_p ()
 
 extern module_state *get_module (tree name, module_state *parent = NULL,
 				 bool partition = false);
-extern bool module_may_redeclare (tree decl);
+extern bool module_may_redeclare (tree olddecl, tree newdecl = NULL);
 
 extern bool module_global_init_needed ();
 extern bool module_determine_import_inits ();
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 65ab64885ff..aa66da4829d 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -2279,18 +2279,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
       && TREE_CODE (olddecl) != NAMESPACE_DECL
       && !hiding)
     {
-      if (!module_may_redeclare (olddecl))
-	{
-	  if (DECL_ARTIFICIAL (olddecl))
-	    error ("declaration %qD conflicts with builtin", newdecl);
-	  else
-	    {
-	      error ("declaration %qD conflicts with import", newdecl);
-	      inform (olddecl_loc, "import declared %q#D here", olddecl);
-	    }
-
-	  return error_mark_node;
-	}
+      if (!module_may_redeclare (olddecl, newdecl))
+	return error_mark_node;
 
       tree not_tmpl = STRIP_TEMPLATE (olddecl);
       if (DECL_LANG_SPECIFIC (not_tmpl)
@@ -16620,12 +16610,7 @@ xref_tag (enum tag_types tag_code, tree name,
 	{
 	  tree decl = TYPE_NAME (t);
 	  if (!module_may_redeclare (decl))
-	    {
-	      auto_diagnostic_group d;
-	      error ("cannot declare %qD in a different module", decl);
-	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
-	      return error_mark_node;
-	    }
+	    return error_mark_node;
 
 	  tree not_tmpl = STRIP_TEMPLATE (decl);
 	  if (DECL_LANG_SPECIFIC (not_tmpl)
@@ -16973,12 +16958,7 @@ start_enum (tree name, tree enumtype, tree underlying_type,
 	{
 	  tree decl = TYPE_NAME (enumtype);
 	  if (!module_may_redeclare (decl))
-	    {
-	      auto_diagnostic_group d;
-	      error ("cannot declare %qD in different module", decl);
-	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
-	      enumtype = error_mark_node;
-	    }
+	    enumtype = error_mark_node;
 	  else
 	    set_instantiating_module (decl);
 	}
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 001430a4a8f..e2d2910ae48 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -18992,11 +18992,15 @@ get_importing_module (tree decl, bool flexible)
   return module->mod;
 }
 
-/* Is it permissible to redeclare DECL.  */
+/* Is it permissible to redeclare OLDDECL with NEWDECL.
+
+   If NEWDECL is NULL, assumes that OLDDECL will be redeclared using
+   the current scope's module and attachment.  */
 
 bool
-module_may_redeclare (tree decl)
+module_may_redeclare (tree olddecl, tree newdecl)
 {
+  tree decl = olddecl;
   for (;;)
     {
       tree ctx = CP_DECL_CONTEXT (decl);
@@ -19010,58 +19014,94 @@ module_may_redeclare (tree decl)
       decl = TYPE_NAME (ctx);
     }
 
-  tree not_tmpl = STRIP_TEMPLATE (decl);
-
   int use_tpl = 0;
-  if (node_template_info (not_tmpl, use_tpl) && use_tpl)
+  if (node_template_info (STRIP_TEMPLATE (decl), use_tpl) && use_tpl)
     // Specializations of any kind can be redeclared anywhere.
     // FIXME: Should we be checking this in more places on the scope chain?
     return true;
 
-  if (!DECL_LANG_SPECIFIC (not_tmpl) || !DECL_MODULE_ATTACH_P (not_tmpl))
-    // Decl is attached to global module.  Current scope needs to be too.
-    return !module_attach_p ();
+  module_state *old_mod = (*modules)[0];
+  module_state *new_mod = old_mod;
 
-  module_state *me = (*modules)[0];
-  module_state *them = me;
+  tree old_origin = get_originating_module_decl (decl);
+  tree old_inner = STRIP_TEMPLATE (old_origin);
+  bool olddecl_attached_p = (DECL_LANG_SPECIFIC (old_inner)
+			     && DECL_MODULE_ATTACH_P (old_inner));
+  if (DECL_LANG_SPECIFIC (old_inner) && DECL_MODULE_IMPORT_P (old_inner))
+    {
+      unsigned index = import_entity_index (old_origin);
+      old_mod = import_entity_module (index);
+    }
 
-  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
+  bool newdecl_attached_p = module_attach_p ();
+  if (newdecl)
     {
-      /* We can be given the TEMPLATE_RESULT.  We want the
-	 TEMPLATE_DECL.  */
-      int use_tpl = -1;
-      if (tree ti = node_template_info (decl, use_tpl))
+      tree new_origin = get_originating_module_decl (newdecl);
+      tree new_inner = STRIP_TEMPLATE (new_origin);
+      newdecl_attached_p = (DECL_LANG_SPECIFIC (new_inner)
+			    && DECL_MODULE_ATTACH_P (new_inner));
+      if (DECL_LANG_SPECIFIC (new_inner) && DECL_MODULE_IMPORT_P (new_inner))
 	{
-	  tree tmpl = TI_TEMPLATE (ti);
-	  if (use_tpl == 2)
-	    {
-	      /* A partial specialization.  Find that specialization's
-		 template_decl.  */
-	      for (tree list = DECL_TEMPLATE_SPECIALIZATIONS (tmpl);
-		   list; list = TREE_CHAIN (list))
-		if (DECL_TEMPLATE_RESULT (TREE_VALUE (list)) == decl)
-		  {
-		    decl = TREE_VALUE (list);
-		    break;
-		}
-	    }
-	  else if (DECL_TEMPLATE_RESULT (tmpl) == decl)
-	    decl = tmpl;
+	  unsigned index = import_entity_index (new_origin);
+	  new_mod = import_entity_module (index);
 	}
-      unsigned index = import_entity_index (decl);
-      them = import_entity_module (index);
     }
 
-  // Decl is attached to named module.  Current scope needs to be
-  // attaching to the same module.
-  if (!module_attach_p ())
-    return false;
+  /* Module attachment needs to match.  */
+  if (olddecl_attached_p == newdecl_attached_p)
+    {
+      if (!olddecl_attached_p)
+	/* Both are GM entities, OK.  */
+	return true;
 
-  // Both attached to named module.
-  if (me == them)
-    return true;
+      if (new_mod == old_mod
+	  || (new_mod && get_primary (new_mod) == get_primary (old_mod)))
+	/* Both attached to same named module, OK.  */
+	return true;
+    }
+
+  /* Attached to different modules, error.  */
+  decl = newdecl ? newdecl : olddecl;
+  location_t loc = newdecl ? DECL_SOURCE_LOCATION (newdecl) : input_location;
+  if (DECL_ARTIFICIAL (olddecl) && !DECL_IMPLICIT_TYPEDEF_P (olddecl))
+    error_at (loc, "declaration %qD conflicts with builtin", decl);
+  else if (DECL_LANG_SPECIFIC (old_inner) && DECL_MODULE_IMPORT_P (old_inner))
+    {
+      auto_diagnostic_group d;
+      if (newdecl_attached_p)
+	error_at (loc, "redeclaring %qD in module %qs conflicts with import",
+		  decl, new_mod->get_flatname ());
+      else
+	error_at (loc, "redeclaring %qD in global module conflicts with import",
+		  decl);
 
-  return me && get_primary (them) == get_primary (me);
+      if (olddecl_attached_p)
+	inform (DECL_SOURCE_LOCATION (olddecl),
+		"import declared attached to module %qs",
+		old_mod->get_flatname ());
+      else
+	inform (DECL_SOURCE_LOCATION (olddecl),
+		"import declared in global module");
+    }
+  else
+    {
+      auto_diagnostic_group d;
+      if (newdecl_attached_p)
+	error_at (loc, "conflicting declaration of %qD in module %qs",
+		  decl, new_mod->get_flatname ());
+      else
+	error_at (loc, "conflicting declaration of %qD in global module",
+		  decl);
+
+      if (olddecl_attached_p)
+	inform (DECL_SOURCE_LOCATION (olddecl),
+		"previously declared in module %qs",
+		old_mod->get_flatname ());
+      else
+	inform (DECL_SOURCE_LOCATION (olddecl),
+		"previously declared in global module");
+    }
+  return false;
 }
 
 /* DECL is being created by this TU.  Record it came from here.  We
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 02c7c1bf5a4..2dde65a970b 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -3777,11 +3777,7 @@ begin_class_definition (tree t)
   if (modules_p ())
     {
       if (!module_may_redeclare (TYPE_NAME (t)))
-	{
-	  error ("cannot declare %qD in a different module", TYPE_NAME (t));
-	  inform (DECL_SOURCE_LOCATION (TYPE_NAME (t)), "declared here");
-	  return error_mark_node;
-	}
+	return error_mark_node;
       set_instantiating_module (TYPE_NAME (t));
       set_defining_module (TYPE_NAME (t));
     }
diff --git a/gcc/testsuite/g++.dg/modules/enum-12.C b/gcc/testsuite/g++.dg/modules/enum-12.C
index 57eeb85d92a..019c3da4218 100644
--- a/gcc/testsuite/g++.dg/modules/enum-12.C
+++ b/gcc/testsuite/g++.dg/modules/enum-12.C
@@ -4,7 +4,7 @@
 
 export module foo;
 namespace std {
-  enum class align_val_t : decltype(sizeof(int)) {};  // { dg-error "different module" }
+  enum class align_val_t : decltype(sizeof(int)) {};  // { dg-error "conflicting declaration" }
 }
 
 // { dg-prune-output "not writing module" }
diff --git a/gcc/testsuite/g++.dg/modules/friend-5_b.C b/gcc/testsuite/g++.dg/modules/friend-5_b.C
index f043d7a340d..6b561265155 100644
--- a/gcc/testsuite/g++.dg/modules/friend-5_b.C
+++ b/gcc/testsuite/g++.dg/modules/friend-5_b.C
@@ -4,7 +4,7 @@
 export module bar;
 import foo;
 
-class B { // { dg-error "in a different module" }
+class B { // { dg-error "conflicts with import" }
   B() { object.value = 42; }
   A object;
 };
diff --git a/gcc/testsuite/g++.dg/modules/shadow-1_b.C b/gcc/testsuite/g++.dg/modules/shadow-1_b.C
index 646381237ac..7f6a3182998 100644
--- a/gcc/testsuite/g++.dg/modules/shadow-1_b.C
+++ b/gcc/testsuite/g++.dg/modules/shadow-1_b.C
@@ -1,8 +1,5 @@
 // { dg-additional-options -fmodules-ts }
 import shadow;
 
-// unfortunately not the exact same diagnostic in both cases :(
-
 void stat (); // { dg-error "conflicts with import" }
-
-struct stat {}; // { dg-error "in a different module" }
+struct stat {}; // { dg-error "conflicts with import" }
-- 
2.43.2


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v2 2/2] c++/modules: Fix instantiation of imported temploid friends [PR114275]
  2024-03-25  9:17 [PATCH] c++/modules: Fix instantiation of imported temploid friends [PR114275] Nathaniel Shead
  2024-04-15  4:49 ` [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare Nathaniel Shead
@ 2024-04-15  4:53 ` Nathaniel Shead
  2024-04-17 18:02   ` Patrick Palka
  1 sibling, 1 reply; 12+ messages in thread
From: Nathaniel Shead @ 2024-04-15  4:53 UTC (permalink / raw)
  To: gcc-patches; +Cc: Jason Merrill, Nathan Sidwell, Patrick Palka

I'm not a huge fan of always streaming 'imported_temploid_friends' for
all decls, but I don't think it adds much performance cost over adding a
new flag to categorise decls that might be marked as such.

Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

-- >8 --

This patch fixes a number of issues with the handling of temploid friend
declarations.

The primary issue is that instantiations of friend declarations should
attach the declaration to the same module as the befriending class, by
[module.unit] p7.1 and [temp.friend] p2; this could be a different
module from the current TU, and so needs special handling.

The other main issue here is that we can't assume that just because name
lookup didn't find a definition for a hidden template class, it doesn't
mean that it doesn't exist: it could be a non-exported entity that we've
nevertheless streamed in from an imported module.  We need to ensure
that when instantiating friend classes that we return the same TYPE_DECL
that we got from our imports, otherwise we will get later issues with
'duplicate_decls' (rightfully) complaining that they're different.

This doesn't appear necessary for functions due to the existing name
lookup handling already finding these hidden declarations.

	PR c++/105320
	PR c++/114275

gcc/cp/ChangeLog:

	* cp-tree.h (propagate_defining_module): Declare.
	(lookup_imported_hidden_friend): Declare.
	* decl.cc (duplicate_decls): Also check if hidden declarations
	can be redeclared in this module.
	* module.cc (imported_temploid_friends): New map.
	(init_modules): Initialize it.
	(trees_out::decl_value): Write it.
	(trees_in::decl_value): Read it.
	(get_originating_module_decl): Follow the owning decl for an
	imported temploid friend.
	(propagate_defining_module): New function.
	* name-lookup.cc (lookup_imported_hidden_friend): New function.
	* pt.cc (tsubst_friend_function): Propagate defining module for
	new friend functions.
	(tsubst_friend_class): Lookup imported hidden friends. Check
	for valid redeclaration. Propagate defining module for new
	friend classes.

gcc/testsuite/ChangeLog:

	* g++.dg/modules/tpl-friend-10_a.C: New test.
	* g++.dg/modules/tpl-friend-10_b.C: New test.
	* g++.dg/modules/tpl-friend-10_c.C: New test.
	* g++.dg/modules/tpl-friend-11_a.C: New test.
	* g++.dg/modules/tpl-friend-11_b.C: New test.
	* g++.dg/modules/tpl-friend-12_a.C: New test.
	* g++.dg/modules/tpl-friend-12_b.C: New test.
	* g++.dg/modules/tpl-friend-12_c.C: New test.
	* g++.dg/modules/tpl-friend-12_d.C: New test.
	* g++.dg/modules/tpl-friend-12_e.C: New test.
	* g++.dg/modules/tpl-friend-12_f.C: New test.
	* g++.dg/modules/tpl-friend-13_a.C: New test.
	* g++.dg/modules/tpl-friend-13_b.C: New test.
	* g++.dg/modules/tpl-friend-13_c.C: New test.
	* g++.dg/modules/tpl-friend-13_d.C: New test.
	* g++.dg/modules/tpl-friend-13_e.C: New test.
	* g++.dg/modules/tpl-friend-9.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                              |  2 +
 gcc/cp/decl.cc                                | 36 +++++++------
 gcc/cp/module.cc                              | 52 +++++++++++++++++++
 gcc/cp/name-lookup.cc                         | 42 +++++++++++++++
 gcc/cp/pt.cc                                  | 19 +++++++
 .../g++.dg/modules/tpl-friend-10_a.C          | 15 ++++++
 .../g++.dg/modules/tpl-friend-10_b.C          |  5 ++
 .../g++.dg/modules/tpl-friend-10_c.C          |  7 +++
 .../g++.dg/modules/tpl-friend-11_a.C          | 14 +++++
 .../g++.dg/modules/tpl-friend-11_b.C          |  5 ++
 .../g++.dg/modules/tpl-friend-12_a.C          | 10 ++++
 .../g++.dg/modules/tpl-friend-12_b.C          |  9 ++++
 .../g++.dg/modules/tpl-friend-12_c.C          | 10 ++++
 .../g++.dg/modules/tpl-friend-12_d.C          |  8 +++
 .../g++.dg/modules/tpl-friend-12_e.C          |  7 +++
 .../g++.dg/modules/tpl-friend-12_f.C          |  8 +++
 .../g++.dg/modules/tpl-friend-13_a.C          | 12 +++++
 .../g++.dg/modules/tpl-friend-13_b.C          |  9 ++++
 .../g++.dg/modules/tpl-friend-13_c.C          | 11 ++++
 .../g++.dg/modules/tpl-friend-13_d.C          |  7 +++
 .../g++.dg/modules/tpl-friend-13_e.C          | 14 +++++
 gcc/testsuite/g++.dg/modules/tpl-friend-9.C   | 13 +++++
 22 files changed, 299 insertions(+), 16 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-9.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index faa7a0052a5..67cc7d7bcec 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7417,6 +7417,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
 extern void set_instantiating_module (tree);
 extern void set_defining_module (tree);
 extern void maybe_key_decl (tree ctx, tree decl);
+extern void propagate_defining_module (tree decl, tree orig);
 
 extern void mangle_module (int m, bool include_partition);
 extern void mangle_module_fini ();
@@ -7649,6 +7650,7 @@ extern bool template_guide_p			(const_tree);
 extern bool builtin_guide_p			(const_tree);
 extern void store_explicit_specifier		(tree, tree);
 extern tree lookup_explicit_specifier		(tree);
+extern tree lookup_imported_hidden_friend	(tree);
 extern void walk_specializations		(bool,
 						 void (*)(bool, spec_entry *,
 							  void *),
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index aa66da4829d..ba8689efe21 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -2276,30 +2276,34 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
 
   if (modules_p ()
       && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
-      && TREE_CODE (olddecl) != NAMESPACE_DECL
-      && !hiding)
+      && TREE_CODE (olddecl) != NAMESPACE_DECL)
     {
       if (!module_may_redeclare (olddecl, newdecl))
 	return error_mark_node;
 
-      tree not_tmpl = STRIP_TEMPLATE (olddecl);
-      if (DECL_LANG_SPECIFIC (not_tmpl)
-	  && DECL_MODULE_ATTACH_P (not_tmpl)
-	  /* Typedefs are not entities and so are OK to be redeclared
-	     as exported: see [module.interface]/p6.  */
-	  && TREE_CODE (olddecl) != TYPE_DECL)
+      if (!hiding)
 	{
-	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
-	      && !DECL_MODULE_EXPORT_P (not_tmpl))
+	  /* Hidden friends declarations just use exportingness of the
+	     old declaration; see CWG2588.  */
+	  tree not_tmpl = STRIP_TEMPLATE (olddecl);
+	  if (DECL_LANG_SPECIFIC (not_tmpl)
+	      && DECL_MODULE_ATTACH_P (not_tmpl)
+	      /* Typedefs are not entities and so are OK to be redeclared
+		 as exported: see [module.interface]/p6.  */
+	      && TREE_CODE (olddecl) != TYPE_DECL)
 	    {
-	      auto_diagnostic_group d;
-	      error ("conflicting exporting for declaration %qD", newdecl);
-	      inform (olddecl_loc,
-		      "previously declared here without exporting");
+	      if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
+		  && !DECL_MODULE_EXPORT_P (not_tmpl))
+		{
+		  auto_diagnostic_group d;
+		  error ("conflicting exporting for declaration %qD", newdecl);
+		  inform (olddecl_loc,
+			  "previously declared here without exporting");
+		}
 	    }
+	  else if (DECL_MODULE_EXPORT_P (newdecl))
+	    DECL_MODULE_EXPORT_P (not_tmpl) = true;
 	}
-      else if (DECL_MODULE_EXPORT_P (newdecl))
-	DECL_MODULE_EXPORT_P (not_tmpl) = true;
     }
 
   /* We have committed to returning OLDDECL at this point.  */
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index e2d2910ae48..1a064e4ea79 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2727,6 +2727,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
 typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
 static keyed_map_t *keyed_table;
 
+/* Instantiations of temploid friends imported from another module
+   need to be owned by the same module as their instantiating template.
+   This maps these to the template that instantiated them.  */
+static hash_map<tree, tree> *imported_temploid_friends;
+
 /********************************************************************/
 /* Tree streaming.   The tree streaming is very specific to the tree
    structures themselves.  A tag indicates the kind of tree being
@@ -7997,6 +8002,11 @@ trees_out::decl_value (tree decl, depset *dep)
 	}
     }
 
+  /* Write imported temploid friends so that importers can reconstruct
+     this information on stream-in.  */
+  tree* slot = imported_temploid_friends->get (decl);
+  tree_node (slot ? *slot : NULL_TREE);
+
   bool is_typedef = false;
   if (!type && TREE_CODE (inner) == TYPE_DECL)
     {
@@ -8303,6 +8313,12 @@ trees_in::decl_value ()
 	}
     }
 
+  if (tree owner = tree_node ())
+    {
+      bool exists = imported_temploid_friends->put (decl, owner);
+      gcc_assert (exists == !is_new);
+    }
+
   /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
   unsigned tdef_flags = 0;
   bool is_typedef = false;
@@ -18930,6 +18946,12 @@ get_originating_module_decl (tree decl)
 	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
 	decl = TYPE_NAME (DECL_CHAIN (decl));
 
+      /* An imported temploid friend is attached to the same module the
+	 befriending class was.  */
+      if (imported_temploid_friends)
+	if (tree *slot = imported_temploid_friends->get (decl))
+	  decl = *slot;
+
       int use;
       if (tree ti = node_template_info (decl, use))
 	{
@@ -19238,6 +19260,34 @@ maybe_key_decl (tree ctx, tree decl)
   vec.safe_push (decl);
 }
 
+/* DECL is an instantiated friend that should be attached to the same
+   module that ORIG is.  */
+
+void
+propagate_defining_module (tree decl, tree orig)
+{
+  if (!modules_p ())
+    return;
+
+  tree not_tmpl = STRIP_TEMPLATE (orig);
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
+    {
+      tree inner = STRIP_TEMPLATE (decl);
+      retrofit_lang_decl (inner);
+      DECL_MODULE_ATTACH_P (inner) = true;
+    }
+
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
+    {
+      bool exists = imported_temploid_friends->put (decl, orig);
+
+      /* We should only be called if lookup for an existing decl
+	 failed, in which case there shouldn't already be an entry
+	 in the map.  */
+      gcc_assert (!exists);
+    }
+}
+
 /* Create the flat name string.  It is simplest to have it handy.  */
 
 void
@@ -20451,6 +20501,8 @@ init_modules (cpp_reader *reader)
       pending_table = new pending_map_t (EXPERIMENT (1, 400));
       entity_map = new entity_map_t (EXPERIMENT (1, 400));
       vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
+      imported_temploid_friends
+	= new hash_map<tree,tree> (EXPERIMENT (1, 400));
     }
 
 #if CHECKING_P
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 7af7f00e34c..dd6e7b6eaea 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
   add_decl_to_level (b, decl);
 }
 
+/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
+   instantiations of temploid hidden friends from imported modules.  */
+
+tree
+lookup_imported_hidden_friend (tree friend_tmpl)
+{
+  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
+  if (!DECL_LANG_SPECIFIC (inner)
+      || !DECL_MODULE_IMPORT_P (inner))
+    return NULL_TREE;
+
+  tree name = DECL_NAME (inner);
+  tree *slot = find_namespace_slot (current_namespace, name);
+  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
+    return NULL_TREE;
+
+  /* Look in the appropriate slot, as with check_module_override.  */
+  binding_slot mslot;
+  if (named_module_p ())
+    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
+				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
+      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
+  else
+    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
+  gcc_assert (!mslot.is_lazy ());
+
+  tree ovl = mslot;
+  if (!ovl)
+    return NULL_TREE;
+
+  /* We're only interested in declarations coming from the same module
+     of the friend class we're attempting to instantiate.  */
+  int m = get_originating_module (friend_tmpl);
+  gcc_assert (m != 0);
+
+  for (ovl_iterator iter (ovl); iter; ++iter)
+    if (get_originating_module (*iter) == m)
+      return *iter;
+
+  return NULL_TREE;
+}
+
 \f
 /* true means unconditionally make a BLOCK for the next level pushed.  */
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 3b2106dd3f6..e7e7f2fbc3b 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11512,6 +11512,10 @@ tsubst_friend_function (tree decl, tree args)
 	  new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
 	}
 
+      /* We need to propagate module attachment for the new friend from the
+	 owner of this template.  */
+      propagate_defining_module (new_friend, decl);
+
       /* Inside pushdecl_namespace_level, we will push into the
 	 current namespace. However, the friend function should go
 	 into the namespace of the template.  */
@@ -11715,6 +11719,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
   tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
 		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
 
+  if (!tmpl)
+    /* If we didn't find by name lookup, the type may still exist but as a
+       'hidden' import; we should check for this too to avoid accidentally
+       instantiating a duplicate.  */
+    tmpl = lookup_imported_hidden_friend (friend_tmpl);
+
   if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
     {
       /* The friend template has already been declared.  Just
@@ -11723,6 +11733,11 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 	 of course.  We only need the innermost template parameters
 	 because that is all that redeclare_class_template will look
 	 at.  */
+
+      if (modules_p ())
+	/* Check that we can redeclare TMPL in the current context.  */
+	module_may_redeclare (tmpl, friend_tmpl);
+
       if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
 	  > TMPL_ARGS_DEPTH (args))
 	{
@@ -11772,6 +11787,10 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 						     args, tf_warning_or_error);
 	    }
 
+	  /* We need to propagate the attachment of the original template to the
+	     newly instantiated template type.  */
+	  propagate_defining_module (tmpl, friend_tmpl);
+
 	  /* Inject this template into the enclosing namspace scope.  */
 	  tmpl = pushdecl_namespace_level (tmpl, /*hiding=*/true);
 	}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
new file mode 100644
index 00000000000..7547326e554
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
@@ -0,0 +1,15 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi test_support }
+
+module;
+template<class> struct _Sp_atomic;
+template<class> struct shared_ptr {
+  template<class> friend struct _Sp_atomic;
+  using atomic_type = _Sp_atomic<int>;
+};
+export module test_support;
+export
+template<class T> struct A {
+   shared_ptr<T> data;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
new file mode 100644
index 00000000000..6b88ee4258b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
@@ -0,0 +1,5 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+
+import test_support;
+A<int> a;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
new file mode 100644
index 00000000000..789bdeb64d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
@@ -0,0 +1,7 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi user }
+
+export module user;
+import test_support; 
+A<int> b;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
new file mode 100644
index 00000000000..f29eebd1a7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
@@ -0,0 +1,14 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+
+template <typename... _Elements> struct T;
+
+template <typename H> struct T<H> {
+  template <typename...> friend struct T;
+};
+
+export module M;
+export template <typename=void> void fun() { T<int> t; }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
new file mode 100644
index 00000000000..5bf79998139
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
@@ -0,0 +1,5 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+int main() { fun(); }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
new file mode 100644
index 00000000000..216dbf62c71
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:A }
+
+module M:A;
+
+template <typename T> struct A {
+  template <typename U> friend struct B;
+private:
+  int x = 42;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
new file mode 100644
index 00000000000..26e1c38b518
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:B }
+
+export module M:B;
+import :A;
+
+export template <typename U> struct B {
+  int foo(A<U> a) { return a.x; }
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
new file mode 100644
index 00000000000..e44c2819cfd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:C }
+
+export module M:C;
+import :A;
+
+template <typename T> struct B;
+export template <typename T, typename U> int bar(B<T> t, U u) {
+  return t.foo(u);
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
new file mode 100644
index 00000000000..9a575ad5046
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export import :B;
+export import :C;
+
+export int go_in_module();
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
new file mode 100644
index 00000000000..329d1e8b263
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+
+module M;
+
+int go_in_module() {
+  return bar(B<int>{}, A<int>{});
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
new file mode 100644
index 00000000000..c9855663fbd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+int main() {
+  B<double> b{};
+  go_in_module();
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
new file mode 100644
index 00000000000..a044485248f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
@@ -0,0 +1,12 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export template <typename> struct A {
+  template <typename> friend struct B;
+};
+
+export template <typename> struct C {
+  friend void f();
+  template <typename> friend void g();
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
new file mode 100644
index 00000000000..72dc8611e39
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+A<int> a;
+template <typename> struct B {};  // { dg-error "conflicts with import" }
+
+C<int> c;
+void f() {}  // { dg-error "conflicts with import" }
+template <typename> void g() {}  // { dg-error "conflicts with import" }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
new file mode 100644
index 00000000000..e1d2860bfe6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+template <typename> struct B {};  // { dg-message "previously declared" }
+A<int> a;  // { dg-message "required from here" }
+
+void f() {}  // { dg-message "previously declared" }
+template <typename> void g();  // { dg-message "previously declared" }
+C<int> c;  // { dg-message "required from here" }
+
+// { dg-error "conflicting declaration" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
new file mode 100644
index 00000000000..44b0f441db2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi O }
+
+export module O;
+export import M;
+A<int> a;
+C<int> c;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
new file mode 100644
index 00000000000..cec899e426b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
@@ -0,0 +1,14 @@
+// { dg-additional-options "-fmodules-ts" }
+
+template <typename> struct B {};  // { dg-message "previous declaration" }
+void f() {}
+template <typename T> void g() {}
+import O;
+A<double> b;  // { dg-message "required from here" }
+C<double> d;  // { dg-message "required from here" }
+
+// specifically, B is defined in M, not O, despite the instantiation being in O
+// { dg-error "conflicting declaration \[^\n\r\]* B@M" "" { target *-*-* } 0 }
+// and similarly for f and g
+// { dg-error "conflicting declaration \[^\n\r\]* f@M" "" { target *-*-* } 0 }
+// { dg-error "conflicting declaration \[^\n\r\]* g@M" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-9.C b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
new file mode 100644
index 00000000000..c7216f0f8c1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
@@ -0,0 +1,13 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+template<class> struct A {
+  template<class> friend struct B;
+  friend void C();
+};
+A<int> a;
+void C() {}
+template<class> struct B { };
-- 
2.43.2


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare
  2024-04-15  4:49 ` [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare Nathaniel Shead
@ 2024-04-17 15:13   ` Patrick Palka
  2024-04-19 16:18   ` Nathaniel Shead
  1 sibling, 0 replies; 12+ messages in thread
From: Patrick Palka @ 2024-04-17 15:13 UTC (permalink / raw)
  To: Nathaniel Shead; +Cc: gcc-patches, Jason Merrill, Nathan Sidwell, Patrick Palka

On Mon, 15 Apr 2024, Nathaniel Shead wrote:

> I took another look at this patch and have split it into two, one (this
> one) to standardise the error messages used and prepare
> 'module_may_redeclare' for use with temploid friends, and another
> followup patch to actually handle them correctly.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?

LGTM

> 
> -- >8 --
> 
> Currently different places calling 'module_may_redeclare' all emit very
> similar but slightly different error messages, and handle different
> kinds of declarations differently.  This patch makes the function
> perform its own error messages so that they're all in one place, and
> prepares it for use with temploid friends (PR c++/114275).
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (module_may_redeclare): Add default parameter.
> 	* decl.cc (duplicate_decls): Don't emit errors for failed
> 	module_may_redeclare.
> 	(xref_tag): Likewise.
> 	(start_enum): Likewise.
> 	* semantics.cc (begin_class_definition): Likewise.
> 	* module.cc (module_may_redeclare): Clean up logic. Emit error
> 	messages on failure.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/modules/enum-12.C: Update error message.
> 	* g++.dg/modules/friend-5_b.C: Likewise.
> 	* g++.dg/modules/shadow-1_b.C: Likewise.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>  gcc/cp/cp-tree.h                          |   2 +-
>  gcc/cp/decl.cc                            |  28 +----
>  gcc/cp/module.cc                          | 120 ++++++++++++++--------
>  gcc/cp/semantics.cc                       |   6 +-
>  gcc/testsuite/g++.dg/modules/enum-12.C    |   2 +-
>  gcc/testsuite/g++.dg/modules/friend-5_b.C |   2 +-
>  gcc/testsuite/g++.dg/modules/shadow-1_b.C |   5 +-
>  7 files changed, 89 insertions(+), 76 deletions(-)
> 
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 1dbb577a38d..faa7a0052a5 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7401,7 +7401,7 @@ inline bool module_exporting_p ()
>  
>  extern module_state *get_module (tree name, module_state *parent = NULL,
>  				 bool partition = false);
> -extern bool module_may_redeclare (tree decl);
> +extern bool module_may_redeclare (tree olddecl, tree newdecl = NULL);
>  
>  extern bool module_global_init_needed ();
>  extern bool module_determine_import_inits ();
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 65ab64885ff..aa66da4829d 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -2279,18 +2279,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
>        && TREE_CODE (olddecl) != NAMESPACE_DECL
>        && !hiding)
>      {
> -      if (!module_may_redeclare (olddecl))
> -	{
> -	  if (DECL_ARTIFICIAL (olddecl))
> -	    error ("declaration %qD conflicts with builtin", newdecl);
> -	  else
> -	    {
> -	      error ("declaration %qD conflicts with import", newdecl);
> -	      inform (olddecl_loc, "import declared %q#D here", olddecl);
> -	    }
> -
> -	  return error_mark_node;
> -	}
> +      if (!module_may_redeclare (olddecl, newdecl))
> +	return error_mark_node;
>  
>        tree not_tmpl = STRIP_TEMPLATE (olddecl);
>        if (DECL_LANG_SPECIFIC (not_tmpl)
> @@ -16620,12 +16610,7 @@ xref_tag (enum tag_types tag_code, tree name,
>  	{
>  	  tree decl = TYPE_NAME (t);
>  	  if (!module_may_redeclare (decl))
> -	    {
> -	      auto_diagnostic_group d;
> -	      error ("cannot declare %qD in a different module", decl);
> -	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
> -	      return error_mark_node;
> -	    }
> +	    return error_mark_node;
>  
>  	  tree not_tmpl = STRIP_TEMPLATE (decl);
>  	  if (DECL_LANG_SPECIFIC (not_tmpl)
> @@ -16973,12 +16958,7 @@ start_enum (tree name, tree enumtype, tree underlying_type,
>  	{
>  	  tree decl = TYPE_NAME (enumtype);
>  	  if (!module_may_redeclare (decl))
> -	    {
> -	      auto_diagnostic_group d;
> -	      error ("cannot declare %qD in different module", decl);
> -	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
> -	      enumtype = error_mark_node;
> -	    }
> +	    enumtype = error_mark_node;
>  	  else
>  	    set_instantiating_module (decl);
>  	}
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 001430a4a8f..e2d2910ae48 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -18992,11 +18992,15 @@ get_importing_module (tree decl, bool flexible)
>    return module->mod;
>  }
>  
> -/* Is it permissible to redeclare DECL.  */
> +/* Is it permissible to redeclare OLDDECL with NEWDECL.
> +
> +   If NEWDECL is NULL, assumes that OLDDECL will be redeclared using
> +   the current scope's module and attachment.  */
>  
>  bool
> -module_may_redeclare (tree decl)
> +module_may_redeclare (tree olddecl, tree newdecl)
>  {
> +  tree decl = olddecl;
>    for (;;)
>      {
>        tree ctx = CP_DECL_CONTEXT (decl);
> @@ -19010,58 +19014,94 @@ module_may_redeclare (tree decl)
>        decl = TYPE_NAME (ctx);
>      }
>  
> -  tree not_tmpl = STRIP_TEMPLATE (decl);
> -
>    int use_tpl = 0;
> -  if (node_template_info (not_tmpl, use_tpl) && use_tpl)
> +  if (node_template_info (STRIP_TEMPLATE (decl), use_tpl) && use_tpl)
>      // Specializations of any kind can be redeclared anywhere.
>      // FIXME: Should we be checking this in more places on the scope chain?
>      return true;
>  
> -  if (!DECL_LANG_SPECIFIC (not_tmpl) || !DECL_MODULE_ATTACH_P (not_tmpl))
> -    // Decl is attached to global module.  Current scope needs to be too.
> -    return !module_attach_p ();
> +  module_state *old_mod = (*modules)[0];
> +  module_state *new_mod = old_mod;
>  
> -  module_state *me = (*modules)[0];
> -  module_state *them = me;
> +  tree old_origin = get_originating_module_decl (decl);
> +  tree old_inner = STRIP_TEMPLATE (old_origin);
> +  bool olddecl_attached_p = (DECL_LANG_SPECIFIC (old_inner)
> +			     && DECL_MODULE_ATTACH_P (old_inner));
> +  if (DECL_LANG_SPECIFIC (old_inner) && DECL_MODULE_IMPORT_P (old_inner))
> +    {
> +      unsigned index = import_entity_index (old_origin);
> +      old_mod = import_entity_module (index);
> +    }
>  
> -  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
> +  bool newdecl_attached_p = module_attach_p ();
> +  if (newdecl)
>      {
> -      /* We can be given the TEMPLATE_RESULT.  We want the
> -	 TEMPLATE_DECL.  */
> -      int use_tpl = -1;
> -      if (tree ti = node_template_info (decl, use_tpl))
> +      tree new_origin = get_originating_module_decl (newdecl);
> +      tree new_inner = STRIP_TEMPLATE (new_origin);
> +      newdecl_attached_p = (DECL_LANG_SPECIFIC (new_inner)
> +			    && DECL_MODULE_ATTACH_P (new_inner));
> +      if (DECL_LANG_SPECIFIC (new_inner) && DECL_MODULE_IMPORT_P (new_inner))
>  	{
> -	  tree tmpl = TI_TEMPLATE (ti);
> -	  if (use_tpl == 2)
> -	    {
> -	      /* A partial specialization.  Find that specialization's
> -		 template_decl.  */
> -	      for (tree list = DECL_TEMPLATE_SPECIALIZATIONS (tmpl);
> -		   list; list = TREE_CHAIN (list))
> -		if (DECL_TEMPLATE_RESULT (TREE_VALUE (list)) == decl)
> -		  {
> -		    decl = TREE_VALUE (list);
> -		    break;
> -		}
> -	    }
> -	  else if (DECL_TEMPLATE_RESULT (tmpl) == decl)
> -	    decl = tmpl;
> +	  unsigned index = import_entity_index (new_origin);
> +	  new_mod = import_entity_module (index);
>  	}
> -      unsigned index = import_entity_index (decl);
> -      them = import_entity_module (index);
>      }
>  
> -  // Decl is attached to named module.  Current scope needs to be
> -  // attaching to the same module.
> -  if (!module_attach_p ())
> -    return false;
> +  /* Module attachment needs to match.  */
> +  if (olddecl_attached_p == newdecl_attached_p)
> +    {
> +      if (!olddecl_attached_p)
> +	/* Both are GM entities, OK.  */
> +	return true;
>  
> -  // Both attached to named module.
> -  if (me == them)
> -    return true;
> +      if (new_mod == old_mod
> +	  || (new_mod && get_primary (new_mod) == get_primary (old_mod)))
> +	/* Both attached to same named module, OK.  */
> +	return true;
> +    }
> +
> +  /* Attached to different modules, error.  */
> +  decl = newdecl ? newdecl : olddecl;
> +  location_t loc = newdecl ? DECL_SOURCE_LOCATION (newdecl) : input_location;
> +  if (DECL_ARTIFICIAL (olddecl) && !DECL_IMPLICIT_TYPEDEF_P (olddecl))
> +    error_at (loc, "declaration %qD conflicts with builtin", decl);
> +  else if (DECL_LANG_SPECIFIC (old_inner) && DECL_MODULE_IMPORT_P (old_inner))
> +    {
> +      auto_diagnostic_group d;
> +      if (newdecl_attached_p)
> +	error_at (loc, "redeclaring %qD in module %qs conflicts with import",
> +		  decl, new_mod->get_flatname ());
> +      else
> +	error_at (loc, "redeclaring %qD in global module conflicts with import",
> +		  decl);
>  
> -  return me && get_primary (them) == get_primary (me);
> +      if (olddecl_attached_p)
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"import declared attached to module %qs",
> +		old_mod->get_flatname ());
> +      else
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"import declared in global module");
> +    }
> +  else
> +    {
> +      auto_diagnostic_group d;
> +      if (newdecl_attached_p)
> +	error_at (loc, "conflicting declaration of %qD in module %qs",
> +		  decl, new_mod->get_flatname ());
> +      else
> +	error_at (loc, "conflicting declaration of %qD in global module",
> +		  decl);
> +
> +      if (olddecl_attached_p)
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"previously declared in module %qs",
> +		old_mod->get_flatname ());
> +      else
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"previously declared in global module");
> +    }
> +  return false;
>  }
>  
>  /* DECL is being created by this TU.  Record it came from here.  We
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 02c7c1bf5a4..2dde65a970b 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -3777,11 +3777,7 @@ begin_class_definition (tree t)
>    if (modules_p ())
>      {
>        if (!module_may_redeclare (TYPE_NAME (t)))
> -	{
> -	  error ("cannot declare %qD in a different module", TYPE_NAME (t));
> -	  inform (DECL_SOURCE_LOCATION (TYPE_NAME (t)), "declared here");
> -	  return error_mark_node;
> -	}
> +	return error_mark_node;
>        set_instantiating_module (TYPE_NAME (t));
>        set_defining_module (TYPE_NAME (t));
>      }
> diff --git a/gcc/testsuite/g++.dg/modules/enum-12.C b/gcc/testsuite/g++.dg/modules/enum-12.C
> index 57eeb85d92a..019c3da4218 100644
> --- a/gcc/testsuite/g++.dg/modules/enum-12.C
> +++ b/gcc/testsuite/g++.dg/modules/enum-12.C
> @@ -4,7 +4,7 @@
>  
>  export module foo;
>  namespace std {
> -  enum class align_val_t : decltype(sizeof(int)) {};  // { dg-error "different module" }
> +  enum class align_val_t : decltype(sizeof(int)) {};  // { dg-error "conflicting declaration" }
>  }
>  
>  // { dg-prune-output "not writing module" }
> diff --git a/gcc/testsuite/g++.dg/modules/friend-5_b.C b/gcc/testsuite/g++.dg/modules/friend-5_b.C
> index f043d7a340d..6b561265155 100644
> --- a/gcc/testsuite/g++.dg/modules/friend-5_b.C
> +++ b/gcc/testsuite/g++.dg/modules/friend-5_b.C
> @@ -4,7 +4,7 @@
>  export module bar;
>  import foo;
>  
> -class B { // { dg-error "in a different module" }
> +class B { // { dg-error "conflicts with import" }
>    B() { object.value = 42; }
>    A object;
>  };
> diff --git a/gcc/testsuite/g++.dg/modules/shadow-1_b.C b/gcc/testsuite/g++.dg/modules/shadow-1_b.C
> index 646381237ac..7f6a3182998 100644
> --- a/gcc/testsuite/g++.dg/modules/shadow-1_b.C
> +++ b/gcc/testsuite/g++.dg/modules/shadow-1_b.C
> @@ -1,8 +1,5 @@
>  // { dg-additional-options -fmodules-ts }
>  import shadow;
>  
> -// unfortunately not the exact same diagnostic in both cases :(
> -
>  void stat (); // { dg-error "conflicts with import" }
> -
> -struct stat {}; // { dg-error "in a different module" }
> +struct stat {}; // { dg-error "conflicts with import" }
> -- 
> 2.43.2
> 
> 


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] c++/modules: Fix instantiation of imported temploid friends [PR114275]
  2024-04-15  4:53 ` [PATCH v2 2/2] c++/modules: Fix instantiation of imported temploid friends [PR114275] Nathaniel Shead
@ 2024-04-17 18:02   ` Patrick Palka
  2024-04-19  2:14     ` Nathaniel Shead
  0 siblings, 1 reply; 12+ messages in thread
From: Patrick Palka @ 2024-04-17 18:02 UTC (permalink / raw)
  To: Nathaniel Shead; +Cc: gcc-patches, Jason Merrill, Nathan Sidwell, Patrick Palka

On Mon, 15 Apr 2024, Nathaniel Shead wrote:

> I'm not a huge fan of always streaming 'imported_temploid_friends' for
> all decls, but I don't think it adds much performance cost over adding a
> new flag to categorise decls that might be marked as such.

IIUC this value is going to be almost always null which is encoded as a
single 0 byte, which should be fast to stream.  But I wonder how much
larger <bits/stdc++.h> gets?  Can we get away with streaming this value
only for TEMPLATE_DECLs?

> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> 
> -- >8 --
> 
> This patch fixes a number of issues with the handling of temploid friend
> declarations.
> 
> The primary issue is that instantiations of friend declarations should
> attach the declaration to the same module as the befriending class, by
> [module.unit] p7.1 and [temp.friend] p2; this could be a different
> module from the current TU, and so needs special handling.

Nice, your approach seems consistent with Nathan's comments in the past
about this issue:

https://gcc.gnu.org/pipermail/gcc-patches/2022-October/603288.html
https://gcc.gnu.org/pipermail/gcc-patches/2023-February/611215.htmlw

> 
> The other main issue here is that we can't assume that just because name
> lookup didn't find a definition for a hidden template class, it doesn't
> mean that it doesn't exist: it could be a non-exported entity that we've
> nevertheless streamed in from an imported module.  We need to ensure
> that when instantiating friend classes that we return the same TYPE_DECL
> that we got from our imports, otherwise we will get later issues with
> 'duplicate_decls' (rightfully) complaining that they're different.
> 
> This doesn't appear necessary for functions due to the existing name
> lookup handling already finding these hidden declarations.

It does seem like a weird inconsistency that tsubst_friend_class needs
this workaround but not tsubst_friend_function.

I wonder if we can relax duplicate_decls to treat an instantiated
template friend class as a redeclaration instead of complaining,
mirroring its behavior for functions, which in turn would let us get rid
of the name lookup in tsubst_friend_class and eliminate the need for
lookup_imported_hidden_friend?  This may be too speculative/risky of
a refactoring at this stage though, and your approach has the nice
advantage of changing only modules code paths.

In any case I hope we can fix this issue for GCC 14!  LGTM overall.

> 
> 	PR c++/105320
> 	PR c++/114275
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (propagate_defining_module): Declare.
> 	(lookup_imported_hidden_friend): Declare.
> 	* decl.cc (duplicate_decls): Also check if hidden declarations
> 	can be redeclared in this module.
> 	* module.cc (imported_temploid_friends): New map.
> 	(init_modules): Initialize it.
> 	(trees_out::decl_value): Write it.
> 	(trees_in::decl_value): Read it.
> 	(get_originating_module_decl): Follow the owning decl for an
> 	imported temploid friend.
> 	(propagate_defining_module): New function.
> 	* name-lookup.cc (lookup_imported_hidden_friend): New function.
> 	* pt.cc (tsubst_friend_function): Propagate defining module for
> 	new friend functions.
> 	(tsubst_friend_class): Lookup imported hidden friends. Check
> 	for valid redeclaration. Propagate defining module for new
> 	friend classes.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/modules/tpl-friend-10_a.C: New test.
> 	* g++.dg/modules/tpl-friend-10_b.C: New test.
> 	* g++.dg/modules/tpl-friend-10_c.C: New test.
> 	* g++.dg/modules/tpl-friend-11_a.C: New test.
> 	* g++.dg/modules/tpl-friend-11_b.C: New test.
> 	* g++.dg/modules/tpl-friend-12_a.C: New test.
> 	* g++.dg/modules/tpl-friend-12_b.C: New test.
> 	* g++.dg/modules/tpl-friend-12_c.C: New test.
> 	* g++.dg/modules/tpl-friend-12_d.C: New test.
> 	* g++.dg/modules/tpl-friend-12_e.C: New test.
> 	* g++.dg/modules/tpl-friend-12_f.C: New test.
> 	* g++.dg/modules/tpl-friend-13_a.C: New test.
> 	* g++.dg/modules/tpl-friend-13_b.C: New test.
> 	* g++.dg/modules/tpl-friend-13_c.C: New test.
> 	* g++.dg/modules/tpl-friend-13_d.C: New test.
> 	* g++.dg/modules/tpl-friend-13_e.C: New test.
> 	* g++.dg/modules/tpl-friend-9.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>  gcc/cp/cp-tree.h                              |  2 +
>  gcc/cp/decl.cc                                | 36 +++++++------
>  gcc/cp/module.cc                              | 52 +++++++++++++++++++
>  gcc/cp/name-lookup.cc                         | 42 +++++++++++++++
>  gcc/cp/pt.cc                                  | 19 +++++++
>  .../g++.dg/modules/tpl-friend-10_a.C          | 15 ++++++
>  .../g++.dg/modules/tpl-friend-10_b.C          |  5 ++
>  .../g++.dg/modules/tpl-friend-10_c.C          |  7 +++
>  .../g++.dg/modules/tpl-friend-11_a.C          | 14 +++++
>  .../g++.dg/modules/tpl-friend-11_b.C          |  5 ++
>  .../g++.dg/modules/tpl-friend-12_a.C          | 10 ++++
>  .../g++.dg/modules/tpl-friend-12_b.C          |  9 ++++
>  .../g++.dg/modules/tpl-friend-12_c.C          | 10 ++++
>  .../g++.dg/modules/tpl-friend-12_d.C          |  8 +++
>  .../g++.dg/modules/tpl-friend-12_e.C          |  7 +++
>  .../g++.dg/modules/tpl-friend-12_f.C          |  8 +++
>  .../g++.dg/modules/tpl-friend-13_a.C          | 12 +++++
>  .../g++.dg/modules/tpl-friend-13_b.C          |  9 ++++
>  .../g++.dg/modules/tpl-friend-13_c.C          | 11 ++++
>  .../g++.dg/modules/tpl-friend-13_d.C          |  7 +++
>  .../g++.dg/modules/tpl-friend-13_e.C          | 14 +++++
>  gcc/testsuite/g++.dg/modules/tpl-friend-9.C   | 13 +++++
>  22 files changed, 299 insertions(+), 16 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
>  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-9.C
> 
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index faa7a0052a5..67cc7d7bcec 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7417,6 +7417,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
>  extern void set_instantiating_module (tree);
>  extern void set_defining_module (tree);
>  extern void maybe_key_decl (tree ctx, tree decl);
> +extern void propagate_defining_module (tree decl, tree orig);
>  
>  extern void mangle_module (int m, bool include_partition);
>  extern void mangle_module_fini ();
> @@ -7649,6 +7650,7 @@ extern bool template_guide_p			(const_tree);
>  extern bool builtin_guide_p			(const_tree);
>  extern void store_explicit_specifier		(tree, tree);
>  extern tree lookup_explicit_specifier		(tree);
> +extern tree lookup_imported_hidden_friend	(tree);
>  extern void walk_specializations		(bool,
>  						 void (*)(bool, spec_entry *,
>  							  void *),
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index aa66da4829d..ba8689efe21 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -2276,30 +2276,34 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
>  
>    if (modules_p ()
>        && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
> -      && TREE_CODE (olddecl) != NAMESPACE_DECL
> -      && !hiding)
> +      && TREE_CODE (olddecl) != NAMESPACE_DECL)
>      {
>        if (!module_may_redeclare (olddecl, newdecl))
>  	return error_mark_node;
>  
> -      tree not_tmpl = STRIP_TEMPLATE (olddecl);
> -      if (DECL_LANG_SPECIFIC (not_tmpl)
> -	  && DECL_MODULE_ATTACH_P (not_tmpl)
> -	  /* Typedefs are not entities and so are OK to be redeclared
> -	     as exported: see [module.interface]/p6.  */
> -	  && TREE_CODE (olddecl) != TYPE_DECL)
> +      if (!hiding)
>  	{
> -	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
> -	      && !DECL_MODULE_EXPORT_P (not_tmpl))
> +	  /* Hidden friends declarations just use exportingness of the
> +	     old declaration; see CWG2588.  */
> +	  tree not_tmpl = STRIP_TEMPLATE (olddecl);
> +	  if (DECL_LANG_SPECIFIC (not_tmpl)
> +	      && DECL_MODULE_ATTACH_P (not_tmpl)
> +	      /* Typedefs are not entities and so are OK to be redeclared
> +		 as exported: see [module.interface]/p6.  */
> +	      && TREE_CODE (olddecl) != TYPE_DECL)
>  	    {
> -	      auto_diagnostic_group d;
> -	      error ("conflicting exporting for declaration %qD", newdecl);
> -	      inform (olddecl_loc,
> -		      "previously declared here without exporting");
> +	      if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
> +		  && !DECL_MODULE_EXPORT_P (not_tmpl))
> +		{
> +		  auto_diagnostic_group d;
> +		  error ("conflicting exporting for declaration %qD", newdecl);
> +		  inform (olddecl_loc,
> +			  "previously declared here without exporting");
> +		}
>  	    }
> +	  else if (DECL_MODULE_EXPORT_P (newdecl))
> +	    DECL_MODULE_EXPORT_P (not_tmpl) = true;
>  	}
> -      else if (DECL_MODULE_EXPORT_P (newdecl))
> -	DECL_MODULE_EXPORT_P (not_tmpl) = true;
>      }
>  
>    /* We have committed to returning OLDDECL at this point.  */
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index e2d2910ae48..1a064e4ea79 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -2727,6 +2727,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
>  typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
>  static keyed_map_t *keyed_table;
>  
> +/* Instantiations of temploid friends imported from another module
> +   need to be owned by the same module as their instantiating template.
> +   This maps these to the template that instantiated them.  */
> +static hash_map<tree, tree> *imported_temploid_friends;
> +
>  /********************************************************************/
>  /* Tree streaming.   The tree streaming is very specific to the tree
>     structures themselves.  A tag indicates the kind of tree being
> @@ -7997,6 +8002,11 @@ trees_out::decl_value (tree decl, depset *dep)
>  	}
>      }
>  
> +  /* Write imported temploid friends so that importers can reconstruct
> +     this information on stream-in.  */
> +  tree* slot = imported_temploid_friends->get (decl);
> +  tree_node (slot ? *slot : NULL_TREE);
> +
>    bool is_typedef = false;
>    if (!type && TREE_CODE (inner) == TYPE_DECL)
>      {
> @@ -8303,6 +8313,12 @@ trees_in::decl_value ()
>  	}
>      }
>  
> +  if (tree owner = tree_node ())
> +    {
> +      bool exists = imported_temploid_friends->put (decl, owner);
> +      gcc_assert (exists == !is_new);
> +    }
> +
>    /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
>    unsigned tdef_flags = 0;
>    bool is_typedef = false;
> @@ -18930,6 +18946,12 @@ get_originating_module_decl (tree decl)
>  	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
>  	decl = TYPE_NAME (DECL_CHAIN (decl));
>  
> +      /* An imported temploid friend is attached to the same module the
> +	 befriending class was.  */
> +      if (imported_temploid_friends)
> +	if (tree *slot = imported_temploid_friends->get (decl))
> +	  decl = *slot;
> +
>        int use;
>        if (tree ti = node_template_info (decl, use))
>  	{
> @@ -19238,6 +19260,34 @@ maybe_key_decl (tree ctx, tree decl)
>    vec.safe_push (decl);
>  }
>  
> +/* DECL is an instantiated friend that should be attached to the same
> +   module that ORIG is.  */
> +
> +void
> +propagate_defining_module (tree decl, tree orig)
> +{
> +  if (!modules_p ())
> +    return;
> +
> +  tree not_tmpl = STRIP_TEMPLATE (orig);
> +  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
> +    {
> +      tree inner = STRIP_TEMPLATE (decl);
> +      retrofit_lang_decl (inner);
> +      DECL_MODULE_ATTACH_P (inner) = true;
> +    }
> +
> +  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
> +    {
> +      bool exists = imported_temploid_friends->put (decl, orig);
> +
> +      /* We should only be called if lookup for an existing decl
> +	 failed, in which case there shouldn't already be an entry
> +	 in the map.  */
> +      gcc_assert (!exists);
> +    }
> +}
> +
>  /* Create the flat name string.  It is simplest to have it handy.  */
>  
>  void
> @@ -20451,6 +20501,8 @@ init_modules (cpp_reader *reader)
>        pending_table = new pending_map_t (EXPERIMENT (1, 400));
>        entity_map = new entity_map_t (EXPERIMENT (1, 400));
>        vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
> +      imported_temploid_friends
> +	= new hash_map<tree,tree> (EXPERIMENT (1, 400));
>      }
>  
>  #if CHECKING_P
> diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
> index 7af7f00e34c..dd6e7b6eaea 100644
> --- a/gcc/cp/name-lookup.cc
> +++ b/gcc/cp/name-lookup.cc
> @@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
>    add_decl_to_level (b, decl);
>  }
>  
> +/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
> +   instantiations of temploid hidden friends from imported modules.  */
> +
> +tree
> +lookup_imported_hidden_friend (tree friend_tmpl)
> +{
> +  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
> +  if (!DECL_LANG_SPECIFIC (inner)
> +      || !DECL_MODULE_IMPORT_P (inner))
> +    return NULL_TREE;
> +
> +  tree name = DECL_NAME (inner);
> +  tree *slot = find_namespace_slot (current_namespace, name);
> +  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
> +    return NULL_TREE;
> +
> +  /* Look in the appropriate slot, as with check_module_override.  */
> +  binding_slot mslot;
> +  if (named_module_p ())
> +    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
> +				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
> +      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
> +  else
> +    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
> +  gcc_assert (!mslot.is_lazy ());
> +
> +  tree ovl = mslot;
> +  if (!ovl)
> +    return NULL_TREE;
> +
> +  /* We're only interested in declarations coming from the same module
> +     of the friend class we're attempting to instantiate.  */
> +  int m = get_originating_module (friend_tmpl);
> +  gcc_assert (m != 0);
> +
> +  for (ovl_iterator iter (ovl); iter; ++iter)
> +    if (get_originating_module (*iter) == m)
> +      return *iter;
> +
> +  return NULL_TREE;
> +}
> +
>  \f
>  /* true means unconditionally make a BLOCK for the next level pushed.  */
>  
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 3b2106dd3f6..e7e7f2fbc3b 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -11512,6 +11512,10 @@ tsubst_friend_function (tree decl, tree args)
>  	  new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
>  	}
>  
> +      /* We need to propagate module attachment for the new friend from the
> +	 owner of this template.  */
> +      propagate_defining_module (new_friend, decl);
> +
>        /* Inside pushdecl_namespace_level, we will push into the
>  	 current namespace. However, the friend function should go
>  	 into the namespace of the template.  */
> @@ -11715,6 +11719,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>    tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
>  		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
>  
> +  if (!tmpl)
> +    /* If we didn't find by name lookup, the type may still exist but as a
> +       'hidden' import; we should check for this too to avoid accidentally
> +       instantiating a duplicate.  */
> +    tmpl = lookup_imported_hidden_friend (friend_tmpl);
> +
>    if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
>      {
>        /* The friend template has already been declared.  Just
> @@ -11723,6 +11733,11 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>  	 of course.  We only need the innermost template parameters
>  	 because that is all that redeclare_class_template will look
>  	 at.  */
> +
> +      if (modules_p ())
> +	/* Check that we can redeclare TMPL in the current context.  */
> +	module_may_redeclare (tmpl, friend_tmpl);
> +
>        if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
>  	  > TMPL_ARGS_DEPTH (args))
>  	{
> @@ -11772,6 +11787,10 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>  						     args, tf_warning_or_error);
>  	    }
>  
> +	  /* We need to propagate the attachment of the original template to the
> +	     newly instantiated template type.  */
> +	  propagate_defining_module (tmpl, friend_tmpl);
> +
>  	  /* Inject this template into the enclosing namspace scope.  */
>  	  tmpl = pushdecl_namespace_level (tmpl, /*hiding=*/true);
>  	}
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
> new file mode 100644
> index 00000000000..7547326e554
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
> @@ -0,0 +1,15 @@
> +// PR c++/105320
> +// { dg-additional-options "-fmodules-ts -Wno-global-module" }
> +// { dg-module-cmi test_support }
> +
> +module;
> +template<class> struct _Sp_atomic;
> +template<class> struct shared_ptr {
> +  template<class> friend struct _Sp_atomic;
> +  using atomic_type = _Sp_atomic<int>;
> +};
> +export module test_support;
> +export
> +template<class T> struct A {
> +   shared_ptr<T> data;
> +};
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
> new file mode 100644
> index 00000000000..6b88ee4258b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
> @@ -0,0 +1,5 @@
> +// PR c++/105320
> +// { dg-additional-options "-fmodules-ts" }
> +
> +import test_support;
> +A<int> a;
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
> new file mode 100644
> index 00000000000..789bdeb64d5
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
> @@ -0,0 +1,7 @@
> +// PR c++/105320
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi user }
> +
> +export module user;
> +import test_support; 
> +A<int> b;
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
> new file mode 100644
> index 00000000000..f29eebd1a7f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
> @@ -0,0 +1,14 @@
> +// PR c++/114275
> +// { dg-additional-options "-fmodules-ts -Wno-global-module" }
> +// { dg-module-cmi M }
> +
> +module;
> +
> +template <typename... _Elements> struct T;
> +
> +template <typename H> struct T<H> {
> +  template <typename...> friend struct T;
> +};
> +
> +export module M;
> +export template <typename=void> void fun() { T<int> t; }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
> new file mode 100644
> index 00000000000..5bf79998139
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
> @@ -0,0 +1,5 @@
> +// PR c++/114275
> +// { dg-additional-options "-fmodules-ts" }
> +
> +import M;
> +int main() { fun(); }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
> new file mode 100644
> index 00000000000..216dbf62c71
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
> @@ -0,0 +1,10 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi M:A }
> +
> +module M:A;
> +
> +template <typename T> struct A {
> +  template <typename U> friend struct B;
> +private:
> +  int x = 42;
> +};
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
> new file mode 100644
> index 00000000000..26e1c38b518
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
> @@ -0,0 +1,9 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi M:B }
> +
> +export module M:B;
> +import :A;
> +
> +export template <typename U> struct B {
> +  int foo(A<U> a) { return a.x; }
> +};
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
> new file mode 100644
> index 00000000000..e44c2819cfd
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
> @@ -0,0 +1,10 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi M:C }
> +
> +export module M:C;
> +import :A;
> +
> +template <typename T> struct B;
> +export template <typename T, typename U> int bar(B<T> t, U u) {
> +  return t.foo(u);
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
> new file mode 100644
> index 00000000000..9a575ad5046
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
> @@ -0,0 +1,8 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi M }
> +
> +export module M;
> +export import :B;
> +export import :C;
> +
> +export int go_in_module();
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
> new file mode 100644
> index 00000000000..329d1e8b263
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
> @@ -0,0 +1,7 @@
> +// { dg-additional-options "-fmodules-ts" }
> +
> +module M;
> +
> +int go_in_module() {
> +  return bar(B<int>{}, A<int>{});
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
> new file mode 100644
> index 00000000000..c9855663fbd
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
> @@ -0,0 +1,8 @@
> +// { dg-additional-options "-fmodules-ts" }
> +
> +import M;
> +
> +int main() {
> +  B<double> b{};
> +  go_in_module();
> +}
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
> new file mode 100644
> index 00000000000..a044485248f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
> @@ -0,0 +1,12 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi M }
> +
> +export module M;
> +export template <typename> struct A {
> +  template <typename> friend struct B;
> +};
> +
> +export template <typename> struct C {
> +  friend void f();
> +  template <typename> friend void g();
> +};
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
> new file mode 100644
> index 00000000000..72dc8611e39
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
> @@ -0,0 +1,9 @@
> +// { dg-additional-options "-fmodules-ts" }
> +
> +import M;
> +A<int> a;
> +template <typename> struct B {};  // { dg-error "conflicts with import" }
> +
> +C<int> c;
> +void f() {}  // { dg-error "conflicts with import" }
> +template <typename> void g() {}  // { dg-error "conflicts with import" }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
> new file mode 100644
> index 00000000000..e1d2860bfe6
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
> @@ -0,0 +1,11 @@
> +// { dg-additional-options "-fmodules-ts" }
> +
> +import M;
> +template <typename> struct B {};  // { dg-message "previously declared" }
> +A<int> a;  // { dg-message "required from here" }
> +
> +void f() {}  // { dg-message "previously declared" }
> +template <typename> void g();  // { dg-message "previously declared" }
> +C<int> c;  // { dg-message "required from here" }
> +
> +// { dg-error "conflicting declaration" "" { target *-*-* } 0 }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
> new file mode 100644
> index 00000000000..44b0f441db2
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
> @@ -0,0 +1,7 @@
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi O }
> +
> +export module O;
> +export import M;
> +A<int> a;
> +C<int> c;
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
> new file mode 100644
> index 00000000000..cec899e426b
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
> @@ -0,0 +1,14 @@
> +// { dg-additional-options "-fmodules-ts" }
> +
> +template <typename> struct B {};  // { dg-message "previous declaration" }
> +void f() {}
> +template <typename T> void g() {}
> +import O;
> +A<double> b;  // { dg-message "required from here" }
> +C<double> d;  // { dg-message "required from here" }
> +
> +// specifically, B is defined in M, not O, despite the instantiation being in O
> +// { dg-error "conflicting declaration \[^\n\r\]* B@M" "" { target *-*-* } 0 }
> +// and similarly for f and g
> +// { dg-error "conflicting declaration \[^\n\r\]* f@M" "" { target *-*-* } 0 }
> +// { dg-error "conflicting declaration \[^\n\r\]* g@M" "" { target *-*-* } 0 }
> diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-9.C b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
> new file mode 100644
> index 00000000000..c7216f0f8c1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
> @@ -0,0 +1,13 @@
> +// PR c++/114275
> +// { dg-additional-options "-fmodules-ts" }
> +// { dg-module-cmi M }
> +
> +export module M;
> +
> +template<class> struct A {
> +  template<class> friend struct B;
> +  friend void C();
> +};
> +A<int> a;
> +void C() {}
> +template<class> struct B { };
> -- 
> 2.43.2
> 
> 


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 2/2] c++/modules: Fix instantiation of imported temploid friends [PR114275]
  2024-04-17 18:02   ` Patrick Palka
@ 2024-04-19  2:14     ` Nathaniel Shead
  2024-04-19 16:29       ` [PATCH v3 2/2] c++: " Nathaniel Shead
  0 siblings, 1 reply; 12+ messages in thread
From: Nathaniel Shead @ 2024-04-19  2:14 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, Jason Merrill, Nathan Sidwell

On Wed, Apr 17, 2024 at 02:02:21PM -0400, Patrick Palka wrote:
> On Mon, 15 Apr 2024, Nathaniel Shead wrote:
> 
> > I'm not a huge fan of always streaming 'imported_temploid_friends' for
> > all decls, but I don't think it adds much performance cost over adding a
> > new flag to categorise decls that might be marked as such.
> 
> IIUC this value is going to be almost always null which is encoded as a
> single 0 byte, which should be fast to stream.  But I wonder how much
> larger <bits/stdc++.h> gets?  Can we get away with streaming this value
> only for TEMPLATE_DECLs?

Yes, it should either just be a 0 byte or an additional backref
somewhere, which will likely also be small. On my system it increases
the size by 0.26%, from 31186800 bytes to 31268672.

But I've just found that this patch has a bug anyway, in that it doesn't
correctly dedup if the friend types are instantiated in two separate
modules that are then both imported.  I'll see what I need to do to fix
this which may influence what we need to stream here.

> > 
> > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> > 
> > -- >8 --
> > 
> > This patch fixes a number of issues with the handling of temploid friend
> > declarations.
> > 
> > The primary issue is that instantiations of friend declarations should
> > attach the declaration to the same module as the befriending class, by
> > [module.unit] p7.1 and [temp.friend] p2; this could be a different
> > module from the current TU, and so needs special handling.
> 
> Nice, your approach seems consistent with Nathan's comments in the past
> about this issue:
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2022-October/603288.html
> https://gcc.gnu.org/pipermail/gcc-patches/2023-February/611215.htmlw
> 
> > 
> > The other main issue here is that we can't assume that just because name
> > lookup didn't find a definition for a hidden template class, it doesn't
> > mean that it doesn't exist: it could be a non-exported entity that we've
> > nevertheless streamed in from an imported module.  We need to ensure
> > that when instantiating friend classes that we return the same TYPE_DECL
> > that we got from our imports, otherwise we will get later issues with
> > 'duplicate_decls' (rightfully) complaining that they're different.
> > 
> > This doesn't appear necessary for functions due to the existing name
> > lookup handling already finding these hidden declarations.
> 
> It does seem like a weird inconsistency that tsubst_friend_class needs
> this workaround but not tsubst_friend_function.
> 
> I wonder if we can relax duplicate_decls to treat an instantiated
> template friend class as a redeclaration instead of complaining,
> mirroring its behavior for functions, which in turn would let us get rid
> of the name lookup in tsubst_friend_class and eliminate the need for
> lookup_imported_hidden_friend?  This may be too speculative/risky of
> a refactoring at this stage though, and your approach has the nice
> advantage of changing only modules code paths.

Hm, that's a good idea.  I've played around a little bit with trying
this but I've gotten a little stuck, might try again later.  It also
feels a little silly to do a full instantiation of a (potentially) large
type only to immediately throw it away when we could just look for it
beforehand, but this does feel like a neater solution anyway.

> In any case I hope we can fix this issue for GCC 14!  LGTM overall.
> 
> > 
> > 	PR c++/105320
> > 	PR c++/114275
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* cp-tree.h (propagate_defining_module): Declare.
> > 	(lookup_imported_hidden_friend): Declare.
> > 	* decl.cc (duplicate_decls): Also check if hidden declarations
> > 	can be redeclared in this module.
> > 	* module.cc (imported_temploid_friends): New map.
> > 	(init_modules): Initialize it.
> > 	(trees_out::decl_value): Write it.
> > 	(trees_in::decl_value): Read it.
> > 	(get_originating_module_decl): Follow the owning decl for an
> > 	imported temploid friend.
> > 	(propagate_defining_module): New function.
> > 	* name-lookup.cc (lookup_imported_hidden_friend): New function.
> > 	* pt.cc (tsubst_friend_function): Propagate defining module for
> > 	new friend functions.
> > 	(tsubst_friend_class): Lookup imported hidden friends. Check
> > 	for valid redeclaration. Propagate defining module for new
> > 	friend classes.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/modules/tpl-friend-10_a.C: New test.
> > 	* g++.dg/modules/tpl-friend-10_b.C: New test.
> > 	* g++.dg/modules/tpl-friend-10_c.C: New test.
> > 	* g++.dg/modules/tpl-friend-11_a.C: New test.
> > 	* g++.dg/modules/tpl-friend-11_b.C: New test.
> > 	* g++.dg/modules/tpl-friend-12_a.C: New test.
> > 	* g++.dg/modules/tpl-friend-12_b.C: New test.
> > 	* g++.dg/modules/tpl-friend-12_c.C: New test.
> > 	* g++.dg/modules/tpl-friend-12_d.C: New test.
> > 	* g++.dg/modules/tpl-friend-12_e.C: New test.
> > 	* g++.dg/modules/tpl-friend-12_f.C: New test.
> > 	* g++.dg/modules/tpl-friend-13_a.C: New test.
> > 	* g++.dg/modules/tpl-friend-13_b.C: New test.
> > 	* g++.dg/modules/tpl-friend-13_c.C: New test.
> > 	* g++.dg/modules/tpl-friend-13_d.C: New test.
> > 	* g++.dg/modules/tpl-friend-13_e.C: New test.
> > 	* g++.dg/modules/tpl-friend-9.C: New test.
> > 
> > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> > ---
> >  gcc/cp/cp-tree.h                              |  2 +
> >  gcc/cp/decl.cc                                | 36 +++++++------
> >  gcc/cp/module.cc                              | 52 +++++++++++++++++++
> >  gcc/cp/name-lookup.cc                         | 42 +++++++++++++++
> >  gcc/cp/pt.cc                                  | 19 +++++++
> >  .../g++.dg/modules/tpl-friend-10_a.C          | 15 ++++++
> >  .../g++.dg/modules/tpl-friend-10_b.C          |  5 ++
> >  .../g++.dg/modules/tpl-friend-10_c.C          |  7 +++
> >  .../g++.dg/modules/tpl-friend-11_a.C          | 14 +++++
> >  .../g++.dg/modules/tpl-friend-11_b.C          |  5 ++
> >  .../g++.dg/modules/tpl-friend-12_a.C          | 10 ++++
> >  .../g++.dg/modules/tpl-friend-12_b.C          |  9 ++++
> >  .../g++.dg/modules/tpl-friend-12_c.C          | 10 ++++
> >  .../g++.dg/modules/tpl-friend-12_d.C          |  8 +++
> >  .../g++.dg/modules/tpl-friend-12_e.C          |  7 +++
> >  .../g++.dg/modules/tpl-friend-12_f.C          |  8 +++
> >  .../g++.dg/modules/tpl-friend-13_a.C          | 12 +++++
> >  .../g++.dg/modules/tpl-friend-13_b.C          |  9 ++++
> >  .../g++.dg/modules/tpl-friend-13_c.C          | 11 ++++
> >  .../g++.dg/modules/tpl-friend-13_d.C          |  7 +++
> >  .../g++.dg/modules/tpl-friend-13_e.C          | 14 +++++
> >  gcc/testsuite/g++.dg/modules/tpl-friend-9.C   | 13 +++++
> >  22 files changed, 299 insertions(+), 16 deletions(-)
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
> >  create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-9.C
> > 
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index faa7a0052a5..67cc7d7bcec 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -7417,6 +7417,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
> >  extern void set_instantiating_module (tree);
> >  extern void set_defining_module (tree);
> >  extern void maybe_key_decl (tree ctx, tree decl);
> > +extern void propagate_defining_module (tree decl, tree orig);
> >  
> >  extern void mangle_module (int m, bool include_partition);
> >  extern void mangle_module_fini ();
> > @@ -7649,6 +7650,7 @@ extern bool template_guide_p			(const_tree);
> >  extern bool builtin_guide_p			(const_tree);
> >  extern void store_explicit_specifier		(tree, tree);
> >  extern tree lookup_explicit_specifier		(tree);
> > +extern tree lookup_imported_hidden_friend	(tree);
> >  extern void walk_specializations		(bool,
> >  						 void (*)(bool, spec_entry *,
> >  							  void *),
> > diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> > index aa66da4829d..ba8689efe21 100644
> > --- a/gcc/cp/decl.cc
> > +++ b/gcc/cp/decl.cc
> > @@ -2276,30 +2276,34 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
> >  
> >    if (modules_p ()
> >        && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
> > -      && TREE_CODE (olddecl) != NAMESPACE_DECL
> > -      && !hiding)
> > +      && TREE_CODE (olddecl) != NAMESPACE_DECL)
> >      {
> >        if (!module_may_redeclare (olddecl, newdecl))
> >  	return error_mark_node;
> >  
> > -      tree not_tmpl = STRIP_TEMPLATE (olddecl);
> > -      if (DECL_LANG_SPECIFIC (not_tmpl)
> > -	  && DECL_MODULE_ATTACH_P (not_tmpl)
> > -	  /* Typedefs are not entities and so are OK to be redeclared
> > -	     as exported: see [module.interface]/p6.  */
> > -	  && TREE_CODE (olddecl) != TYPE_DECL)
> > +      if (!hiding)
> >  	{
> > -	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
> > -	      && !DECL_MODULE_EXPORT_P (not_tmpl))
> > +	  /* Hidden friends declarations just use exportingness of the
> > +	     old declaration; see CWG2588.  */
> > +	  tree not_tmpl = STRIP_TEMPLATE (olddecl);
> > +	  if (DECL_LANG_SPECIFIC (not_tmpl)
> > +	      && DECL_MODULE_ATTACH_P (not_tmpl)
> > +	      /* Typedefs are not entities and so are OK to be redeclared
> > +		 as exported: see [module.interface]/p6.  */
> > +	      && TREE_CODE (olddecl) != TYPE_DECL)
> >  	    {
> > -	      auto_diagnostic_group d;
> > -	      error ("conflicting exporting for declaration %qD", newdecl);
> > -	      inform (olddecl_loc,
> > -		      "previously declared here without exporting");
> > +	      if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
> > +		  && !DECL_MODULE_EXPORT_P (not_tmpl))
> > +		{
> > +		  auto_diagnostic_group d;
> > +		  error ("conflicting exporting for declaration %qD", newdecl);
> > +		  inform (olddecl_loc,
> > +			  "previously declared here without exporting");
> > +		}
> >  	    }
> > +	  else if (DECL_MODULE_EXPORT_P (newdecl))
> > +	    DECL_MODULE_EXPORT_P (not_tmpl) = true;
> >  	}
> > -      else if (DECL_MODULE_EXPORT_P (newdecl))
> > -	DECL_MODULE_EXPORT_P (not_tmpl) = true;
> >      }
> >  
> >    /* We have committed to returning OLDDECL at this point.  */
> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index e2d2910ae48..1a064e4ea79 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -2727,6 +2727,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
> >  typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
> >  static keyed_map_t *keyed_table;
> >  
> > +/* Instantiations of temploid friends imported from another module
> > +   need to be owned by the same module as their instantiating template.
> > +   This maps these to the template that instantiated them.  */
> > +static hash_map<tree, tree> *imported_temploid_friends;
> > +
> >  /********************************************************************/
> >  /* Tree streaming.   The tree streaming is very specific to the tree
> >     structures themselves.  A tag indicates the kind of tree being
> > @@ -7997,6 +8002,11 @@ trees_out::decl_value (tree decl, depset *dep)
> >  	}
> >      }
> >  
> > +  /* Write imported temploid friends so that importers can reconstruct
> > +     this information on stream-in.  */
> > +  tree* slot = imported_temploid_friends->get (decl);
> > +  tree_node (slot ? *slot : NULL_TREE);
> > +
> >    bool is_typedef = false;
> >    if (!type && TREE_CODE (inner) == TYPE_DECL)
> >      {
> > @@ -8303,6 +8313,12 @@ trees_in::decl_value ()
> >  	}
> >      }
> >  
> > +  if (tree owner = tree_node ())
> > +    {
> > +      bool exists = imported_temploid_friends->put (decl, owner);
> > +      gcc_assert (exists == !is_new);
> > +    }
> > +
> >    /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
> >    unsigned tdef_flags = 0;
> >    bool is_typedef = false;
> > @@ -18930,6 +18946,12 @@ get_originating_module_decl (tree decl)
> >  	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
> >  	decl = TYPE_NAME (DECL_CHAIN (decl));
> >  
> > +      /* An imported temploid friend is attached to the same module the
> > +	 befriending class was.  */
> > +      if (imported_temploid_friends)
> > +	if (tree *slot = imported_temploid_friends->get (decl))
> > +	  decl = *slot;
> > +
> >        int use;
> >        if (tree ti = node_template_info (decl, use))
> >  	{
> > @@ -19238,6 +19260,34 @@ maybe_key_decl (tree ctx, tree decl)
> >    vec.safe_push (decl);
> >  }
> >  
> > +/* DECL is an instantiated friend that should be attached to the same
> > +   module that ORIG is.  */
> > +
> > +void
> > +propagate_defining_module (tree decl, tree orig)
> > +{
> > +  if (!modules_p ())
> > +    return;
> > +
> > +  tree not_tmpl = STRIP_TEMPLATE (orig);
> > +  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
> > +    {
> > +      tree inner = STRIP_TEMPLATE (decl);
> > +      retrofit_lang_decl (inner);
> > +      DECL_MODULE_ATTACH_P (inner) = true;
> > +    }
> > +
> > +  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
> > +    {
> > +      bool exists = imported_temploid_friends->put (decl, orig);
> > +
> > +      /* We should only be called if lookup for an existing decl
> > +	 failed, in which case there shouldn't already be an entry
> > +	 in the map.  */
> > +      gcc_assert (!exists);
> > +    }
> > +}
> > +
> >  /* Create the flat name string.  It is simplest to have it handy.  */
> >  
> >  void
> > @@ -20451,6 +20501,8 @@ init_modules (cpp_reader *reader)
> >        pending_table = new pending_map_t (EXPERIMENT (1, 400));
> >        entity_map = new entity_map_t (EXPERIMENT (1, 400));
> >        vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
> > +      imported_temploid_friends
> > +	= new hash_map<tree,tree> (EXPERIMENT (1, 400));
> >      }
> >  
> >  #if CHECKING_P
> > diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
> > index 7af7f00e34c..dd6e7b6eaea 100644
> > --- a/gcc/cp/name-lookup.cc
> > +++ b/gcc/cp/name-lookup.cc
> > @@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
> >    add_decl_to_level (b, decl);
> >  }
> >  
> > +/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
> > +   instantiations of temploid hidden friends from imported modules.  */
> > +
> > +tree
> > +lookup_imported_hidden_friend (tree friend_tmpl)
> > +{
> > +  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
> > +  if (!DECL_LANG_SPECIFIC (inner)
> > +      || !DECL_MODULE_IMPORT_P (inner))
> > +    return NULL_TREE;
> > +
> > +  tree name = DECL_NAME (inner);
> > +  tree *slot = find_namespace_slot (current_namespace, name);
> > +  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
> > +    return NULL_TREE;
> > +
> > +  /* Look in the appropriate slot, as with check_module_override.  */
> > +  binding_slot mslot;
> > +  if (named_module_p ())
> > +    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
> > +				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
> > +      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
> > +  else
> > +    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
> > +  gcc_assert (!mslot.is_lazy ());
> > +
> > +  tree ovl = mslot;
> > +  if (!ovl)
> > +    return NULL_TREE;
> > +
> > +  /* We're only interested in declarations coming from the same module
> > +     of the friend class we're attempting to instantiate.  */
> > +  int m = get_originating_module (friend_tmpl);
> > +  gcc_assert (m != 0);
> > +
> > +  for (ovl_iterator iter (ovl); iter; ++iter)
> > +    if (get_originating_module (*iter) == m)
> > +      return *iter;
> > +
> > +  return NULL_TREE;
> > +}
> > +
> >  \f
> >  /* true means unconditionally make a BLOCK for the next level pushed.  */
> >  
> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 3b2106dd3f6..e7e7f2fbc3b 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -11512,6 +11512,10 @@ tsubst_friend_function (tree decl, tree args)
> >  	  new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
> >  	}
> >  
> > +      /* We need to propagate module attachment for the new friend from the
> > +	 owner of this template.  */
> > +      propagate_defining_module (new_friend, decl);
> > +
> >        /* Inside pushdecl_namespace_level, we will push into the
> >  	 current namespace. However, the friend function should go
> >  	 into the namespace of the template.  */
> > @@ -11715,6 +11719,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
> >    tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
> >  		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
> >  
> > +  if (!tmpl)
> > +    /* If we didn't find by name lookup, the type may still exist but as a
> > +       'hidden' import; we should check for this too to avoid accidentally
> > +       instantiating a duplicate.  */
> > +    tmpl = lookup_imported_hidden_friend (friend_tmpl);
> > +
> >    if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
> >      {
> >        /* The friend template has already been declared.  Just
> > @@ -11723,6 +11733,11 @@ tsubst_friend_class (tree friend_tmpl, tree args)
> >  	 of course.  We only need the innermost template parameters
> >  	 because that is all that redeclare_class_template will look
> >  	 at.  */
> > +
> > +      if (modules_p ())
> > +	/* Check that we can redeclare TMPL in the current context.  */
> > +	module_may_redeclare (tmpl, friend_tmpl);
> > +
> >        if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
> >  	  > TMPL_ARGS_DEPTH (args))
> >  	{
> > @@ -11772,6 +11787,10 @@ tsubst_friend_class (tree friend_tmpl, tree args)
> >  						     args, tf_warning_or_error);
> >  	    }
> >  
> > +	  /* We need to propagate the attachment of the original template to the
> > +	     newly instantiated template type.  */
> > +	  propagate_defining_module (tmpl, friend_tmpl);
> > +
> >  	  /* Inject this template into the enclosing namspace scope.  */
> >  	  tmpl = pushdecl_namespace_level (tmpl, /*hiding=*/true);
> >  	}
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
> > new file mode 100644
> > index 00000000000..7547326e554
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
> > @@ -0,0 +1,15 @@
> > +// PR c++/105320
> > +// { dg-additional-options "-fmodules-ts -Wno-global-module" }
> > +// { dg-module-cmi test_support }
> > +
> > +module;
> > +template<class> struct _Sp_atomic;
> > +template<class> struct shared_ptr {
> > +  template<class> friend struct _Sp_atomic;
> > +  using atomic_type = _Sp_atomic<int>;
> > +};
> > +export module test_support;
> > +export
> > +template<class T> struct A {
> > +   shared_ptr<T> data;
> > +};
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
> > new file mode 100644
> > index 00000000000..6b88ee4258b
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
> > @@ -0,0 +1,5 @@
> > +// PR c++/105320
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +import test_support;
> > +A<int> a;
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
> > new file mode 100644
> > index 00000000000..789bdeb64d5
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
> > @@ -0,0 +1,7 @@
> > +// PR c++/105320
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi user }
> > +
> > +export module user;
> > +import test_support; 
> > +A<int> b;
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
> > new file mode 100644
> > index 00000000000..f29eebd1a7f
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
> > @@ -0,0 +1,14 @@
> > +// PR c++/114275
> > +// { dg-additional-options "-fmodules-ts -Wno-global-module" }
> > +// { dg-module-cmi M }
> > +
> > +module;
> > +
> > +template <typename... _Elements> struct T;
> > +
> > +template <typename H> struct T<H> {
> > +  template <typename...> friend struct T;
> > +};
> > +
> > +export module M;
> > +export template <typename=void> void fun() { T<int> t; }
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
> > new file mode 100644
> > index 00000000000..5bf79998139
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
> > @@ -0,0 +1,5 @@
> > +// PR c++/114275
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +import M;
> > +int main() { fun(); }
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
> > new file mode 100644
> > index 00000000000..216dbf62c71
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
> > @@ -0,0 +1,10 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi M:A }
> > +
> > +module M:A;
> > +
> > +template <typename T> struct A {
> > +  template <typename U> friend struct B;
> > +private:
> > +  int x = 42;
> > +};
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
> > new file mode 100644
> > index 00000000000..26e1c38b518
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
> > @@ -0,0 +1,9 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi M:B }
> > +
> > +export module M:B;
> > +import :A;
> > +
> > +export template <typename U> struct B {
> > +  int foo(A<U> a) { return a.x; }
> > +};
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
> > new file mode 100644
> > index 00000000000..e44c2819cfd
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
> > @@ -0,0 +1,10 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi M:C }
> > +
> > +export module M:C;
> > +import :A;
> > +
> > +template <typename T> struct B;
> > +export template <typename T, typename U> int bar(B<T> t, U u) {
> > +  return t.foo(u);
> > +}
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
> > new file mode 100644
> > index 00000000000..9a575ad5046
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
> > @@ -0,0 +1,8 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi M }
> > +
> > +export module M;
> > +export import :B;
> > +export import :C;
> > +
> > +export int go_in_module();
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
> > new file mode 100644
> > index 00000000000..329d1e8b263
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
> > @@ -0,0 +1,7 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +module M;
> > +
> > +int go_in_module() {
> > +  return bar(B<int>{}, A<int>{});
> > +}
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
> > new file mode 100644
> > index 00000000000..c9855663fbd
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
> > @@ -0,0 +1,8 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +import M;
> > +
> > +int main() {
> > +  B<double> b{};
> > +  go_in_module();
> > +}
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
> > new file mode 100644
> > index 00000000000..a044485248f
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
> > @@ -0,0 +1,12 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi M }
> > +
> > +export module M;
> > +export template <typename> struct A {
> > +  template <typename> friend struct B;
> > +};
> > +
> > +export template <typename> struct C {
> > +  friend void f();
> > +  template <typename> friend void g();
> > +};
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
> > new file mode 100644
> > index 00000000000..72dc8611e39
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
> > @@ -0,0 +1,9 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +import M;
> > +A<int> a;
> > +template <typename> struct B {};  // { dg-error "conflicts with import" }
> > +
> > +C<int> c;
> > +void f() {}  // { dg-error "conflicts with import" }
> > +template <typename> void g() {}  // { dg-error "conflicts with import" }
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
> > new file mode 100644
> > index 00000000000..e1d2860bfe6
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
> > @@ -0,0 +1,11 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +import M;
> > +template <typename> struct B {};  // { dg-message "previously declared" }
> > +A<int> a;  // { dg-message "required from here" }
> > +
> > +void f() {}  // { dg-message "previously declared" }
> > +template <typename> void g();  // { dg-message "previously declared" }
> > +C<int> c;  // { dg-message "required from here" }
> > +
> > +// { dg-error "conflicting declaration" "" { target *-*-* } 0 }
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
> > new file mode 100644
> > index 00000000000..44b0f441db2
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
> > @@ -0,0 +1,7 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi O }
> > +
> > +export module O;
> > +export import M;
> > +A<int> a;
> > +C<int> c;
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
> > new file mode 100644
> > index 00000000000..cec899e426b
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
> > @@ -0,0 +1,14 @@
> > +// { dg-additional-options "-fmodules-ts" }
> > +
> > +template <typename> struct B {};  // { dg-message "previous declaration" }
> > +void f() {}
> > +template <typename T> void g() {}
> > +import O;
> > +A<double> b;  // { dg-message "required from here" }
> > +C<double> d;  // { dg-message "required from here" }
> > +
> > +// specifically, B is defined in M, not O, despite the instantiation being in O
> > +// { dg-error "conflicting declaration \[^\n\r\]* B@M" "" { target *-*-* } 0 }
> > +// and similarly for f and g
> > +// { dg-error "conflicting declaration \[^\n\r\]* f@M" "" { target *-*-* } 0 }
> > +// { dg-error "conflicting declaration \[^\n\r\]* g@M" "" { target *-*-* } 0 }
> > diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-9.C b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
> > new file mode 100644
> > index 00000000000..c7216f0f8c1
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
> > @@ -0,0 +1,13 @@
> > +// PR c++/114275
> > +// { dg-additional-options "-fmodules-ts" }
> > +// { dg-module-cmi M }
> > +
> > +export module M;
> > +
> > +template<class> struct A {
> > +  template<class> friend struct B;
> > +  friend void C();
> > +};
> > +A<int> a;
> > +void C() {}
> > +template<class> struct B { };
> > -- 
> > 2.43.2
> > 
> > 
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare
  2024-04-15  4:49 ` [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare Nathaniel Shead
  2024-04-17 15:13   ` Patrick Palka
@ 2024-04-19 16:18   ` Nathaniel Shead
  2024-04-25 19:52     ` Jason Merrill
  1 sibling, 1 reply; 12+ messages in thread
From: Nathaniel Shead @ 2024-04-19 16:18 UTC (permalink / raw)
  To: gcc-patches; +Cc: Jason Merrill, Nathan Sidwell, Patrick Palka

On Mon, Apr 15, 2024 at 02:49:35PM +1000, Nathaniel Shead wrote:
> I took another look at this patch and have split it into two, one (this
> one) to standardise the error messages used and prepare
> 'module_may_redeclare' for use with temploid friends, and another
> followup patch to actually handle them correctly.
> 
> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
> 
> -- >8 --
> 
> Currently different places calling 'module_may_redeclare' all emit very
> similar but slightly different error messages, and handle different
> kinds of declarations differently.  This patch makes the function
> perform its own error messages so that they're all in one place, and
> prepares it for use with temploid friends (PR c++/114275).
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (module_may_redeclare): Add default parameter.
> 	* decl.cc (duplicate_decls): Don't emit errors for failed
> 	module_may_redeclare.
> 	(xref_tag): Likewise.
> 	(start_enum): Likewise.
> 	* semantics.cc (begin_class_definition): Likewise.
> 	* module.cc (module_may_redeclare): Clean up logic. Emit error
> 	messages on failure.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/modules/enum-12.C: Update error message.
> 	* g++.dg/modules/friend-5_b.C: Likewise.
> 	* g++.dg/modules/shadow-1_b.C: Likewise.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>  gcc/cp/cp-tree.h                          |   2 +-
>  gcc/cp/decl.cc                            |  28 +----
>  gcc/cp/module.cc                          | 120 ++++++++++++++--------
>  gcc/cp/semantics.cc                       |   6 +-
>  gcc/testsuite/g++.dg/modules/enum-12.C    |   2 +-
>  gcc/testsuite/g++.dg/modules/friend-5_b.C |   2 +-
>  gcc/testsuite/g++.dg/modules/shadow-1_b.C |   5 +-
>  7 files changed, 89 insertions(+), 76 deletions(-)
> 
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 1dbb577a38d..faa7a0052a5 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7401,7 +7401,7 @@ inline bool module_exporting_p ()
>  
>  extern module_state *get_module (tree name, module_state *parent = NULL,
>  				 bool partition = false);
> -extern bool module_may_redeclare (tree decl);
> +extern bool module_may_redeclare (tree olddecl, tree newdecl = NULL);
>  
>  extern bool module_global_init_needed ();
>  extern bool module_determine_import_inits ();
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 65ab64885ff..aa66da4829d 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -2279,18 +2279,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
>        && TREE_CODE (olddecl) != NAMESPACE_DECL
>        && !hiding)
>      {
> -      if (!module_may_redeclare (olddecl))
> -	{
> -	  if (DECL_ARTIFICIAL (olddecl))
> -	    error ("declaration %qD conflicts with builtin", newdecl);
> -	  else
> -	    {
> -	      error ("declaration %qD conflicts with import", newdecl);
> -	      inform (olddecl_loc, "import declared %q#D here", olddecl);
> -	    }
> -
> -	  return error_mark_node;
> -	}
> +      if (!module_may_redeclare (olddecl, newdecl))
> +	return error_mark_node;
>  
>        tree not_tmpl = STRIP_TEMPLATE (olddecl);
>        if (DECL_LANG_SPECIFIC (not_tmpl)
> @@ -16620,12 +16610,7 @@ xref_tag (enum tag_types tag_code, tree name,
>  	{
>  	  tree decl = TYPE_NAME (t);
>  	  if (!module_may_redeclare (decl))
> -	    {
> -	      auto_diagnostic_group d;
> -	      error ("cannot declare %qD in a different module", decl);
> -	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
> -	      return error_mark_node;
> -	    }
> +	    return error_mark_node;
>  
>  	  tree not_tmpl = STRIP_TEMPLATE (decl);
>  	  if (DECL_LANG_SPECIFIC (not_tmpl)
> @@ -16973,12 +16958,7 @@ start_enum (tree name, tree enumtype, tree underlying_type,
>  	{
>  	  tree decl = TYPE_NAME (enumtype);
>  	  if (!module_may_redeclare (decl))
> -	    {
> -	      auto_diagnostic_group d;
> -	      error ("cannot declare %qD in different module", decl);
> -	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
> -	      enumtype = error_mark_node;
> -	    }
> +	    enumtype = error_mark_node;
>  	  else
>  	    set_instantiating_module (decl);
>  	}
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 001430a4a8f..e2d2910ae48 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -18992,11 +18992,15 @@ get_importing_module (tree decl, bool flexible)
>    return module->mod;
>  }
>  
> -/* Is it permissible to redeclare DECL.  */
> +/* Is it permissible to redeclare OLDDECL with NEWDECL.
> +
> +   If NEWDECL is NULL, assumes that OLDDECL will be redeclared using
> +   the current scope's module and attachment.  */
>  
>  bool
> -module_may_redeclare (tree decl)
> +module_may_redeclare (tree olddecl, tree newdecl)
>  {
> +  tree decl = olddecl;
>    for (;;)
>      {
>        tree ctx = CP_DECL_CONTEXT (decl);
> @@ -19010,58 +19014,94 @@ module_may_redeclare (tree decl)
>        decl = TYPE_NAME (ctx);
>      }
>  
> -  tree not_tmpl = STRIP_TEMPLATE (decl);
> -
>    int use_tpl = 0;
> -  if (node_template_info (not_tmpl, use_tpl) && use_tpl)
> +  if (node_template_info (STRIP_TEMPLATE (decl), use_tpl) && use_tpl)
>      // Specializations of any kind can be redeclared anywhere.
>      // FIXME: Should we be checking this in more places on the scope chain?
>      return true;
>  
> -  if (!DECL_LANG_SPECIFIC (not_tmpl) || !DECL_MODULE_ATTACH_P (not_tmpl))
> -    // Decl is attached to global module.  Current scope needs to be too.
> -    return !module_attach_p ();
> +  module_state *old_mod = (*modules)[0];
> +  module_state *new_mod = old_mod;
>  
> -  module_state *me = (*modules)[0];
> -  module_state *them = me;
> +  tree old_origin = get_originating_module_decl (decl);
> +  tree old_inner = STRIP_TEMPLATE (old_origin);
> +  bool olddecl_attached_p = (DECL_LANG_SPECIFIC (old_inner)
> +			     && DECL_MODULE_ATTACH_P (old_inner));
> +  if (DECL_LANG_SPECIFIC (old_inner) && DECL_MODULE_IMPORT_P (old_inner))
> +    {
> +      unsigned index = import_entity_index (old_origin);
> +      old_mod = import_entity_module (index);
> +    }
>  
> -  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
> +  bool newdecl_attached_p = module_attach_p ();
> +  if (newdecl)
>      {
> -      /* We can be given the TEMPLATE_RESULT.  We want the
> -	 TEMPLATE_DECL.  */
> -      int use_tpl = -1;
> -      if (tree ti = node_template_info (decl, use_tpl))
> +      tree new_origin = get_originating_module_decl (newdecl);
> +      tree new_inner = STRIP_TEMPLATE (new_origin);
> +      newdecl_attached_p = (DECL_LANG_SPECIFIC (new_inner)
> +			    && DECL_MODULE_ATTACH_P (new_inner));
> +      if (DECL_LANG_SPECIFIC (new_inner) && DECL_MODULE_IMPORT_P (new_inner))
>  	{
> -	  tree tmpl = TI_TEMPLATE (ti);
> -	  if (use_tpl == 2)
> -	    {
> -	      /* A partial specialization.  Find that specialization's
> -		 template_decl.  */
> -	      for (tree list = DECL_TEMPLATE_SPECIALIZATIONS (tmpl);
> -		   list; list = TREE_CHAIN (list))
> -		if (DECL_TEMPLATE_RESULT (TREE_VALUE (list)) == decl)
> -		  {
> -		    decl = TREE_VALUE (list);
> -		    break;
> -		}
> -	    }
> -	  else if (DECL_TEMPLATE_RESULT (tmpl) == decl)
> -	    decl = tmpl;
> +	  unsigned index = import_entity_index (new_origin);
> +	  new_mod = import_entity_module (index);
>  	}
> -      unsigned index = import_entity_index (decl);
> -      them = import_entity_module (index);
>      }
>  
> -  // Decl is attached to named module.  Current scope needs to be
> -  // attaching to the same module.
> -  if (!module_attach_p ())
> -    return false;
> +  /* Module attachment needs to match.  */
> +  if (olddecl_attached_p == newdecl_attached_p)
> +    {
> +      if (!olddecl_attached_p)
> +	/* Both are GM entities, OK.  */
> +	return true;
>  
> -  // Both attached to named module.
> -  if (me == them)
> -    return true;
> +      if (new_mod == old_mod
> +	  || (new_mod && get_primary (new_mod) == get_primary (old_mod)))
> +	/* Both attached to same named module, OK.  */
> +	return true;
> +    }
> +
> +  /* Attached to different modules, error.  */
> +  decl = newdecl ? newdecl : olddecl;
> +  location_t loc = newdecl ? DECL_SOURCE_LOCATION (newdecl) : input_location;
> +  if (DECL_ARTIFICIAL (olddecl) && !DECL_IMPLICIT_TYPEDEF_P (olddecl))
> +    error_at (loc, "declaration %qD conflicts with builtin", decl);

Or maybe this should be

  if (DECL_SOURCE_LOCATION (olddecl) == BUILTINS_LOCATION)
    error_at (loc, "declaration %qD conflicts with builtin", decl);

and update the error message in enum-12.C?

> +  else if (DECL_LANG_SPECIFIC (old_inner) && DECL_MODULE_IMPORT_P (old_inner))
> +    {
> +      auto_diagnostic_group d;
> +      if (newdecl_attached_p)
> +	error_at (loc, "redeclaring %qD in module %qs conflicts with import",
> +		  decl, new_mod->get_flatname ());
> +      else
> +	error_at (loc, "redeclaring %qD in global module conflicts with import",
> +		  decl);
>  
> -  return me && get_primary (them) == get_primary (me);
> +      if (olddecl_attached_p)
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"import declared attached to module %qs",
> +		old_mod->get_flatname ());
> +      else
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"import declared in global module");
> +    }
> +  else
> +    {
> +      auto_diagnostic_group d;
> +      if (newdecl_attached_p)
> +	error_at (loc, "conflicting declaration of %qD in module %qs",
> +		  decl, new_mod->get_flatname ());
> +      else
> +	error_at (loc, "conflicting declaration of %qD in global module",
> +		  decl);
> +
> +      if (olddecl_attached_p)
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"previously declared in module %qs",
> +		old_mod->get_flatname ());
> +      else
> +	inform (DECL_SOURCE_LOCATION (olddecl),
> +		"previously declared in global module");
> +    }
> +  return false;
>  }
>  
>  /* DECL is being created by this TU.  Record it came from here.  We
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 02c7c1bf5a4..2dde65a970b 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -3777,11 +3777,7 @@ begin_class_definition (tree t)
>    if (modules_p ())
>      {
>        if (!module_may_redeclare (TYPE_NAME (t)))
> -	{
> -	  error ("cannot declare %qD in a different module", TYPE_NAME (t));
> -	  inform (DECL_SOURCE_LOCATION (TYPE_NAME (t)), "declared here");
> -	  return error_mark_node;
> -	}
> +	return error_mark_node;
>        set_instantiating_module (TYPE_NAME (t));
>        set_defining_module (TYPE_NAME (t));
>      }
> diff --git a/gcc/testsuite/g++.dg/modules/enum-12.C b/gcc/testsuite/g++.dg/modules/enum-12.C
> index 57eeb85d92a..019c3da4218 100644
> --- a/gcc/testsuite/g++.dg/modules/enum-12.C
> +++ b/gcc/testsuite/g++.dg/modules/enum-12.C
> @@ -4,7 +4,7 @@
>  
>  export module foo;
>  namespace std {
> -  enum class align_val_t : decltype(sizeof(int)) {};  // { dg-error "different module" }
> +  enum class align_val_t : decltype(sizeof(int)) {};  // { dg-error "conflicting declaration" }
>  }
>  
>  // { dg-prune-output "not writing module" }
> diff --git a/gcc/testsuite/g++.dg/modules/friend-5_b.C b/gcc/testsuite/g++.dg/modules/friend-5_b.C
> index f043d7a340d..6b561265155 100644
> --- a/gcc/testsuite/g++.dg/modules/friend-5_b.C
> +++ b/gcc/testsuite/g++.dg/modules/friend-5_b.C
> @@ -4,7 +4,7 @@
>  export module bar;
>  import foo;
>  
> -class B { // { dg-error "in a different module" }
> +class B { // { dg-error "conflicts with import" }
>    B() { object.value = 42; }
>    A object;
>  };
> diff --git a/gcc/testsuite/g++.dg/modules/shadow-1_b.C b/gcc/testsuite/g++.dg/modules/shadow-1_b.C
> index 646381237ac..7f6a3182998 100644
> --- a/gcc/testsuite/g++.dg/modules/shadow-1_b.C
> +++ b/gcc/testsuite/g++.dg/modules/shadow-1_b.C
> @@ -1,8 +1,5 @@
>  // { dg-additional-options -fmodules-ts }
>  import shadow;
>  
> -// unfortunately not the exact same diagnostic in both cases :(
> -
>  void stat (); // { dg-error "conflicts with import" }
> -
> -struct stat {}; // { dg-error "in a different module" }
> +struct stat {}; // { dg-error "conflicts with import" }
> -- 
> 2.43.2
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v3 2/2] c++: Fix instantiation of imported temploid friends [PR114275]
  2024-04-19  2:14     ` Nathaniel Shead
@ 2024-04-19 16:29       ` Nathaniel Shead
  2024-04-27  1:16         ` Jason Merrill
  0 siblings, 1 reply; 12+ messages in thread
From: Nathaniel Shead @ 2024-04-19 16:29 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches, Jason Merrill, Nathan Sidwell

On Fri, Apr 19, 2024 at 12:14:06PM +1000, Nathaniel Shead wrote:
> On Wed, Apr 17, 2024 at 02:02:21PM -0400, Patrick Palka wrote:
> > On Mon, 15 Apr 2024, Nathaniel Shead wrote:
> > 
> > > I'm not a huge fan of always streaming 'imported_temploid_friends' for
> > > all decls, but I don't think it adds much performance cost over adding a
> > > new flag to categorise decls that might be marked as such.
> > 
> > IIUC this value is going to be almost always null which is encoded as a
> > single 0 byte, which should be fast to stream.  But I wonder how much
> > larger <bits/stdc++.h> gets?  Can we get away with streaming this value
> > only for TEMPLATE_DECLs?
> 
> Yes, it should either just be a 0 byte or an additional backref
> somewhere, which will likely also be small. On my system it increases
> the size by 0.26%, from 31186800 bytes to 31268672.
> 
> But I've just found that this patch has a bug anyway, in that it doesn't
> correctly dedup if the friend types are instantiated in two separate
> modules that are then both imported.  I'll see what I need to do to fix
> this which may influence what we need to stream here.
> 

Here's an updated version of the patch that fixes this. Also changed to
only stream when 'inner' is either TYPE_DECL or FUNCTION_DECL, which
cuts the size of <bits/stdc++.h> down a bit to 31246992 (0.19% growth).

Another alternative would be to add another boolean flag at the top of
'decl_value' and branch on that; that would make use of the bitpacking
logic and probably cut down on the size further.  (I haven't measured
this yet though.)

Bootstrapped and regtested (so far just dg.exp and modules.exp) on
x86_64-pc-linux-gnu, OK for trunk if full regtest succeeds?

-- >8 --

This patch fixes a number of issues with the handling of temploid friend
declarations.

The primary issue is that instantiations of friend declarations should
attach the declaration to the same module as the befriending class, by
[module.unit] p7.1 and [temp.friend] p2; this could be a different
module from the current TU, and so needs special handling.

The other main issue here is that we can't assume that just because name
lookup didn't find a definition for a hidden template class, it doesn't
mean that it doesn't exist: it could be a non-exported entity that we've
nevertheless streamed in from an imported module.  We need to ensure
that when instantiating friend classes that we return the same TYPE_DECL
that we got from our imports, otherwise we will get later issues with
'duplicate_decls' (rightfully) complaining that they're different.

This doesn't appear necessary for functions due to the existing name
lookup handling already finding these hidden declarations.

	PR c++/105320
	PR c++/114275

gcc/cp/ChangeLog:

	* cp-tree.h (propagate_defining_module): Declare.
	(lookup_imported_hidden_friend): Declare.
	* decl.cc (duplicate_decls): Also check if hidden declarations
	can be redeclared in this module.
	* module.cc (imported_temploid_friends): New map.
	(init_modules): Initialize it.
	(trees_out::decl_value): Write it; don't consider imported
	temploid friends as attached to this module.
	(trees_in::decl_value): Read it.
	(depset::hash::add_specializations): Don't treat instantiations
	of a friend type as a specialisation.
	(get_originating_module_decl): Follow the owning decl for an
	imported temploid friend.
	(propagate_defining_module): New function.
	* name-lookup.cc (lookup_imported_hidden_friend): New function.
	* pt.cc (tsubst_friend_function): Propagate defining module for
	new friend functions.
	(tsubst_friend_class): Lookup imported hidden friends. Check
	for valid redeclaration. Propagate defining module for new
	friend classes.

gcc/testsuite/ChangeLog:

	* g++.dg/modules/tpl-friend-10_a.C: New test.
	* g++.dg/modules/tpl-friend-10_b.C: New test.
	* g++.dg/modules/tpl-friend-10_c.C: New test.
	* g++.dg/modules/tpl-friend-11_a.C: New test.
	* g++.dg/modules/tpl-friend-11_b.C: New test.
	* g++.dg/modules/tpl-friend-12_a.C: New test.
	* g++.dg/modules/tpl-friend-12_b.C: New test.
	* g++.dg/modules/tpl-friend-12_c.C: New test.
	* g++.dg/modules/tpl-friend-12_d.C: New test.
	* g++.dg/modules/tpl-friend-12_e.C: New test.
	* g++.dg/modules/tpl-friend-12_f.C: New test.
	* g++.dg/modules/tpl-friend-13_a.C: New test.
	* g++.dg/modules/tpl-friend-13_b.C: New test.
	* g++.dg/modules/tpl-friend-13_c.C: New test.
	* g++.dg/modules/tpl-friend-13_d.C: New test.
	* g++.dg/modules/tpl-friend-13_e.C: New test.
	* g++.dg/modules/tpl-friend-13_f.C: New test.
	* g++.dg/modules/tpl-friend-13_g.C: New test.
	* g++.dg/modules/tpl-friend-14_a.C: New test.
	* g++.dg/modules/tpl-friend-14_b.C: New test.
	* g++.dg/modules/tpl-friend-14_c.C: New test.
	* g++.dg/modules/tpl-friend-14_d.C: New test.
	* g++.dg/modules/tpl-friend-9.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                              |  2 +
 gcc/cp/decl.cc                                | 36 +++++----
 gcc/cp/module.cc                              | 74 ++++++++++++++++++-
 gcc/cp/name-lookup.cc                         | 42 +++++++++++
 gcc/cp/pt.cc                                  | 19 +++++
 .../g++.dg/modules/tpl-friend-10_a.C          | 15 ++++
 .../g++.dg/modules/tpl-friend-10_b.C          |  5 ++
 .../g++.dg/modules/tpl-friend-10_c.C          |  7 ++
 .../g++.dg/modules/tpl-friend-11_a.C          | 14 ++++
 .../g++.dg/modules/tpl-friend-11_b.C          |  5 ++
 .../g++.dg/modules/tpl-friend-12_a.C          | 10 +++
 .../g++.dg/modules/tpl-friend-12_b.C          |  9 +++
 .../g++.dg/modules/tpl-friend-12_c.C          | 10 +++
 .../g++.dg/modules/tpl-friend-12_d.C          |  8 ++
 .../g++.dg/modules/tpl-friend-12_e.C          |  7 ++
 .../g++.dg/modules/tpl-friend-12_f.C          |  8 ++
 .../g++.dg/modules/tpl-friend-13_a.C          | 13 ++++
 .../g++.dg/modules/tpl-friend-13_b.C          | 11 +++
 .../g++.dg/modules/tpl-friend-13_c.C          | 13 ++++
 .../g++.dg/modules/tpl-friend-13_d.C          |  7 ++
 .../g++.dg/modules/tpl-friend-13_e.C          | 18 +++++
 .../g++.dg/modules/tpl-friend-13_f.C          |  7 ++
 .../g++.dg/modules/tpl-friend-13_g.C          | 11 +++
 .../g++.dg/modules/tpl-friend-14_a.C          |  8 ++
 .../g++.dg/modules/tpl-friend-14_b.C          |  8 ++
 .../g++.dg/modules/tpl-friend-14_c.C          |  7 ++
 .../g++.dg/modules/tpl-friend-14_d.C          |  9 +++
 gcc/testsuite/g++.dg/modules/tpl-friend-9.C   | 13 ++++
 28 files changed, 376 insertions(+), 20 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-9.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index faa7a0052a5..67cc7d7bcec 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7417,6 +7417,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
 extern void set_instantiating_module (tree);
 extern void set_defining_module (tree);
 extern void maybe_key_decl (tree ctx, tree decl);
+extern void propagate_defining_module (tree decl, tree orig);
 
 extern void mangle_module (int m, bool include_partition);
 extern void mangle_module_fini ();
@@ -7649,6 +7650,7 @@ extern bool template_guide_p			(const_tree);
 extern bool builtin_guide_p			(const_tree);
 extern void store_explicit_specifier		(tree, tree);
 extern tree lookup_explicit_specifier		(tree);
+extern tree lookup_imported_hidden_friend	(tree);
 extern void walk_specializations		(bool,
 						 void (*)(bool, spec_entry *,
 							  void *),
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index aa66da4829d..56752cf6872 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -2276,30 +2276,34 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
 
   if (modules_p ()
       && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
-      && TREE_CODE (olddecl) != NAMESPACE_DECL
-      && !hiding)
+      && TREE_CODE (olddecl) != NAMESPACE_DECL)
     {
       if (!module_may_redeclare (olddecl, newdecl))
 	return error_mark_node;
 
-      tree not_tmpl = STRIP_TEMPLATE (olddecl);
-      if (DECL_LANG_SPECIFIC (not_tmpl)
-	  && DECL_MODULE_ATTACH_P (not_tmpl)
-	  /* Typedefs are not entities and so are OK to be redeclared
-	     as exported: see [module.interface]/p6.  */
-	  && TREE_CODE (olddecl) != TYPE_DECL)
+      if (!hiding)
 	{
-	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
-	      && !DECL_MODULE_EXPORT_P (not_tmpl))
+	  /* Hidden friend declarations just use exportingness of the
+	     old declaration; see CWG2588.  */
+	  tree not_tmpl = STRIP_TEMPLATE (olddecl);
+	  if (DECL_LANG_SPECIFIC (not_tmpl)
+	      && DECL_MODULE_ATTACH_P (not_tmpl)
+	      /* Typedefs are not entities and so are OK to be redeclared
+		 as exported: see [module.interface]/p6.  */
+	      && TREE_CODE (olddecl) != TYPE_DECL)
 	    {
-	      auto_diagnostic_group d;
-	      error ("conflicting exporting for declaration %qD", newdecl);
-	      inform (olddecl_loc,
-		      "previously declared here without exporting");
+	      if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
+		  && !DECL_MODULE_EXPORT_P (not_tmpl))
+		{
+		  auto_diagnostic_group d;
+		  error ("conflicting exporting for declaration %qD", newdecl);
+		  inform (olddecl_loc,
+			  "previously declared here without exporting");
+		}
 	    }
+	  else if (DECL_MODULE_EXPORT_P (newdecl))
+	    DECL_MODULE_EXPORT_P (not_tmpl) = true;
 	}
-      else if (DECL_MODULE_EXPORT_P (newdecl))
-	DECL_MODULE_EXPORT_P (not_tmpl) = true;
     }
 
   /* We have committed to returning OLDDECL at this point.  */
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 36e035544f4..bd3ab686543 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2727,6 +2727,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
 typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
 static keyed_map_t *keyed_table;
 
+/* Instantiations of temploid friends imported from another module
+   need to be owned by the same module as their instantiating template.
+   This maps these to the template that instantiated them.  */
+static hash_map<tree, tree> *imported_temploid_friends;
+
 /********************************************************************/
 /* Tree streaming.   The tree streaming is very specific to the tree
    structures themselves.  A tag indicates the kind of tree being
@@ -7820,6 +7825,10 @@ trees_out::decl_value (tree decl, depset *dep)
 		  && DECL_MODULE_ATTACH_P (not_tmpl))
 		is_attached = true;
 
+	      /* But don't consider imported temploid friends as attached.  */
+	      if (imported_temploid_friends->get (decl))
+		is_attached = false;
+
 	      bits.b (is_attached);
 	    }
 	  bits.b (dep && dep->has_defn ());
@@ -7997,6 +8006,15 @@ trees_out::decl_value (tree decl, depset *dep)
 	}
     }
 
+  if (TREE_CODE (inner) == FUNCTION_DECL
+      || TREE_CODE (inner) == TYPE_DECL)
+    {
+      /* Write imported temploid friends so that importers can reconstruct
+	 this information on stream-in.  */
+      tree* slot = imported_temploid_friends->get (decl);
+      tree_node (slot ? *slot : NULL_TREE);
+    }
+
   bool is_typedef = false;
   if (!type && TREE_CODE (inner) == TYPE_DECL)
     {
@@ -8303,6 +8321,11 @@ trees_in::decl_value ()
 	}
     }
 
+  if (TREE_CODE (inner) == FUNCTION_DECL
+      || TREE_CODE (inner) == TYPE_DECL)
+    if (tree owner = tree_node ())
+      imported_temploid_friends->put (decl, owner);
+
   /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
   unsigned tdef_flags = 0;
   bool is_typedef = false;
@@ -13370,10 +13393,17 @@ depset::hash::add_specializations (bool decl_p)
       int use_tpl = 0;
       bool is_friend = false;
 
-      if (decl_p && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
-	/* A friend of a template.  This is keyed to the
-	   instantiation.  */
-	is_friend = true;
+      if (DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
+	{
+	  if (decl_p)
+	    /* A friend of a template.  This is keyed to the
+	       instantiation.  */
+	    is_friend = true;
+	  else
+	    /* An instantiated friend struct.  Don't count this as
+	       a specialization, it'll be picked up later.  */
+	    continue;
+	}
 
       if (decl_p)
 	{
@@ -18930,6 +18960,12 @@ get_originating_module_decl (tree decl)
 	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
 	decl = TYPE_NAME (DECL_CHAIN (decl));
 
+      /* An imported temploid friend is attached to the same module the
+	 befriending class was.  */
+      if (imported_temploid_friends)
+	if (tree *slot = imported_temploid_friends->get (decl))
+	  decl = *slot;
+
       int use;
       if (tree ti = node_template_info (decl, use))
 	{
@@ -19238,6 +19274,34 @@ maybe_key_decl (tree ctx, tree decl)
   vec.safe_push (decl);
 }
 
+/* DECL is an instantiated friend that should be attached to the same
+   module that ORIG is.  */
+
+void
+propagate_defining_module (tree decl, tree orig)
+{
+  if (!modules_p ())
+    return;
+
+  tree not_tmpl = STRIP_TEMPLATE (orig);
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
+    {
+      tree inner = STRIP_TEMPLATE (decl);
+      retrofit_lang_decl (inner);
+      DECL_MODULE_ATTACH_P (inner) = true;
+    }
+
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
+    {
+      bool exists = imported_temploid_friends->put (decl, orig);
+
+      /* We should only be called if lookup for an existing decl
+	 failed, in which case there shouldn't already be an entry
+	 in the map.  */
+      gcc_assert (!exists);
+    }
+}
+
 /* Create the flat name string.  It is simplest to have it handy.  */
 
 void
@@ -20451,6 +20515,8 @@ init_modules (cpp_reader *reader)
       pending_table = new pending_map_t (EXPERIMENT (1, 400));
       entity_map = new entity_map_t (EXPERIMENT (1, 400));
       vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
+      imported_temploid_friends
+	= new hash_map<tree,tree> (EXPERIMENT (1, 400));
     }
 
 #if CHECKING_P
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 7af7f00e34c..dd6e7b6eaea 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
   add_decl_to_level (b, decl);
 }
 
+/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
+   instantiations of temploid hidden friends from imported modules.  */
+
+tree
+lookup_imported_hidden_friend (tree friend_tmpl)
+{
+  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
+  if (!DECL_LANG_SPECIFIC (inner)
+      || !DECL_MODULE_IMPORT_P (inner))
+    return NULL_TREE;
+
+  tree name = DECL_NAME (inner);
+  tree *slot = find_namespace_slot (current_namespace, name);
+  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
+    return NULL_TREE;
+
+  /* Look in the appropriate slot, as with check_module_override.  */
+  binding_slot mslot;
+  if (named_module_p ())
+    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
+				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
+      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
+  else
+    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
+  gcc_assert (!mslot.is_lazy ());
+
+  tree ovl = mslot;
+  if (!ovl)
+    return NULL_TREE;
+
+  /* We're only interested in declarations coming from the same module
+     of the friend class we're attempting to instantiate.  */
+  int m = get_originating_module (friend_tmpl);
+  gcc_assert (m != 0);
+
+  for (ovl_iterator iter (ovl); iter; ++iter)
+    if (get_originating_module (*iter) == m)
+      return *iter;
+
+  return NULL_TREE;
+}
+
 \f
 /* true means unconditionally make a BLOCK for the next level pushed.  */
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 3b2106dd3f6..e7e7f2fbc3b 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11512,6 +11512,10 @@ tsubst_friend_function (tree decl, tree args)
 	  new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
 	}
 
+      /* We need to propagate module attachment for the new friend from the
+	 owner of this template.  */
+      propagate_defining_module (new_friend, decl);
+
       /* Inside pushdecl_namespace_level, we will push into the
 	 current namespace. However, the friend function should go
 	 into the namespace of the template.  */
@@ -11715,6 +11719,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
   tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
 		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
 
+  if (!tmpl)
+    /* If we didn't find by name lookup, the type may still exist but as a
+       'hidden' import; we should check for this too to avoid accidentally
+       instantiating a duplicate.  */
+    tmpl = lookup_imported_hidden_friend (friend_tmpl);
+
   if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
     {
       /* The friend template has already been declared.  Just
@@ -11723,6 +11733,11 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 	 of course.  We only need the innermost template parameters
 	 because that is all that redeclare_class_template will look
 	 at.  */
+
+      if (modules_p ())
+	/* Check that we can redeclare TMPL in the current context.  */
+	module_may_redeclare (tmpl, friend_tmpl);
+
       if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
 	  > TMPL_ARGS_DEPTH (args))
 	{
@@ -11772,6 +11787,10 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 						     args, tf_warning_or_error);
 	    }
 
+	  /* We need to propagate the attachment of the original template to the
+	     newly instantiated template type.  */
+	  propagate_defining_module (tmpl, friend_tmpl);
+
 	  /* Inject this template into the enclosing namspace scope.  */
 	  tmpl = pushdecl_namespace_level (tmpl, /*hiding=*/true);
 	}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
new file mode 100644
index 00000000000..7547326e554
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
@@ -0,0 +1,15 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi test_support }
+
+module;
+template<class> struct _Sp_atomic;
+template<class> struct shared_ptr {
+  template<class> friend struct _Sp_atomic;
+  using atomic_type = _Sp_atomic<int>;
+};
+export module test_support;
+export
+template<class T> struct A {
+   shared_ptr<T> data;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
new file mode 100644
index 00000000000..6b88ee4258b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
@@ -0,0 +1,5 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+
+import test_support;
+A<int> a;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
new file mode 100644
index 00000000000..789bdeb64d5
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
@@ -0,0 +1,7 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi user }
+
+export module user;
+import test_support; 
+A<int> b;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
new file mode 100644
index 00000000000..f29eebd1a7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
@@ -0,0 +1,14 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+
+template <typename... _Elements> struct T;
+
+template <typename H> struct T<H> {
+  template <typename...> friend struct T;
+};
+
+export module M;
+export template <typename=void> void fun() { T<int> t; }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
new file mode 100644
index 00000000000..5bf79998139
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
@@ -0,0 +1,5 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+int main() { fun(); }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
new file mode 100644
index 00000000000..216dbf62c71
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:A }
+
+module M:A;
+
+template <typename T> struct A {
+  template <typename U> friend struct B;
+private:
+  int x = 42;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
new file mode 100644
index 00000000000..26e1c38b518
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:B }
+
+export module M:B;
+import :A;
+
+export template <typename U> struct B {
+  int foo(A<U> a) { return a.x; }
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
new file mode 100644
index 00000000000..e44c2819cfd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:C }
+
+export module M:C;
+import :A;
+
+template <typename T> struct B;
+export template <typename T, typename U> int bar(B<T> t, U u) {
+  return t.foo(u);
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
new file mode 100644
index 00000000000..9a575ad5046
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export import :B;
+export import :C;
+
+export int go_in_module();
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
new file mode 100644
index 00000000000..329d1e8b263
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+
+module M;
+
+int go_in_module() {
+  return bar(B<int>{}, A<int>{});
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
new file mode 100644
index 00000000000..c9855663fbd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+int main() {
+  B<double> b{};
+  go_in_module();
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
new file mode 100644
index 00000000000..8c972776d60
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
@@ -0,0 +1,13 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export template <typename> struct A {
+  friend struct S;
+  template <typename> friend struct T;
+};
+
+export template <typename> struct B {
+  friend void f();
+  template <typename> friend void g();
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
new file mode 100644
index 00000000000..0e27e97b113
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+A<int> a;
+struct S {};  // { dg-error "conflicts with import" }
+template <typename> struct T {};  // { dg-error "conflicts with import" }
+
+B<int> c;
+void f() {}  // { dg-error "conflicts with import" }
+template <typename> void g() {}  // { dg-error "conflicts with import" }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
new file mode 100644
index 00000000000..3464aa26bf8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
@@ -0,0 +1,13 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+struct S {};  // { dg-error "conflicts with import" }
+template <typename> struct T {};  // { dg-message "previously declared" }
+A<int> a;  // { dg-message "required from here" }
+
+void f() {}  // { dg-message "previously declared" }
+template <typename> void g() {}  // { dg-message "previously declared" }
+B<int> b;  // { dg-message "required from here" }
+
+// { dg-error "conflicting declaration" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
new file mode 100644
index 00000000000..5b935474ab2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi X }
+
+export module X;
+export import M;
+A<int> ax;
+B<int> bx;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
new file mode 100644
index 00000000000..afbd0a39c23
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
@@ -0,0 +1,18 @@
+// { dg-additional-options "-fmodules-ts" }
+
+// 'import X' does not correctly notice that S has already been declared.
+struct S {};  // { dg-message "previously declared" "" { xfail *-*-* } }
+template <typename> struct T {};  // { dg-message "previously declared" }
+void f() {}  // { dg-message "previously declared" }
+template <typename T> void g() {}  // { dg-message "previously declared" }
+
+import X;
+A<double> a2;  // { dg-message "required from here" }
+B<double> b2;  // { dg-message "required from here" }
+
+// specifically, S and T are defined in M, not X, despite the instantiation being in X
+// { dg-error "conflicting declaration \[^\n\r\]* S@M" "" { xfail *-*-* } 0 }
+// { dg-error "conflicting declaration \[^\n\r\]* T@M" "" { target *-*-* } 0 }
+// and similarly for f and g
+// { dg-error "conflicting declaration \[^\n\r\]* f@M" "" { target *-*-* } 0 }
+// { dg-error "conflicting declaration \[^\n\r\]* g@M" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C
new file mode 100644
index 00000000000..287f95c7bdc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi Y }
+
+export module Y;
+export import M;
+A<double> ay;
+B<double> by;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C
new file mode 100644
index 00000000000..b7da60f2322
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import X;
+import Y;
+
+// This should happily refer to the same S and T
+// as already instantiated in both X and Y
+A<long> az;
+
+// And same for f and g
+B<long> bz;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C
new file mode 100644
index 00000000000..6912512ecf7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+export extern "C++" template <typename> struct A {
+  template <typename> friend struct B;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C
new file mode 100644
index 00000000000..5f8aa7fd62e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi X }
+
+export module X;
+export import M;
+
+A<int> x;
+export extern "C++" template <typename T> struct B { using type = T; };
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C
new file mode 100644
index 00000000000..8d89298878a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi Y }
+
+export module Y;
+export import M;
+
+A<double> x;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C
new file mode 100644
index 00000000000..7a842586b62
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import X;
+import Y;
+
+int main() {
+  A<long> a;
+  B<int>::type r = 10;
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-9.C b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
new file mode 100644
index 00000000000..c7216f0f8c1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
@@ -0,0 +1,13 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+template<class> struct A {
+  template<class> friend struct B;
+  friend void C();
+};
+A<int> a;
+void C() {}
+template<class> struct B { };
-- 
2.43.2


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare
  2024-04-19 16:18   ` Nathaniel Shead
@ 2024-04-25 19:52     ` Jason Merrill
  0 siblings, 0 replies; 12+ messages in thread
From: Jason Merrill @ 2024-04-25 19:52 UTC (permalink / raw)
  To: Nathaniel Shead, gcc-patches; +Cc: Nathan Sidwell, Patrick Palka

On 4/19/24 09:18, Nathaniel Shead wrote:
> On Mon, Apr 15, 2024 at 02:49:35PM +1000, Nathaniel Shead wrote:
>> I took another look at this patch and have split it into two, one (this
>> one) to standardise the error messages used and prepare
>> 'module_may_redeclare' for use with temploid friends, and another
>> followup patch to actually handle them correctly.
>>
>> Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk?
>>
>> -- >8 --
>>
>> Currently different places calling 'module_may_redeclare' all emit very
>> similar but slightly different error messages, and handle different
>> kinds of declarations differently.  This patch makes the function
>> perform its own error messages so that they're all in one place, and
>> prepares it for use with temploid friends (PR c++/114275).
>>
>> gcc/cp/ChangeLog:
>>
>> 	* cp-tree.h (module_may_redeclare): Add default parameter.
>> 	* decl.cc (duplicate_decls): Don't emit errors for failed
>> 	module_may_redeclare.
>> 	(xref_tag): Likewise.
>> 	(start_enum): Likewise.
>> 	* semantics.cc (begin_class_definition): Likewise.
>> 	* module.cc (module_may_redeclare): Clean up logic. Emit error
>> 	messages on failure.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	* g++.dg/modules/enum-12.C: Update error message.
>> 	* g++.dg/modules/friend-5_b.C: Likewise.
>> 	* g++.dg/modules/shadow-1_b.C: Likewise.
>>
>> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
>> ---
>>   gcc/cp/cp-tree.h                          |   2 +-
>>   gcc/cp/decl.cc                            |  28 +----
>>   gcc/cp/module.cc                          | 120 ++++++++++++++--------
>>   gcc/cp/semantics.cc                       |   6 +-
>>   gcc/testsuite/g++.dg/modules/enum-12.C    |   2 +-
>>   gcc/testsuite/g++.dg/modules/friend-5_b.C |   2 +-
>>   gcc/testsuite/g++.dg/modules/shadow-1_b.C |   5 +-
>>   7 files changed, 89 insertions(+), 76 deletions(-)
>>
>> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
>> index 1dbb577a38d..faa7a0052a5 100644
>> --- a/gcc/cp/cp-tree.h
>> +++ b/gcc/cp/cp-tree.h
>> @@ -7401,7 +7401,7 @@ inline bool module_exporting_p ()
>>   
>>   extern module_state *get_module (tree name, module_state *parent = NULL,
>>   				 bool partition = false);
>> -extern bool module_may_redeclare (tree decl);
>> +extern bool module_may_redeclare (tree olddecl, tree newdecl = NULL);
>>   
>>   extern bool module_global_init_needed ();
>>   extern bool module_determine_import_inits ();
>> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
>> index 65ab64885ff..aa66da4829d 100644
>> --- a/gcc/cp/decl.cc
>> +++ b/gcc/cp/decl.cc
>> @@ -2279,18 +2279,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
>>         && TREE_CODE (olddecl) != NAMESPACE_DECL
>>         && !hiding)
>>       {
>> -      if (!module_may_redeclare (olddecl))
>> -	{
>> -	  if (DECL_ARTIFICIAL (olddecl))
>> -	    error ("declaration %qD conflicts with builtin", newdecl);
>> -	  else
>> -	    {
>> -	      error ("declaration %qD conflicts with import", newdecl);
>> -	      inform (olddecl_loc, "import declared %q#D here", olddecl);
>> -	    }
>> -
>> -	  return error_mark_node;
>> -	}
>> +      if (!module_may_redeclare (olddecl, newdecl))
>> +	return error_mark_node;
>>   
>>         tree not_tmpl = STRIP_TEMPLATE (olddecl);
>>         if (DECL_LANG_SPECIFIC (not_tmpl)
>> @@ -16620,12 +16610,7 @@ xref_tag (enum tag_types tag_code, tree name,
>>   	{
>>   	  tree decl = TYPE_NAME (t);
>>   	  if (!module_may_redeclare (decl))
>> -	    {
>> -	      auto_diagnostic_group d;
>> -	      error ("cannot declare %qD in a different module", decl);
>> -	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
>> -	      return error_mark_node;
>> -	    }
>> +	    return error_mark_node;
>>   
>>   	  tree not_tmpl = STRIP_TEMPLATE (decl);
>>   	  if (DECL_LANG_SPECIFIC (not_tmpl)
>> @@ -16973,12 +16958,7 @@ start_enum (tree name, tree enumtype, tree underlying_type,
>>   	{
>>   	  tree decl = TYPE_NAME (enumtype);
>>   	  if (!module_may_redeclare (decl))
>> -	    {
>> -	      auto_diagnostic_group d;
>> -	      error ("cannot declare %qD in different module", decl);
>> -	      inform (DECL_SOURCE_LOCATION (decl), "previously declared here");
>> -	      enumtype = error_mark_node;
>> -	    }
>> +	    enumtype = error_mark_node;
>>   	  else
>>   	    set_instantiating_module (decl);
>>   	}
>> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
>> index 001430a4a8f..e2d2910ae48 100644
>> --- a/gcc/cp/module.cc
>> +++ b/gcc/cp/module.cc
>> @@ -18992,11 +18992,15 @@ get_importing_module (tree decl, bool flexible)
>>     return module->mod;
>>   }
>>   
>> -/* Is it permissible to redeclare DECL.  */
>> +/* Is it permissible to redeclare OLDDECL with NEWDECL.
>> +
>> +   If NEWDECL is NULL, assumes that OLDDECL will be redeclared using
>> +   the current scope's module and attachment.  */
>>   
>>   bool
>> -module_may_redeclare (tree decl)
>> +module_may_redeclare (tree olddecl, tree newdecl)
>>   {
>> +  tree decl = olddecl;
>>     for (;;)
>>       {
>>         tree ctx = CP_DECL_CONTEXT (decl);
>> @@ -19010,58 +19014,94 @@ module_may_redeclare (tree decl)
>>         decl = TYPE_NAME (ctx);
>>       }
>>   
>> -  tree not_tmpl = STRIP_TEMPLATE (decl);
>> -
>>     int use_tpl = 0;
>> -  if (node_template_info (not_tmpl, use_tpl) && use_tpl)
>> +  if (node_template_info (STRIP_TEMPLATE (decl), use_tpl) && use_tpl)
>>       // Specializations of any kind can be redeclared anywhere.
>>       // FIXME: Should we be checking this in more places on the scope chain?
>>       return true;
>>   
>> -  if (!DECL_LANG_SPECIFIC (not_tmpl) || !DECL_MODULE_ATTACH_P (not_tmpl))
>> -    // Decl is attached to global module.  Current scope needs to be too.
>> -    return !module_attach_p ();
>> +  module_state *old_mod = (*modules)[0];
>> +  module_state *new_mod = old_mod;
>>   
>> -  module_state *me = (*modules)[0];
>> -  module_state *them = me;
>> +  tree old_origin = get_originating_module_decl (decl);
>> +  tree old_inner = STRIP_TEMPLATE (old_origin);
>> +  bool olddecl_attached_p = (DECL_LANG_SPECIFIC (old_inner)
>> +			     && DECL_MODULE_ATTACH_P (old_inner));
>> +  if (DECL_LANG_SPECIFIC (old_inner) && DECL_MODULE_IMPORT_P (old_inner))
>> +    {
>> +      unsigned index = import_entity_index (old_origin);
>> +      old_mod = import_entity_module (index);
>> +    }
>>   
>> -  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
>> +  bool newdecl_attached_p = module_attach_p ();
>> +  if (newdecl)
>>       {
>> -      /* We can be given the TEMPLATE_RESULT.  We want the
>> -	 TEMPLATE_DECL.  */
>> -      int use_tpl = -1;
>> -      if (tree ti = node_template_info (decl, use_tpl))
>> +      tree new_origin = get_originating_module_decl (newdecl);
>> +      tree new_inner = STRIP_TEMPLATE (new_origin);
>> +      newdecl_attached_p = (DECL_LANG_SPECIFIC (new_inner)
>> +			    && DECL_MODULE_ATTACH_P (new_inner));
>> +      if (DECL_LANG_SPECIFIC (new_inner) && DECL_MODULE_IMPORT_P (new_inner))
>>   	{
>> -	  tree tmpl = TI_TEMPLATE (ti);
>> -	  if (use_tpl == 2)
>> -	    {
>> -	      /* A partial specialization.  Find that specialization's
>> -		 template_decl.  */
>> -	      for (tree list = DECL_TEMPLATE_SPECIALIZATIONS (tmpl);
>> -		   list; list = TREE_CHAIN (list))
>> -		if (DECL_TEMPLATE_RESULT (TREE_VALUE (list)) == decl)
>> -		  {
>> -		    decl = TREE_VALUE (list);
>> -		    break;
>> -		}
>> -	    }
>> -	  else if (DECL_TEMPLATE_RESULT (tmpl) == decl)
>> -	    decl = tmpl;
>> +	  unsigned index = import_entity_index (new_origin);
>> +	  new_mod = import_entity_module (index);
>>   	}
>> -      unsigned index = import_entity_index (decl);
>> -      them = import_entity_module (index);
>>       }
>>   
>> -  // Decl is attached to named module.  Current scope needs to be
>> -  // attaching to the same module.
>> -  if (!module_attach_p ())
>> -    return false;
>> +  /* Module attachment needs to match.  */
>> +  if (olddecl_attached_p == newdecl_attached_p)
>> +    {
>> +      if (!olddecl_attached_p)
>> +	/* Both are GM entities, OK.  */
>> +	return true;
>>   
>> -  // Both attached to named module.
>> -  if (me == them)
>> -    return true;
>> +      if (new_mod == old_mod
>> +	  || (new_mod && get_primary (new_mod) == get_primary (old_mod)))
>> +	/* Both attached to same named module, OK.  */
>> +	return true;
>> +    }
>> +
>> +  /* Attached to different modules, error.  */
>> +  decl = newdecl ? newdecl : olddecl;
>> +  location_t loc = newdecl ? DECL_SOURCE_LOCATION (newdecl) : input_location;
>> +  if (DECL_ARTIFICIAL (olddecl) && !DECL_IMPLICIT_TYPEDEF_P (olddecl))
>> +    error_at (loc, "declaration %qD conflicts with builtin", decl);
> 
> Or maybe this should be
> 
>    if (DECL_SOURCE_LOCATION (olddecl) == BUILTINS_LOCATION)
>      error_at (loc, "declaration %qD conflicts with builtin", decl);
> 
> and update the error message in enum-12.C?

Probably DECL_IS_UNDECLARED_BUILTIN?  OK with that change.

Jason


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 2/2] c++: Fix instantiation of imported temploid friends [PR114275]
  2024-04-19 16:29       ` [PATCH v3 2/2] c++: " Nathaniel Shead
@ 2024-04-27  1:16         ` Jason Merrill
  2024-04-29  9:34           ` [PATCH v4 " Nathaniel Shead
  0 siblings, 1 reply; 12+ messages in thread
From: Jason Merrill @ 2024-04-27  1:16 UTC (permalink / raw)
  To: Nathaniel Shead, Patrick Palka; +Cc: gcc-patches, Nathan Sidwell

On 4/19/24 09:29, Nathaniel Shead wrote:
> On Fri, Apr 19, 2024 at 12:14:06PM +1000, Nathaniel Shead wrote:
>> On Wed, Apr 17, 2024 at 02:02:21PM -0400, Patrick Palka wrote:
>>> On Mon, 15 Apr 2024, Nathaniel Shead wrote:
>>>
>>>> I'm not a huge fan of always streaming 'imported_temploid_friends' for
>>>> all decls, but I don't think it adds much performance cost over adding a
>>>> new flag to categorise decls that might be marked as such.
>>>
>>> IIUC this value is going to be almost always null which is encoded as a
>>> single 0 byte, which should be fast to stream.  But I wonder how much
>>> larger <bits/stdc++.h> gets?  Can we get away with streaming this value
>>> only for TEMPLATE_DECLs?
>>
>> Yes, it should either just be a 0 byte or an additional backref
>> somewhere, which will likely also be small. On my system it increases
>> the size by 0.26%, from 31186800 bytes to 31268672.
>>
>> But I've just found that this patch has a bug anyway, in that it doesn't
>> correctly dedup if the friend types are instantiated in two separate
>> modules that are then both imported.  I'll see what I need to do to fix
>> this which may influence what we need to stream here.
>>
> 
> Here's an updated version of the patch that fixes this. Also changed to
> only stream when 'inner' is either TYPE_DECL or FUNCTION_DECL, which
> cuts the size of <bits/stdc++.h> down a bit to 31246992 (0.19% growth).
> 
> Another alternative would be to add another boolean flag at the top of
> 'decl_value' and branch on that; that would make use of the bitpacking
> logic and probably cut down on the size further.  (I haven't measured
> this yet though.)
> 
> Bootstrapped and regtested (so far just dg.exp and modules.exp) on
> x86_64-pc-linux-gnu, OK for trunk if full regtest succeeds?
> 
> -- >8 --
> 
> This patch fixes a number of issues with the handling of temploid friend
> declarations.
> 
> The primary issue is that instantiations of friend declarations should
> attach the declaration to the same module as the befriending class, by
> [module.unit] p7.1 and [temp.friend] p2; this could be a different
> module from the current TU, and so needs special handling.

This is only an issue for DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P, right?

Hmm, CWG2588 should probably touch [module.unit]/7.1 as well as 
[basic.link].

> The other main issue here is that we can't assume that just because name
> lookup didn't find a definition for a hidden template class, it doesn't
> mean that it doesn't exist: it could be a non-exported entity that we've
> nevertheless streamed in from an imported module.  We need to ensure
> that when instantiating friend classes that we return the same TYPE_DECL
> that we got from our imports, otherwise we will get later issues with
> 'duplicate_decls' (rightfully) complaining that they're different.

Tricksy.

> This doesn't appear necessary for functions due to the existing name
> lookup handling already finding these hidden declarations.
> 
> 	PR c++/105320
> 	PR c++/114275
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (propagate_defining_module): Declare.
> 	(lookup_imported_hidden_friend): Declare.
> 	* decl.cc (duplicate_decls): Also check if hidden declarations
> 	can be redeclared in this module.
> 	* module.cc (imported_temploid_friends): New map.
> 	(init_modules): Initialize it.
> 	(trees_out::decl_value): Write it; don't consider imported
> 	temploid friends as attached to this module.
> 	(trees_in::decl_value): Read it.
> 	(depset::hash::add_specializations): Don't treat instantiations
> 	of a friend type as a specialisation.
> 	(get_originating_module_decl): Follow the owning decl for an
> 	imported temploid friend.
> 	(propagate_defining_module): New function.
> 	* name-lookup.cc (lookup_imported_hidden_friend): New function.
> 	* pt.cc (tsubst_friend_function): Propagate defining module for
> 	new friend functions.
> 	(tsubst_friend_class): Lookup imported hidden friends. Check
> 	for valid redeclaration. Propagate defining module for new
> 	friend classes.
> 
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index aa66da4829d..56752cf6872 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -2276,30 +2276,34 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
>   
>     if (modules_p ()
>         && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
> -      && TREE_CODE (olddecl) != NAMESPACE_DECL
> -      && !hiding)
> +      && TREE_CODE (olddecl) != NAMESPACE_DECL)
>       {
>         if (!module_may_redeclare (olddecl, newdecl))
>   	return error_mark_node;
>   
> -      tree not_tmpl = STRIP_TEMPLATE (olddecl);
> -      if (DECL_LANG_SPECIFIC (not_tmpl)
> -	  && DECL_MODULE_ATTACH_P (not_tmpl)
> -	  /* Typedefs are not entities and so are OK to be redeclared
> -	     as exported: see [module.interface]/p6.  */
> -	  && TREE_CODE (olddecl) != TYPE_DECL)
> +      if (!hiding)
>   	{
> -	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
> -	      && !DECL_MODULE_EXPORT_P (not_tmpl))
> +	  /* Hidden friend declarations just use exportingness of the
> +	     old declaration; see CWG2588.  */

I'm not sure what this comment is trying to say, wouldn't hiding be true 
for a hidden friend?

> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 36e035544f4..bd3ab686543 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -2727,6 +2727,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
>   typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
>   static keyed_map_t *keyed_table;
>   
> +/* Instantiations of temploid friends imported from another module
> +   need to be owned by the same module as their instantiating template.
> +   This maps these to the template that instantiated them.  */
> +static hash_map<tree, tree> *imported_temploid_friends;

So IIUC the way to express the attachment to an imported module is by 
association with an actually imported decl.

And the problem is that there's no current way to get from the result of 
tsubst_friend_* to the original temploid friend; as 
tsubst_class_template says, "The new TMPL is not an instantiation of 
anything, so we forget its origins."  And this map provides that lookup?

Rather than "their instantiating template" and "the template that 
instantiated them" I'd just say "the temploid", maybe "the temploid they 
are instantiated from".

I'd also change "owned" to "attached".

> @@ -13370,10 +13393,17 @@ depset::hash::add_specializations (bool decl_p)
>         int use_tpl = 0;
>         bool is_friend = false;
>   
> -      if (decl_p && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
> -	/* A friend of a template.  This is keyed to the
> -	   instantiation.  */
> -	is_friend = true;
> +      if (DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
> +	{
> +	  if (decl_p)
> +	    /* A friend of a template.  This is keyed to the
> +	       instantiation.  */
> +	    is_friend = true;
> +	  else
> +	    /* An instantiated friend struct.  Don't count this as
> +	       a specialization, it'll be picked up later.  */

You mean, in propagate_defining_module?

Would it make sense to call propagate_defining_module here, rather than 
in tsubst_friend_class?

> @@ -18930,6 +18960,12 @@ get_originating_module_decl (tree decl)
>   	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
>   	decl = TYPE_NAME (DECL_CHAIN (decl));
>   
> +      /* An imported temploid friend is attached to the same module the
> +	 befriending class was.  */
> +      if (imported_temploid_friends)
> +	if (tree *slot = imported_temploid_friends->get (decl))
> +	  decl = *slot;
> +
>         int use;
>         if (tree ti = node_template_info (decl, use))
>   	{

I note that just below this is

>           decl = TI_TEMPLATE (ti);
>           if (TREE_CODE (decl) != TEMPLATE_DECL)
>             {
>               /* A friend template specialization.  */
>               gcc_checking_assert (OVL_P (decl));
>               return global_namespace;
>             }

which seems to want to say "global module" for a friend template 
specialization, rather than the correct module.  Is this code obsolete 
with this patch?

> diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
> index 7af7f00e34c..dd6e7b6eaea 100644
> --- a/gcc/cp/name-lookup.cc
> +++ b/gcc/cp/name-lookup.cc
> @@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
>     add_decl_to_level (b, decl);
>   }
>   
> +/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
> +   instantiations of temploid hidden friends from imported modules.  */
> +
> +tree
> +lookup_imported_hidden_friend (tree friend_tmpl)
> +{
> +  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
> +  if (!DECL_LANG_SPECIFIC (inner)
> +      || !DECL_MODULE_IMPORT_P (inner))
> +    return NULL_TREE;
> +
> +  tree name = DECL_NAME (inner);
> +  tree *slot = find_namespace_slot (current_namespace, name);

Maybe checking_assert CP_DECL_CONTEXT (friend_tmpl) == current_namespace?

> +  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
> +    return NULL_TREE;
> +
> +  /* Look in the appropriate slot, as with check_module_override.  */
> +  binding_slot mslot;
> +  if (named_module_p ())
> +    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
> +				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
> +      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
> +  else
> +    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
> +  gcc_assert (!mslot.is_lazy ());
> +
> +  tree ovl = mslot;
> +  if (!ovl)
> +    return NULL_TREE;
> +
> +  /* We're only interested in declarations coming from the same module
> +     of the friend class we're attempting to instantiate.  */
> +  int m = get_originating_module (friend_tmpl);
> +  gcc_assert (m != 0);
> +
> +  for (ovl_iterator iter (ovl); iter; ++iter)
> +    if (get_originating_module (*iter) == m)
> +      return *iter;
> +
> +  return NULL_TREE;
> +}

Maybe factor much of this out into a get_namespace_binding_in_module 
function?
    \f
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 3b2106dd3f6..e7e7f2fbc3b 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -11723,6 +11733,11 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>   	 of course.  We only need the innermost template parameters
>   	 because that is all that redeclare_class_template will look
>   	 at.  */
> +
> +      if (modules_p ())
> +	/* Check that we can redeclare TMPL in the current context.  */
> +	module_may_redeclare (tmpl, friend_tmpl);

Isn't this redundant with the duplicate_decls change?

Jason


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH v4 2/2] c++: Fix instantiation of imported temploid friends [PR114275]
  2024-04-27  1:16         ` Jason Merrill
@ 2024-04-29  9:34           ` Nathaniel Shead
  2024-04-29 22:37             ` Jason Merrill
  0 siblings, 1 reply; 12+ messages in thread
From: Nathaniel Shead @ 2024-04-29  9:34 UTC (permalink / raw)
  To: Jason Merrill; +Cc: Patrick Palka, gcc-patches, Nathan Sidwell

On Fri, Apr 26, 2024 at 09:16:40PM -0400, Jason Merrill wrote:
> On 4/19/24 09:29, Nathaniel Shead wrote:
> > On Fri, Apr 19, 2024 at 12:14:06PM +1000, Nathaniel Shead wrote:
> > > On Wed, Apr 17, 2024 at 02:02:21PM -0400, Patrick Palka wrote:
> > > > On Mon, 15 Apr 2024, Nathaniel Shead wrote:
> > > > 
> > > > > I'm not a huge fan of always streaming 'imported_temploid_friends' for
> > > > > all decls, but I don't think it adds much performance cost over adding a
> > > > > new flag to categorise decls that might be marked as such.
> > > > 
> > > > IIUC this value is going to be almost always null which is encoded as a
> > > > single 0 byte, which should be fast to stream.  But I wonder how much
> > > > larger <bits/stdc++.h> gets?  Can we get away with streaming this value
> > > > only for TEMPLATE_DECLs?
> > > 
> > > Yes, it should either just be a 0 byte or an additional backref
> > > somewhere, which will likely also be small. On my system it increases
> > > the size by 0.26%, from 31186800 bytes to 31268672.
> > > 
> > > But I've just found that this patch has a bug anyway, in that it doesn't
> > > correctly dedup if the friend types are instantiated in two separate
> > > modules that are then both imported.  I'll see what I need to do to fix
> > > this which may influence what we need to stream here.
> > > 
> > 
> > Here's an updated version of the patch that fixes this. Also changed to
> > only stream when 'inner' is either TYPE_DECL or FUNCTION_DECL, which
> > cuts the size of <bits/stdc++.h> down a bit to 31246992 (0.19% growth).
> > 
> > Another alternative would be to add another boolean flag at the top of
> > 'decl_value' and branch on that; that would make use of the bitpacking
> > logic and probably cut down on the size further.  (I haven't measured
> > this yet though.)
> > 
> > Bootstrapped and regtested (so far just dg.exp and modules.exp) on
> > x86_64-pc-linux-gnu, OK for trunk if full regtest succeeds?
> > 
> > -- >8 --
> > 
> > This patch fixes a number of issues with the handling of temploid friend
> > declarations.
> > 
> > The primary issue is that instantiations of friend declarations should
> > attach the declaration to the same module as the befriending class, by
> > [module.unit] p7.1 and [temp.friend] p2; this could be a different
> > module from the current TU, and so needs special handling.
> 
> This is only an issue for DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P, right?

It's true for any friend instantiations, including e.g. friend
functions, not just template class friends.

> 
> Hmm, CWG2588 should probably touch [module.unit]/7.1 as well as
> [basic.link].
> 
> > The other main issue here is that we can't assume that just because name
> > lookup didn't find a definition for a hidden template class, it doesn't
> > mean that it doesn't exist: it could be a non-exported entity that we've
> > nevertheless streamed in from an imported module.  We need to ensure
> > that when instantiating friend classes that we return the same TYPE_DECL
> > that we got from our imports, otherwise we will get later issues with
> > 'duplicate_decls' (rightfully) complaining that they're different.
> 
> Tricksy.
> 
> > This doesn't appear necessary for functions due to the existing name
> > lookup handling already finding these hidden declarations.
> > 
> > 	PR c++/105320
> > 	PR c++/114275
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* cp-tree.h (propagate_defining_module): Declare.
> > 	(lookup_imported_hidden_friend): Declare.
> > 	* decl.cc (duplicate_decls): Also check if hidden declarations
> > 	can be redeclared in this module.
> > 	* module.cc (imported_temploid_friends): New map.
> > 	(init_modules): Initialize it.
> > 	(trees_out::decl_value): Write it; don't consider imported
> > 	temploid friends as attached to this module.
> > 	(trees_in::decl_value): Read it.
> > 	(depset::hash::add_specializations): Don't treat instantiations
> > 	of a friend type as a specialisation.
> > 	(get_originating_module_decl): Follow the owning decl for an
> > 	imported temploid friend.
> > 	(propagate_defining_module): New function.
> > 	* name-lookup.cc (lookup_imported_hidden_friend): New function.
> > 	* pt.cc (tsubst_friend_function): Propagate defining module for
> > 	new friend functions.
> > 	(tsubst_friend_class): Lookup imported hidden friends. Check
> > 	for valid redeclaration. Propagate defining module for new
> > 	friend classes.
> > 
> > diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> > index aa66da4829d..56752cf6872 100644
> > --- a/gcc/cp/decl.cc
> > +++ b/gcc/cp/decl.cc
> > @@ -2276,30 +2276,34 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
> >     if (modules_p ()
> >         && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
> > -      && TREE_CODE (olddecl) != NAMESPACE_DECL
> > -      && !hiding)
> > +      && TREE_CODE (olddecl) != NAMESPACE_DECL)
> >       {
> >         if (!module_may_redeclare (olddecl, newdecl))
> >   	return error_mark_node;
> > -      tree not_tmpl = STRIP_TEMPLATE (olddecl);
> > -      if (DECL_LANG_SPECIFIC (not_tmpl)
> > -	  && DECL_MODULE_ATTACH_P (not_tmpl)
> > -	  /* Typedefs are not entities and so are OK to be redeclared
> > -	     as exported: see [module.interface]/p6.  */
> > -	  && TREE_CODE (olddecl) != TYPE_DECL)
> > +      if (!hiding)
> >   	{
> > -	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
> > -	      && !DECL_MODULE_EXPORT_P (not_tmpl))
> > +	  /* Hidden friend declarations just use exportingness of the
> > +	     old declaration; see CWG2588.  */
> 
> I'm not sure what this comment is trying to say, wouldn't hiding be true for
> a hidden friend?
> 

Right, it wasn't clear; the comment was meant to be explaining why we
don't do the following hunk for hidden friends, because for a hidden
friend we only care about the exportingness of OLDDECL (and NEWDECL
doesn't need to change).  I've updated the comment to be clearer.

I took a closer look at the code as well and it doesn't seem to exactly
match the rules given by [module.interface] anyway, but I think that
should be fixed as a separate patch.  (In particular, this allows
redeclaring any GM entity as exported even if the initial declaration
was not, whereas [module.interface] p2 only allows this for names
declared within header units.)

> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index 36e035544f4..bd3ab686543 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -2727,6 +2727,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
> >   typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
> >   static keyed_map_t *keyed_table;
> > +/* Instantiations of temploid friends imported from another module
> > +   need to be owned by the same module as their instantiating template.
> > +   This maps these to the template that instantiated them.  */
> > +static hash_map<tree, tree> *imported_temploid_friends;
> 
> So IIUC the way to express the attachment to an imported module is by
> association with an actually imported decl.
> 
> And the problem is that there's no current way to get from the result of
> tsubst_friend_* to the original temploid friend; as tsubst_class_template
> says, "The new TMPL is not an instantiation of anything, so we forget its
> origins."  And this map provides that lookup?
> 
> Rather than "their instantiating template" and "the template that
> instantiated them" I'd just say "the temploid", maybe "the temploid they are
> instantiated from".
> 
> I'd also change "owned" to "attached".
> 

Yup, right.  I'll change the comment.

> > @@ -13370,10 +13393,17 @@ depset::hash::add_specializations (bool decl_p)
> >         int use_tpl = 0;
> >         bool is_friend = false;
> > -      if (decl_p && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
> > -	/* A friend of a template.  This is keyed to the
> > -	   instantiation.  */
> > -	is_friend = true;
> > +      if (DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
> > +	{
> > +	  if (decl_p)
> > +	    /* A friend of a template.  This is keyed to the
> > +	       instantiation.  */
> > +	    is_friend = true;
> > +	  else
> > +	    /* An instantiated friend struct.  Don't count this as
> > +	       a specialization, it'll be picked up later.  */
> 
> You mean, in propagate_defining_module?
> 
> Would it make sense to call propagate_defining_module here, rather than in
> tsubst_friend_class?
> 

No sorry, in 'add_namespace_entities'.  The issue here is that even
though this isn't counted as a specialisation anymore, it's still kept
in 'type_specializations' as-if it was a dependent specialization of
the instantiating type.  As such, e.g. for

  template <typename T>
  struct A {
    template <typename> friend struct B;
  };

Instantiating A<int> in one module and A<double> in a different module
will emit two different 'specialization' merge keys with two sets of
template parameters, the outermost set being 'int' for the former and
'double' for the latter, preventing deduplication.

An alternative here I've tried would be to instead also remove the new
entry from 'type_specializations' in 'tsubst_friend_class' (given that
it shouldn't be used anywhere else either); this might be the neater
approach so I've done it below, but happy either way.

Either way we need to do 'propagate_defining_module' earlier, because
this function only happens after parsing is complete but we need to
ensure that 'get_originating_module_decl' gives the correct values
before then.

> > @@ -18930,6 +18960,12 @@ get_originating_module_decl (tree decl)
> >   	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
> >   	decl = TYPE_NAME (DECL_CHAIN (decl));
> > +      /* An imported temploid friend is attached to the same module the
> > +	 befriending class was.  */
> > +      if (imported_temploid_friends)
> > +	if (tree *slot = imported_temploid_friends->get (decl))
> > +	  decl = *slot;
> > +
> >         int use;
> >         if (tree ti = node_template_info (decl, use))
> >   	{
> 
> I note that just below this is
> 
> >           decl = TI_TEMPLATE (ti);
> >           if (TREE_CODE (decl) != TEMPLATE_DECL)
> >             {
> >               /* A friend template specialization.  */
> >               gcc_checking_assert (OVL_P (decl));
> >               return global_namespace;
> >             }
> 
> which seems to want to say "global module" for a friend template
> specialization, rather than the correct module.  Is this code obsolete with
> this patch?
> 

It doesn't look like it; removing this regresses e.g. tpl-friend-6_a.C,
and seems to be handling friend specializations of existing
declarations, whereas the imported_temploid_friends logic is for
handling names first declared as friends.

> > diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
> > index 7af7f00e34c..dd6e7b6eaea 100644
> > --- a/gcc/cp/name-lookup.cc
> > +++ b/gcc/cp/name-lookup.cc
> > @@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
> >     add_decl_to_level (b, decl);
> >   }
> > +/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
> > +   instantiations of temploid hidden friends from imported modules.  */
> > +
> > +tree
> > +lookup_imported_hidden_friend (tree friend_tmpl)
> > +{
> > +  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
> > +  if (!DECL_LANG_SPECIFIC (inner)
> > +      || !DECL_MODULE_IMPORT_P (inner))
> > +    return NULL_TREE;
> > +
> > +  tree name = DECL_NAME (inner);
> > +  tree *slot = find_namespace_slot (current_namespace, name);
> 
> Maybe checking_assert CP_DECL_CONTEXT (friend_tmpl) == current_namespace?
> 

Initially I hadn't because I thought that the context could possibly be
a class type.  But if that was the case then I suppose that the original
lookup_name must succeed, so this assert should hold.

> > +  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
> > +    return NULL_TREE;
> > +
> > +  /* Look in the appropriate slot, as with check_module_override.  */
> > +  binding_slot mslot;
> > +  if (named_module_p ())
> > +    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
> > +				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
> > +      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
> > +  else
> > +    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
> > +  gcc_assert (!mslot.is_lazy ());
> > +
> > +  tree ovl = mslot;
> > +  if (!ovl)
> > +    return NULL_TREE;
> > +
> > +  /* We're only interested in declarations coming from the same module
> > +     of the friend class we're attempting to instantiate.  */
> > +  int m = get_originating_module (friend_tmpl);
> > +  gcc_assert (m != 0);
> > +
> > +  for (ovl_iterator iter (ovl); iter; ++iter)
> > +    if (get_originating_module (*iter) == m)
> > +      return *iter;
> > +
> > +  return NULL_TREE;
> > +}
> 
> Maybe factor much of this out into a get_namespace_binding_in_module
> function?

I've factored and clarified the slot-finding parts into a new
'get_mergeable_namespace_binding' to correspond with
'add_mergeable_namespace_entity'.  Most of the rest I think relies on
implementation details specific to handling friend classes, I've added
some more comments too to expand on that.

> > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> > index 3b2106dd3f6..e7e7f2fbc3b 100644
> > --- a/gcc/cp/pt.cc
> > +++ b/gcc/cp/pt.cc
> > @@ -11723,6 +11733,11 @@ tsubst_friend_class (tree friend_tmpl, tree args)
> >   	 of course.  We only need the innermost template parameters
> >   	 because that is all that redeclare_class_template will look
> >   	 at.  */
> > +
> > +      if (modules_p ())
> > +	/* Check that we can redeclare TMPL in the current context.  */
> > +	module_may_redeclare (tmpl, friend_tmpl);
> 
> Isn't this redundant with the duplicate_decls change?
> 

No, because in this code path 'duplicate_decls' never actually gets
called: we just return the existing declaration directly.  And this
check is needed for tpl-friend-13_[ce].C.  Maybe we should be, but there
isn't really a corresponding 'olddecl' to compare with that I can see,
since friend_tmpl is just the template we're instantiating from rather
than a duplicate.

I've updated the comment to hopefully make it a bit clearer what this
check is actually doing.

Here's an updated version of the patch. Bootstrapped and regtested on
x86_64-pc-linux-gnu, OK for trunk?

-- >8 --

This patch fixes a number of issues with the handling of temploid friend
declarations.

The primary issue is that instantiations of friend declarations should
attach the declaration to the same module as the befriending class, by
[module.unit] p7.1 and [temp.friend] p2; this could be a different
module from the current TU, and so needs special handling.

The other main issue here is that we can't assume that just because name
lookup didn't find a definition for a hidden class template, that it
doesn't exist at all: it could be a non-exported entity that we've
nevertheless streamed in from an imported module.  We need to ensure
that when instantiating template friend classes that we return the same
TEMPLATE_DECL that we got from our imports, otherwise we will get later
issues with 'duplicate_decls' (rightfully) complaining that they're
different when trying to merge.

This doesn't appear necessary for function templates due to the existing
name lookup handling already finding these hidden declarations.

	PR c++/105320
	PR c++/114275

gcc/cp/ChangeLog:

	* cp-tree.h (propagate_defining_module): Declare.
	(lookup_imported_hidden_friend): Declare.
	* decl.cc (duplicate_decls): Also check if hidden decls can be
	redeclared in this module.
	* module.cc (imported_temploid_friends): New.
	(init_modules): Initialize it.
	(trees_out::decl_value): Write it; don't consider imported
	temploid friends as attached to a module.
	(trees_in::decl_value): Read it.
	(get_originating_module_decl): Follow the owning decl for an
	imported temploid friend.
	(propagate_defining_module): New.
	* name-lookup.cc (get_mergeable_namespace_binding): New.
	(lookup_imported_hidden_friend): New.
	* pt.cc (tsubst_friend_function): Propagate defining module for
	new friend functions.
	(tsubst_friend_class): Lookup imported hidden friends.  Check
	for valid module attachment of existing names.  Propagate
	defining module for new classes.

gcc/testsuite/ChangeLog:

	* g++.dg/modules/tpl-friend-10_a.C: New test.
	* g++.dg/modules/tpl-friend-10_b.C: New test.
	* g++.dg/modules/tpl-friend-10_c.C: New test.
	* g++.dg/modules/tpl-friend-10_d.C: New test.
	* g++.dg/modules/tpl-friend-11_a.C: New test.
	* g++.dg/modules/tpl-friend-11_b.C: New test.
	* g++.dg/modules/tpl-friend-12_a.C: New test.
	* g++.dg/modules/tpl-friend-12_b.C: New test.
	* g++.dg/modules/tpl-friend-12_c.C: New test.
	* g++.dg/modules/tpl-friend-12_d.C: New test.
	* g++.dg/modules/tpl-friend-12_e.C: New test.
	* g++.dg/modules/tpl-friend-12_f.C: New test.
	* g++.dg/modules/tpl-friend-13_a.C: New test.
	* g++.dg/modules/tpl-friend-13_b.C: New test.
	* g++.dg/modules/tpl-friend-13_c.C: New test.
	* g++.dg/modules/tpl-friend-13_d.C: New test.
	* g++.dg/modules/tpl-friend-13_e.C: New test.
	* g++.dg/modules/tpl-friend-13_f.C: New test.
	* g++.dg/modules/tpl-friend-13_g.C: New test.
	* g++.dg/modules/tpl-friend-14_a.C: New test.
	* g++.dg/modules/tpl-friend-14_b.C: New test.
	* g++.dg/modules/tpl-friend-14_c.C: New test.
	* g++.dg/modules/tpl-friend-14_d.C: New test.
	* g++.dg/modules/tpl-friend-9.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                              |  2 +
 gcc/cp/decl.cc                                | 37 ++++++-----
 gcc/cp/module.cc                              | 62 +++++++++++++++++++
 gcc/cp/name-lookup.cc                         | 53 ++++++++++++++++
 gcc/cp/pt.cc                                  | 29 ++++++++-
 .../g++.dg/modules/tpl-friend-10_a.C          | 15 +++++
 .../g++.dg/modules/tpl-friend-10_b.C          |  5 ++
 .../g++.dg/modules/tpl-friend-10_c.C          |  7 +++
 .../g++.dg/modules/tpl-friend-10_d.C          |  8 +++
 .../g++.dg/modules/tpl-friend-11_a.C          | 14 +++++
 .../g++.dg/modules/tpl-friend-11_b.C          |  5 ++
 .../g++.dg/modules/tpl-friend-12_a.C          | 10 +++
 .../g++.dg/modules/tpl-friend-12_b.C          |  9 +++
 .../g++.dg/modules/tpl-friend-12_c.C          | 10 +++
 .../g++.dg/modules/tpl-friend-12_d.C          |  8 +++
 .../g++.dg/modules/tpl-friend-12_e.C          |  7 +++
 .../g++.dg/modules/tpl-friend-12_f.C          |  8 +++
 .../g++.dg/modules/tpl-friend-13_a.C          | 13 ++++
 .../g++.dg/modules/tpl-friend-13_b.C          | 11 ++++
 .../g++.dg/modules/tpl-friend-13_c.C          | 13 ++++
 .../g++.dg/modules/tpl-friend-13_d.C          |  7 +++
 .../g++.dg/modules/tpl-friend-13_e.C          | 18 ++++++
 .../g++.dg/modules/tpl-friend-13_f.C          |  7 +++
 .../g++.dg/modules/tpl-friend-13_g.C          | 11 ++++
 .../g++.dg/modules/tpl-friend-14_a.C          |  8 +++
 .../g++.dg/modules/tpl-friend-14_b.C          |  8 +++
 .../g++.dg/modules/tpl-friend-14_c.C          |  7 +++
 .../g++.dg/modules/tpl-friend-14_d.C          |  9 +++
 gcc/testsuite/g++.dg/modules/tpl-friend-9.C   | 13 ++++
 29 files changed, 397 insertions(+), 17 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-9.C

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index faa7a0052a5..67cc7d7bcec 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7417,6 +7417,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
 extern void set_instantiating_module (tree);
 extern void set_defining_module (tree);
 extern void maybe_key_decl (tree ctx, tree decl);
+extern void propagate_defining_module (tree decl, tree orig);
 
 extern void mangle_module (int m, bool include_partition);
 extern void mangle_module_fini ();
@@ -7649,6 +7650,7 @@ extern bool template_guide_p			(const_tree);
 extern bool builtin_guide_p			(const_tree);
 extern void store_explicit_specifier		(tree, tree);
 extern tree lookup_explicit_specifier		(tree);
+extern tree lookup_imported_hidden_friend	(tree);
 extern void walk_specializations		(bool,
 						 void (*)(bool, spec_entry *,
 							  void *),
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 91268ff631d..d88e0698652 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -2276,30 +2276,35 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
 
   if (modules_p ()
       && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
-      && TREE_CODE (olddecl) != NAMESPACE_DECL
-      && !hiding)
+      && TREE_CODE (olddecl) != NAMESPACE_DECL)
     {
       if (!module_may_redeclare (olddecl, newdecl))
 	return error_mark_node;
 
-      tree not_tmpl = STRIP_TEMPLATE (olddecl);
-      if (DECL_LANG_SPECIFIC (not_tmpl)
-	  && DECL_MODULE_ATTACH_P (not_tmpl)
-	  /* Typedefs are not entities and so are OK to be redeclared
-	     as exported: see [module.interface]/p6.  */
-	  && TREE_CODE (olddecl) != TYPE_DECL)
+      if (!hiding)
 	{
-	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
-	      && !DECL_MODULE_EXPORT_P (not_tmpl))
+	  /* The old declaration should match the exportingness of the new
+	     declaration.  But hidden friend declarations just keep the
+	     exportingness of the old declaration; see CWG2588.  */
+	  tree not_tmpl = STRIP_TEMPLATE (olddecl);
+	  if (DECL_LANG_SPECIFIC (not_tmpl)
+	      && DECL_MODULE_ATTACH_P (not_tmpl)
+	      /* Typedefs are not entities and so are OK to be redeclared
+		 as exported: see [module.interface]/p6.  */
+	      && TREE_CODE (olddecl) != TYPE_DECL)
 	    {
-	      auto_diagnostic_group d;
-	      error ("conflicting exporting for declaration %qD", newdecl);
-	      inform (olddecl_loc,
-		      "previously declared here without exporting");
+	      if (DECL_MODULE_EXPORT_P (newdecl)
+		  && !DECL_MODULE_EXPORT_P (not_tmpl))
+		{
+		  auto_diagnostic_group d;
+		  error ("conflicting exporting for declaration %qD", newdecl);
+		  inform (olddecl_loc,
+			  "previously declared here without exporting");
+		}
 	    }
+	  else if (DECL_MODULE_EXPORT_P (newdecl))
+	    DECL_MODULE_EXPORT_P (not_tmpl) = true;
 	}
-      else if (DECL_MODULE_EXPORT_P (newdecl))
-	DECL_MODULE_EXPORT_P (not_tmpl) = true;
     }
 
   /* We have committed to returning OLDDECL at this point.  */
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 0e2e60f85ae..c5141e5245b 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2727,6 +2727,12 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
 typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
 static keyed_map_t *keyed_table;
 
+/* Instantiations of temploid friends imported from another module
+   need to be attached to the same module as the temploid.  This maps
+   these decls to the temploid they are instantiated them, as there is
+   no other easy way to get this information.  */
+static hash_map<tree, tree> *imported_temploid_friends;
+
 /********************************************************************/
 /* Tree streaming.   The tree streaming is very specific to the tree
    structures themselves.  A tag indicates the kind of tree being
@@ -7820,6 +7826,12 @@ trees_out::decl_value (tree decl, depset *dep)
 		  && DECL_MODULE_ATTACH_P (not_tmpl))
 		is_attached = true;
 
+	      /* But don't consider imported temploid friends as attached,
+		 since importers will need to merge this decl even if it was
+		 attached to a different module.  */
+	      if (imported_temploid_friends->get (decl))
+		is_attached = false;
+
 	      bits.b (is_attached);
 	    }
 	  bits.b (dep && dep->has_defn ());
@@ -7997,6 +8009,15 @@ trees_out::decl_value (tree decl, depset *dep)
 	}
     }
 
+  if (TREE_CODE (inner) == FUNCTION_DECL
+      || TREE_CODE (inner) == TYPE_DECL)
+    {
+      /* Write imported temploid friends so that importers can reconstruct
+	 this information on stream-in.  */
+      tree* slot = imported_temploid_friends->get (decl);
+      tree_node (slot ? *slot : NULL_TREE);
+    }
+
   bool is_typedef = false;
   if (!type && TREE_CODE (inner) == TYPE_DECL)
     {
@@ -8303,6 +8324,11 @@ trees_in::decl_value ()
 	}
     }
 
+  if (TREE_CODE (inner) == FUNCTION_DECL
+      || TREE_CODE (inner) == TYPE_DECL)
+    if (tree owner = tree_node ())
+      imported_temploid_friends->put (decl, owner);
+
   /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
   unsigned tdef_flags = 0;
   bool is_typedef = false;
@@ -18939,6 +18965,12 @@ get_originating_module_decl (tree decl)
 	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
 	decl = TYPE_NAME (DECL_CHAIN (decl));
 
+      /* An imported temploid friend is attached to the same module the
+	 befriending class was.  */
+      if (imported_temploid_friends)
+	if (tree *slot = imported_temploid_friends->get (decl))
+	  decl = *slot;
+
       int use;
       if (tree ti = node_template_info (decl, use))
 	{
@@ -19247,6 +19279,34 @@ maybe_key_decl (tree ctx, tree decl)
   vec.safe_push (decl);
 }
 
+/* DECL is an instantiated friend that should be attached to the same
+   module that ORIG is.  */
+
+void
+propagate_defining_module (tree decl, tree orig)
+{
+  if (!modules_p ())
+    return;
+
+  tree not_tmpl = STRIP_TEMPLATE (orig);
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
+    {
+      tree inner = STRIP_TEMPLATE (decl);
+      retrofit_lang_decl (inner);
+      DECL_MODULE_ATTACH_P (inner) = true;
+    }
+
+  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
+    {
+      bool exists = imported_temploid_friends->put (decl, orig);
+
+      /* We should only be called if lookup for an existing decl
+	 failed, in which case there shouldn't already be an entry
+	 in the map.  */
+      gcc_assert (!exists);
+    }
+}
+
 /* Create the flat name string.  It is simplest to have it handy.  */
 
 void
@@ -20460,6 +20520,8 @@ init_modules (cpp_reader *reader)
       pending_table = new pending_map_t (EXPERIMENT (1, 400));
       entity_map = new entity_map_t (EXPERIMENT (1, 400));
       vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
+      imported_temploid_friends
+	= new hash_map<tree,tree> (EXPERIMENT (1, 400));
     }
 
 #if CHECKING_P
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 7af7f00e34c..4dffc0e9acc 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -4131,6 +4131,22 @@ mergeable_namespace_slots (tree ns, tree name, bool is_attached, tree *vec)
   return vslot;
 }
 
+/* Retrieve the bindings for an existing mergeable entity in namespace
+   NS slot NAME.  Returns NULL if no such bindings exists.  */
+
+static tree
+get_mergeable_namespace_binding (tree ns, tree name, bool is_attached)
+{
+  tree *mslot = find_namespace_slot (ns, name, false);
+  if (!mslot || !*mslot || TREE_CODE (*mslot) != BINDING_VECTOR)
+    return NULL_TREE;
+
+  tree *vslot = get_fixed_binding_slot
+    (mslot, name, is_attached ? BINDING_SLOT_PARTITION : BINDING_SLOT_GLOBAL,
+     false);
+  return vslot ? *vslot : NULL_TREE;
+}
+
 /* DECL is a new mergeable namespace-scope decl.  Add it to the
    mergeable entities on GSLOT.  */
 
@@ -4453,6 +4469,43 @@ push_local_binding (tree id, tree decl, bool is_using)
   add_decl_to_level (b, decl);
 }
 
+/* Lookup the FRIEND_TMPL within all merged module imports.  Used to dedup
+   instantiations of temploid hidden friends from imported modules.  */
+
+tree
+lookup_imported_hidden_friend (tree friend_tmpl)
+{
+  /* For a class-scope friend class it should have been found by regular
+     name lookup.  Otherwise we're looking in the current namespace.  */
+  gcc_checking_assert (CP_DECL_CONTEXT (friend_tmpl) == current_namespace);
+
+  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
+  if (!DECL_LANG_SPECIFIC (inner)
+      || !DECL_MODULE_IMPORT_P (inner))
+    return NULL_TREE;
+
+  /* Imported temploid friends are not considered as attached to this
+     module for merging purposes.  */
+  tree bind = get_mergeable_namespace_binding (current_namespace,
+					       DECL_NAME (inner), false);
+  if (!bind)
+    return NULL_TREE;
+
+  /* We're only interested in declarations coming from the same module
+     of the friend class we're attempting to instantiate.  */
+  int m = get_originating_module (friend_tmpl);
+  gcc_assert (m != 0);
+
+  /* There should be at most one class template from the module we're
+     looking for, return it.  */
+  for (ovl_iterator iter (bind); iter; ++iter)
+    if (DECL_CLASS_TEMPLATE_P (*iter)
+	&& get_originating_module (*iter) == m)
+      return *iter;
+
+  return NULL_TREE;
+}
+
 \f
 /* true means unconditionally make a BLOCK for the next level pushed.  */
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 3b2106dd3f6..1c3eef60c06 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -11512,6 +11512,10 @@ tsubst_friend_function (tree decl, tree args)
 	  new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
 	}
 
+      /* We need to propagate module attachment for the new friend from the
+	 owner of this template.  */
+      propagate_defining_module (new_friend, decl);
+
       /* Inside pushdecl_namespace_level, we will push into the
 	 current namespace. However, the friend function should go
 	 into the namespace of the template.  */
@@ -11715,6 +11719,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
   tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
 		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
 
+  if (!tmpl)
+    /* If we didn't find by name lookup, the type may still exist but as a
+       'hidden' import; we should check for this too to avoid accidentally
+       instantiating a duplicate.  */
+    tmpl = lookup_imported_hidden_friend (friend_tmpl);
+
   if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
     {
       /* The friend template has already been declared.  Just
@@ -11723,6 +11733,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 	 of course.  We only need the innermost template parameters
 	 because that is all that redeclare_class_template will look
 	 at.  */
+
+      if (modules_p ())
+	/* Check that the existing declaration's module attachment is
+	   compatible with the attachment of the friend template.  */
+	module_may_redeclare (tmpl, friend_tmpl);
+
       if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
 	  > TMPL_ARGS_DEPTH (args))
 	{
@@ -11751,9 +11767,16 @@ tsubst_friend_class (tree friend_tmpl, tree args)
       if (tmpl != error_mark_node)
 	{
 	  /* The new TMPL is not an instantiation of anything, so we
-	     forget its origins.  We don't reset CLASSTYPE_TI_TEMPLATE
+	     forget its origins.  It is also not a specialization of
+	     anything.  We don't reset CLASSTYPE_TI_TEMPLATE
 	     for the new type because that is supposed to be the
 	     corresponding template decl, i.e., TMPL.  */
+	  spec_entry elt;
+	  elt.tmpl = friend_tmpl;
+	  elt.args = CLASSTYPE_TI_ARGS (TREE_TYPE (tmpl));
+	  elt.spec = TREE_TYPE (tmpl);
+	  type_specializations->remove_elt (&elt);
+
 	  DECL_USE_TEMPLATE (tmpl) = 0;
 	  DECL_TEMPLATE_INFO (tmpl) = NULL_TREE;
 	  CLASSTYPE_USE_TEMPLATE (TREE_TYPE (tmpl)) = 0;
@@ -11772,6 +11795,10 @@ tsubst_friend_class (tree friend_tmpl, tree args)
 						     args, tf_warning_or_error);
 	    }
 
+	  /* We need to propagate the attachment of the original template to the
+	     newly instantiated template type.  */
+	  propagate_defining_module (tmpl, friend_tmpl);
+
 	  /* Inject this template into the enclosing namspace scope.  */
 	  tmpl = pushdecl_namespace_level (tmpl, /*hiding=*/true);
 	}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
new file mode 100644
index 00000000000..7547326e554
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
@@ -0,0 +1,15 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi test_support }
+
+module;
+template<class> struct _Sp_atomic;
+template<class> struct shared_ptr {
+  template<class> friend struct _Sp_atomic;
+  using atomic_type = _Sp_atomic<int>;
+};
+export module test_support;
+export
+template<class T> struct A {
+   shared_ptr<T> data;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
new file mode 100644
index 00000000000..6b88ee4258b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
@@ -0,0 +1,5 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+
+import test_support;
+A<int> a;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
new file mode 100644
index 00000000000..90bcd18a45e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
@@ -0,0 +1,7 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi user:part }
+
+export module user:part;
+import test_support; 
+export A<int> b;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-10_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-10_d.C
new file mode 100644
index 00000000000..861d19c9eaa
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-10_d.C
@@ -0,0 +1,8 @@
+// PR c++/105320
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi user }
+
+export module user;
+export import :part;
+import test_support; 
+A<double> c;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
new file mode 100644
index 00000000000..f29eebd1a7f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
@@ -0,0 +1,14 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts -Wno-global-module" }
+// { dg-module-cmi M }
+
+module;
+
+template <typename... _Elements> struct T;
+
+template <typename H> struct T<H> {
+  template <typename...> friend struct T;
+};
+
+export module M;
+export template <typename=void> void fun() { T<int> t; }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
new file mode 100644
index 00000000000..5bf79998139
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
@@ -0,0 +1,5 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+int main() { fun(); }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
new file mode 100644
index 00000000000..216dbf62c71
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:A }
+
+module M:A;
+
+template <typename T> struct A {
+  template <typename U> friend struct B;
+private:
+  int x = 42;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
new file mode 100644
index 00000000000..26e1c38b518
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:B }
+
+export module M:B;
+import :A;
+
+export template <typename U> struct B {
+  int foo(A<U> a) { return a.x; }
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
new file mode 100644
index 00000000000..e44c2819cfd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:C }
+
+export module M:C;
+import :A;
+
+template <typename T> struct B;
+export template <typename T, typename U> int bar(B<T> t, U u) {
+  return t.foo(u);
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
new file mode 100644
index 00000000000..9a575ad5046
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export import :B;
+export import :C;
+
+export int go_in_module();
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
new file mode 100644
index 00000000000..329d1e8b263
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+
+module M;
+
+int go_in_module() {
+  return bar(B<int>{}, A<int>{});
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
new file mode 100644
index 00000000000..c9855663fbd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+int main() {
+  B<double> b{};
+  go_in_module();
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
new file mode 100644
index 00000000000..8c972776d60
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
@@ -0,0 +1,13 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+export template <typename> struct A {
+  friend struct S;
+  template <typename> friend struct T;
+};
+
+export template <typename> struct B {
+  friend void f();
+  template <typename> friend void g();
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
new file mode 100644
index 00000000000..0e27e97b113
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+A<int> a;
+struct S {};  // { dg-error "conflicts with import" }
+template <typename> struct T {};  // { dg-error "conflicts with import" }
+
+B<int> c;
+void f() {}  // { dg-error "conflicts with import" }
+template <typename> void g() {}  // { dg-error "conflicts with import" }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
new file mode 100644
index 00000000000..3464aa26bf8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
@@ -0,0 +1,13 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+struct S {};  // { dg-error "conflicts with import" }
+template <typename> struct T {};  // { dg-message "previously declared" }
+A<int> a;  // { dg-message "required from here" }
+
+void f() {}  // { dg-message "previously declared" }
+template <typename> void g() {}  // { dg-message "previously declared" }
+B<int> b;  // { dg-message "required from here" }
+
+// { dg-error "conflicting declaration" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
new file mode 100644
index 00000000000..5b935474ab2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi X }
+
+export module X;
+export import M;
+A<int> ax;
+B<int> bx;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
new file mode 100644
index 00000000000..afbd0a39c23
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
@@ -0,0 +1,18 @@
+// { dg-additional-options "-fmodules-ts" }
+
+// 'import X' does not correctly notice that S has already been declared.
+struct S {};  // { dg-message "previously declared" "" { xfail *-*-* } }
+template <typename> struct T {};  // { dg-message "previously declared" }
+void f() {}  // { dg-message "previously declared" }
+template <typename T> void g() {}  // { dg-message "previously declared" }
+
+import X;
+A<double> a2;  // { dg-message "required from here" }
+B<double> b2;  // { dg-message "required from here" }
+
+// specifically, S and T are defined in M, not X, despite the instantiation being in X
+// { dg-error "conflicting declaration \[^\n\r\]* S@M" "" { xfail *-*-* } 0 }
+// { dg-error "conflicting declaration \[^\n\r\]* T@M" "" { target *-*-* } 0 }
+// and similarly for f and g
+// { dg-error "conflicting declaration \[^\n\r\]* f@M" "" { target *-*-* } 0 }
+// { dg-error "conflicting declaration \[^\n\r\]* g@M" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C
new file mode 100644
index 00000000000..287f95c7bdc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi Y }
+
+export module Y;
+export import M;
+A<double> ay;
+B<double> by;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C b/gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C
new file mode 100644
index 00000000000..b7da60f2322
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import X;
+import Y;
+
+// This should happily refer to the same S and T
+// as already instantiated in both X and Y
+A<long> az;
+
+// And same for f and g
+B<long> bz;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C
new file mode 100644
index 00000000000..6912512ecf7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+export extern "C++" template <typename> struct A {
+  template <typename> friend struct B;
+};
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C
new file mode 100644
index 00000000000..5f8aa7fd62e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C
@@ -0,0 +1,8 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi X }
+
+export module X;
+export import M;
+
+A<int> x;
+export extern "C++" template <typename T> struct B { using type = T; };
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C
new file mode 100644
index 00000000000..8d89298878a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi Y }
+
+export module Y;
+export import M;
+
+A<double> x;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C b/gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C
new file mode 100644
index 00000000000..7a842586b62
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C
@@ -0,0 +1,9 @@
+// { dg-additional-options "-fmodules-ts" }
+
+import X;
+import Y;
+
+int main() {
+  A<long> a;
+  B<int>::type r = 10;
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-9.C b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
new file mode 100644
index 00000000000..c7216f0f8c1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-9.C
@@ -0,0 +1,13 @@
+// PR c++/114275
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+template<class> struct A {
+  template<class> friend struct B;
+  friend void C();
+};
+A<int> a;
+void C() {}
+template<class> struct B { };
-- 
2.43.2


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v4 2/2] c++: Fix instantiation of imported temploid friends [PR114275]
  2024-04-29  9:34           ` [PATCH v4 " Nathaniel Shead
@ 2024-04-29 22:37             ` Jason Merrill
  0 siblings, 0 replies; 12+ messages in thread
From: Jason Merrill @ 2024-04-29 22:37 UTC (permalink / raw)
  To: Nathaniel Shead; +Cc: Patrick Palka, gcc-patches, Nathan Sidwell

On 4/29/24 02:34, Nathaniel Shead wrote:
> On Fri, Apr 26, 2024 at 09:16:40PM -0400, Jason Merrill wrote:
>> On 4/19/24 09:29, Nathaniel Shead wrote:
>>> On Fri, Apr 19, 2024 at 12:14:06PM +1000, Nathaniel Shead wrote:
>>>> On Wed, Apr 17, 2024 at 02:02:21PM -0400, Patrick Palka wrote:
>>>>> On Mon, 15 Apr 2024, Nathaniel Shead wrote:
>>>>>
>>>>>> I'm not a huge fan of always streaming 'imported_temploid_friends' for
>>>>>> all decls, but I don't think it adds much performance cost over adding a
>>>>>> new flag to categorise decls that might be marked as such.
>>>>>
>>>>> IIUC this value is going to be almost always null which is encoded as a
>>>>> single 0 byte, which should be fast to stream.  But I wonder how much
>>>>> larger <bits/stdc++.h> gets?  Can we get away with streaming this value
>>>>> only for TEMPLATE_DECLs?
>>>>
>>>> Yes, it should either just be a 0 byte or an additional backref
>>>> somewhere, which will likely also be small. On my system it increases
>>>> the size by 0.26%, from 31186800 bytes to 31268672.
>>>>
>>>> But I've just found that this patch has a bug anyway, in that it doesn't
>>>> correctly dedup if the friend types are instantiated in two separate
>>>> modules that are then both imported.  I'll see what I need to do to fix
>>>> this which may influence what we need to stream here.
>>>>
>>>
>>> Here's an updated version of the patch that fixes this. Also changed to
>>> only stream when 'inner' is either TYPE_DECL or FUNCTION_DECL, which
>>> cuts the size of <bits/stdc++.h> down a bit to 31246992 (0.19% growth).
>>>
>>> Another alternative would be to add another boolean flag at the top of
>>> 'decl_value' and branch on that; that would make use of the bitpacking
>>> logic and probably cut down on the size further.  (I haven't measured
>>> this yet though.)
>>>
>>> Bootstrapped and regtested (so far just dg.exp and modules.exp) on
>>> x86_64-pc-linux-gnu, OK for trunk if full regtest succeeds?
>>>
>>> -- >8 --
>>>
>>> This patch fixes a number of issues with the handling of temploid friend
>>> declarations.
>>>
>>> The primary issue is that instantiations of friend declarations should
>>> attach the declaration to the same module as the befriending class, by
>>> [module.unit] p7.1 and [temp.friend] p2; this could be a different
>>> module from the current TU, and so needs special handling.
>>
>> This is only an issue for DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P, right?
> 
> It's true for any friend instantiations, including e.g. friend
> functions, not just template class friends.
> 
>>
>> Hmm, CWG2588 should probably touch [module.unit]/7.1 as well as
>> [basic.link].
>>
>>> The other main issue here is that we can't assume that just because name
>>> lookup didn't find a definition for a hidden template class, it doesn't
>>> mean that it doesn't exist: it could be a non-exported entity that we've
>>> nevertheless streamed in from an imported module.  We need to ensure
>>> that when instantiating friend classes that we return the same TYPE_DECL
>>> that we got from our imports, otherwise we will get later issues with
>>> 'duplicate_decls' (rightfully) complaining that they're different.
>>
>> Tricksy.
>>
>>> This doesn't appear necessary for functions due to the existing name
>>> lookup handling already finding these hidden declarations.
>>>
>>> 	PR c++/105320
>>> 	PR c++/114275
>>>
>>> gcc/cp/ChangeLog:
>>>
>>> 	* cp-tree.h (propagate_defining_module): Declare.
>>> 	(lookup_imported_hidden_friend): Declare.
>>> 	* decl.cc (duplicate_decls): Also check if hidden declarations
>>> 	can be redeclared in this module.
>>> 	* module.cc (imported_temploid_friends): New map.
>>> 	(init_modules): Initialize it.
>>> 	(trees_out::decl_value): Write it; don't consider imported
>>> 	temploid friends as attached to this module.
>>> 	(trees_in::decl_value): Read it.
>>> 	(depset::hash::add_specializations): Don't treat instantiations
>>> 	of a friend type as a specialisation.
>>> 	(get_originating_module_decl): Follow the owning decl for an
>>> 	imported temploid friend.
>>> 	(propagate_defining_module): New function.
>>> 	* name-lookup.cc (lookup_imported_hidden_friend): New function.
>>> 	* pt.cc (tsubst_friend_function): Propagate defining module for
>>> 	new friend functions.
>>> 	(tsubst_friend_class): Lookup imported hidden friends. Check
>>> 	for valid redeclaration. Propagate defining module for new
>>> 	friend classes.
>>>
>>> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
>>> index aa66da4829d..56752cf6872 100644
>>> --- a/gcc/cp/decl.cc
>>> +++ b/gcc/cp/decl.cc
>>> @@ -2276,30 +2276,34 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
>>>      if (modules_p ()
>>>          && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
>>> -      && TREE_CODE (olddecl) != NAMESPACE_DECL
>>> -      && !hiding)
>>> +      && TREE_CODE (olddecl) != NAMESPACE_DECL)
>>>        {
>>>          if (!module_may_redeclare (olddecl, newdecl))
>>>    	return error_mark_node;
>>> -      tree not_tmpl = STRIP_TEMPLATE (olddecl);
>>> -      if (DECL_LANG_SPECIFIC (not_tmpl)
>>> -	  && DECL_MODULE_ATTACH_P (not_tmpl)
>>> -	  /* Typedefs are not entities and so are OK to be redeclared
>>> -	     as exported: see [module.interface]/p6.  */
>>> -	  && TREE_CODE (olddecl) != TYPE_DECL)
>>> +      if (!hiding)
>>>    	{
>>> -	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
>>> -	      && !DECL_MODULE_EXPORT_P (not_tmpl))
>>> +	  /* Hidden friend declarations just use exportingness of the
>>> +	     old declaration; see CWG2588.  */
>>
>> I'm not sure what this comment is trying to say, wouldn't hiding be true for
>> a hidden friend?
>>
> 
> Right, it wasn't clear; the comment was meant to be explaining why we
> don't do the following hunk for hidden friends, because for a hidden
> friend we only care about the exportingness of OLDDECL (and NEWDECL
> doesn't need to change).  I've updated the comment to be clearer.
> 
> I took a closer look at the code as well and it doesn't seem to exactly
> match the rules given by [module.interface] anyway, but I think that
> should be fixed as a separate patch.  (In particular, this allows
> redeclaring any GM entity as exported even if the initial declaration
> was not, whereas [module.interface] p2 only allows this for names
> declared within header units.)
> 
>>> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
>>> index 36e035544f4..bd3ab686543 100644
>>> --- a/gcc/cp/module.cc
>>> +++ b/gcc/cp/module.cc
>>> @@ -2727,6 +2727,11 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
>>>    typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
>>>    static keyed_map_t *keyed_table;
>>> +/* Instantiations of temploid friends imported from another module
>>> +   need to be owned by the same module as their instantiating template.
>>> +   This maps these to the template that instantiated them.  */
>>> +static hash_map<tree, tree> *imported_temploid_friends;
>>
>> So IIUC the way to express the attachment to an imported module is by
>> association with an actually imported decl.
>>
>> And the problem is that there's no current way to get from the result of
>> tsubst_friend_* to the original temploid friend; as tsubst_class_template
>> says, "The new TMPL is not an instantiation of anything, so we forget its
>> origins."  And this map provides that lookup?
>>
>> Rather than "their instantiating template" and "the template that
>> instantiated them" I'd just say "the temploid", maybe "the temploid they are
>> instantiated from".
>>
>> I'd also change "owned" to "attached".
>>
> 
> Yup, right.  I'll change the comment.
> 
>>> @@ -13370,10 +13393,17 @@ depset::hash::add_specializations (bool decl_p)
>>>          int use_tpl = 0;
>>>          bool is_friend = false;
>>> -      if (decl_p && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
>>> -	/* A friend of a template.  This is keyed to the
>>> -	   instantiation.  */
>>> -	is_friend = true;
>>> +      if (DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (entry->tmpl))
>>> +	{
>>> +	  if (decl_p)
>>> +	    /* A friend of a template.  This is keyed to the
>>> +	       instantiation.  */
>>> +	    is_friend = true;
>>> +	  else
>>> +	    /* An instantiated friend struct.  Don't count this as
>>> +	       a specialization, it'll be picked up later.  */
>>
>> You mean, in propagate_defining_module?
>>
>> Would it make sense to call propagate_defining_module here, rather than in
>> tsubst_friend_class?
>>
> 
> No sorry, in 'add_namespace_entities'.  The issue here is that even
> though this isn't counted as a specialisation anymore, it's still kept
> in 'type_specializations' as-if it was a dependent specialization of
> the instantiating type.  As such, e.g. for
> 
>    template <typename T>
>    struct A {
>      template <typename> friend struct B;
>    };
> 
> Instantiating A<int> in one module and A<double> in a different module
> will emit two different 'specialization' merge keys with two sets of
> template parameters, the outermost set being 'int' for the former and
> 'double' for the latter, preventing deduplication.
> 
> An alternative here I've tried would be to instead also remove the new
> entry from 'type_specializations' in 'tsubst_friend_class' (given that
> it shouldn't be used anywhere else either); this might be the neater
> approach so I've done it below, but happy either way.
> 
> Either way we need to do 'propagate_defining_module' earlier, because
> this function only happens after parsing is complete but we need to
> ensure that 'get_originating_module_decl' gives the correct values
> before then.
> 
>>> @@ -18930,6 +18960,12 @@ get_originating_module_decl (tree decl)
>>>    	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
>>>    	decl = TYPE_NAME (DECL_CHAIN (decl));
>>> +      /* An imported temploid friend is attached to the same module the
>>> +	 befriending class was.  */
>>> +      if (imported_temploid_friends)
>>> +	if (tree *slot = imported_temploid_friends->get (decl))
>>> +	  decl = *slot;
>>> +
>>>          int use;
>>>          if (tree ti = node_template_info (decl, use))
>>>    	{
>>
>> I note that just below this is
>>
>>>            decl = TI_TEMPLATE (ti);
>>>            if (TREE_CODE (decl) != TEMPLATE_DECL)
>>>              {
>>>                /* A friend template specialization.  */
>>>                gcc_checking_assert (OVL_P (decl));
>>>                return global_namespace;
>>>              }
>>
>> which seems to want to say "global module" for a friend template
>> specialization, rather than the correct module.  Is this code obsolete with
>> this patch?
>>
> 
> It doesn't look like it; removing this regresses e.g. tpl-friend-6_a.C,
> and seems to be handling friend specializations of existing
> declarations, whereas the imported_temploid_friends logic is for
> handling names first declared as friends.
> 
>>> diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
>>> index 7af7f00e34c..dd6e7b6eaea 100644
>>> --- a/gcc/cp/name-lookup.cc
>>> +++ b/gcc/cp/name-lookup.cc
>>> @@ -4453,6 +4453,48 @@ push_local_binding (tree id, tree decl, bool is_using)
>>>      add_decl_to_level (b, decl);
>>>    }
>>> +/* Lookup the FRIEND_TMPL within all module imports.  Used to dedup
>>> +   instantiations of temploid hidden friends from imported modules.  */
>>> +
>>> +tree
>>> +lookup_imported_hidden_friend (tree friend_tmpl)
>>> +{
>>> +  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
>>> +  if (!DECL_LANG_SPECIFIC (inner)
>>> +      || !DECL_MODULE_IMPORT_P (inner))
>>> +    return NULL_TREE;
>>> +
>>> +  tree name = DECL_NAME (inner);
>>> +  tree *slot = find_namespace_slot (current_namespace, name);
>>
>> Maybe checking_assert CP_DECL_CONTEXT (friend_tmpl) == current_namespace?
>>
> 
> Initially I hadn't because I thought that the context could possibly be
> a class type.  But if that was the case then I suppose that the original
> lookup_name must succeed, so this assert should hold.
> 
>>> +  if (!slot || !*slot || TREE_CODE (*slot) != BINDING_VECTOR)
>>> +    return NULL_TREE;
>>> +
>>> +  /* Look in the appropriate slot, as with check_module_override.  */
>>> +  binding_slot mslot;
>>> +  if (named_module_p ())
>>> +    mslot = BINDING_VECTOR_CLUSTER (*slot, BINDING_SLOT_PARTITION
>>> +				    / BINDING_VECTOR_SLOTS_PER_CLUSTER)
>>> +      .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
>>> +  else
>>> +    mslot = BINDING_VECTOR_CLUSTER (*slot, 0).slots[BINDING_SLOT_GLOBAL];
>>> +  gcc_assert (!mslot.is_lazy ());
>>> +
>>> +  tree ovl = mslot;
>>> +  if (!ovl)
>>> +    return NULL_TREE;
>>> +
>>> +  /* We're only interested in declarations coming from the same module
>>> +     of the friend class we're attempting to instantiate.  */
>>> +  int m = get_originating_module (friend_tmpl);
>>> +  gcc_assert (m != 0);
>>> +
>>> +  for (ovl_iterator iter (ovl); iter; ++iter)
>>> +    if (get_originating_module (*iter) == m)
>>> +      return *iter;
>>> +
>>> +  return NULL_TREE;
>>> +}
>>
>> Maybe factor much of this out into a get_namespace_binding_in_module
>> function?
> 
> I've factored and clarified the slot-finding parts into a new
> 'get_mergeable_namespace_binding' to correspond with
> 'add_mergeable_namespace_entity'.  Most of the rest I think relies on
> implementation details specific to handling friend classes, I've added
> some more comments too to expand on that.
> 
>>> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
>>> index 3b2106dd3f6..e7e7f2fbc3b 100644
>>> --- a/gcc/cp/pt.cc
>>> +++ b/gcc/cp/pt.cc
>>> @@ -11723,6 +11733,11 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>>>    	 of course.  We only need the innermost template parameters
>>>    	 because that is all that redeclare_class_template will look
>>>    	 at.  */
>>> +
>>> +      if (modules_p ())
>>> +	/* Check that we can redeclare TMPL in the current context.  */
>>> +	module_may_redeclare (tmpl, friend_tmpl);
>>
>> Isn't this redundant with the duplicate_decls change?
>>
> 
> No, because in this code path 'duplicate_decls' never actually gets
> called: we just return the existing declaration directly.  And this
> check is needed for tpl-friend-13_[ce].C.  Maybe we should be, but there
> isn't really a corresponding 'olddecl' to compare with that I can see,
> since friend_tmpl is just the template we're instantiating from rather
> than a duplicate.
> 
> I've updated the comment to hopefully make it a bit clearer what this
> check is actually doing.
> 
> Here's an updated version of the patch. Bootstrapped and regtested on
> x86_64-pc-linux-gnu, OK for trunk?
> 
> -- >8 --
> 
> This patch fixes a number of issues with the handling of temploid friend
> declarations.
> 
> The primary issue is that instantiations of friend declarations should
> attach the declaration to the same module as the befriending class, by
> [module.unit] p7.1 and [temp.friend] p2; this could be a different
> module from the current TU, and so needs special handling.
> 
> The other main issue here is that we can't assume that just because name
> lookup didn't find a definition for a hidden class template, that it
> doesn't exist at all: it could be a non-exported entity that we've
> nevertheless streamed in from an imported module.  We need to ensure
> that when instantiating template friend classes that we return the same
> TEMPLATE_DECL that we got from our imports, otherwise we will get later
> issues with 'duplicate_decls' (rightfully) complaining that they're
> different when trying to merge.
> 
> This doesn't appear necessary for function templates due to the existing
> name lookup handling already finding these hidden declarations.
> 
> 	PR c++/105320
> 	PR c++/114275
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (propagate_defining_module): Declare.
> 	(lookup_imported_hidden_friend): Declare.
> 	* decl.cc (duplicate_decls): Also check if hidden decls can be
> 	redeclared in this module.
> 	* module.cc (imported_temploid_friends): New.
> 	(init_modules): Initialize it.
> 	(trees_out::decl_value): Write it; don't consider imported
> 	temploid friends as attached to a module.
> 	(trees_in::decl_value): Read it.
> 	(get_originating_module_decl): Follow the owning decl for an
> 	imported temploid friend.
> 	(propagate_defining_module): New.
> 	* name-lookup.cc (get_mergeable_namespace_binding): New.
> 	(lookup_imported_hidden_friend): New.
> 	* pt.cc (tsubst_friend_function): Propagate defining module for
> 	new friend functions.
> 	(tsubst_friend_class): Lookup imported hidden friends.  Check
> 	for valid module attachment of existing names.  Propagate
> 	defining module for new classes.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/modules/tpl-friend-10_a.C: New test.
> 	* g++.dg/modules/tpl-friend-10_b.C: New test.
> 	* g++.dg/modules/tpl-friend-10_c.C: New test.
> 	* g++.dg/modules/tpl-friend-10_d.C: New test.
> 	* g++.dg/modules/tpl-friend-11_a.C: New test.
> 	* g++.dg/modules/tpl-friend-11_b.C: New test.
> 	* g++.dg/modules/tpl-friend-12_a.C: New test.
> 	* g++.dg/modules/tpl-friend-12_b.C: New test.
> 	* g++.dg/modules/tpl-friend-12_c.C: New test.
> 	* g++.dg/modules/tpl-friend-12_d.C: New test.
> 	* g++.dg/modules/tpl-friend-12_e.C: New test.
> 	* g++.dg/modules/tpl-friend-12_f.C: New test.
> 	* g++.dg/modules/tpl-friend-13_a.C: New test.
> 	* g++.dg/modules/tpl-friend-13_b.C: New test.
> 	* g++.dg/modules/tpl-friend-13_c.C: New test.
> 	* g++.dg/modules/tpl-friend-13_d.C: New test.
> 	* g++.dg/modules/tpl-friend-13_e.C: New test.
> 	* g++.dg/modules/tpl-friend-13_f.C: New test.
> 	* g++.dg/modules/tpl-friend-13_g.C: New test.
> 	* g++.dg/modules/tpl-friend-14_a.C: New test.
> 	* g++.dg/modules/tpl-friend-14_b.C: New test.
> 	* g++.dg/modules/tpl-friend-14_c.C: New test.
> 	* g++.dg/modules/tpl-friend-14_d.C: New test.
> 	* g++.dg/modules/tpl-friend-9.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>   gcc/cp/cp-tree.h                              |  2 +
>   gcc/cp/decl.cc                                | 37 ++++++-----
>   gcc/cp/module.cc                              | 62 +++++++++++++++++++
>   gcc/cp/name-lookup.cc                         | 53 ++++++++++++++++
>   gcc/cp/pt.cc                                  | 29 ++++++++-
>   .../g++.dg/modules/tpl-friend-10_a.C          | 15 +++++
>   .../g++.dg/modules/tpl-friend-10_b.C          |  5 ++
>   .../g++.dg/modules/tpl-friend-10_c.C          |  7 +++
>   .../g++.dg/modules/tpl-friend-10_d.C          |  8 +++
>   .../g++.dg/modules/tpl-friend-11_a.C          | 14 +++++
>   .../g++.dg/modules/tpl-friend-11_b.C          |  5 ++
>   .../g++.dg/modules/tpl-friend-12_a.C          | 10 +++
>   .../g++.dg/modules/tpl-friend-12_b.C          |  9 +++
>   .../g++.dg/modules/tpl-friend-12_c.C          | 10 +++
>   .../g++.dg/modules/tpl-friend-12_d.C          |  8 +++
>   .../g++.dg/modules/tpl-friend-12_e.C          |  7 +++
>   .../g++.dg/modules/tpl-friend-12_f.C          |  8 +++
>   .../g++.dg/modules/tpl-friend-13_a.C          | 13 ++++
>   .../g++.dg/modules/tpl-friend-13_b.C          | 11 ++++
>   .../g++.dg/modules/tpl-friend-13_c.C          | 13 ++++
>   .../g++.dg/modules/tpl-friend-13_d.C          |  7 +++
>   .../g++.dg/modules/tpl-friend-13_e.C          | 18 ++++++
>   .../g++.dg/modules/tpl-friend-13_f.C          |  7 +++
>   .../g++.dg/modules/tpl-friend-13_g.C          | 11 ++++
>   .../g++.dg/modules/tpl-friend-14_a.C          |  8 +++
>   .../g++.dg/modules/tpl-friend-14_b.C          |  8 +++
>   .../g++.dg/modules/tpl-friend-14_c.C          |  7 +++
>   .../g++.dg/modules/tpl-friend-14_d.C          |  9 +++
>   gcc/testsuite/g++.dg/modules/tpl-friend-9.C   | 13 ++++
>   29 files changed, 397 insertions(+), 17 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_a.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_b.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_c.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-10_d.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_a.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-11_b.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_a.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_b.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_c.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_d.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_e.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-12_f.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_a.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_b.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_c.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_d.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_f.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-13_g.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_a.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_b.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_c.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-14_d.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-9.C
> 
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index faa7a0052a5..67cc7d7bcec 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7417,6 +7417,7 @@ extern unsigned get_importing_module (tree, bool = false) ATTRIBUTE_PURE;
>   extern void set_instantiating_module (tree);
>   extern void set_defining_module (tree);
>   extern void maybe_key_decl (tree ctx, tree decl);
> +extern void propagate_defining_module (tree decl, tree orig);
>   
>   extern void mangle_module (int m, bool include_partition);
>   extern void mangle_module_fini ();
> @@ -7649,6 +7650,7 @@ extern bool template_guide_p			(const_tree);
>   extern bool builtin_guide_p			(const_tree);
>   extern void store_explicit_specifier		(tree, tree);
>   extern tree lookup_explicit_specifier		(tree);
> +extern tree lookup_imported_hidden_friend	(tree);
>   extern void walk_specializations		(bool,
>   						 void (*)(bool, spec_entry *,
>   							  void *),
> diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
> index 91268ff631d..d88e0698652 100644
> --- a/gcc/cp/decl.cc
> +++ b/gcc/cp/decl.cc
> @@ -2276,30 +2276,35 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden)
>   
>     if (modules_p ()
>         && TREE_CODE (CP_DECL_CONTEXT (olddecl)) == NAMESPACE_DECL
> -      && TREE_CODE (olddecl) != NAMESPACE_DECL
> -      && !hiding)
> +      && TREE_CODE (olddecl) != NAMESPACE_DECL)
>       {
>         if (!module_may_redeclare (olddecl, newdecl))
>   	return error_mark_node;
>   
> -      tree not_tmpl = STRIP_TEMPLATE (olddecl);
> -      if (DECL_LANG_SPECIFIC (not_tmpl)
> -	  && DECL_MODULE_ATTACH_P (not_tmpl)
> -	  /* Typedefs are not entities and so are OK to be redeclared
> -	     as exported: see [module.interface]/p6.  */
> -	  && TREE_CODE (olddecl) != TYPE_DECL)
> +      if (!hiding)
>   	{
> -	  if (DECL_MODULE_EXPORT_P (STRIP_TEMPLATE (newdecl))
> -	      && !DECL_MODULE_EXPORT_P (not_tmpl))
> +	  /* The old declaration should match the exportingness of the new
> +	     declaration.  But hidden friend declarations just keep the
> +	     exportingness of the old declaration; see CWG2588.  */
> +	  tree not_tmpl = STRIP_TEMPLATE (olddecl);
> +	  if (DECL_LANG_SPECIFIC (not_tmpl)
> +	      && DECL_MODULE_ATTACH_P (not_tmpl)
> +	      /* Typedefs are not entities and so are OK to be redeclared
> +		 as exported: see [module.interface]/p6.  */
> +	      && TREE_CODE (olddecl) != TYPE_DECL)
>   	    {
> -	      auto_diagnostic_group d;
> -	      error ("conflicting exporting for declaration %qD", newdecl);
> -	      inform (olddecl_loc,
> -		      "previously declared here without exporting");
> +	      if (DECL_MODULE_EXPORT_P (newdecl)
> +		  && !DECL_MODULE_EXPORT_P (not_tmpl))
> +		{
> +		  auto_diagnostic_group d;
> +		  error ("conflicting exporting for declaration %qD", newdecl);
> +		  inform (olddecl_loc,
> +			  "previously declared here without exporting");
> +		}
>   	    }
> +	  else if (DECL_MODULE_EXPORT_P (newdecl))
> +	    DECL_MODULE_EXPORT_P (not_tmpl) = true;
>   	}
> -      else if (DECL_MODULE_EXPORT_P (newdecl))
> -	DECL_MODULE_EXPORT_P (not_tmpl) = true;
>       }
>   
>     /* We have committed to returning OLDDECL at this point.  */
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index 0e2e60f85ae..c5141e5245b 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -2727,6 +2727,12 @@ vec<tree, va_heap, vl_embed> *post_load_decls;
>   typedef hash_map<tree, auto_vec<tree>> keyed_map_t;
>   static keyed_map_t *keyed_table;
>   
> +/* Instantiations of temploid friends imported from another module
> +   need to be attached to the same module as the temploid.  This maps
> +   these decls to the temploid they are instantiated them, as there is
> +   no other easy way to get this information.  */
> +static hash_map<tree, tree> *imported_temploid_friends;
> +
>   /********************************************************************/
>   /* Tree streaming.   The tree streaming is very specific to the tree
>      structures themselves.  A tag indicates the kind of tree being
> @@ -7820,6 +7826,12 @@ trees_out::decl_value (tree decl, depset *dep)
>   		  && DECL_MODULE_ATTACH_P (not_tmpl))
>   		is_attached = true;
>   
> +	      /* But don't consider imported temploid friends as attached,
> +		 since importers will need to merge this decl even if it was
> +		 attached to a different module.  */
> +	      if (imported_temploid_friends->get (decl))
> +		is_attached = false;
> +
>   	      bits.b (is_attached);
>   	    }
>   	  bits.b (dep && dep->has_defn ());
> @@ -7997,6 +8009,15 @@ trees_out::decl_value (tree decl, depset *dep)
>   	}
>       }
>   
> +  if (TREE_CODE (inner) == FUNCTION_DECL
> +      || TREE_CODE (inner) == TYPE_DECL)
> +    {
> +      /* Write imported temploid friends so that importers can reconstruct
> +	 this information on stream-in.  */
> +      tree* slot = imported_temploid_friends->get (decl);
> +      tree_node (slot ? *slot : NULL_TREE);
> +    }
> +
>     bool is_typedef = false;
>     if (!type && TREE_CODE (inner) == TYPE_DECL)
>       {
> @@ -8303,6 +8324,11 @@ trees_in::decl_value ()
>   	}
>       }
>   
> +  if (TREE_CODE (inner) == FUNCTION_DECL
> +      || TREE_CODE (inner) == TYPE_DECL)
> +    if (tree owner = tree_node ())
> +      imported_temploid_friends->put (decl, owner);
> +
>     /* Regular typedefs will have a NULL TREE_TYPE at this point.  */
>     unsigned tdef_flags = 0;
>     bool is_typedef = false;
> @@ -18939,6 +18965,12 @@ get_originating_module_decl (tree decl)
>   	  && DECL_UNINSTANTIATED_TEMPLATE_FRIEND_P (decl))
>   	decl = TYPE_NAME (DECL_CHAIN (decl));
>   
> +      /* An imported temploid friend is attached to the same module the
> +	 befriending class was.  */
> +      if (imported_temploid_friends)
> +	if (tree *slot = imported_temploid_friends->get (decl))
> +	  decl = *slot;
> +
>         int use;
>         if (tree ti = node_template_info (decl, use))
>   	{
> @@ -19247,6 +19279,34 @@ maybe_key_decl (tree ctx, tree decl)
>     vec.safe_push (decl);
>   }
>   
> +/* DECL is an instantiated friend that should be attached to the same
> +   module that ORIG is.  */
> +
> +void
> +propagate_defining_module (tree decl, tree orig)
> +{
> +  if (!modules_p ())
> +    return;
> +
> +  tree not_tmpl = STRIP_TEMPLATE (orig);
> +  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_ATTACH_P (not_tmpl))
> +    {
> +      tree inner = STRIP_TEMPLATE (decl);
> +      retrofit_lang_decl (inner);
> +      DECL_MODULE_ATTACH_P (inner) = true;
> +    }
> +
> +  if (DECL_LANG_SPECIFIC (not_tmpl) && DECL_MODULE_IMPORT_P (not_tmpl))
> +    {
> +      bool exists = imported_temploid_friends->put (decl, orig);
> +
> +      /* We should only be called if lookup for an existing decl
> +	 failed, in which case there shouldn't already be an entry
> +	 in the map.  */
> +      gcc_assert (!exists);
> +    }
> +}
> +
>   /* Create the flat name string.  It is simplest to have it handy.  */
>   
>   void
> @@ -20460,6 +20520,8 @@ init_modules (cpp_reader *reader)
>         pending_table = new pending_map_t (EXPERIMENT (1, 400));
>         entity_map = new entity_map_t (EXPERIMENT (1, 400));
>         vec_safe_reserve (entity_ary, EXPERIMENT (1, 400));
> +      imported_temploid_friends
> +	= new hash_map<tree,tree> (EXPERIMENT (1, 400));
>       }
>   
>   #if CHECKING_P
> diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
> index 7af7f00e34c..4dffc0e9acc 100644
> --- a/gcc/cp/name-lookup.cc
> +++ b/gcc/cp/name-lookup.cc
> @@ -4131,6 +4131,22 @@ mergeable_namespace_slots (tree ns, tree name, bool is_attached, tree *vec)
>     return vslot;
>   }
>   
> +/* Retrieve the bindings for an existing mergeable entity in namespace
> +   NS slot NAME.  Returns NULL if no such bindings exists.  */
> +
> +static tree
> +get_mergeable_namespace_binding (tree ns, tree name, bool is_attached)
> +{
> +  tree *mslot = find_namespace_slot (ns, name, false);
> +  if (!mslot || !*mslot || TREE_CODE (*mslot) != BINDING_VECTOR)
> +    return NULL_TREE;
> +
> +  tree *vslot = get_fixed_binding_slot
> +    (mslot, name, is_attached ? BINDING_SLOT_PARTITION : BINDING_SLOT_GLOBAL,
> +     false);
> +  return vslot ? *vslot : NULL_TREE;
> +}
> +
>   /* DECL is a new mergeable namespace-scope decl.  Add it to the
>      mergeable entities on GSLOT.  */
>   
> @@ -4453,6 +4469,43 @@ push_local_binding (tree id, tree decl, bool is_using)
>     add_decl_to_level (b, decl);
>   }
>   
> +/* Lookup the FRIEND_TMPL within all merged module imports.  Used to dedup
> +   instantiations of temploid hidden friends from imported modules.  */
> +
> +tree
> +lookup_imported_hidden_friend (tree friend_tmpl)
> +{
> +  /* For a class-scope friend class it should have been found by regular
> +     name lookup.  Otherwise we're looking in the current namespace.  */
> +  gcc_checking_assert (CP_DECL_CONTEXT (friend_tmpl) == current_namespace);
> +
> +  tree inner = DECL_TEMPLATE_RESULT (friend_tmpl);
> +  if (!DECL_LANG_SPECIFIC (inner)
> +      || !DECL_MODULE_IMPORT_P (inner))
> +    return NULL_TREE;
> +
> +  /* Imported temploid friends are not considered as attached to this
> +     module for merging purposes.  */
> +  tree bind = get_mergeable_namespace_binding (current_namespace,
> +					       DECL_NAME (inner), false);
> +  if (!bind)
> +    return NULL_TREE;
> +
> +  /* We're only interested in declarations coming from the same module
> +     of the friend class we're attempting to instantiate.  */
> +  int m = get_originating_module (friend_tmpl);
> +  gcc_assert (m != 0);
> +
> +  /* There should be at most one class template from the module we're
> +     looking for, return it.  */
> +  for (ovl_iterator iter (bind); iter; ++iter)
> +    if (DECL_CLASS_TEMPLATE_P (*iter)
> +	&& get_originating_module (*iter) == m)
> +      return *iter;
> +
> +  return NULL_TREE;
> +}
> +
>   \f
>   /* true means unconditionally make a BLOCK for the next level pushed.  */
>   
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 3b2106dd3f6..1c3eef60c06 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -11512,6 +11512,10 @@ tsubst_friend_function (tree decl, tree args)
>   	  new_friend_result_template_info = DECL_TEMPLATE_INFO (not_tmpl);
>   	}
>   
> +      /* We need to propagate module attachment for the new friend from the
> +	 owner of this template.  */
> +      propagate_defining_module (new_friend, decl);
> +
>         /* Inside pushdecl_namespace_level, we will push into the
>   	 current namespace. However, the friend function should go
>   	 into the namespace of the template.  */
> @@ -11715,6 +11719,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>     tmpl = lookup_name (DECL_NAME (friend_tmpl), LOOK_where::CLASS_NAMESPACE,
>   		      LOOK_want::NORMAL | LOOK_want::HIDDEN_FRIEND);
>   
> +  if (!tmpl)
> +    /* If we didn't find by name lookup, the type may still exist but as a
> +       'hidden' import; we should check for this too to avoid accidentally
> +       instantiating a duplicate.  */
> +    tmpl = lookup_imported_hidden_friend (friend_tmpl);
> +
>     if (tmpl && DECL_CLASS_TEMPLATE_P (tmpl))
>       {
>         /* The friend template has already been declared.  Just
> @@ -11723,6 +11733,12 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>   	 of course.  We only need the innermost template parameters
>   	 because that is all that redeclare_class_template will look
>   	 at.  */
> +
> +      if (modules_p ())
> +	/* Check that the existing declaration's module attachment is
> +	   compatible with the attachment of the friend template.  */
> +	module_may_redeclare (tmpl, friend_tmpl);
> +
>         if (TMPL_PARMS_DEPTH (DECL_TEMPLATE_PARMS (friend_tmpl))
>   	  > TMPL_ARGS_DEPTH (args))
>   	{
> @@ -11751,9 +11767,16 @@ tsubst_friend_class (tree friend_tmpl, tree args)
>         if (tmpl != error_mark_node)
>   	{
>   	  /* The new TMPL is not an instantiation of anything, so we
> -	     forget its origins.  We don't reset CLASSTYPE_TI_TEMPLATE
> +	     forget its origins.  It is also not a specialization of
> +	     anything.  We don't reset CLASSTYPE_TI_TEMPLATE
>   	     for the new type because that is supposed to be the
>   	     corresponding template decl, i.e., TMPL.  */
> +	  spec_entry elt;
> +	  elt.tmpl = friend_tmpl;
> +	  elt.args = CLASSTYPE_TI_ARGS (TREE_TYPE (tmpl));
> +	  elt.spec = TREE_TYPE (tmpl);
> +	  type_specializations->remove_elt (&elt);

For GCC 14.2 let's guard this with if (modules_p ()); for GCC 15 it can 
be unconditional.  OK.

Jason


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2024-04-29 22:37 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-25  9:17 [PATCH] c++/modules: Fix instantiation of imported temploid friends [PR114275] Nathaniel Shead
2024-04-15  4:49 ` [PATCH v2 1/2] c++: Standardise errors for module_may_redeclare Nathaniel Shead
2024-04-17 15:13   ` Patrick Palka
2024-04-19 16:18   ` Nathaniel Shead
2024-04-25 19:52     ` Jason Merrill
2024-04-15  4:53 ` [PATCH v2 2/2] c++/modules: Fix instantiation of imported temploid friends [PR114275] Nathaniel Shead
2024-04-17 18:02   ` Patrick Palka
2024-04-19  2:14     ` Nathaniel Shead
2024-04-19 16:29       ` [PATCH v3 2/2] c++: " Nathaniel Shead
2024-04-27  1:16         ` Jason Merrill
2024-04-29  9:34           ` [PATCH v4 " Nathaniel Shead
2024-04-29 22:37             ` Jason Merrill

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).