diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index 6444db3f0eb..743102d8393 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -9053,8 +9053,14 @@ push_namespace (tree name, bool make_inline)
     {
       /* A public namespace is exported only if explicitly marked, or
 	 it contains exported entities.  */
-      if (TREE_PUBLIC (ns) && module_exporting_p ())
-	DECL_MODULE_EXPORT_P (ns) = true;
+      if (module_exporting_p ())
+	{
+	  if (TREE_PUBLIC (ns))
+	    DECL_MODULE_EXPORT_P (ns) = true;
+	  else if (!header_module_p ())
+	    error_at (input_location,
+		      "exporting namespace with internal linkage");
+	}
       if (module_purview_p ())
 	DECL_MODULE_PURVIEW_P (ns) = true;
 
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index a310b9e8c07..448392e1bd9 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -560,6 +560,8 @@ cp_debug_parser (FILE *file, cp_parser *parser)
 			       & THIS_FORBIDDEN));
   cp_debug_print_flag (file, "In unbraced linkage specification",
 			      parser->in_unbraced_linkage_specification_p);
+  cp_debug_print_flag (file, "In unbraced export declaration",
+			      parser->in_unbraced_export_declaration_p);
   cp_debug_print_flag (file, "Parsing a declarator",
 			      parser->in_declarator_p);
   cp_debug_print_flag (file, "In template argument list",
@@ -4425,6 +4427,9 @@ cp_parser_new (cp_lexer *lexer)
   /* We are not processing an `extern "C"' declaration.  */
   parser->in_unbraced_linkage_specification_p = false;
 
+  /* We aren't parsing an export-declaration.  */
+  parser->in_unbraced_export_declaration_p = false;
+
   /* We are not processing a declarator.  */
   parser->in_declarator_p = false;
 
@@ -15249,10 +15254,6 @@ cp_parser_import_declaration (cp_parser *parser, module_parse mp_state,
 	goto skip_eol;
       cp_parser_require_pragma_eol (parser, token);
 
-      if (parser->in_unbraced_linkage_specification_p)
-	error_at (token->location, "import cannot appear directly in"
-		  " a linkage-specification");
-
       if (mp_state == MP_PURVIEW_IMPORTS || mp_state == MP_PRIVATE_IMPORTS)
 	{
 	  /* Module-purview imports must not be from source inclusion
@@ -15273,7 +15274,7 @@ cp_parser_import_declaration (cp_parser *parser, module_parse mp_state,
 
 /*  export-declaration.
 
-    export declaration
+    export name-declaration
     export { declaration-seq-opt }  */
 
 static void
@@ -15315,7 +15316,13 @@ cp_parser_module_export (cp_parser *parser)
 	  || cp_lexer_next_token_is_keyword (parser->lexer, RID__EXPORT))
 	error_at (token->location, "%<export%> not part of following"
 		  " module-directive");
+
+      bool saved_in_unbraced_export_declaration_p
+	= parser->in_unbraced_export_declaration_p;
+      parser->in_unbraced_export_declaration_p = true;
       cp_parser_declaration (parser, NULL_TREE);
+      parser->in_unbraced_export_declaration_p
+	= saved_in_unbraced_export_declaration_p;
     }
 
   module_kind = mk;
@@ -15346,27 +15353,29 @@ cp_parser_declaration_seq_opt (cp_parser* parser)
     }
 }
 
