diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -4467,6 +4467,16 @@ /// @import std.vector; /// \endcode /// +/// A C++20 module import declaration imports the named module or partition. +/// Periods are permitted in C++20 module names, but have no semantic meaning. +/// For example: +/// \code +/// import NamedModule; +/// import :SomePartition; // Must be a partition of the current module. +/// import Names.Like.this; // Allowed. +/// import :and.Also.Partition.names; +/// \endcode +/// /// Import declarations can also be implicitly generated from /// \#include/\#import directives. class ImportDecl final : public Decl, diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1541,9 +1541,11 @@ "expected ';' after private module fragment declaration">; def err_missing_before_module_end : Error<"expected %0 at end of module">; def err_unsupported_module_partition : Error< - "sorry, module partitions are not yet supported">; + "module partitions are only supported for C++20 onwards">; def err_import_not_allowed_here : Error< "imports must immediately follow the module declaration">; +def err_partition_import_outside_module : Error< + "module partition imports must be within a module purview">; def err_import_in_wrong_fragment : Error< "module%select{| partition}0 imports cannot be in the %select{global|private}1 module fragment">; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2210,6 +2210,7 @@ SourceLocation BeginLoc; clang::Module *Module = nullptr; bool ModuleInterface = false; + bool IsPartition = false; bool ImplicitGlobalModuleFragment = false; VisibleModuleSet OuterVisibleModules; }; @@ -2962,7 +2963,7 @@ /// of a module interface or implementation. DeclGroupPtrTy ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, ModuleDeclKind MDK, - ModuleIdPath Path, + ModuleIdPath Path, ModuleIdPath Partition, ModuleImportState &ImportState); /// The parser has processed a global-module-fragment declaration that begins @@ -2983,10 +2984,12 @@ /// could be the location of an '@', 'export', or 'import'. /// \param ExportLoc The location of the 'export' keyword, if any. /// \param ImportLoc The location of the 'import' keyword. - /// \param Path The module access path. + /// \param Path The module toplevel name as an access path. + /// \param Partition The module partition name as an access path. DeclResult ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, - SourceLocation ImportLoc, ModuleIdPath Path); + SourceLocation ImportLoc, ModuleIdPath Path, + ModuleIdPath Partition = {}); DeclResult ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, SourceLocation ImportLoc, Module *M, diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -2364,20 +2364,19 @@ } SmallVector, 2> Path; - if (ParseModuleName(ModuleLoc, Path, /*IsImport*/false)) + if (ParseModuleName(ModuleLoc, Path, /*IsImport*/ false)) return nullptr; // Parse the optional module-partition. + SmallVector, 2> Partition; if (Tok.is(tok::colon)) { SourceLocation ColonLoc = ConsumeToken(); - SmallVector, 2> Partition; - if (ParseModuleName(ModuleLoc, Partition, /*IsImport*/false)) + if (!getLangOpts().CPlusPlusModules) + Diag(ColonLoc, diag::err_unsupported_module_partition) + << SourceRange(ColonLoc, Partition.back().second); + // Recover by ignoring the partition name. + else if (ParseModuleName(ModuleLoc, Partition, /*IsImport*/ false)) return nullptr; - - // FIXME: Support module partition declarations. - Diag(ColonLoc, diag::err_unsupported_module_partition) - << SourceRange(ColonLoc, Partition.back().second); - // Recover by parsing as a non-partition. } // We don't support any module attributes yet; just parse them and diagnose. @@ -2387,18 +2386,19 @@ ExpectAndConsumeSemi(diag::err_module_expected_semi); - return Actions.ActOnModuleDecl(StartLoc, ModuleLoc, MDK, Path, ImportState); + return Actions.ActOnModuleDecl(StartLoc, ModuleLoc, MDK, Path, Partition, + ImportState); } /// Parse a module import declaration. This is essentially the same for -/// Objective-C and the C++ Modules TS, except for the leading '@' (in ObjC) -/// and the trailing optional attributes (in C++). +/// Objective-C and C++20 except for the leading '@' (in ObjC) and the +/// trailing optional attributes (in C++). /// /// [ObjC] @import declaration: /// '@' 'import' module-name ';' /// [ModTS] module-import-declaration: /// 'import' module-name attribute-specifier-seq[opt] ';' -/// [C++2a] module-import-declaration: +/// [C++20] module-import-declaration: /// 'export'[opt] 'import' module-name /// attribute-specifier-seq[opt] ';' /// 'export'[opt] 'import' module-partition @@ -2418,9 +2418,10 @@ bool IsObjCAtImport = Tok.isObjCAtKeyword(tok::objc_import); SourceLocation ImportLoc = ConsumeToken(); + // For C++20 modules, we can have "name" or ":Partition name" as valid input. SmallVector, 2> Path; + SmallVector, 2> Partition; Module *HeaderUnit = nullptr; - if (Tok.is(tok::header_name)) { // This is a header import that the preprocessor decided we should skip // because it was malformed in some way. Parse and ignore it; it's already @@ -2430,17 +2431,16 @@ // This is a header import that the preprocessor mapped to a module import. HeaderUnit = reinterpret_cast(Tok.getAnnotationValue()); ConsumeAnnotationToken(); - } else if (getLangOpts().CPlusPlusModules && Tok.is(tok::colon)) { + } else if (Tok.is(tok::colon)) { SourceLocation ColonLoc = ConsumeToken(); - if (ParseModuleName(ImportLoc, Path, /*IsImport*/true)) + if (!getLangOpts().CPlusPlusModules) + Diag(ColonLoc, diag::err_unsupported_module_partition) + << SourceRange(ColonLoc, Partition.back().second); + // Recover by leaving partition empty. + else if (ParseModuleName(ColonLoc, Partition, /*IsImport*/ true)) return nullptr; - - // FIXME: Support module partition import. - Diag(ColonLoc, diag::err_unsupported_module_partition) - << SourceRange(ColonLoc, Path.back().second); - return nullptr; } else { - if (ParseModuleName(ImportLoc, Path, /*IsImport*/true)) + if (ParseModuleName(ImportLoc, Path, /*IsImport*/ true)) return nullptr; } @@ -2457,21 +2457,24 @@ // Diagnose mis-imports. bool SeenError = true; + bool HasPart = !Partition.empty(); switch (ImportState) { case Sema::ModuleImportState::ImportAllowed: SeenError = false; break; case Sema::ModuleImportState::FirstDecl: case Sema::ModuleImportState::NotACXX20Module: - // TODO: These cases will be an error when partitions are implemented. - SeenError = false; + // We can only import a partition within a module purview. + if (HasPart) + Diag(ImportLoc, diag::err_partition_import_outside_module); + else + SeenError = false; break; case Sema::ModuleImportState::GlobalFragment: // We can only have pre-processor directives in the global module // fragment. We can, however have a header unit import here. if (!HeaderUnit) - // We do not have partition support yet, so first arg is 0. - Diag(ImportLoc, diag::err_import_in_wrong_fragment) << 0 << 0; + Diag(ImportLoc, diag::err_import_in_wrong_fragment) << HasPart << 0; else SeenError = false; break; @@ -2482,8 +2485,7 @@ SeenError = false; break; case Sema::ModuleImportState::PrivateFragment: - // We do not have partition support yet, so first arg is 0. - Diag(ImportLoc, diag::err_import_in_wrong_fragment) << 0 << 1; + Diag(ImportLoc, diag::err_import_in_wrong_fragment) << HasPart << 1; break; } if (SeenError) { @@ -2495,8 +2497,9 @@ if (HeaderUnit) Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, HeaderUnit); - else if (!Path.empty()) - Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Path); + else if (!Path.empty() || !Partition.empty()) + Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Path, + Partition); ExpectAndConsumeSemi(diag::err_module_expected_semi); if (Import.isInvalid()) return nullptr; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -1608,6 +1608,14 @@ if (OldM && OldM->Kind == Module::PrivateModuleFragment) OldM = OldM->Parent; + // If we have a decl in a module partition, it is part of the containing + // module (which is the only thing that can be importing it). + if (NewM && OldM && + (OldM->Kind == Module::ModulePartitionInterface || + OldM->Kind == Module::ModulePartitionImplementation)) { + return false; + } + if (NewM == OldM) return false; diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp --- a/clang/lib/Sema/SemaLookup.cpp +++ b/clang/lib/Sema/SemaLookup.cpp @@ -1561,8 +1561,12 @@ static bool isInCurrentModule(const Module *M, const LangOptions &LangOpts) { // If M is the global module fragment of a module that we've not yet finished // parsing, then it must be part of the current module. + // If it's a partition, then it must be visible to an importer (since only + // another partition or the named module can import it). return M->getTopLevelModuleName() == LangOpts.CurrentModule || - (M->Kind == Module::GlobalModuleFragment && !M->Parent); + (M->Kind == Module::GlobalModuleFragment && !M->Parent) || + M->Kind == Module::ModulePartitionInterface || + M->Kind == Module::ModulePartitionImplementation; } bool Sema::hasVisibleMergedDefinition(NamedDecl *Def) { diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp --- a/clang/lib/Sema/SemaModule.cpp +++ b/clang/lib/Sema/SemaModule.cpp @@ -54,6 +54,23 @@ } } +// We represent the primary and partition names as 'Paths' which are sections +// of the hierarchical access path for a clang module. However for C++20 +// the periods in a name are just another character, and we will need to +// flatten them into a string. +static std::string stringFromPath(ModuleIdPath Path) { + std::string Name; + if (Path.empty()) + return Name; + + for (auto &Piece : Path) { + if (!Name.empty()) + Name += "."; + Name += Piece.first->getName(); + } + return Name; +} + Sema::DeclGroupPtrTy Sema::ActOnGlobalModuleFragmentDecl(SourceLocation ModuleLoc) { if (!ModuleScopes.empty() && @@ -80,11 +97,10 @@ return nullptr; } -Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, - SourceLocation ModuleLoc, - ModuleDeclKind MDK, - ModuleIdPath Path, - ModuleImportState &ImportState) { +Sema::DeclGroupPtrTy +Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, + ModuleDeclKind MDK, ModuleIdPath Path, + ModuleIdPath Partition, ModuleImportState &ImportState) { assert((getLangOpts().ModulesTS || getLangOpts().CPlusPlusModules) && "should only have module decl in Modules TS or C++20"); @@ -163,19 +179,20 @@ // Flatten the dots in a module name. Unlike Clang's hierarchical module map // modules, the dots here are just another character that can appear in a // module name. - std::string ModuleName; - for (auto &Piece : Path) { - if (!ModuleName.empty()) - ModuleName += "."; - ModuleName += Piece.first->getName(); + std::string ModuleName = stringFromPath(Path); + bool IsPartition = !Partition.empty(); + if (IsPartition) { + ModuleName += ":"; + ModuleName += stringFromPath(Partition); } - // If a module name was explicitly specified on the command line, it must be // correct. if (!getLangOpts().CurrentModule.empty() && getLangOpts().CurrentModule != ModuleName) { Diag(Path.front().second, diag::err_current_module_name_mismatch) - << SourceRange(Path.front().second, Path.back().second) + << SourceRange(Path.front().second, IsPartition + ? Partition.back().second + : Path.back().second) << getLangOpts().CurrentModule; return nullptr; } @@ -202,6 +219,8 @@ // Create a Module for the module that we're defining. Mod = Map.createModuleForInterfaceUnit(ModuleLoc, ModuleName, GlobalModuleFragment); + if (IsPartition) + Mod->Kind = Module::ModulePartitionInterface; assert(Mod && "module creation should not fail"); break; } @@ -209,14 +228,26 @@ case ModuleDeclKind::Implementation: std::pair ModuleNameLoc( PP.getIdentifierInfo(ModuleName), Path[0].second); - Mod = getModuleLoader().loadModule(ModuleLoc, {ModuleNameLoc}, - Module::AllVisible, - /*IsInclusionDirective=*/false); - if (!Mod) { - Diag(ModuleLoc, diag::err_module_not_defined) << ModuleName; - // Create an empty module interface unit for error recovery. + if (IsPartition) { + // Create an interface, but note that it is an implementation + // unit. Mod = Map.createModuleForInterfaceUnit(ModuleLoc, ModuleName, GlobalModuleFragment); + Mod->Kind = Module::ModulePartitionImplementation; + } else { + // C++20 A module-declaration that contains neither an export- + // keyword nor a module-partition implicitly imports the primary + // module interface unit of the module as if by a module-import- + // declaration. + Mod = getModuleLoader().loadModule(ModuleLoc, {ModuleNameLoc}, + Module::AllVisible, + /*IsInclusionDirective=*/false); + if (!Mod) { + Diag(ModuleLoc, diag::err_module_not_defined) << ModuleName; + // Create an empty module interface unit for error recovery. + Mod = Map.createModuleForInterfaceUnit(ModuleLoc, ModuleName, + GlobalModuleFragment); + } } break; } @@ -233,7 +264,9 @@ // Switch from the global module fragment (if any) to the named module. ModuleScopes.back().BeginLoc = StartLoc; ModuleScopes.back().Module = Mod; - ModuleScopes.back().ModuleInterface = MDK != ModuleDeclKind::Implementation; + ModuleScopes.back().ModuleInterface = + (MDK != ModuleDeclKind::Implementation || IsPartition); + ModuleScopes.back().IsPartition = IsPartition; VisibleModules.setVisible(Mod, ModuleLoc); // From now on, we have an owning module for all declarations we see. @@ -317,17 +350,40 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, - SourceLocation ImportLoc, - ModuleIdPath Path) { - // Flatten the module path for a C++20 or Modules TS module name. + SourceLocation ImportLoc, ModuleIdPath Path, + ModuleIdPath Partition) { + + bool IsPartition = !Partition.empty(); + bool Cxx20Mode = getLangOpts().CPlusPlusModules || getLangOpts().ModulesTS; + assert((!IsPartition || Cxx20Mode) && "partition seen in non-C++20 code?"); + assert((!IsPartition || Path.empty()) && + "trying to import a partition with its named module specified?"); + + // For a C++20 module name, flatten into a single identifier with the source + // location of the first component. std::pair ModuleNameLoc; + std::string ModuleName; - if (getLangOpts().CPlusPlusModules || getLangOpts().ModulesTS) { - for (auto &Piece : Path) { - if (!ModuleName.empty()) - ModuleName += "."; - ModuleName += Piece.first->getName(); + if (IsPartition) { + // We already checked that we are in a module purview in the parser. + assert(!ModuleScopes.empty() && "in a module purview, but no module?"); + Module *NamedMod = ModuleScopes.back().Module; + if (ModuleScopes.back().IsPartition) { + // We're importing a partition into a partition, find the name of the + // owning named module. + size_t P = NamedMod->Name.find_first_of(":"); + ModuleName = NamedMod->Name.substr(0, P + 1); + } else { + // We're importing a partition into the named module itself (either the + // interface or an implementation TU). + ModuleName = NamedMod->Name; + ModuleName += ":"; } + ModuleName += stringFromPath(Partition); + ModuleNameLoc = {PP.getIdentifierInfo(ModuleName), Partition[0].second}; + Partition = ModuleIdPath(ModuleNameLoc); + } else if (Cxx20Mode) { + ModuleName = stringFromPath(Path); ModuleNameLoc = {PP.getIdentifierInfo(ModuleName), Path[0].second}; Path = ModuleIdPath(ModuleNameLoc); } @@ -340,13 +396,14 @@ return true; } - Module *Mod = - getModuleLoader().loadModule(ImportLoc, Path, Module::AllVisible, - /*IsInclusionDirective=*/false); + Module *Mod = getModuleLoader().loadModule( + ImportLoc, IsPartition ? Partition : Path, Module::AllVisible, + /*IsInclusionDirective=*/false); if (!Mod) return true; - return ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Mod, Path); + return ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Mod, + IsPartition ? Partition : Path); } /// Determine whether \p D is lexically within an export-declaration. @@ -359,8 +416,8 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, - SourceLocation ImportLoc, - Module *Mod, ModuleIdPath Path) { + SourceLocation ImportLoc, Module *Mod, + ModuleIdPath Path) { VisibleModules.setVisible(Mod, ImportLoc); checkModuleImportContext(*this, Mod, ImportLoc, CurContext); @@ -379,22 +436,26 @@ } SmallVector IdentifierLocs; - Module *ModCheck = Mod; - for (unsigned I = 0, N = Path.size(); I != N; ++I) { - // If we've run out of module parents, just drop the remaining identifiers. - // We need the length to be consistent. - if (!ModCheck) - break; - ModCheck = ModCheck->Parent; - - IdentifierLocs.push_back(Path[I].second); - } - // If this was a header import, pad out with dummy locations. - // FIXME: Pass in and use the location of the header-name token in this case. if (Path.empty()) { - for (; ModCheck; ModCheck = ModCheck->Parent) { + // If this was a header import, pad out with dummy locations. + // FIXME: Pass in and use the location of the header-name token in this + // case. + for (Module *ModCheck = Mod; ModCheck; ModCheck = ModCheck->Parent) IdentifierLocs.push_back(SourceLocation()); + } else if (getLangOpts().CPlusPlusModules && !Mod->Parent) { + // A single identifier for the whole name. + IdentifierLocs.push_back(Path[0].second); + } else { + Module *ModCheck = Mod; + for (unsigned I = 0, N = Path.size(); I != N; ++I) { + // If we've run out of module parents, just drop the remaining + // identifiers. We need the length to be consistent. + if (!ModCheck) + break; + ModCheck = ModCheck->Parent; + + IdentifierLocs.push_back(Path[I].second); } } @@ -420,6 +481,10 @@ // An export-declaration shall inhabit a namespace scope and appear in the // purview of a module interface unit. Diag(ExportLoc, diag::err_export_not_in_module_interface) << 0; + } else if (getLangOpts().isCompilingModule()) { + Module *ThisModule = PP.getHeaderSearchInfo().lookupModule( + getLangOpts().CurrentModule, ExportLoc, false, false); + assert(ThisModule && "was expecting a module if building one"); } return Import; @@ -457,6 +522,12 @@ getModuleLoader().makeModuleVisible(Mod, Module::AllVisible, DirectiveLoc); VisibleModules.setVisible(Mod, DirectiveLoc); + + if (getLangOpts().isCompilingModule()) { + Module *ThisModule = PP.getHeaderSearchInfo().lookupModule( + getLangOpts().CurrentModule, DirectiveLoc, false, false); + assert(ThisModule && "was expecting a module if building one"); + } } void Sema::ActOnModuleBegin(SourceLocation DirectiveLoc, Module *Mod) { @@ -757,8 +828,9 @@ // Enter the scope of the global module. ModuleScopes.push_back({BeginLoc, GlobalModuleFragment, /*ModuleInterface=*/false, + /*IsPartition=*/false, /*ImplicitGlobalModuleFragment=*/IsImplicit, - /*VisibleModuleSet*/ {}}); + /*OuterVisibleModules=*/{}}); VisibleModules.setVisible(GlobalModuleFragment, BeginLoc); return GlobalModuleFragment; diff --git a/clang/test/CXX/module/module.unit/p3.cpp b/clang/test/CXX/module/module.unit/p3.cpp --- a/clang/test/CXX/module/module.unit/p3.cpp +++ b/clang/test/CXX/module/module.unit/p3.cpp @@ -1,4 +1,4 @@ // RUN: %clang_cc1 -std=c++2a -verify %s -export module foo:bar; // expected-error {{sorry, module partitions are not yet supported}} -import :baz; // expected-error {{sorry, module partitions are not yet supported}} +export module foo:bar; +import :baz; // expected-error {{module 'foo:baz' not found}} diff --git a/clang/test/CXX/module/module.unit/p8.cpp b/clang/test/CXX/module/module.unit/p8.cpp --- a/clang/test/CXX/module/module.unit/p8.cpp +++ b/clang/test/CXX/module/module.unit/p8.cpp @@ -12,7 +12,7 @@ #elif MODE == 1 // expected-no-diagnostics -module foo; +module foo; // Implementation, implicitly imports foo. #define IMPORTED #elif MODE == 2 @@ -21,15 +21,15 @@ #define IMPORTED #elif MODE == 3 -export module bar; +export module bar; // A different module #elif MODE == 4 -module foo:bar; // expected-error {{not yet supported}} -#define IMPORTED // FIXME +module foo:bar; // Partition implementation +//#define IMPORTED (we don't import foo here) #elif MODE == 5 -export module foo:bar; // expected-error {{not yet supported}} expected-error {{redefinition}} expected-note@* {{loaded from}} -#define IMPORTED // FIXME +export module foo:bar; // Partition interface +//#define IMPORTED (we don't import foo here) #endif diff --git a/clang/test/Modules/cxx20-multiple-partitions.cpp b/clang/test/Modules/cxx20-multiple-partitions.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Modules/cxx20-multiple-partitions.cpp @@ -0,0 +1,46 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/partition1.cpp \ +// RUN: -o %t/A_part1.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/partition2.cpp \ +// RUN: -o %t/A_part2.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/partition3.cpp \ +// RUN: -o %t/A_part3.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/moduleA.cpp \ +// RUN: -fmodule-file=%t/A_part1.pcm -fmodule-file=%t/A_part2.pcm \ +// RUN: -fmodule-file=%t/A_part3.pcm -o %t/A.pcm + +// expected-no-diagnostics + +//--- partition1.cpp + +export module A:Part1; + +int part1(); + +//--- partition2.cpp + +export module A:Part2; + +int part2(); + +//--- partition3.cpp + +export module A:Part3; + +int part3(); + +//--- moduleA.cpp + +export module A; + +import :Part1; +export import :Part2; +import :Part3; + +int foo(); diff --git a/clang/test/Modules/cxx20-partition-diagnostics-a.cpp b/clang/test/Modules/cxx20-partition-diagnostics-a.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Modules/cxx20-partition-diagnostics-a.cpp @@ -0,0 +1,19 @@ +// Module Partition diagnostics + +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t + +// RUN: %clang_cc1 -std=c++20 -fsyntax-only %t/bad-import.cpp -verify + +// RUN: %clang_cc1 -std=c++20 -fsyntax-only %t/bad-partition.cpp -verify + +//--- bad-import.cpp + +import :B; // expected-error {{module partition imports must be within a module purview}} + +//--- bad-partition.cpp + +module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}} + +import :Part; // expected-error {{module partition imports cannot be in the global module fragment}}