def err_export_within_export : Error<
"export declaration appears within another export declaration">;
def err_export_not_in_module_interface : Error<
- "export declaration can only be used within a module interface unit after "
- "the module declaration">;
+ "export declaration can only be used within a module interface unit"
+ "%select{ after the module declaration|}0">;
+def err_export_in_private_module_fragment : Error<
+ "export declaration cannot be used in a private module fragment">;
+def note_private_module_fragment : Note<
+ "private module fragment begins here">;
+def err_private_module_fragment_not_module : Error<
+ "private module fragment declaration with no preceding module declaration">;
+def err_private_module_fragment_redefined : Error<
+ "private module fragment redefined">;
+def err_private_module_fragment_not_module_interface : Error<
+ "private module fragment in module implementation unit">;
+def note_not_module_interface_add_export : Note<
+ "add 'export' here if this is intended to be a module interface unit">;
def ext_equivalent_internal_linkage_decl_in_modules : ExtWarn<
"ambiguous use of internal linkage declaration %0 defined in multiple modules">,
/// This is a C++ Modules TS module interface unit.
ModuleInterfaceUnit,
- /// This is a fragment of the global module within some C++ Modules
- /// TS module.
+ /// This is a fragment of the global module within some C++ module.
GlobalModuleFragment,
+
+ /// This is the private module fragment within some C++ module.
+ PrivateModuleFragment,
};
/// The kind of this module.
/// eventually be exposed, for use in "private" modules.
std::string ExportAsModule;
+ /// Does this Module scope describe part of the purview of a named C++ module?
+ bool isModulePurview() const {
+ return Kind == ModuleInterfaceUnit || Kind == PrivateModuleFragment;
+ }
+
private:
/// The submodules of this module, indexed by name.
std::vector<Module *> SubModules;
bool IsFramework,
bool IsExplicit);
- /// Create a 'global module' for a C++ Modules TS module interface unit.
+ /// Create a global module fragment for a C++ module unit.
///
- /// We model the global module as a submodule of the module interface unit.
- /// Unfortunately, we can't create the module interface unit's Module until
- /// later, because we don't know what it will be called.
- Module *createGlobalModuleForInterfaceUnit(SourceLocation Loc);
+ /// We model the global module fragment as a submodule of the module
+ /// interface unit. Unfortunately, we can't create the module interface
+ /// unit's Module until later, because we don't know what it will be called.
+ Module *createGlobalModuleFragmentForModuleUnit(SourceLocation Loc);
- /// Create a new module for a C++ Modules TS module interface unit.
+ /// Create a global module fragment for a C++ module interface unit.
+ Module *createPrivateModuleFragmentForInterfaceUnit(Module *Parent,
+ SourceLocation Loc);
+
+ /// Create a new module for a C++ module interface unit.
/// The module must not already exist, and will be configured for the current
/// compilation.
///
void emitAndClearUnusedLocalTypedefWarnings();
+ enum TUFragmentKind {
+ /// The global module fragment, between 'module;' and a module-declaration.
+ Global,
+ /// A normal translation unit fragment. For a non-module unit, this is the
+ /// entire translation unit. Otherwise, it runs from the module-declaration
+ /// to the private-module-fragment (if any) or the end of the TU (if not).
+ Normal,
+ /// The private module fragment, between 'module :private;' and the end of
+ /// the translation unit.
+ Private
+ };
+
void ActOnStartOfTranslationUnit();
void ActOnEndOfTranslationUnit();
+ void ActOnEndOfTranslationUnitFragment(TUFragmentKind Kind);
void CheckDelegatingCtorCycles();
/// \param ModuleLoc The location of the 'module' keyword.
/// \param PrivateLoc The location of the 'private' keyword.
DeclGroupPtrTy ActOnPrivateModuleFragmentDecl(SourceLocation ModuleLoc,
- SourceLocation PrivateLoc) {
- // FIXME
- return DeclGroupPtrTy();
- }
+ SourceLocation PrivateLoc);
/// The parser has processed a module import declaration.
///
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 isExportedFromModuleIntefaceUnit(const NamedDecl *D) {
// FIXME: Handle isModulePrivate.
switch (D->getModuleOwnershipKind()) {
return false;
case Decl::ModuleOwnershipKind::Visible:
case Decl::ModuleOwnershipKind::VisibleWhenImported:
- if (auto *M = D->getOwningModule())
- return M->Kind == Module::ModuleInterfaceUnit;
+ return isInModulePurview(D);
}
llvm_unreachable("unexpected module ownership kind");
}
// as "module-internal linkage", which means that they have internal linkage
// formally but can be indirectly accessed from outside the module via inline
// functions and templates defined within the module.
- if (auto *M = D->getOwningModule())
- if (M->Kind == Module::ModuleInterfaceUnit)
- return LinkageInfo(ModuleInternalLinkage, DefaultVisibility, false);
+ if (isInModulePurview(D))
+ return LinkageInfo(ModuleInternalLinkage, DefaultVisibility, false);
return LinkageInfo::internal();
}
// - A name declared at namespace scope that does not have internal linkage
// by the previous rules and that is introduced by a non-exported
// declaration has module linkage.
- if (auto *M = D->getOwningModule())
- if (M->Kind == Module::ModuleInterfaceUnit)
- if (!isExportedFromModuleIntefaceUnit(
- cast<NamedDecl>(D->getCanonicalDecl())))
- return LinkageInfo(ModuleLinkage, DefaultVisibility, false);
+ if (isInModulePurview(D) &&
+ !isExportedFromModuleIntefaceUnit(cast<NamedDecl>(D->getCanonicalDecl())))
+ return LinkageInfo(ModuleLinkage, DefaultVisibility, false);
return LinkageInfo::external();
}
}
return InternalLinkage ? M->Parent : nullptr;
}
+
+ case Module::PrivateModuleFragment:
+ // The private module fragment is part of its containing module for linkage
+ // purposes.
+ return M->Parent;
}
llvm_unreachable("unknown module kind");
return std::make_pair(Result, true);
}
-Module *ModuleMap::createGlobalModuleForInterfaceUnit(SourceLocation Loc) {
+Module *ModuleMap::createGlobalModuleFragmentForModuleUnit(SourceLocation Loc) {
PendingSubmodules.emplace_back(
new Module("<global>", Loc, nullptr, /*IsFramework*/ false,
/*IsExplicit*/ true, NumCreatedModules++));
return PendingSubmodules.back().get();
}
+Module *
+ModuleMap::createPrivateModuleFragmentForInterfaceUnit(Module *Parent,
+ SourceLocation Loc) {
+ auto *Result =
+ new Module("<private>", Loc, Parent, /*IsFramework*/ false,
+ /*IsExplicit*/ true, NumCreatedModules++);
+ Result->Kind = Module::PrivateModuleFragment;
+ return Result;
+}
+
Module *ModuleMap::createModuleForInterfaceUnit(SourceLocation Loc,
StringRef Name,
Module *GlobalModule) {
}
}
-/// ActOnEndOfTranslationUnit - This is called at the very end of the
-/// translation unit when EOF is reached and all but the top-level scope is
-/// popped.
-void Sema::ActOnEndOfTranslationUnit() {
- assert(DelayedDiagnostics.getCurrentPool() == nullptr
- && "reached end of translation unit with a pool attached?");
-
- // If code completion is enabled, don't perform any end-of-translation-unit
- // work.
- if (PP.isCodeCompletionEnabled())
+void Sema::ActOnEndOfTranslationUnitFragment(TUFragmentKind Kind) {
+ // No explicit actions are required at the end of the global module fragment.
+ if (Kind == TUFragmentKind::Global)
return;
// Transfer late parsed template instantiations over to the pending template
- // instantiation list. During normal compliation, the late template parser
+ // instantiation list. During normal compilation, the late template parser
// will be installed and instantiating these templates will succeed.
//
// If we are building a TU prefix for serialization, it is also safe to
LateParsedInstantiations.end());
LateParsedInstantiations.clear();
+ // If DefinedUsedVTables ends up marking any virtual member functions it
+ // might lead to more pending template instantiations, which we then need
+ // to instantiate.
+ DefineUsedVTables();
+
+ // C++: Perform implicit template instantiations.
+ //
+ // FIXME: When we perform these implicit instantiations, we do not
+ // carefully keep track of the point of instantiation (C++ [temp.point]).
+ // This means that name lookup that occurs within the template
+ // instantiation will always happen at the end of the translation unit,
+ // so it will find some names that are not required to be found. This is
+ // valid, but we could do better by diagnosing if an instantiation uses a
+ // name that was not visible at its first point of instantiation.
+ if (ExternalSource) {
+ // Load pending instantiations from the external source.
+ SmallVector<PendingImplicitInstantiation, 4> Pending;
+ ExternalSource->ReadPendingInstantiations(Pending);
+ for (auto PII : Pending)
+ if (auto Func = dyn_cast<FunctionDecl>(PII.first))
+ Func->setInstantiationIsPending(true);
+ PendingInstantiations.insert(PendingInstantiations.begin(),
+ Pending.begin(), Pending.end());
+ }
+
+ {
+ llvm::TimeTraceScope TimeScope("PerformPendingInstantiations",
+ StringRef(""));
+ PerformPendingInstantiations();
+ }
+
+ assert(LateParsedInstantiations.empty() &&
+ "end of TU template instantiation should not create more "
+ "late-parsed templates");
+}
+
+/// ActOnEndOfTranslationUnit - This is called at the very end of the
+/// translation unit when EOF is reached and all but the top-level scope is
+/// popped.
+void Sema::ActOnEndOfTranslationUnit() {
+ assert(DelayedDiagnostics.getCurrentPool() == nullptr
+ && "reached end of translation unit with a pool attached?");
+
+ // If code completion is enabled, don't perform any end-of-translation-unit
+ // work.
+ if (PP.isCodeCompletionEnabled())
+ return;
+
// Complete translation units and modules define vtables and perform implicit
// instantiations. PCH files do not.
if (TUKind != TU_Prefix) {
DiagnoseUseOfUnimplementedSelectors();
- // If DefinedUsedVTables ends up marking any virtual member functions it
- // might lead to more pending template instantiations, which we then need
- // to instantiate.
- DefineUsedVTables();
-
- // C++: Perform implicit template instantiations.
- //
- // FIXME: When we perform these implicit instantiations, we do not
- // carefully keep track of the point of instantiation (C++ [temp.point]).
- // This means that name lookup that occurs within the template
- // instantiation will always happen at the end of the translation unit,
- // so it will find some names that are not required to be found. This is
- // valid, but we could do better by diagnosing if an instantiation uses a
- // name that was not visible at its first point of instantiation.
- if (ExternalSource) {
- // Load pending instantiations from the external source.
- SmallVector<PendingImplicitInstantiation, 4> Pending;
- ExternalSource->ReadPendingInstantiations(Pending);
- for (auto PII : Pending)
- if (auto Func = dyn_cast<FunctionDecl>(PII.first))
- Func->setInstantiationIsPending(true);
- PendingInstantiations.insert(PendingInstantiations.begin(),
- Pending.begin(), Pending.end());
- }
-
- {
- llvm::TimeTraceScope TimeScope("PerformPendingInstantiations",
- StringRef(""));
- PerformPendingInstantiations();
- }
-
- assert(LateParsedInstantiations.empty() &&
- "end of TU template instantiation should not create more "
- "late-parsed templates");
+ ActOnEndOfTranslationUnitFragment(
+ !ModuleScopes.empty() && ModuleScopes.back().Module->Kind ==
+ Module::PrivateModuleFragment
+ ? TUFragmentKind::Private
+ : TUFragmentKind::Normal);
if (LateTemplateParserCleanup)
LateTemplateParserCleanup(OpaqueParser);
CheckDelayedMemberExceptionSpecs();
+ } else {
+ // If we are building a TU prefix for serialization, it is safe to transfer
+ // these over, even though they are not parsed. The end of the TU should be
+ // outside of any eager template instantiation scope, so when this AST is
+ // deserialized, these templates will not be parsed until the end of the
+ // combined TU.
+ PendingInstantiations.insert(PendingInstantiations.end(),
+ LateParsedInstantiations.begin(),
+ LateParsedInstantiations.end());
+ LateParsedInstantiations.clear();
}
DiagnoseUnterminatedPragmaPack();
if (getLangOpts().getCompilingModule() ==
LangOptions::CMK_ModuleInterface &&
(ModuleScopes.empty() ||
- ModuleScopes.back().Module->Kind != Module::ModuleInterfaceUnit) &&
+ !ModuleScopes.back().Module->isModulePurview()) &&
!DiagnosedMissingModuleDeclaration) {
// FIXME: Make a better guess as to where to put the module declaration.
Diag(getSourceManager().getLocForStartOfFile(
Module *NewM = New->getOwningModule();
Module *OldM = Old->getOwningModule();
+
+ if (NewM && NewM->Kind == Module::PrivateModuleFragment)
+ NewM = NewM->Parent;
+ if (OldM && OldM->Kind == Module::PrivateModuleFragment)
+ OldM = OldM->Parent;
+
if (NewM == OldM)
return false;
- // FIXME: Check proclaimed-ownership-declarations here too.
- bool NewIsModuleInterface = NewM && NewM->Kind == Module::ModuleInterfaceUnit;
- bool OldIsModuleInterface = OldM && OldM->Kind == Module::ModuleInterfaceUnit;
+ bool NewIsModuleInterface = NewM && NewM->isModulePurview();
+ bool OldIsModuleInterface = OldM && OldM->isModulePurview();
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
// We start in the global module; all those declarations are implicitly
// module-private (though they do not have module linkage).
auto &Map = PP.getHeaderSearchInfo().getModuleMap();
- auto *GlobalModule = Map.createGlobalModuleForInterfaceUnit(ModuleLoc);
+ auto *GlobalModule = Map.createGlobalModuleFragmentForModuleUnit(ModuleLoc);
assert(GlobalModule && "module creation should not fail");
// Enter the scope of the global module.
// Only one module-declaration is permitted per source file.
if (!ModuleScopes.empty() &&
- ModuleScopes.back().Module->Kind == Module::ModuleInterfaceUnit) {
+ ModuleScopes.back().Module->isModulePurview()) {
Diag(ModuleLoc, diag::err_module_redeclaration);
Diag(VisibleModules.getImportLoc(ModuleScopes.back().Module),
diag::note_prev_module_declaration);
ModuleScopes.push_back({});
if (getLangOpts().ModulesLocalVisibility)
ModuleScopes.back().OuterVisibleModules = std::move(VisibleModules);
+ } else {
+ // We're done with the global module fragment now.
+ ActOnEndOfTranslationUnitFragment(TUFragmentKind::Global);
}
// Switch from the global module fragment (if any) to the named module.
return nullptr;
}
+Sema::DeclGroupPtrTy
+Sema::ActOnPrivateModuleFragmentDecl(SourceLocation ModuleLoc,
+ SourceLocation PrivateLoc) {
+ // C++20 [basic.link]/2:
+ // A private-module-fragment shall appear only in a primary module
+ // interface unit.
+ switch (ModuleScopes.empty() ? Module::GlobalModuleFragment
+ : ModuleScopes.back().Module->Kind) {
+ case Module::ModuleMapModule:
+ case Module::GlobalModuleFragment:
+ Diag(PrivateLoc, diag::err_private_module_fragment_not_module);
+ return nullptr;
+
+ case Module::PrivateModuleFragment:
+ Diag(PrivateLoc, diag::err_private_module_fragment_redefined);
+ Diag(ModuleScopes.back().BeginLoc, diag::note_previous_definition);
+ return nullptr;
+
+ case Module::ModuleInterfaceUnit:
+ break;
+ }
+
+ if (!ModuleScopes.back().ModuleInterface) {
+ Diag(PrivateLoc, diag::err_private_module_fragment_not_module_interface);
+ Diag(ModuleScopes.back().BeginLoc,
+ diag::note_not_module_interface_add_export)
+ << FixItHint::CreateInsertion(ModuleScopes.back().BeginLoc, "export ");
+ return nullptr;
+ }
+
+ // FIXME: Check this isn't a module interface partition.
+ // FIXME: Check that this translation unit does not import any partitions;
+ // such imports would violate [basic.link]/2's "shall be the only module unit"
+ // restriction.
+
+ // We've finished the public fragment of the translation unit.
+ ActOnEndOfTranslationUnitFragment(TUFragmentKind::Normal);
+
+ auto &Map = PP.getHeaderSearchInfo().getModuleMap();
+ Module *PrivateModuleFragment =
+ Map.createPrivateModuleFragmentForInterfaceUnit(
+ ModuleScopes.back().Module, PrivateLoc);
+ assert(PrivateModuleFragment && "module creation should not fail");
+
+ // Enter the scope of the private module fragment.
+ ModuleScopes.push_back({});
+ ModuleScopes.back().BeginLoc = ModuleLoc;
+ ModuleScopes.back().Module = PrivateModuleFragment;
+ ModuleScopes.back().ModuleInterface = true;
+ VisibleModules.setVisible(PrivateModuleFragment, ModuleLoc);
+
+ // All declarations created from now on are scoped to the private module
+ // fragment (and are neither visible nor reachable in importers of the module
+ // interface).
+ auto *TU = Context.getTranslationUnitDecl();
+ TU->setModuleOwnershipKind(Decl::ModuleOwnershipKind::ModulePrivate);
+ TU->setLocalOwningModule(PrivateModuleFragment);
+
+ // FIXME: Consider creating an explicit representation of this declaration.
+ return nullptr;
+}
+
DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc,
SourceLocation ExportLoc,
SourceLocation ImportLoc,
SourceLocation LBraceLoc) {
ExportDecl *D = ExportDecl::Create(Context, CurContext, ExportLoc);
- // C++ Modules TS draft:
- // An export-declaration shall appear in the purview of a module other than
- // the global module.
- if (ModuleScopes.empty() || !ModuleScopes.back().ModuleInterface)
- Diag(ExportLoc, diag::err_export_not_in_module_interface);
+ // C++20 [module.interface]p1:
+ // An export-declaration shall appear only [...] in the purview of a module
+ // interface unit. An export-declaration shall not appear directly or
+ // indirectly within an unnamed namespace or a private-module-fragment.
+ // FIXME: Check for the unnamed namespace case.
+ if (ModuleScopes.empty() || !ModuleScopes.back().Module->isModulePurview()) {
+ Diag(ExportLoc, diag::err_export_not_in_module_interface) << 0;
+ } else if (!ModuleScopes.back().ModuleInterface) {
+ Diag(ExportLoc, diag::err_export_not_in_module_interface) << 1;
+ Diag(ModuleScopes.back().BeginLoc,
+ diag::note_not_module_interface_add_export)
+ << FixItHint::CreateInsertion(ModuleScopes.back().BeginLoc, "export ");
+ } else if (ModuleScopes.back().Module->Kind ==
+ Module::PrivateModuleFragment) {
+ Diag(ExportLoc, diag::err_export_in_private_module_fragment);
+ Diag(ModuleScopes.back().BeginLoc, diag::note_private_module_fragment);
+ }
- // An export-declaration [...] shall not contain more than one
- // export keyword.
- //
- // The intent here is that an export-declaration cannot appear within another
- // export-declaration.
+ // [...] its declaration or declaration-seq shall not contain an
+ // export-declaration.
if (D->isExported())
Diag(ExportLoc, diag::err_export_within_export);
// RUN: %clang_cc1 -std=c++2a -verify %s
// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG %s
// RUN: %clang_cc1 -std=c++2a -verify -DNO_MODULE_DECL %s
+// RUN: %clang_cc1 -std=c++2a -verify -DNO_PRIVATE_FRAG %s
+// RUN: %clang_cc1 -std=c++2a -verify -DNO_MODULE_DECL -DNO_PRIVATE_FRAG %s
+// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_PRIVATE_FRAG %s
+// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_MODULE_DECL %s
+// RUN: %clang_cc1 -std=c++2a -verify -DNO_GLOBAL_FRAG -DNO_MODULE_DECL -DNO_PRIVATE_FRAG %s
// RUN: %clang_cc1 -std=c++2a -verify -DEXPORT_FRAGS %s
-#ifdef NO_GLOBAL_FRAG
-// expected-error@#mod-decl {{module declaration must occur at the start of the translation unit}}
-// expected-note@1 {{add 'module;' to the start of the file to introduce a global module fragment}}
-#else
+#ifndef NO_GLOBAL_FRAG
#ifdef EXPORT_FRAGS
export // expected-error {{global module fragment cannot be exported}}
#endif
-module; // #glob-frag
+module;
+#ifdef NO_MODULE_DECL
+// expected-error@-2 {{missing 'module' declaration at end of global module fragment introduced here}}
+#endif
#endif
extern int a; // #a1
-#ifdef NO_MODULE_DECL
-// expected-error@#glob-frag {{missing 'module' declaration at end of global module fragment introduced here}}
-#else
-export module Foo; // #mod-decl
+#ifndef NO_MODULE_DECL
+export module Foo;
+#ifdef NO_GLOBAL_FRAG
+// expected-error@-2 {{module declaration must occur at the start of the translation unit}}
+// expected-note@1 {{add 'module;' to the start of the file to introduce a global module fragment}}
+#endif
// expected-error@#a2 {{declaration of 'a' in module Foo follows declaration in the global module}}
// expected-note@#a1 {{previous decl}}
module; // expected-error {{'module;' introducing a global module fragment can appear only at the start of the translation unit}}
+#ifndef NO_PRIVATE_FRAG
#ifdef EXPORT_FRAGS
export // expected-error {{private module fragment cannot be exported}}
#endif
-module :private;
+module :private; // #priv-frag
+#ifdef NO_MODULE_DECL
+// expected-error@-2 {{private module fragment declaration with no preceding module declaration}}
+#endif
+#endif
int b; // ok
+
+
+#ifndef NO_PRIVATE_FRAG
+#ifndef NO_MODULE_DECL
+module :private; // expected-error {{private module fragment redefined}}
+// expected-note@#priv-frag {{previous definition is here}}
+#endif
+#endif
--- /dev/null
+// RUN: %clang_cc1 -std=c++2a -DEXPORT %s -verify
+// RUN: %clang_cc1 -std=c++2a -DEXPORT %s -emit-module-interface -o %t.pcm
+// RUN: %clang_cc1 -std=c++2a -UEXPORT %s -verify -fmodule-file=%t.pcm
+
+#ifdef EXPORT
+// expected-no-diagnostics
+export
+#else
+// expected-note@+2 {{add 'export' here}}
+#endif
+module M;
+
+#ifndef EXPORT
+// expected-error@+2 {{private module fragment in module implementation unit}}
+#endif
+module :private;
--- /dev/null
+// RUN: %clang_cc1 -std=c++2a %s -DERRORS -verify
+// RUN: %clang_cc1 -std=c++2a %s -emit-module-interface -o %t.pcm
+// RUN: %clang_cc1 -std=c++2a %s -fmodule-file=%t.pcm -DIMPLEMENTATION -verify -Db=b2 -Dc=c2
+
+module;
+
+#ifdef ERRORS
+export int a; // expected-error {{after the module declaration}}
+#endif
+
+#ifndef IMPLEMENTATION
+export
+#else
+// expected-error@#1 {{can only be used within a module interface unit}}
+// expected-error@#2 {{can only be used within a module interface unit}}
+// expected-note@+2 1+{{add 'export'}}
+#endif
+module M;
+
+export int b; // #1
+namespace N {
+ export int c; // #2
+}
+
+#ifdef ERRORS
+namespace {
+ export int d1; // FIXME: invalid
+ namespace X {
+ export int d2; // FIXME: invalid
+ }
+}
+
+export export int e; // expected-error {{within another export declaration}}
+export { export int f; } // expected-error {{within another export declaration}}
+
+module :private; // expected-note {{private module fragment begins here}}
+export int priv; // expected-error {{export declaration cannot be used in a private module fragment}}
+#endif
// expected-no-diagnostics
export module A;
#elif IMPLEMENTATION
-module A;
+module A; // #module-decl
#ifdef BUILT_AS_INTERFACE
// expected-error@-2 {{missing 'export' specifier in module declaration while building module interface}}
#define INTERFACE
#ifndef INTERFACE
export int b; // expected-error {{export declaration can only be used within a module interface unit}}
+#ifdef IMPLEMENTATION
+// expected-note@#module-decl {{add 'export' here}}
+#endif
#else
export int a;
#endif