-/* Parse a declaration.
+/* Parse a declaration.  The distinction between name-declaration
+   and special-declaration is only since C++20.
 
    declaration:
+     name-declaration
+     special-declaration
+
+   name-declaration:
      block-declaration
+     nodeclspec-function-declaration
      function-definition
      template-declaration
-     explicit-instantiation
-     explicit-specialization
+     deduction-guide  (C++17)
      linkage-specification
      namespace-definition
+     empty-declaration
+     attribute-declaration
+     module-import-declaration  (modules)
 
-   C++17:
-     deduction-guide
-
-   modules:
-     (all these are only allowed at the outermost level, check
-   	that semantically, for better diagnostics)
-     module-declaration
-     module-export-declaration
-     module-import-declaration
-     export-declaration
+   special-declaration:
+     explicit-instantiation
+     explicit-specialization
+     export-declaration   (modules)
 
    GNU extension:
 
@@ -15389,6 +15398,13 @@ cp_parser_declaration (cp_parser* parser, tree prefix_attrs)
       return;
     }
 
+  /* P2615: Determine if we're parsing a name-declaration specifically,
+     or if special-declarations are OK too.  */
+  bool require_name_decl_p
+    = (parser->in_unbraced_export_declaration_p
+       || (parser->in_unbraced_linkage_specification_p
+	   && cxx_dialect >= cxx20));
+
   /* Try to figure out what kind of declaration is present.  */
   cp_token *token1 = cp_lexer_peek_token (parser->lexer);
   cp_token *token2 = (token1->type == CPP_EOF
@@ -15496,13 +15512,30 @@ cp_parser_declaration (cp_parser* parser, tree prefix_attrs)
       /* `template <>' indicates a template specialization.  */
       if (token2->type == CPP_LESS
 	  && cp_lexer_peek_nth_token (parser->lexer, 3)->type == CPP_GREATER)
-	cp_parser_explicit_specialization (parser);
+	{
+	  if (require_name_decl_p)
+	    {
+	      auto_diagnostic_group d;
+	      cp_token *token3 = cp_lexer_peek_nth_token (parser->lexer, 3);
+	      location_t loc = make_location (token1, token1, token3);
+	      error_at (loc, "explicit specializations are not permitted here");
+	      if (parser->in_unbraced_export_declaration_p)
+		inform (loc, "a specialization is always exported alongside "
+			"its primary template");
+	    }
+	  cp_parser_explicit_specialization (parser);
+	}
       /* `template <' indicates a template declaration.  */
       else if (token2->type == CPP_LESS)
 	cp_parser_template_declaration (parser, /*member_p=*/false);
       /* Anything else must be an explicit instantiation.  */
       else
-	cp_parser_explicit_instantiation (parser);
+	{
+	  if (require_name_decl_p)
+	    error_at (token1->location,
+		     "explicit instantiations are not permitted here");
+	  cp_parser_explicit_instantiation (parser);
+	}
     }
   /* If the next token is `export', it's new-style modules or
      old-style template.  */
@@ -15511,7 +15544,14 @@ cp_parser_declaration (cp_parser* parser, tree prefix_attrs)
       if (!modules_p ())
 	cp_parser_template_declaration (parser, /*member_p=*/false);
       else
