[gcc/devel/c++-modules] Constrained partial specializations

Nathan Sidwell nathan@gcc.gnu.org
Wed Jun 24 20:35:24 GMT 2020


https://gcc.gnu.org/g:eb150e498d05088bf4b7a193d610e92d9425eca7

commit eb150e498d05088bf4b7a193d610e92d9425eca7
Author: Nathan Sidwell <nathan@acm.org>
Date:   Wed Jun 24 13:35:06 2020 -0700

    Constrained partial specializations
    
            gcc/cp/
            * module.cc (depset::hash::add_partial_redirect): Add slot parm.
            (enum merge_kind): Add MK_partial.
            (merge_kind_name): Likewise.
            (trees_out::decl_node): A redirect may be for an EK_DECL.
            (trees_out::get_merge_kind): Determine MK_partial.
            (trees_{in,out}::key_mergeable): Deal with MK_partial.
            (depset::hash::make_dependency): Deal with discovering a partial
            specialization.
            * pt.c (maybe_new_partial_specialization): Add module bits to the
            new typedef.
            (tsubst_template_decl): Relax module import assert.
            (tsubst_template_decl): Refactor, add fixme to check.
            gcc/testsuite/
            * g++.dg/modules/nested-constr-1.h: New.
            * g++.dg/modules/nested-constr-1_a.H: New.
            * g++.dg/modules/nested-constr-1_b.C: New.
            * g++.dg/modules/nested-constr-2_a.C: New.
            * g++.dg/modules/nested-constr-2_b.C: New.
            * g++.dg/modules/nested-constr-2_c.C: New.
            * g++.dg/modules/tmpl-part-req-1.h: New.
            * g++.dg/modules/tmpl-part-req-1_a.H: New.
            * g++.dg/modules/tmpl-part-req-1_b.C: New.
            * g++.dg/modules/tmpl-part-req-2.h: New.
            * g++.dg/modules/tmpl-part-req-2_a.H: New.
            * g++.dg/modules/tmpl-part-req-2_b.C: New.

Diff:
---
 ChangeLog.modules                                |  30 ++++++
 gcc/cp/module.cc                                 | 123 +++++++++++++++++++----
 gcc/cp/pt.c                                      |  20 +++-
 gcc/testsuite/g++.dg/modules/nested-constr-1.h   |  15 +++
 gcc/testsuite/g++.dg/modules/nested-constr-1_a.H |   3 +
 gcc/testsuite/g++.dg/modules/nested-constr-1_b.C |  13 +++
 gcc/testsuite/g++.dg/modules/nested-constr-2_a.C |  18 ++++
 gcc/testsuite/g++.dg/modules/nested-constr-2_b.C |  11 ++
 gcc/testsuite/g++.dg/modules/nested-constr-2_c.C |   5 +
 gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h   |  15 +++
 gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H |   3 +
 gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C |   6 ++
 gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h   |  24 +++++
 gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H |   3 +
 gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C |   6 ++
 15 files changed, 271 insertions(+), 24 deletions(-)

diff --git a/ChangeLog.modules b/ChangeLog.modules
index ab0d717a3dc..bd9fc065dd7 100644
--- a/ChangeLog.modules
+++ b/ChangeLog.modules
@@ -1,3 +1,33 @@
+2020-06-24  Nathan Sidwell  <nathan@acm.org>
+
+	Constrained partial specializations
+	gcc/cp/
+	* module.cc (depset::hash::add_partial_redirect): Add slot parm.
+	(enum merge_kind): Add MK_partial.
+	(merge_kind_name): Likewise.
+	(trees_out::decl_node): A redirect may be for an EK_DECL.
+	(trees_out::get_merge_kind): Determine MK_partial.
+	(trees_{in,out}::key_mergeable): Deal with MK_partial.
+	(depset::hash::make_dependency): Deal with discovering a partial
+	specialization.
+	* pt.c (maybe_new_partial_specialization): Add module bits to the
+	new typedef.
+	(tsubst_template_decl): Relax module import assert.
+	(tsubst_template_decl): Refactor, add fixme to check.
+	gcc/testsuite/
+	* g++.dg/modules/nested-constr-1.h: New.
+	* g++.dg/modules/nested-constr-1_a.H: New.
+	* g++.dg/modules/nested-constr-1_b.C: New.
+	* g++.dg/modules/nested-constr-2_a.C: New.
+	* g++.dg/modules/nested-constr-2_b.C: New.
+	* g++.dg/modules/nested-constr-2_c.C: New.
+	* g++.dg/modules/tmpl-part-req-1.h: New.
+	* g++.dg/modules/tmpl-part-req-1_a.H: New.
+	* g++.dg/modules/tmpl-part-req-1_b.C: New.
+	* g++.dg/modules/tmpl-part-req-2.h: New.
+	* g++.dg/modules/tmpl-part-req-2_a.H: New.
+	* g++.dg/modules/tmpl-part-req-2_b.C: New.
+
 2020-06-11  Nathan Sidwell  <nathan@acm.org>
 
 	Merge master b825a228907
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 4d6654286b7..5b11cfe4d51 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -2516,7 +2516,7 @@ public:
     void add_namespace_context (depset *, tree ns);
 
   private:
