[clang] 0f7aaeb - [C++20] [Modules] Allow export from language linkage
Chuanqi Xu via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 9 01:45:13 PST 2023
Author: Chuanqi Xu
Date: 2023-11-09T17:44:41+08:00
New Revision: 0f7aaeb3241c3803489a45753190e82dbc7fd5fa
URL: https://github.com/llvm/llvm-project/commit/0f7aaeb3241c3803489a45753190e82dbc7fd5fa
DIFF: https://github.com/llvm/llvm-project/commit/0f7aaeb3241c3803489a45753190e82dbc7fd5fa.diff
LOG: [C++20] [Modules] Allow export from language linkage
Close https://github.com/llvm/llvm-project/issues/71347
Previously I misread the concept of module purview. I thought if a
declaration attached to a unnamed module, it can't be part of the module
purview. But after the issue report, I recognized that module purview is
more of a concept about locations instead of semantics.
Concretely, the things in the language linkage after module declarations
can be exported.
This patch refactors `Module::isModulePurview()` and introduces some
possible code cleanups.
Added:
Modified:
clang/include/clang/Basic/Module.h
clang/include/clang/Lex/ModuleMap.h
clang/include/clang/Sema/Sema.h
clang/include/clang/Serialization/ASTWriter.h
clang/lib/AST/ASTContext.cpp
clang/lib/AST/Decl.cpp
clang/lib/AST/DeclBase.cpp
clang/lib/CodeGen/CodeGenModule.cpp
clang/lib/Frontend/ASTUnit.cpp
clang/lib/Lex/ModuleMap.cpp
clang/lib/Sema/Sema.cpp
clang/lib/Sema/SemaDecl.cpp
clang/lib/Sema/SemaDeclCXX.cpp
clang/lib/Sema/SemaLookup.cpp
clang/lib/Sema/SemaModule.cpp
clang/lib/Serialization/ASTWriterDecl.cpp
clang/test/Modules/export-language-linkage.cppm
clang/test/SemaCXX/modules.cppm
Removed:
################################################################################
diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h
index 239eb5a637f3ecf..08b153e8c1c9d33 100644
--- a/clang/include/clang/Basic/Module.h
+++ b/clang/include/clang/Basic/Module.h
@@ -178,9 +178,8 @@ class alignas(8) Module {
/// eventually be exposed, for use in "private" modules.
std::string ExportAsModule;
- /// Does this Module scope describe part of the purview of a standard named
- /// C++ module?
- bool isModulePurview() const {
+ /// Does this Module is a named module of a standard named module?
+ bool isNamedModule() const {
switch (Kind) {
case ModuleInterfaceUnit:
case ModuleImplementationUnit:
diff --git a/clang/include/clang/Lex/ModuleMap.h b/clang/include/clang/Lex/ModuleMap.h
index d5824713970ea7b..32e7e8f899e502c 100644
--- a/clang/include/clang/Lex/ModuleMap.h
+++ b/clang/include/clang/Lex/ModuleMap.h
@@ -556,8 +556,8 @@ class ModuleMap {
/// parent.
Module *createGlobalModuleFragmentForModuleUnit(SourceLocation Loc,
Module *Parent = nullptr);
- Module *createImplicitGlobalModuleFragmentForModuleUnit(
- SourceLocation Loc, bool IsExported, Module *Parent = nullptr);
+ Module *createImplicitGlobalModuleFragmentForModuleUnit(SourceLocation Loc,
+ Module *Parent);
/// Create a global module fragment for a C++ module interface unit.
Module *createPrivateModuleFragmentForInterfaceUnit(Module *Parent,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index fe8b387f198c56e..63d548c30da7f6e 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2317,14 +2317,9 @@ class Sema final {
clang::Module *TheGlobalModuleFragment = nullptr;
/// The implicit global module fragments of the current translation unit.
- /// We would only create at most two implicit global module fragments to
- /// avoid performance penalties when there are many language linkage
- /// exports.
///
- /// The contents in the implicit global module fragment can't be discarded
- /// no matter if it is exported or not.
+ /// The contents in the implicit global module fragment can't be discarded.
clang::Module *TheImplicitGlobalModuleFragment = nullptr;
- clang::Module *TheExportedImplicitGlobalModuleFragment = nullptr;
/// Namespace definitions that we will export when they finish.
llvm::SmallPtrSet<const NamespaceDecl*, 8> DeferredExportedNamespaces;
@@ -2336,9 +2331,7 @@ class Sema final {
/// Helper function to judge if we are in module purview.
/// Return false if we are not in a module.
- bool isCurrentModulePurview() const {
- return getCurrentModule() ? getCurrentModule()->isModulePurview() : false;
- }
+ bool isCurrentModulePurview() const;
/// Enter the scope of the explicit global module fragment.
Module *PushGlobalModuleFragment(SourceLocation BeginLoc);
@@ -2346,8 +2339,7 @@ class Sema final {
void PopGlobalModuleFragment();
/// Enter the scope of an implicit global module fragment.
- Module *PushImplicitGlobalModuleFragment(SourceLocation BeginLoc,
- bool IsExported);
+ Module *PushImplicitGlobalModuleFragment(SourceLocation BeginLoc);
/// Leave the scope of an implicit global module fragment.
void PopImplicitGlobalModuleFragment();
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index 3019bbc2ddc9cc7..a56929ef0245ee9 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -746,7 +746,7 @@ class ASTWriter : public ASTDeserializationListener,
ASTReader *getChain() const { return Chain; }
bool isWritingStdCXXNamedModules() const {
- return WritingModule && WritingModule->isModulePurview();
+ return WritingModule && WritingModule->isNamedModule();
}
private:
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 014ba4913581f1a..4f54791b4c1e5ce 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -1096,7 +1096,7 @@ ArrayRef<Decl *> ASTContext::getModuleInitializers(Module *M) {
}
void ASTContext::setCurrentNamedModule(Module *M) {
- assert(M->isModulePurview());
+ assert(M->isNamedModule());
assert(!CurrentCXXNamedModule &&
"We should set named module for ASTContext for only once");
CurrentCXXNamedModule = M;
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index ed848e574233468..c5c2edf1bfe3aba 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -579,27 +579,6 @@ static bool isSingleLineLanguageLinkage(const Decl &D) {
return false;
}
-/// Determine whether D is declared in the purview of a named module.
-static bool isInModulePurview(const NamedDecl *D) {
- if (auto *M = D->getOwningModule())
- return M->isModulePurview();
- return false;
-}
-
-static bool isExportedFromModuleInterfaceUnit(const NamedDecl *D) {
- // FIXME: Handle isModulePrivate.
- switch (D->getModuleOwnershipKind()) {
- case Decl::ModuleOwnershipKind::Unowned:
- case Decl::ModuleOwnershipKind::ReachableWhenImported:
- case Decl::ModuleOwnershipKind::ModulePrivate:
- return false;
- case Decl::ModuleOwnershipKind::Visible:
- case Decl::ModuleOwnershipKind::VisibleWhenImported:
- return isInModulePurview(D);
- }
- llvm_unreachable("unexpected module ownership kind");
-}
-
static bool isDeclaredInModuleInterfaceOrPartition(const NamedDecl *D) {
if (auto *M = D->getOwningModule())
return M->isInterfaceOrPartition();
@@ -1191,6 +1170,27 @@ Linkage NamedDecl::getLinkageInternal() const {
.getLinkage();
}
+/// Determine whether D is attached to a named module.
+static bool isInNamedModule(const NamedDecl *D) {
+ if (auto *M = D->getOwningModule())
+ return M->isNamedModule();
+ return false;
+}
+
+static bool isExportedFromModuleInterfaceUnit(const NamedDecl *D) {
+ // FIXME: Handle isModulePrivate.
+ switch (D->getModuleOwnershipKind()) {
+ case Decl::ModuleOwnershipKind::Unowned:
+ case Decl::ModuleOwnershipKind::ReachableWhenImported:
+ case Decl::ModuleOwnershipKind::ModulePrivate:
+ return false;
+ case Decl::ModuleOwnershipKind::Visible:
+ case Decl::ModuleOwnershipKind::VisibleWhenImported:
+ return isInNamedModule(D);
+ }
+ llvm_unreachable("unexpected module ownership kind");
+}
+
/// Get the linkage from a semantic point of view. Entities in
/// anonymous namespaces are external (in c++98).
Linkage NamedDecl::getFormalLinkage() const {
@@ -1204,7 +1204,7 @@ Linkage NamedDecl::getFormalLinkage() const {
// [basic.namespace.general]/p2
// A namespace is never attached to a named module and never has a name with
// module linkage.
- if (isInModulePurview(this) && InternalLinkage == Linkage::External &&
+ if (isInNamedModule(this) && InternalLinkage == Linkage::External &&
!isExportedFromModuleInterfaceUnit(
cast<NamedDecl>(this->getCanonicalDecl())) &&
!isa<NamespaceDecl>(this))
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 3fd4751d6d1f31d..4fcc2e7302c034c 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -1113,7 +1113,7 @@ bool Decl::isInAnotherModuleUnit() const {
if (M->isGlobalModule())
return false;
- assert(M->isModulePurview() && "New module kind?");
+ assert(M->isNamedModule() && "New module kind?");
return M != getASTContext().getCurrentNamedModule();
}
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 2e96fff808113ba..75355282878f2f3 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -3859,7 +3859,7 @@ bool CodeGenModule::shouldEmitFunction(GlobalDecl GD) {
// We don't import function bodies from other named module units since that
// behavior may break ABI compatibility of the current unit.
if (const Module *M = F->getOwningModule();
- M && M->isModulePurview() &&
+ M && M->getTopLevelModule()->isNamedModule() &&
getContext().getCurrentNamedModule() != M->getTopLevelModule() &&
!F->hasAttr<AlwaysInlineAttr>())
return false;
diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp
index 85157c3b21b6648..c4badcd00c477e0 100644
--- a/clang/lib/Frontend/ASTUnit.cpp
+++ b/clang/lib/Frontend/ASTUnit.cpp
@@ -883,7 +883,7 @@ std::unique_ptr<ASTUnit> ASTUnit::LoadFromASTFile(
PP.setCounterValue(Counter);
Module *M = HeaderInfo.lookupModule(AST->getLangOpts().CurrentModule);
- if (M && AST->getLangOpts().isCompilingModule() && M->isModulePurview())
+ if (M && AST->getLangOpts().isCompilingModule() && M->isNamedModule())
AST->Ctx->setCurrentNamedModule(M);
// Create an AST consumer, even though it isn't used.
diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp
index e4f3cdd3b163f50..00e13c9be4a7d73 100644
--- a/clang/lib/Lex/ModuleMap.cpp
+++ b/clang/lib/Lex/ModuleMap.cpp
@@ -881,17 +881,17 @@ Module *ModuleMap::createGlobalModuleFragmentForModuleUnit(SourceLocation Loc,
return Result;
}
-Module *ModuleMap::createImplicitGlobalModuleFragmentForModuleUnit(
- SourceLocation Loc, bool IsExported, Module *Parent) {
+Module *
+ModuleMap::createImplicitGlobalModuleFragmentForModuleUnit(SourceLocation Loc,
+ Module *Parent) {
assert(Parent && "We should only create an implicit global module fragment "
"in a module purview");
// Note: Here the `IsExplicit` parameter refers to the semantics in clang
// modules. All the non-explicit submodules in clang modules will be exported
// too. Here we simplify the implementation by using the concept.
- auto *Result = new Module(IsExported ? "<exported implicit global>"
- : "<implicit global>",
- Loc, Parent, /*IsFramework*/ false,
- /*IsExplicit*/ !IsExported, NumCreatedModules++);
+ auto *Result =
+ new Module("<implicit global>", Loc, Parent, /*IsFramework=*/false,
+ /*IsExplicit=*/false, NumCreatedModules++);
Result->Kind = Module::ImplicitGlobalModuleFragment;
return Result;
}
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index d25edd470334f63..d7d8d2eaa37e1d6 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1286,8 +1286,6 @@ void Sema::ActOnEndOfTranslationUnit() {
if (auto *FD = dyn_cast<FunctionDecl>(D)) {
bool DefInPMF = false;
if (auto *FDD = FD->getDefinition()) {
- assert(FDD->getOwningModule() &&
- FDD->getOwningModule()->isModulePurview());
DefInPMF = FDD->getOwningModule()->isPrivateModule();
if (!DefInPMF)
continue;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 7a424eaa5fe7d8e..3876eb501083acb 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -1684,8 +1684,8 @@ bool Sema::CheckRedeclarationModuleOwnership(NamedDecl *New, NamedDecl *Old) {
return false;
}
- bool NewIsModuleInterface = NewM && NewM->isModulePurview();
- bool OldIsModuleInterface = OldM && OldM->isModulePurview();
+ bool NewIsModuleInterface = NewM && NewM->isNamedModule();
+ bool OldIsModuleInterface = OldM && OldM->isNamedModule();
if (NewIsModuleInterface || OldIsModuleInterface) {
// C++ Modules TS [basic.def.odr] 6.2/6.7 [sic]:
// if a declaration of D [...] appears in the purview of a module, all
@@ -1819,7 +1819,7 @@ bool Sema::IsRedefinitionInModule(const NamedDecl *New,
// [basic.def.odr]p14.3
// Each such definition shall not be attached to a named module
// ([module.unit]).
- if ((NewM && NewM->isModulePurview()) || (OldM && OldM->isModulePurview()))
+ if ((NewM && NewM->isNamedModule()) || (OldM && OldM->isNamedModule()))
return true;
// Then New and Old lives in the same TU if their share one same module unit.
@@ -10165,8 +10165,7 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
// check at the end of the TU (or when the PMF starts) to see that we
// have a definition at that point.
if (isInline && !D.isFunctionDefinition() && getLangOpts().CPlusPlus20 &&
- NewFD->hasOwningModule() &&
- NewFD->getOwningModule()->isModulePurview()) {
+ NewFD->hasOwningModule() && NewFD->getOwningModule()->isNamedModule()) {
PendingInlineFuncDecls.insert(NewFD);
}
}
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 60786a880b9d3fd..3688192e6cbe5c5 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -16798,8 +16798,7 @@ Decl *Sema::ActOnStartLinkageSpecification(Scope *S, SourceLocation ExternLoc,
/// If the declaration is already in global module fragment, we don't
/// need to attach it again.
if (getLangOpts().CPlusPlusModules && isCurrentModulePurview()) {
- Module *GlobalModule = PushImplicitGlobalModuleFragment(
- ExternLoc, /*IsExported=*/D->isInExportDeclContext());
+ Module *GlobalModule = PushImplicitGlobalModuleFragment(ExternLoc);
D->setLocalOwningModule(GlobalModule);
}
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 324a57095560b79..be011b9f4368d2c 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -1593,7 +1593,6 @@ bool Sema::isUsableModule(const Module *M) {
// The global module fragment can be used to provide declarations that are
// attached to the global module and usable within the module unit.
if (M == TheGlobalModuleFragment || M == TheImplicitGlobalModuleFragment ||
- M == TheExportedImplicitGlobalModuleFragment ||
// If M is the module we're parsing, it should be usable. This covers the
// private module fragment. The private module fragment is usable only if
// it is within the current module unit. And it must be the current
@@ -3911,7 +3910,7 @@ void Sema::ArgumentDependentLookup(DeclarationName Name, SourceLocation Loc,
// exports are only valid in module purview and outside of any
// PMF (although a PMF should not even be present in a module
// with an import).
- assert(FM && FM->isModulePurview() && !FM->isPrivateModule() &&
+ assert(FM && FM->isNamedModule() && !FM->isPrivateModule() &&
"bad export context");
// .. are attached to a named module M, do not appear in the
// translation unit containing the point of the lookup..
diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index 99b4d7d0fe40ee3..9282ceb8dee03de 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -939,22 +939,20 @@ void Sema::PopGlobalModuleFragment() {
ModuleScopes.pop_back();
}
-Module *Sema::PushImplicitGlobalModuleFragment(SourceLocation BeginLoc,
- bool IsExported) {
- Module **M = IsExported ? &TheExportedImplicitGlobalModuleFragment
- : &TheImplicitGlobalModuleFragment;
- if (!*M) {
+Module *Sema::PushImplicitGlobalModuleFragment(SourceLocation BeginLoc) {
+ if (!TheImplicitGlobalModuleFragment) {
ModuleMap &Map = PP.getHeaderSearchInfo().getModuleMap();
- *M = Map.createImplicitGlobalModuleFragmentForModuleUnit(
- BeginLoc, IsExported, getCurrentModule());
+ TheImplicitGlobalModuleFragment =
+ Map.createImplicitGlobalModuleFragmentForModuleUnit(BeginLoc,
+ getCurrentModule());
}
- assert(*M && "module creation should not fail");
+ assert(TheImplicitGlobalModuleFragment && "module creation should not fail");
// Enter the scope of the global module.
- ModuleScopes.push_back({BeginLoc, *M,
+ ModuleScopes.push_back({BeginLoc, TheImplicitGlobalModuleFragment,
/*OuterVisibleModules=*/{}});
- VisibleModules.setVisible(*M, BeginLoc);
- return *M;
+ VisibleModules.setVisible(TheImplicitGlobalModuleFragment, BeginLoc);
+ return TheImplicitGlobalModuleFragment;
}
void Sema::PopImplicitGlobalModuleFragment() {
@@ -963,3 +961,22 @@ void Sema::PopImplicitGlobalModuleFragment() {
"left the wrong module scope, which is not global module fragment");
ModuleScopes.pop_back();
}
+
+bool Sema::isCurrentModulePurview() const {
+ if (!getCurrentModule())
+ return false;
+
+ /// Does this Module scope describe part of the purview of a standard named
+ /// C++ module?
+ switch (getCurrentModule()->Kind) {
+ case Module::ModuleInterfaceUnit:
+ case Module::ModuleImplementationUnit:
+ case Module::ModulePartitionInterface:
+ case Module::ModulePartitionImplementation:
+ case Module::PrivateModuleFragment:
+ case Module::ImplicitGlobalModuleFragment:
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 763b610288f1182..bf082e5b8eac61a 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -2443,7 +2443,7 @@ static bool isRequiredDecl(const Decl *D, ASTContext &Context,
// Named modules have
diff erent semantics than header modules. Every named
// module units owns a translation unit. So the importer of named modules
// doesn't need to deserilize everything ahead of time.
- if (WritingModule && WritingModule->isModulePurview()) {
+ if (WritingModule && WritingModule->isNamedModule()) {
// The PragmaCommentDecl and PragmaDetectMismatchDecl are MSVC's extension.
// And the behavior of MSVC for such cases will leak this to the module
// users. Given pragma is not a standard thing, the compiler has the space
diff --git a/clang/test/Modules/export-language-linkage.cppm b/clang/test/Modules/export-language-linkage.cppm
index 331a620aaad0613..420e4be0ce45b63 100644
--- a/clang/test/Modules/export-language-linkage.cppm
+++ b/clang/test/Modules/export-language-linkage.cppm
@@ -3,9 +3,10 @@
// RUN: cd %t
//
// RUN: %clang_cc1 -std=c++20 %t/a.cppm -emit-module-interface -o %t/a.pcm
-// RUN: %clang_cc1 -std=c++20 %t/b.cpp -fmodule-file=a=%t/a.pcm -fsyntax-only -verify
-// RUN: %clang_cc1 -std=c++20 %t/c.cppm -fsyntax-only -verify
// RUN: %clang_cc1 -module-file-info %t/a.pcm | FileCheck %t/a.cppm
+// RUN: %clang_cc1 -std=c++20 %t/b.cpp -fmodule-file=a=%t/a.pcm -fsyntax-only -verify
+// RUN: %clang_cc1 -std=c++20 %t/c.cppm -emit-module-interface -o %t/c.pcm
+// RUN: %clang_cc1 -std=c++20 %t/d.cpp -fsyntax-only -verify -fmodule-file=c=%t/c.pcm
//--- a.cppm
export module a;
@@ -25,7 +26,6 @@ export {
extern "C++" void unexported();
// CHECK: Sub Modules:
-// CHECK-NEXT: Implicit Module Fragment '<exported implicit global>'
// CHECK-NEXT: Implicit Module Fragment '<implicit global>'
//--- b.cpp
@@ -45,8 +45,19 @@ int use() {
//--- c.cppm
export module c;
extern "C++" {
- // We can't use `export` in an unnamed module.
- export int f(); // expected-error {{export declaration can only be used within a module purview}}
+ export int f();
+ int h();
}
-extern "C++" export int g(); // expected-error {{export declaration can only be used within a module purview}}
+extern "C++" export int g();
+
+//--- d.cpp
+import c;
+int use() {
+ return f() + g();
+}
+
+int use_of_nonexported() {
+ return h(); // expected-error {{missing '#include'; 'h' must be declared before it is used}}
+ // expected-note at c.cppm:4 {{declaration here is not visible}}
+}
diff --git a/clang/test/SemaCXX/modules.cppm b/clang/test/SemaCXX/modules.cppm
index 409a6b2f1661e46..41204be76eafa19 100644
--- a/clang/test/SemaCXX/modules.cppm
+++ b/clang/test/SemaCXX/modules.cppm
@@ -62,10 +62,10 @@ struct S {
// language rules right now, but (per personal correspondence between zygoloid
// and gdr) is the intent.
#if TEST == 1
-export {
+export { // expected-note {{export block begins here}}
extern "C++" {
namespace NestedExport {
- export { // expected-error {{export declaration can only be used within a module purview}}
+ export { // expected-error {{export declaration appears within another export declaration}}
int q;
}
} // namespace NestedExport
More information about the cfe-commits
mailing list