-	cp_parser_module_export (parser);
+	{
+	  /* We check for nested exports in cp_parser_module_export.  */
+	  if (require_name_decl_p
+	      && !parser->in_unbraced_export_declaration_p)
+	    error_at (token1->location,
+		      "export-declarations are not permitted here");
+	  cp_parser_module_export (parser);
+	}
     }
   else if (cp_token_is_module_directive (token1))
     {
@@ -16809,7 +16849,7 @@ cp_parser_function_specifier_opt (cp_parser* parser,
 
    linkage-specification:
      extern string-literal { declaration-seq [opt] }
-     extern string-literal declaration  */
+     extern string-literal name-declaration  */
 
 static void
 cp_parser_linkage_specification (cp_parser* parser, tree prefix_attr)
@@ -26767,6 +26807,7 @@ cp_parser_class_specifier (cp_parser* parser)
   unsigned char in_statement;
   bool in_switch_statement_p;
   bool saved_in_unbraced_linkage_specification_p;
+  bool saved_in_unbraced_export_declaration_p;
   tree old_scope = NULL_TREE;
   tree scope = NULL_TREE;
   cp_token *closing_brace;
@@ -26818,6 +26859,10 @@ cp_parser_class_specifier (cp_parser* parser)
   saved_in_unbraced_linkage_specification_p
     = parser->in_unbraced_linkage_specification_p;
   parser->in_unbraced_linkage_specification_p = false;
+  /* Or in an export-declaration.  */
+  saved_in_unbraced_export_declaration_p
+    = parser->in_unbraced_export_declaration_p;
+  parser->in_unbraced_export_declaration_p = false;
   /* 'this' from an enclosing non-static member function is unavailable.  */
   tree saved_ccp = current_class_ptr;
   tree saved_ccr = current_class_ref;
@@ -27200,6 +27245,8 @@ cp_parser_class_specifier (cp_parser* parser)
     = saved_num_template_parameter_lists;
   parser->in_unbraced_linkage_specification_p
     = saved_in_unbraced_linkage_specification_p;
+  parser->in_unbraced_export_declaration_p
+    = saved_in_unbraced_export_declaration_p;
   current_class_ptr = saved_ccp;
   current_class_ref = saved_ccr;
 
@@ -27487,6 +27534,20 @@ cp_parser_class_head (cp_parser* parser,
 	permerror (nested_name_specifier_token_start->location,
 		   "extra qualification not allowed");
     }
+  /* The name-declaration of an export-declaration shall not declare
+     a partial specialization.  */
+  if (template_id_p
+      && parser->in_unbraced_export_declaration_p
+      && !processing_specialization
+      && !processing_explicit_instantiation)
+    {
+      auto_diagnostic_group d;
+      location_t loc = type_start_token->location;
+      error_at (loc, "declaration of partial specialization in unbraced "
+		"export-declaration");
+      inform (loc, "a specialization is always exported alongside its "
+	      "primary template");
+    }
   /* An explicit-specialization must be preceded by "template <>".  If
      it is not, try to recover gracefully.  */
   if (at_namespace_scope_p ()
diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h
index 373e78f3ea4..09b356e5e73 100644
--- a/gcc/cp/parser.h
+++ b/gcc/cp/parser.h
@@ -312,9 +312,13 @@ struct GTY(()) cp_parser {
 
   /* TRUE if the declaration we are parsing is part of a
      linkage-specification of the form `extern string-literal
-     declaration'.  */
+     name-declaration'.  */
   bool in_unbraced_linkage_specification_p;
 
+  /* TRUE if the declaration we are parsing is part of an
+     export-declaration of the form 'export name-declaration'.  */
+  bool in_unbraced_export_declaration_p;
+
   /* TRUE if we are presently parsing a declarator, after the
      direct-declarator.  */
   bool in_declarator_p;
diff --git a/gcc/testsuite/g++.dg/cpp2a/linkage-spec1.C b/gcc/testsuite/g++.dg/cpp2a/linkage-spec1.C
new file mode 100644
index 00000000000..9e8d5979059
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/linkage-spec1.C
@@ -0,0 +1,22 @@
+// PR c++/107688
+// P2615R1: Meaningful exports: Newly invalid declarations
+// { dg-do compile { target c++11 } }
+
+extern "C++" template <typename T> struct A {};
+
+extern "C++" template <typename T> struct A<T*> {};
+
+extern "C++" template <> struct A<int*> {};
+// { dg-error "explicit specializations are not permitted here" "" { target c++20 } .-1 }
+
+extern "C++" template struct A<int>;
+// { dg-error "explicit instantiations are not permitted here" "" { target c++20 } .-1 }
+
+
+// These should all still be valid, though
+extern "C++" {
+  template <typename T> struct B {};
+  template <typename T> struct B<T*> {};
+  template <> struct B<int*> {};
+  template struct B<int>;
+}
diff --git a/gcc/testsuite/g++.dg/modules/contracts-1_a.C b/gcc/testsuite/g++.dg/modules/contracts-1_a.C
index f991ef8644e..723726451f4 100644
--- a/gcc/testsuite/g++.dg/modules/contracts-1_a.C
+++ b/gcc/testsuite/g++.dg/modules/contracts-1_a.C
@@ -8,7 +8,7 @@ export module foo;
 // { dg-module-cmi foo }
 
 export int violation_count{0};
-extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation)
+export extern "C++" void handle_contract_violation(const std::experimental::contract_violation &violation)
 {
   violation_count++;
   printf("violation_count: %d\n", violation_count);
diff --git a/gcc/testsuite/g++.dg/modules/contracts-2_a.C b/gcc/testsuite/g++.dg/modules/contracts-2_a.C
index 828d680d2a0..3d78c412d46 100644
--- a/gcc/testsuite/g++.dg/modules/contracts-2_a.C
+++ b/gcc/testsuite/g++.dg/modules/contracts-2_a.C
@@ -12,7 +12,7 @@ export module foo;
 
 export int violation_count{0};
 export int violation_line_sum{0};
-extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation)
+export extern "C++" void handle_contract_violation(const std::experimental::contract_violation &violation)
 {
   violation_count++;
   violation_line_sum += violation.line_number () * violation_count;
diff --git a/gcc/testsuite/g++.dg/modules/contracts-3_a.C b/gcc/testsuite/g++.dg/modules/contracts-3_a.C
index a4f03d35842..b931ec0ddf7 100644
--- a/gcc/testsuite/g++.dg/modules/contracts-3_a.C
+++ b/gcc/testsuite/g++.dg/modules/contracts-3_a.C
@@ -8,7 +8,7 @@ export module foo;
 // { dg-module-cmi foo }
 
 export int violation_count{0};
-extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation)
+export extern "C++" void handle_contract_violation(const std::experimental::contract_violation &violation)
 {
   violation_count++;
   printf("violation_count: %d\n", violation_count);
diff --git a/gcc/testsuite/g++.dg/modules/contracts-4_a.C b/gcc/testsuite/g++.dg/modules/contracts-4_a.C
index f269e6c2078..a135f50fb7d 100644
--- a/gcc/testsuite/g++.dg/modules/contracts-4_a.C
+++ b/gcc/testsuite/g++.dg/modules/contracts-4_a.C
@@ -9,7 +9,7 @@ export module foo;
 // { dg-module-cmi foo }
 
 export int violation_count{0};
-extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation)
+export extern "C++" void handle_contract_violation(const std::experimental::contract_violation &violation)
 {
   violation_count++;
   printf("violation_count: %d\n", violation_count);
diff --git a/gcc/testsuite/g++.dg/modules/export-3.C b/gcc/testsuite/g++.dg/modules/export-3.C
new file mode 100644
index 00000000000..76765fd2b2f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/export-3.C
@@ -0,0 +1,30 @@
+// P2615R1 invalid declarations
+// PR c++/107688
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi !bad }
+
+export module bad;
+
+extern "C++" export void foo();  // { dg-error "export-declarations are not permitted here" "" { target c++20 } }
+
+export template <typename T> struct S {};
+
+export template <typename T> struct S<T*> {};  // { dg-error "partial specialization in unbraced export-declaration" }
+
+export template <> struct S<int*> {};  // { dg-error "explicit specializations are not permitted here" }
+
+export template struct S<int>;  // { dg-error "explicit instantiations are not permitted here" }
+
+template <> export struct S<double>;  // { dg-error "expected unqualified-id" }
+
+export export int x;  // { dg-error ".export. may only occur once" }
+
+export { export int y; }  // { dg-error ".export. may only occur once" }
+
+namespace {
+  export namespace ns {}  // { dg-error "internal linkage" }
+}
+
+export namespace {}  // { dg-error "internal linkage" }
+
+// { dg-prune-output "not writing module" }
diff --git a/gcc/testsuite/g++.dg/modules/export-4_a.C b/gcc/testsuite/g++.dg/modules/export-4_a.C
new file mode 100644
index 00000000000..5f99577dc99
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/export-4_a.C
@@ -0,0 +1,23 @@
+// P2615R1 valid declarations
+// PR c++/107688
+// { dg-additional-options "-fmodules-ts" }
+// { dg-module-cmi M }
+
+export module M;
+
+export {}
+export { static_assert(true); }
+
+export namespace Empty {}
+export using namespace Empty;
+
+export {
+  template <typename T> struct S {};
+  template <typename T> struct S<T*> { using a = int; };
+  template <> struct S<int*> { using b = int; };
+  template struct S<int>;
+}
+
+extern "C++" {
+  export void foo();
+}
diff --git a/gcc/testsuite/g++.dg/modules/export-4_b.C b/gcc/testsuite/g++.dg/modules/export-4_b.C
new file mode 100644
index 00000000000..4d721119916
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/export-4_b.C
@@ -0,0 +1,13 @@
+// PR c++/107688
+// { dg-additional-options "-fmodules-ts" }
+
+import M;
+
+using namespace Empty;
+
+int main() {
+  S<int> x;
+  S<int*>::b y;
+  S<int**>::a z;
+  foo();
+}
diff --git a/gcc/testsuite/g++.dg/modules/lang-1_c.C b/gcc/testsuite/g++.dg/modules/lang-1_c.C
index e3b939df373..b3908ed6188 100644
--- a/gcc/testsuite/g++.dg/modules/lang-1_c.C
+++ b/gcc/testsuite/g++.dg/modules/lang-1_c.C
@@ -6,7 +6,7 @@ extern "C++"
 }
 
 extern "C"
-import "lang-1_a.H"; // { dg-error "cannot appear directly" }
+  import "lang-1_a.H"; // OK since p2615r1
 
 extern "C" int cfunc (int); // { dg-error "conflicting declaration" }
 extern "C" int cxxfunc (int);
diff --git a/gcc/testsuite/g++.dg/template/crash71.C b/gcc/testsuite/g++.dg/template/crash71.C
index 3ac862ed81b..949f46eaf0d 100644
--- a/gcc/testsuite/g++.dg/template/crash71.C
+++ b/gcc/testsuite/g++.dg/template/crash71.C
@@ -1,3 +1,5 @@
 // PR c++/30659
 
-extern "C" template A<char> foo(); // { dg-error "forbids|static data|expected|template" }
+extern "C" template A<char> foo();
+// { dg-error "forbids|static data|expected|template" "" { target c++17_down } .-1 }
+// { dg-error "permitted|forbids|static data|expected|template" "" { target c++20 } .-2 }