-    depset *add_partial_redirect (depset *partial);
+    depset *add_partial_redirect (depset *partial, depset **slot = nullptr);
     bool add_binding (tree ns, tree value);
 
   public:  
@@ -2838,7 +2838,7 @@ enum merge_kind
   MK_indirect_lwm = 0x5,
   MK_local_friend = MK_indirect_lwm, /* Found by CTX, index.  */
   MK_enum,	/* Found by CTX, & 1stMemberNAME.  */
-  MK_attached, /* Found by attachee & index.  */
+  MK_attached,  /* Found by attachee & index.  */
 
   /* Template specialization kinds below. These are all found via
      primary template and specialization args.  */
@@ -2861,7 +2861,10 @@ enum merge_kind
 
   MK_alias_spec = MK_decl_spec | MK_tmpl_alias_mask,
 
-  MK_hwm = 16
+  // FIXME: perhaps the MK_field, MK_vtable, MK_as_base can all be
+  // combined, so that this can be put up there?
+  MK_partial = 16,
+  MK_hwm
 };
 /* This is more than a debugging array.  NULLs are used to determine
    an invalid merge_kind number.  */
@@ -2872,7 +2875,8 @@ static char const *const merge_kind_name[MK_hwm] =
     "type spec", "type tmpl spec",	/*  8,9 type (template).  */
     NULL, "type partial spec",		/* 10,11 partial template. */
     "decl spec", "decl tmpl spec",	/* 12,13 decl (template).  */
-    "alias spec", NULL			/* 14,15 alias. */
+    "alias spec", NULL,			/* 14,15 alias. */
+    "partial",				/* 16 constrained partial */
   };
 
 /* Mergeable entity location data.  */
@@ -7368,7 +7372,12 @@ trees_in::install_entity (tree decl)
     {
       DECL_MODULE_PENDING_MEMBERS_P (decl) = true;
       if (TREE_CODE (decl) == TEMPLATE_DECL)
-	DECL_MODULE_PENDING_MEMBERS_P (DECL_TEMPLATE_RESULT (decl)) = true;
+	{
+	  // FIXME: DECL_MODULE_PENDING_MEMBERS_P requires a
+	  // TYPE_DECL, what was I thinking?
+	  gcc_unreachable ();
+	  DECL_MODULE_PENDING_MEMBERS_P (DECL_TEMPLATE_RESULT (decl)) = true;
+	}
     }
 
   return true;
@@ -8286,9 +8295,7 @@ trees_out::decl_node (tree decl, walk_kind ref)
       /* The DECL_TEMPLATE_RESULT of a partial specialization.
 	 Write the partial specialization's template.  */
       depset *redirect = dep->deps[0];
-      gcc_checking_assert ((redirect->get_entity_kind ()
-			    == depset::EK_SPECIALIZATION)
-			   && redirect->is_partial ());
+      gcc_checking_assert (redirect->is_partial ());
       tpl = redirect->get_entity ();
       goto partial_template;
     }
@@ -9867,6 +9874,11 @@ trees_out::get_merge_kind (tree decl, depset *dep)
       gcc_unreachable ();
 
     case depset::EK_DECL:
+      if (dep->is_partial ())
+	{
+	  mk = MK_partial;
+	  break;
+	}
       {
 	tree ctx = CP_DECL_CONTEXT (decl);
 
@@ -10196,6 +10208,14 @@ trees_out::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
 	    key.index = ix;
 	  }
 	  break;
+
+	case MK_partial:
+	  {
+	    key.constraints = get_constraints (inner);
+	    key.ret = CLASSTYPE_TI_TEMPLATE (TREE_TYPE (inner));
+	    key.args = CLASSTYPE_TI_ARGS (TREE_TYPE (inner));
+	  }
+	  break;
 	}
 
       tree_node (name);
@@ -10205,15 +10225,17 @@ trees_out::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
 	  u (code);
 	}
 
