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

-- >8 --

Currently name lookup generally seems to assume that all entities
declared within a named module (partition) are attached to said module,
which is not true for GM entities (e.g. via extern "C++"), and causes
issues with deduplication.

This patch fixes the issue by ensuring that module attachment of a
declaration is consistently used to handling merging.  Handling this
exposes some issues with deduplicating temploid friends; to resolve this
we always create the BINDING_SLOT_PARTITION slot so that we have
somewhere to place attached names (from any module).

        PR c++/114950

gcc/cp/ChangeLog:

        * module.cc (trees_out::decl_value): Stream bit indicating
        imported temploid friends early.
        (trees_in::decl_value): Use this bit with key_mergeable.
        (trees_in::key_mergeable): Allow merging attached declarations
        if they're imported temploid friends.
        (module_state::read_cluster): Check for GM entities that may
        require merging even when importing from partitions.
        * name-lookup.cc (enum binding_slots): Adjust comment.
        (get_fixed_binding_slot): Always create partition slot.
        (name_lookup::search_namespace_only): Support binding vectors
        with both partition and GM entities to dedup.
        (walk_module_binding): Likewise.
        (name_lookup::adl_namespace_fns): Likewise.
        (set_module_binding): Likewise.
        (check_module_override): Use attachment of the decl when
        checking overrides rather than named_module_p.
        (lookup_imported_hidden_friend): Use partition slot for finding
        mergeable template bindings.
        * name-lookup.h (set_module_binding): Split mod_glob_flag
        parameter into separate global_p and partition_p params.

gcc/testsuite/ChangeLog:

        * g++.dg/modules/tpl-friend-13_e.C: Adjust error message.
        * g++.dg/modules/ambig-2_a.C: New test.
        * g++.dg/modules/ambig-2_b.C: New test.
        * g++.dg/modules/part-9_a.C: New test.
        * g++.dg/modules/part-9_b.C: New test.
        * g++.dg/modules/part-9_c.C: New test.
        * g++.dg/modules/tpl-friend-15.h: New test.
        * g++.dg/modules/tpl-friend-15_a.C: New test.
        * g++.dg/modules/tpl-friend-15_b.C: New test.
        * g++.dg/modules/tpl-friend-15_c.C: New test.

Signed-off-by: Nathaniel Shead <nathanielosh...@gmail.com>
---
 gcc/cp/module.cc                              | 55 ++++++++++------
 gcc/cp/name-lookup.cc                         | 65 ++++++++++---------
 gcc/cp/name-lookup.h                          |  2 +-
 gcc/testsuite/g++.dg/modules/ambig-2_a.C      |  7 ++
 gcc/testsuite/g++.dg/modules/ambig-2_b.C      | 10 +++
 gcc/testsuite/g++.dg/modules/part-9_a.C       | 10 +++
 gcc/testsuite/g++.dg/modules/part-9_b.C       | 10 +++
 gcc/testsuite/g++.dg/modules/part-9_c.C       |  8 +++
 .../g++.dg/modules/tpl-friend-13_e.C          |  4 +-
 gcc/testsuite/g++.dg/modules/tpl-friend-15.h  | 11 ++++
 .../g++.dg/modules/tpl-friend-15_a.C          |  8 +++
 .../g++.dg/modules/tpl-friend-15_b.C          |  8 +++
 .../g++.dg/modules/tpl-friend-15_c.C          |  7 ++
 13 files changed, 150 insertions(+), 55 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/ambig-2_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/part-9_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/part-9_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/part-9_c.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15.h
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C
 create mode 100644 gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C

diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index d1607a06757..e6b569ebca5 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2955,7 +2955,8 @@ private:
 public:
   tree decl_container ();
   tree key_mergeable (int tag, merge_kind, tree decl, tree inner, tree type,
-                     tree container, bool is_attached);
+                     tree container, bool is_attached,
+                     bool is_imported_temploid_friend);
   unsigned binfo_mergeable (tree *);
 
 private:
@@ -7803,6 +7804,7 @@ trees_out::decl_value (tree decl, depset *dep)
                       || !TYPE_PTRMEMFUNC_P (TREE_TYPE (decl)));
 
   merge_kind mk = get_merge_kind (decl, dep);
