[clang] [clang][Modules] Complete the implementation of P2615: Meaningful exports (PR #194201)
Victor Chernyakin via cfe-commits
cfe-commits at lists.llvm.org
Sat Apr 25 22:39:56 PDT 2026
https://github.com/localspook updated https://github.com/llvm/llvm-project/pull/194201
>From 5b1ad9e53a24669408b9ca2f4d4fa9da5512f699 Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Sat, 25 Apr 2026 21:16:00 -0700
Subject: [PATCH 1/2] [clang][Modules] Complete the implementation of P2615:
Meaningful exports
---
clang/docs/ReleaseNotes.rst | 3 ++
.../clang/Basic/DiagnosticSemaKinds.td | 5 ++
clang/lib/Sema/SemaModule.cpp | 43 ++++++++++++++++
clang/test/SemaCXX/P2615.cpp | 49 +++++++++++++++++++
clang/www/cxx_status.html | 2 +-
5 files changed, 101 insertions(+), 1 deletion(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c5c8c1fa12e7a..fc86399b01e34 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -49,6 +49,9 @@ C++ Specific Potentially Breaking Changes
- Clang now correctly rejects ``export`` declarations in module implementation
partitions. (#GH107602)
+- Clang now correctly rejects explicit instantiations and specializations
+ marked with ``export``, completing its implementation of P2615. (#GH160016)
+
ABI Changes in This Version
---------------------------
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 7ed4684c8359e..345b1de52e8bb 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12853,6 +12853,11 @@ def note_not_module_interface_add_export : Note<
def err_invalid_module_name : Error<"%0 is an invalid name for a module">;
def err_extern_def_in_header_unit : Error<
"non-inline external definitions are not permitted in C++ header units">;
+def err_meaningless_export : Error<
+ "%select{an explicit instantiation|a specialization}0 cannot be "
+ "marked 'export'">;
+def note_meaningless_export_explanation : Note<
+ "as long as its primary template is exported, it will be too">;
def warn_exposure : Warning <
"TU local entity %0 is exposed">,
diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index 67f46b64cf047..7db25393c2f87 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -1006,6 +1006,45 @@ static bool checkExportedDecl(Sema &S, Decl *D, SourceLocation BlockStart) {
return true;
}
+static void checkUnbracedExportDecl(Sema &S, const ExportDecl *ED) {
+ if (ED->decls_empty())
+ return;
+
+ // Explicit instantiations and specializations cannot appear in an unbraced
+ // export-declaration ([module.interface], [dcl.pre]/1):
+ //
+ // export-declaration:
+ // export name-declaration
+ //
+ // declaration:
+ // name-declaration
+ // special-declaration
+ //
+ // special-declaration:
+ // explicit-instantiation
+ // explicit-specialization
+ // export-declaration
+ const Decl *D = *ED->decls_begin();
+ TemplateSpecializationKind TSK = [&] {
+ if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D))
+ return CTSD->getTemplateSpecializationKind();
+ if (const auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(D))
+ return VTSD->getTemplateSpecializationKind();
+ if (const auto *FD = dyn_cast<FunctionDecl>(D))
+ return FD->getTemplateSpecializationKind();
+ if (const auto *EID = dyn_cast<ExplicitInstantiationDecl>(D))
+ return EID->getTemplateSpecializationKind();
+ return TSK_Undeclared;
+ }();
+
+ if (TSK != TSK_Undeclared) {
+ S.Diag(ED->getExportLoc(), diag::err_meaningless_export)
+ << (TSK == TSK_ExplicitSpecialization)
+ << FixItHint::CreateRemoval(ED->getExportLoc());
+ S.Diag(ED->getExportLoc(), diag::note_meaningless_export_explanation);
+ }
+}
+
Decl *Sema::ActOnFinishExportDecl(Scope *S, Decl *D, SourceLocation RBraceLoc) {
auto *ED = cast<ExportDecl>(D);
if (RBraceLoc.isValid())
@@ -1016,6 +1055,10 @@ Decl *Sema::ActOnFinishExportDecl(Scope *S, Decl *D, SourceLocation RBraceLoc) {
if (!D->isInvalidDecl()) {
SourceLocation BlockStart =
ED->hasBraces() ? ED->getBeginLoc() : SourceLocation();
+
+ if (!ED->hasBraces())
+ checkUnbracedExportDecl(*this, ED);
+
for (auto *Child : ED->decls()) {
checkExportedDecl(*this, Child, BlockStart);
if (auto *FD = dyn_cast<FunctionDecl>(Child)) {
diff --git a/clang/test/SemaCXX/P2615.cpp b/clang/test/SemaCXX/P2615.cpp
index fed177c21bc79..9c258632c97e9 100644
--- a/clang/test/SemaCXX/P2615.cpp
+++ b/clang/test/SemaCXX/P2615.cpp
@@ -3,9 +3,58 @@
// RUN: %clang_cc1 -std=c++20 -verify -fsyntax-only %t/A.cpp
+// RUN: %clang_cc1 -std=c++20 -verify -fsyntax-only %t/B.cpp
//--- A.cpp
// expected-no-diagnostics
export module A;
export namespace N {int x = 42;}
export using namespace N;
+
+//--- B.cpp
+export module B;
+
+export template <typename T> class s {};
+export template <typename T> class s<T *> {}; // expected-error {{a specialization cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export template <> class s<int> {}; // expected-error {{a specialization cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export template class s<char>; // expected-error {{an explicit instantiation cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export extern template class s<unsigned>; // expected-error {{an explicit instantiation cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+
+export template <typename T> int v = 0;
+export template <typename T> int v<T *> = 0; // expected-error {{a specialization cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export template <> int v<int> = 0; // expected-error {{a specialization cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export template int v<char>; // expected-error {{an explicit instantiation cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export extern template int v<unsigned>; // expected-error {{an explicit instantiation cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+
+export template <typename T> void f() {}
+export template <> void f<int>() {} // expected-error {{a specialization cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export template void f<char>(); // expected-error {{an explicit instantiation cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+export extern template void f<unsigned>(); // expected-error {{an explicit instantiation cannot be marked 'export'}}
+// expected-note at -1 {{as long as its primary template is exported, it will be too}}
+
+export { template <typename T> class s2 {}; }
+export { template <typename T> class s2<T *> {}; }
+export { template <> class s2<int> {}; }
+export { template class s2<char>; }
+export { extern template class s2<unsigned>; }
+
+export { template <typename T> int v2 = 0; }
+export { template <typename T> int v2<T *> = 0; }
+export { template <> int v2<int> = 0; }
+export { template int v2<char>; }
+export { extern template int v2<unsigned>; }
+
+export { template <typename T> void f2() {} }
+export { template <> void f2<int>() {} }
+export { template void f2<char>(); }
+export { extern template void f2<unsigned>(); }
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 3096e55803b84..8a0f3a9b28439 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -988,7 +988,7 @@ <h2 id="cxx20">C++20 implementation status</h2>
</tr>
<tr> <!-- from Kona 2022 -->
<td><a href="https://wg21.link/P2615R1">P2615R1</a> (<a href="#dr">DR</a>)</td>
- <td class="full" align="center">Clang 17</td>
+ <td class="unreleased" align="center">Clang 23</td>
</tr>
<tr> <!-- from Issaquah 2023 -->
<td><a href="https://wg21.link/P2788R0">P2788R0</a> (<a href="#dr">DR</a>)</td>
>From d41a2912675efddb254c36ec76743d67ab7fe8aa Mon Sep 17 00:00:00 2001
From: Victor Chernyakin <chernyakin.victor.j at outlook.com>
Date: Sat, 25 Apr 2026 22:13:06 -0700
Subject: [PATCH 2/2] Fix fallout in other tests
---
clang/test/Modules/explicit-specializations.cppm | 12 ++++++------
.../Modules/merge-var-template-spec-cxx-modules.cppm | 6 +++---
clang/test/Modules/pr59780.cppm | 4 ++--
clang/test/Modules/pr60890.cppm | 4 ++--
clang/test/Modules/pr97313.cppm | 6 +++---
.../Modules/template-function-specialization.cpp | 2 +-
.../Language/groupsharedArgs/ExportNoInlineTest.hlsl | 2 +-
clang/unittests/Serialization/LoadSpecLazilyTest.cpp | 2 +-
8 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/clang/test/Modules/explicit-specializations.cppm b/clang/test/Modules/explicit-specializations.cppm
index 914144018e880..b5b8d152e3a7b 100644
--- a/clang/test/Modules/explicit-specializations.cppm
+++ b/clang/test/Modules/explicit-specializations.cppm
@@ -20,7 +20,7 @@ struct S {
export struct A {};
-export template <>
+template <>
struct S<A> {
static constexpr bool selected = true;
};
@@ -34,7 +34,7 @@ struct V {
static constexpr bool selected = false;
};
-export template <>
+template<>
struct V<S> {
static constexpr bool selected = true;
};
@@ -46,7 +46,7 @@ struct Numbers {
static constexpr int value = X;
};
-export template<>
+template<>
struct Numbers<43> {
static constexpr bool selected = true;
static constexpr int value = 43;
@@ -58,7 +58,7 @@ struct Pointers {
};
export int IntegralValue = 0;
-export template<>
+template<>
struct Pointers<&IntegralValue> {
static constexpr bool selected = true;
};
@@ -68,7 +68,7 @@ struct NullPointers {
static constexpr bool selected = false;
};
-export template<>
+template<>
struct NullPointers<nullptr> {
static constexpr bool selected = true;
};
@@ -79,7 +79,7 @@ struct Array {
};
export int array[5];
-export template<>
+template<>
struct Array<array> {
static constexpr bool selected = true;
};
diff --git a/clang/test/Modules/merge-var-template-spec-cxx-modules.cppm b/clang/test/Modules/merge-var-template-spec-cxx-modules.cppm
index db3f4cd518716..aa9d6f42b16cd 100644
--- a/clang/test/Modules/merge-var-template-spec-cxx-modules.cppm
+++ b/clang/test/Modules/merge-var-template-spec-cxx-modules.cppm
@@ -28,7 +28,7 @@ template <class T> constexpr T* zero<T*> = nullptr; // expected-error-re {{decla
// expected-note@* {{previous}}
template <> constexpr int** zero<int**> = nullptr; // ok, new specialization.
-template <class T> constexpr T** zero<T**> = nullptr; // ok, new partial specilization.
+template <class T> constexpr T** zero<T**> = nullptr; // ok, new partial specialization.
//--- var_def.cppm
export module var_def;
@@ -37,8 +37,8 @@ export template <class T> constexpr T zero = 0;
export struct Int {
int value;
};
-export template <> constexpr Int zero<Int> = {0};
-export template <class T> constexpr T* zero<T*> = nullptr;
+template <> constexpr Int zero<Int> = {0};
+template <class T> constexpr T* zero<T*> = nullptr;
//--- reexport1.cppm
export module reexport1;
diff --git a/clang/test/Modules/pr59780.cppm b/clang/test/Modules/pr59780.cppm
index ea5fdd5c4ce26..8e2685e328133 100644
--- a/clang/test/Modules/pr59780.cppm
+++ b/clang/test/Modules/pr59780.cppm
@@ -25,7 +25,7 @@ export module a;
export template<typename T>
int x = 0;
-export template<>
+template<>
int x<int> = 0;
export template<typename T>
@@ -36,7 +36,7 @@ struct Y {
template <typename T>
int Y<T>::value = 0;
-export template<>
+template<>
struct Y<int> {
static int value;
};
diff --git a/clang/test/Modules/pr60890.cppm b/clang/test/Modules/pr60890.cppm
index b1d9114bf1ebe..5df2f2ea4bdf2 100644
--- a/clang/test/Modules/pr60890.cppm
+++ b/clang/test/Modules/pr60890.cppm
@@ -24,12 +24,12 @@ struct a {
void aaa() requires(true) {}
};
-export template struct a<double>;
+template struct a<double>;
export template<typename T>
void foo(T) requires(true) {}
-export template void foo<double>(double);
+template void foo<double>(double);
export template <typename T>
class A {
diff --git a/clang/test/Modules/pr97313.cppm b/clang/test/Modules/pr97313.cppm
index 99795d6e43030..833e7ff416247 100644
--- a/clang/test/Modules/pr97313.cppm
+++ b/clang/test/Modules/pr97313.cppm
@@ -76,7 +76,7 @@ public:
virtual ~Template();
};
-export template<>
+template<>
class Template<char> {
public:
virtual ~Template();
@@ -84,11 +84,11 @@ public:
// CHECK: @_ZTIW3Mod8TemplateIcE = {{.*}}constant
-export template class Template<unsigned>;
+template class Template<unsigned>;
// CHECK: @_ZTIW3Mod8TemplateIjE = {{.*}}weak_odr
-export extern template class Template<double>;
+extern template class Template<double>;
auto v = new Template<signed int>();
diff --git a/clang/test/Modules/template-function-specialization.cpp b/clang/test/Modules/template-function-specialization.cpp
index d5d7d7e812398..7619f76ab5634 100644
--- a/clang/test/Modules/template-function-specialization.cpp
+++ b/clang/test/Modules/template-function-specialization.cpp
@@ -42,7 +42,7 @@ export template <typename T>
void foo4() {
}
-export template <>
+template <>
void foo4<int>() {
}
diff --git a/clang/test/SemaHLSL/Language/groupsharedArgs/ExportNoInlineTest.hlsl b/clang/test/SemaHLSL/Language/groupsharedArgs/ExportNoInlineTest.hlsl
index c120adb4a4fbc..9881b9d43897c 100644
--- a/clang/test/SemaHLSL/Language/groupsharedArgs/ExportNoInlineTest.hlsl
+++ b/clang/test/SemaHLSL/Language/groupsharedArgs/ExportNoInlineTest.hlsl
@@ -15,6 +15,6 @@ void fn3(groupshared T A, groupshared T B) {
A = B;
}
-export template void fn3<uint>(groupshared uint A, groupshared uint B);
+template void fn3<uint>(groupshared uint A, groupshared uint B);
template __attribute__((noinline)) void fn3<float>(groupshared float A, groupshared float B);
// expected-error at -1{{'noinline' attribute is not compatible with 'groupshared' parameter attribute}}
diff --git a/clang/unittests/Serialization/LoadSpecLazilyTest.cpp b/clang/unittests/Serialization/LoadSpecLazilyTest.cpp
index f55925aeae1f2..33fabf451de70 100644
--- a/clang/unittests/Serialization/LoadSpecLazilyTest.cpp
+++ b/clang/unittests/Serialization/LoadSpecLazilyTest.cpp
@@ -238,7 +238,7 @@ export class Temp {
export class ExportedClass {};
-export template<> class A<ExportedClass> {
+template<> class A<ExportedClass> {
A<MayBeLoaded> AS;
A<B> AB;
};
More information about the cfe-commits
mailing list