-      if (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL)
+      if (mk == MK_partial
+	  || (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL))
 	{
 	  tree_node (key.ret);
 	  tree arg = key.args;
-	  while (arg && arg != void_list_node)
-	    {
-	      tree_node (TREE_VALUE (arg));
-	      arg = TREE_CHAIN (arg);
-	    }
+	  if (mk == MK_named)
+	    while (arg && arg != void_list_node)
+	      {
+		tree_node (TREE_VALUE (arg));
+		arg = TREE_CHAIN (arg);
+	      }
 	  tree_node (arg);
 	  tree_node (key.constraints);
 	}
@@ -10397,12 +10419,14 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
       key.ref_q = cp_ref_qualifier ((code >> 0) & 3);
       key.index = code >> 2;
 
-      if (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL)
+      if (mk == MK_partial
+	  || (mk == MK_named && inner && TREE_CODE (inner) == FUNCTION_DECL))
 	{
 	  key.ret = tree_node ();
 	  tree arg, *arg_ptr = &key.args;
 	  while ((arg = tree_node ())
-		 && arg != void_list_node)
+		 && arg != void_list_node
+		 && mk != MK_partial)
 	    {
 	      *arg_ptr = tree_cons (NULL_TREE, arg, NULL_TREE);
 	      arg_ptr = &TREE_CHAIN (*arg_ptr);
@@ -10414,7 +10438,7 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
       if (get_overrun ())
 	return error_mark_node;
 
-      if (mk < MK_indirect_lwm)
+      if (mk < MK_indirect_lwm || mk == MK_partial)
 	{
 	  DECL_NAME (decl) = name;
 	  DECL_CONTEXT (decl) = FROB_CONTEXT (container);
@@ -10425,6 +10449,26 @@ trees_in::key_mergeable (int tag, merge_kind mk, tree decl, tree inner,
 	  DECL_CONTEXT (inner) = DECL_CONTEXT (decl);
 	}
 
+      if (mk == MK_partial)
+	{
+	  for (tree spec = DECL_TEMPLATE_SPECIALIZATIONS (key.ret);
+	       spec; spec = TREE_CHAIN (spec))
+	    {
+	      tree tmpl = TREE_VALUE (spec);
+	      if (template_args_equal (key.args,
+				       CLASSTYPE_TI_ARGS (TREE_TYPE (tmpl)))
+		  && cp_tree_equal (key.constraints,
+				    get_constraints
+				    (DECL_TEMPLATE_RESULT (tmpl))))
+		{
+		  existing = tmpl;
+		  break;
+		}
+	    }
+	  if (!existing)
+	    add_mergeable_specialization (key.ret, key.args, decl, 2);
+	}
+      else
       switch (TREE_CODE (container))
 	{
 	default:
@@ -10884,7 +10928,7 @@ trees_in::find_duplicate (tree existing)
   return duplicates->get (existing);
 }
 
-/* We're starting to read duplicate DECL.  EXISTING is the already
+/* We're starting to read a duplicate DECL.  EXISTING is the already
    known node.  */
 
 void
@@ -11855,6 +11899,44 @@ depset::hash::make_dependency (tree decl, entity_kind ek)
 	 non-DECL entries, or when discovering dependencies.  */
       gcc_checking_assert (ek != EK_DECL || current);
 
+      if (DECL_IMPLICIT_TYPEDEF_P (decl)
+	  /* ... not an enum, for instance.  */
+	  && RECORD_OR_UNION_TYPE_P (TREE_TYPE (decl))
+	  && TYPE_LANG_SPECIFIC (TREE_TYPE (decl))
+	  && CLASSTYPE_USE_TEMPLATE (TREE_TYPE (decl)) == 2)
+	{
+	  /* A partial or explicit specialization. Partial
+	     specializations constrained by requires clauses are not
+	     in the hash table, because they have the same set of
+	     template parameters as their general template:
+
+	     template<typename T> class silly;
+	     template<typename T> requires true class silly {};
+
+	     We need to find them, insert their TEMPLATE_DECL in the
+	     dep_hash, and then convert the dep we just found into a
+	     redirect.  */
+
+	  tree ti = TYPE_TEMPLATE_INFO (TREE_TYPE (decl));
+	  tree tmpl = TI_TEMPLATE (ti);
+	  tree partial = NULL_TREE;
+	  for (tree spec = DECL_TEMPLATE_SPECIALIZATIONS (tmpl);
+	       spec; spec = TREE_CHAIN (spec))
+	    if (DECL_TEMPLATE_RESULT (TREE_VALUE (spec)) == decl)
+	      {
+		partial = TREE_VALUE (spec);
+		break;
+	      }
+
+	  if (partial)
+	    {
+	      depset *tmpl_dep = make_dependency (partial, EK_DECL);
+	      
+	      gcc_checking_assert (tmpl_dep->get_entity_kind () == EK_DECL);
+	      return add_partial_redirect (tmpl_dep, slot);
+	    }
+	}
+
       bool has_def = ek != EK_USING && has_definition (decl);
       if (ek > EK_BINDING)
 	ek = EK_DECL;
@@ -12309,7 +12391,7 @@ specialization_cmp (const void *a_, const void *b_)
    dependency add.  */
 
 depset *
-depset::hash::add_partial_redirect (depset *partial)
+depset::hash::add_partial_redirect (depset *partial, depset **slot)
 {
   partial->set_flag_bit<DB_PARTIAL_BIT> ();
 
@@ -12319,7 +12401,8 @@ depset::hash::add_partial_redirect (depset *partial)
   /* Redirects are never reached -- always snap to their target.  */
   redirect->set_flag_bit<DB_UNREACHED_BIT> ();
 
-  depset **slot = entity_slot (inner, true);
+  if (!slot)
+    slot = entity_slot (inner, true);
   gcc_checking_assert (!*slot);
   *slot = redirect;
   redirect->deps.safe_push (partial);
diff --git a/gcc/cp/pt.c b/gcc/cp/pt.c
index 8062047e86d..0e8334f1a72 100644
--- a/gcc/cp/pt.c
+++ b/gcc/cp/pt.c
@@ -952,6 +952,9 @@ maybe_new_partial_specialization (tree type)
       TREE_PRIVATE (d) = (current_access_specifier == access_private_node);
       TREE_PROTECTED (d) = (current_access_specifier == access_protected_node);
 
+      set_instantiating_module (d);
+      DECL_MODULE_EXPORT_P (d) = DECL_MODULE_EXPORT_P (tmpl);
+
       return t;
     }
 
@@ -14152,7 +14155,13 @@ tsubst_template_decl (tree t, tree args, tsubst_flags_t complain,
       if (DECL_LANG_SPECIFIC (inner))
 	{
 	  DECL_MODULE_PURVIEW_P (r) = DECL_MODULE_PURVIEW_P (inner);
-	  gcc_checking_assert (!DECL_MODULE_IMPORT_P (inner));
+	  /* If this is a constrained template, the above tsubst of
+	     inner can find the unconstrained template, which may have
+	     come from an import.  This is ok, because we don't
+	     register this instantiation (see below).  */
+	  gcc_checking_assert (!DECL_MODULE_IMPORT_P (inner)
+			       || (TEMPLATE_PARMS_CONSTRAINTS
+				   (DECL_TEMPLATE_PARMS (t))));
 	  DECL_MODULE_IMPORT_P (r) = false;
 	}
     }
@@ -14163,13 +14172,16 @@ tsubst_template_decl (tree t, tree args, tsubst_flags_t complain,
   if (PRIMARY_TEMPLATE_P (t))
     DECL_PRIMARY_TEMPLATE (r) = r;
 
-  if (TREE_CODE (decl) != TYPE_DECL && !VAR_P (decl)
-      && !lambda_fntype)
+  if (TREE_CODE (decl) == FUNCTION_DECL && !lambda_fntype)
     /* Record this non-type partial instantiation.  */
+    // FIXME: Should we be registering this if this is a constrained
+    // template?  DECL_TEMPLATE_RESULT (r) might be the unconstrained
+    // template's result we found in the above instantiations.  And
+    // that smells wrong.
     register_specialization (r, t,
 			     DECL_TI_ARGS (DECL_TEMPLATE_RESULT (r)),
 			     false, hash);
-
+  
   return r;
 }
 
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-1.h b/gcc/testsuite/g++.dg/modules/nested-constr-1.h
new file mode 100644
index 00000000000..6962ccad850
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-1.h
@@ -0,0 +1,15 @@
+
+template<typename T>
+struct traits
+{
+  template<typename U>
+    struct nested
+    { using type = void; };
+
+  template<typename U> requires requires { typename U::type; }
+    struct nested<U>
+    { using type = typename U::type; };
+};
+
+using V = traits<char>::nested<int>::type;
+
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-1_a.H b/gcc/testsuite/g++.dg/modules/nested-constr-1_a.H
new file mode 100644
index 00000000000..9398986dd42
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-1_a.H
@@ -0,0 +1,3 @@
+// { dg-additional-options "-std=c++2a -fmodule-header" }
+// { dg-module-cmi {} }
+#include "nested-constr-1.h"
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-1_b.C b/gcc/testsuite/g++.dg/modules/nested-constr-1_b.C
new file mode 100644
index 00000000000..7043ffa8554
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-1_b.C
@@ -0,0 +1,13 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts -fno-module-lazy -fdump-lang-module-alias" }
+
+#include "nested-constr-1.h"
+import "nested-constr-1_a.H";
+
+struct X 
+{
+  using type = int;
+};
+
+traits<char>::nested<X>::type b;
+
+// { dg-final { scan-lang-dump-not {merge key \(new\)} module } }
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-2_a.C b/gcc/testsuite/g++.dg/modules/nested-constr-2_a.C
new file mode 100644
index 00000000000..b29d670a570
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-2_a.C
@@ -0,0 +1,18 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts" }
+export module foo;
+// { dg-module-cmi foo }
+
+export template<typename T>
+struct traits
+{
+  template<typename U>
+    struct nested
+    { using type = void; };
+
+  template<typename U> requires requires { typename U::type; }
+    struct nested<U>
+    { using type = typename U::type; };
+};
+
+export using V = traits<char>::nested<int>::type;
+
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-2_b.C b/gcc/testsuite/g++.dg/modules/nested-constr-2_b.C
new file mode 100644
index 00000000000..a34ef523f90
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-2_b.C
@@ -0,0 +1,11 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts" }
+export module bar;
+// { dg-module-cmi bar }
+import foo;
+
+struct X 
+{
+  using type = int;
+};
+
+export traits<char>::nested<X>::type b;
diff --git a/gcc/testsuite/g++.dg/modules/nested-constr-2_c.C b/gcc/testsuite/g++.dg/modules/nested-constr-2_c.C
new file mode 100644
index 00000000000..5c81b3b6c09
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/nested-constr-2_c.C
@@ -0,0 +1,5 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts" }
+
+import bar;
+
+decltype(b) c;
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h
new file mode 100644
index 00000000000..09de42923da
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1.h
@@ -0,0 +1,15 @@
+
+template<typename _Iterator, typename>
+struct Trait;
+
+template<typename _Iterator>
+struct Trait<_Iterator, void> {};
+
+template<typename _Iterator>
+requires  true
+struct Trait<_Iterator, void>
+{
+  template<typename _Iter> struct __diff {};
+  
+  template<typename _Iter> requires true struct __diff<_Iter> {};
+};
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H
new file mode 100644
index 00000000000..20804d73620
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_a.H
@@ -0,0 +1,3 @@
+// { dg-additional-options "-fmodule-header -std=c++2a" }
+// { dg-module-cmi {} }
+#include "tmpl-part-req-1.h"
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C
new file mode 100644
index 00000000000..4c00d54aee9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-1_b.C
@@ -0,0 +1,6 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts -fno-module-lazy -fdump-lang-module-alias" }
+
+#include "tmpl-part-req-1.h"
+import "tmpl-part-req-1_a.H";
+
+// { dg-final { scan-lang-dump-not {merge key \(new\)} module } }
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h
new file mode 100644
index 00000000000..0ca05933b11
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2.h
@@ -0,0 +1,24 @@
+
+template<typename _Iterator, typename>
+struct Trait;
+
+template<typename _Iterator>
+struct Trait<_Iterator, void> {};
+
+template<typename _Iterator>
+requires  true && true
+struct Trait<_Iterator, void>
+{
+  template<typename _Iter> struct __cat {};
+  
+  template<typename _Iter> requires true struct __cat<_Iter> {};
+};
+
+template<typename _Iterator>
+requires  true
+struct Trait<_Iterator, void>
+{
+  template<typename _Iter> struct __diff {};
+  
+  template<typename _Iter> requires true struct __diff<_Iter> {};
+};
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H
new file mode 100644
index 00000000000..66b97ea372b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_a.H
@@ -0,0 +1,3 @@
+// { dg-additional-options "-fmodule-header -std=c++2a" }
+// { dg-module-cmi {} }
+#include "tmpl-part-req-2.h"
diff --git a/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C
new file mode 100644
index 00000000000..1176f6012c7
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/tmpl-part-req-2_b.C
@@ -0,0 +1,6 @@
+// { dg-additional-options "-std=c++2a -fmodules-ts -fno-module-lazy -fdump-lang-module-alias" }
+
+#include "tmpl-part-req-2.h"
+import "tmpl-part-req-2_a.H";
+
+// { dg-final { scan-lang-dump-not {merge key \(new\)} module } }


More information about the Gcc-cvs mailing list