+  bool is_imported_temploid_friend = imported_temploid_friends->get (decl);
 
   if (CHECKING_P)
     {
@@ -7838,13 +7840,11 @@ 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);
+
+             /* Also tell the importer whether this is an imported temploid
+                friend, which has implications for merging.  */
+             bits.b (is_imported_temploid_friend);
            }
          bits.b (dep && dep->has_defn ());
        }
@@ -8021,13 +8021,12 @@ trees_out::decl_value (tree decl, depset *dep)
        }
     }
 
-  if (TREE_CODE (inner) == FUNCTION_DECL
-      || TREE_CODE (inner) == TYPE_DECL)
+  if (is_imported_temploid_friend)
     {
       /* 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);
+      tree_node (*slot);
     }
 
   bool is_typedef = false;
@@ -8106,6 +8105,7 @@ trees_in::decl_value ()
 {
   int tag = 0;
   bool is_attached = false;
+  bool is_imported_temploid_friend = false;
   bool has_defn = false;
   unsigned mk_u = u ();
   if (mk_u >= MK_hwm || !merge_kind_name[mk_u])
@@ -8126,7 +8126,10 @@ trees_in::decl_value ()
        {
          bits_in bits = stream_bits ();
          if (!(mk & MK_template_mask) && !state->is_header ())
-           is_attached = bits.b ();
+           {
+             is_attached = bits.b ();
+             is_imported_temploid_friend = bits.b ();
+           }
 
          has_defn = bits.b ();
        }
@@ -8231,7 +8234,7 @@ trees_in::decl_value ()
     parm_tag = fn_parms_init (inner);
 
   tree existing = key_mergeable (tag, mk, decl, inner, type, container,
-                                is_attached);
+                                is_attached, is_imported_temploid_friend);
   tree existing_inner = existing;
   if (existing)
     {
@@ -8336,8 +8339,7 @@ trees_in::decl_value ()
        }
     }
 
-  if (TREE_CODE (inner) == FUNCTION_DECL
-      || TREE_CODE (inner) == TYPE_DECL)
+  if (is_imported_temploid_friend)
     if (tree owner = tree_node ())
       if (is_new)
        imported_temploid_friends->put (decl, owner);
@@ -11174,7 +11176,8 @@ check_mergeable_decl (merge_kind mk, tree decl, tree 
ovl, merge_key const &key)
 
 tree
 trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
-                        tree type, tree container, bool is_attached)
+                        tree type, tree container, bool is_attached,
+                        bool is_imported_temploid_friend)
 {
   const char *kind = "new";
   tree existing = NULL_TREE;
@@ -11316,6 +11319,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree 
decl, tree inner,
 
          case NAMESPACE_DECL:
            if (is_attached
+               && !is_imported_temploid_friend
                && !(state->is_module () || state->is_partition ()))
              kind = "unique";
            else
@@ -11347,7 +11351,9 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree 
decl, tree inner,
            break;
 
          case TYPE_DECL:
-           if (is_attached && !(state->is_module () || state->is_partition ())
+           if (is_attached
+               && !is_imported_temploid_friend
+               && !(state->is_module () || state->is_partition ())
                /* Implicit member functions can come from
                   anywhere.  */
                && !(DECL_ARTIFICIAL (decl)
@@ -15291,6 +15297,7 @@ module_state::read_cluster (unsigned snum)
            tree visible = NULL_TREE;
            tree type = NULL_TREE;
            bool dedup = false;
+           bool global_p = false;
 
            /* We rely on the bindings being in the reverse order of
               the resulting overload set.  */
@@ -15308,6 +15315,16 @@ module_state::read_cluster (unsigned snum)
                if (sec.get_overrun ())
                  break;
 
+               if (!global_p)
+                 {
+                   /* Check if the decl could require GM merging.  */
+                   tree orig = get_originating_module_decl (decl);
+                   tree inner = STRIP_TEMPLATE (orig);
+                   if (!DECL_LANG_SPECIFIC (inner)
+                       || !DECL_MODULE_ATTACH_P (inner))
+                     global_p = true;
+                 }
+
                if (decls && TREE_CODE (decl) == TYPE_DECL)
                  {
                    /* Stat hack.  */
@@ -15394,10 +15411,8 @@ module_state::read_cluster (unsigned snum)
              break; /* Bail.  */
 
            dump () && dump ("Binding of %P", ns, name);
-           if (!set_module_binding (ns, name, mod,
-                                    is_header () ? -1
-                                    : is_module () || is_partition () ? 1
-                                    : 0,
+           if (!set_module_binding (ns, name, mod, global_p,
+                                    is_module () || is_partition (),
                                     decls, type, visible))
              sec.set_overrun ();
          }
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 8823ab71c60..872f1af0b2e 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -51,8 +51,8 @@ enum binding_slots
 {
  BINDING_SLOT_CURRENT, /* Slot for current TU.  */
  BINDING_SLOT_GLOBAL,  /* Slot for merged global module. */
- BINDING_SLOT_PARTITION, /* Slot for merged partition entities
-                           (optional).  */
+ BINDING_SLOT_PARTITION, /* Slot for merged partition entities or
+                           imported friends.  */
 
  /* Number of always-allocated slots.  */
  BINDING_SLOTS_FIXED = BINDING_SLOT_GLOBAL + 1
@@ -248,9 +248,10 @@ get_fixed_binding_slot (tree *slot, tree name, unsigned 
ix, int create)
       if (!create)
        return NULL;
 
-      /* The partition slot is only needed when we're a named
-        module.  */
-      bool partition_slot = named_module_p ();
+      /* The partition slot is always needed, in case we have imported
+        temploid friends with attachment different from the module we
+        imported them from.  */
+      bool partition_slot = true;
       unsigned want = ((BINDING_SLOTS_FIXED + partition_slot + (create < 0)
                        + BINDING_VECTOR_SLOTS_PER_CLUSTER - 1)
                       / BINDING_VECTOR_SLOTS_PER_CLUSTER);
@@ -937,7 +938,6 @@ name_lookup::search_namespace_only (tree scope)
                   stat_hack, then everything was exported.  */
                tree type = NULL_TREE;
 
-
                /* If STAT_HACK_P is false, everything is visible, and
                   there's no duplication possibilities.  */
                if (STAT_HACK_P (bind))
@@ -947,9 +947,9 @@ name_lookup::search_namespace_only (tree scope)
                        /* Do we need to engage deduplication?  */
                        int dup = 0;
                        if (MODULE_BINDING_GLOBAL_P (bind))
-                         dup = 1;
-                       else if (MODULE_BINDING_PARTITION_P (bind))
-                         dup = 2;
+                         dup |= 1;
+                       if (MODULE_BINDING_PARTITION_P (bind))
+                         dup |= 2;
                        if (unsigned hit = dup_detect & dup)
                          {
                            if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val))
@@ -1275,9 +1275,9 @@ name_lookup::adl_namespace_fns (tree scope, bitmap 
imports)
                        /* Do we need to engage deduplication?  */
                        int dup = 0;
                        if (MODULE_BINDING_GLOBAL_P (bind))
-                         dup = 1;
-                       else if (MODULE_BINDING_PARTITION_P (bind))
-                         dup = 2;
+                         dup |= 1;
+                       if (MODULE_BINDING_PARTITION_P (bind))
+                         dup |= 2;
                        if (unsigned hit = dup_detect & dup)
                          if ((hit & 1 && BINDING_VECTOR_GLOBAL_DUPS_P (val))
                              || (hit & 2
@@ -3758,6 +3758,9 @@ check_module_override (tree decl, tree mvec, bool hiding,
   binding_cluster *cluster = BINDING_VECTOR_CLUSTER_BASE (mvec);
   unsigned ix = BINDING_VECTOR_NUM_CLUSTERS (mvec);
 
+  tree nontmpl = STRIP_TEMPLATE (decl);
+  bool attached = DECL_LANG_SPECIFIC (nontmpl) && DECL_MODULE_ATTACH_P 
(nontmpl);
+
   if (BINDING_VECTOR_SLOTS_PER_CLUSTER == BINDING_SLOTS_FIXED)
     {
       cluster++;
@@ -3819,7 +3822,7 @@ check_module_override (tree decl, tree mvec, bool hiding,
     {
       /* Look in the appropriate mergeable decl slot.  */
       tree mergeable = NULL_TREE;
-      if (named_module_p ())
+      if (attached)
        mergeable = BINDING_VECTOR_CLUSTER (mvec, BINDING_SLOT_PARTITION
                                           / BINDING_VECTOR_SLOTS_PER_CLUSTER)
          .slots[BINDING_SLOT_PARTITION % BINDING_VECTOR_SLOTS_PER_CLUSTER];
@@ -3839,15 +3842,13 @@ check_module_override (tree decl, tree mvec, bool 
hiding,
  matched:
   if (match != error_mark_node)
     {
-      if (named_module_p ())
+      if (attached)
        BINDING_VECTOR_PARTITION_DUPS_P (mvec) = true;
       else
        BINDING_VECTOR_GLOBAL_DUPS_P (mvec) = true;
     }
 
   return match;
-
-  
 }
 
 /* Record DECL as belonging to the current lexical scope.  Check for
@@ -4300,7 +4301,9 @@ walk_module_binding (tree binding, bitmap partitions,
          cluster++;
        }
 
-      bool maybe_dups = BINDING_VECTOR_PARTITION_DUPS_P (binding);
+      /* There could be duplicate module or GMF entries.  */
+      bool maybe_dups = (BINDING_VECTOR_PARTITION_DUPS_P (binding)
+                        || BINDING_VECTOR_GLOBAL_DUPS_P (binding));
 
       for (; ix--; cluster++)
        for (unsigned jx = 0; jx != BINDING_VECTOR_SLOTS_PER_CLUSTER; jx++)
@@ -4394,14 +4397,14 @@ import_module_binding  (tree ns, tree name, unsigned 
mod, unsigned snum)
 }
 
 /* An import of MODULE is binding NS::NAME.  There should be no
-   existing binding for >= MODULE.  MOD_GLOB indicates whether MODULE
-   is a header_unit (-1) or part of the current module (+1).  VALUE
-   and TYPE are the value and type bindings. VISIBLE are the value
-   bindings being exported.  */
+   existing binding for >= MODULE.  GLOBAL_P indicates whether the
+   bindings include global module entities.  PARTITION_P is true if
+   it is part of the current module. VALUE and TYPE are the value
+   and type bindings. VISIBLE are the value bindings being exported.  */
 
 bool
-set_module_binding (tree ns, tree name, unsigned mod, int mod_glob,
-                   tree value, tree type, tree visible)
+set_module_binding (tree ns, tree name, unsigned mod, bool global_p,
+                   bool partition_p, tree value, tree type, tree visible)
 {
   if (!value)
     /* Bogus BMIs could give rise to nothing to bind.  */
@@ -4419,19 +4422,19 @@ set_module_binding (tree ns, tree name, unsigned mod, 
int mod_glob,
     return false;
 
   tree bind = value;
-  if (type || visible != bind || mod_glob)
+  if (type || visible != bind || partition_p || global_p)
     {
       bind = stat_hack (bind, type);
       STAT_VISIBLE (bind) = visible;
-      if ((mod_glob > 0 && TREE_PUBLIC (ns))
+      if ((partition_p && TREE_PUBLIC (ns))
          || (type && DECL_MODULE_EXPORT_P (type)))
        STAT_TYPE_VISIBLE_P (bind) = true;
     }
 
-  /* Note if this is this-module or global binding.  */
-  if (mod_glob > 0)
+  /* Note if this is this-module and/or global binding.  */
+  if (partition_p)
     MODULE_BINDING_PARTITION_P (bind) = true;
-  else if (mod_glob < 0)
+  if (global_p)
     MODULE_BINDING_GLOBAL_P (bind) = true;
 
   *mslot = bind;
@@ -4540,10 +4543,8 @@ lookup_imported_hidden_friend (tree friend_tmpl)
       || !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);
+  tree bind = get_mergeable_namespace_binding
+    (current_namespace, DECL_NAME (inner), DECL_MODULE_ATTACH_P (inner));
   if (!bind)
     return NULL_TREE;
 
diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h
index 5cf6ae6374a..7c4193444dd 100644
--- a/gcc/cp/name-lookup.h
+++ b/gcc/cp/name-lookup.h
@@ -484,7 +484,7 @@ extern tree lookup_class_binding (tree ctx, tree name);
 extern bool import_module_binding (tree ctx, tree name, unsigned mod,
                                   unsigned snum);
 extern bool set_module_binding (tree ctx, tree name, unsigned mod,
-                               int mod_glob_flag,
+                               bool global_p, bool partition_p,
                                tree value, tree type, tree visible);
 extern void add_module_namespace_decl (tree ns, tree decl);
 
diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_a.C 
b/gcc/testsuite/g++.dg/modules/ambig-2_a.C
new file mode 100644
index 00000000000..d5dcc93584a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/ambig-2_a.C
@@ -0,0 +1,7 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi A }
+
+export module A;
+
+extern "C++" int foo ();
+extern "C++" char bar ();
diff --git a/gcc/testsuite/g++.dg/modules/ambig-2_b.C 
b/gcc/testsuite/g++.dg/modules/ambig-2_b.C
new file mode 100644
index 00000000000..b94416aabbf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/ambig-2_b.C
@@ -0,0 +1,10 @@
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi !B }
+
+export module B;
+import A;
+
+extern "C++" int foo ();
+extern "C++" int bar ();  // { dg-error "ambiguating new declaration" }
+
+// { dg-prune-output "not writing module" }
diff --git a/gcc/testsuite/g++.dg/modules/part-9_a.C 
b/gcc/testsuite/g++.dg/modules/part-9_a.C
new file mode 100644
index 00000000000..dc033d301ee
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/part-9_a.C
@@ -0,0 +1,10 @@
+// PR c++/114950
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:a }
+
+module M:a;
+
+struct A {};
+extern "C++" struct B {};
+void f(int) {}
+extern "C++" void f(double) {}
diff --git a/gcc/testsuite/g++.dg/modules/part-9_b.C 
b/gcc/testsuite/g++.dg/modules/part-9_b.C
new file mode 100644
index 00000000000..7339da22d97
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/part-9_b.C
@@ -0,0 +1,10 @@
+// PR c++/114950
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:b }
+
+module M:b;
+
+struct A {};
+extern "C++" struct B {};
+void f(int) {}
+extern "C++" void f(double) {}
diff --git a/gcc/testsuite/g++.dg/modules/part-9_c.C 
b/gcc/testsuite/g++.dg/modules/part-9_c.C
new file mode 100644
index 00000000000..26ac6777b7a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/part-9_c.C
@@ -0,0 +1,8 @@
+// PR c++/114950
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+// Handle merging definitions of extern "C++" decls across partitions
+
+export module M;
+import :a;
+import :b;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C 
b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
index afbd0a39c23..b32fd98b756 100644
--- a/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-13_e.C
@@ -1,8 +1,8 @@
 // { 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" }
+struct S {};  // { dg-message "previous declaration" "" { xfail *-*-* } }
+template <typename> struct T {};  // { dg-message "previous declaration" }
 void f() {}  // { dg-message "previously declared" }
 template <typename T> void g() {}  // { dg-message "previously declared" }
 
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15.h 
b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h
new file mode 100644
index 00000000000..e4d3fff4445
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15.h
@@ -0,0 +1,11 @@
+// PR c++/114950
+
+template <typename T>
+struct A {
+  friend void x();
+};
+template <typename T>
+struct B {
+  virtual void f() { A<T> r; }
+};
+template struct B<int>;
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C 
b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C
new file mode 100644
index 00000000000..04c800875f4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_a.C
@@ -0,0 +1,8 @@
+// PR c++/114950
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:a }
+
+module M:a;
+extern "C++" {
+  #include "tpl-friend-15.h"
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C 
b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C
new file mode 100644
index 00000000000..781882f97bc
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_b.C
@@ -0,0 +1,8 @@
+// PR c++/114950
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M:b }
+
+module M:b;
+extern "C++" {
+  #include "tpl-friend-15.h"
+}
diff --git a/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C 
b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C
new file mode 100644
index 00000000000..ced7e87d993
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tpl-friend-15_c.C
@@ -0,0 +1,7 @@
+// PR c++/114950
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+import :a;
+import :b;
-- 
2.43.2

Reply via email to