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 @@ -4449,6 +4449,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 @@ -1536,7 +1536,7 @@ "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">; def err_export_empty : Error<"export declaration cannot be empty">; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10970,6 +10970,8 @@ "import of module '%0' appears within same top-level module '%1'">; def err_module_import_in_implementation : Error< "@import of module '%0' in implementation of '%1'; use #import">; +def err_module_partition_import_global : Error< + "module partition '%0' cannot be imported into the global module">; // C++ Modules def err_module_decl_not_at_start : Error< 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 @@ -2213,6 +2213,7 @@ SourceLocation BeginLoc; clang::Module *Module = nullptr; bool ModuleInterface = false; + bool IsPartition = false; bool ImplicitGlobalModuleFragment = false; VisibleModuleSet OuterVisibleModules; }; @@ -2951,7 +2952,8 @@ /// of a module interface or implementation. DeclGroupPtrTy ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, ModuleDeclKind MDK, - ModuleIdPath Path, bool IsFirstDecl); + ModuleIdPath Path, ModuleIdPath Partition, + bool IsFirstDecl); /// The parser has processed a global-module-fragment declaration that begins /// the definition of the global module fragment of the current module unit. @@ -2971,14 +2973,17 @@ /// 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 NamePath 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 NamePath, + ModuleIdPath Partition = {}); DeclResult ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, SourceLocation ImportLoc, Module *M, - ModuleIdPath Path = {}); + ModuleIdPath NamePath = {}); /// The parser has processed a module import translated from a /// #include or similar preprocessing directive. diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -1550,6 +1550,8 @@ return nullptr; case Module::ModuleInterfaceUnit: + // For decls in partitions, inn principle, the owning module is really + // the named module, but that might not be available here. case Module::ModulePartitionInterface: case Module::ModulePartitionImplementation: return 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 @@ -2342,17 +2342,16 @@ 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 parsing as a non-partition. + 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. ParsedAttributesWithRange Attrs(AttrFactory); @@ -2361,7 +2360,8 @@ ExpectAndConsumeSemi(diag::err_module_expected_semi); - return Actions.ActOnModuleDecl(StartLoc, ModuleLoc, MDK, Path, IsFirstDecl); + return Actions.ActOnModuleDecl(StartLoc, ModuleLoc, MDK, Path, + Partition, IsFirstDecl); } /// Parse a module import declaration. This is essentially the same for @@ -2391,8 +2391,10 @@ bool IsObjCAtImport = Tok.isObjCAtKeyword(tok::objc_import); SourceLocation ImportLoc = ConsumeToken(); - SmallVector, 2> Path; + SmallVector, 2> NamePath; + SmallVector, 2> Partition; Module *HeaderUnit = nullptr; + bool IsPartition = false; if (Tok.is(tok::header_name)) { // This is a header import that the preprocessor decided we should skip @@ -2403,17 +2405,18 @@ // 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)) + bool Err = ParseModuleName(ImportLoc, Partition, /*IsImport*/true); + if (!getLangOpts().CPlusPlusModules) { + Diag(ColonLoc, diag::err_unsupported_module_partition) + << SourceRange(ColonLoc, Partition.back().second); return nullptr; - - // FIXME: Support module partition import. - Diag(ColonLoc, diag::err_unsupported_module_partition) - << SourceRange(ColonLoc, Path.back().second); - return nullptr; + } else if (Err) + return nullptr; + IsPartition = true; } else { - if (ParseModuleName(ImportLoc, Path, /*IsImport*/true)) + if (ParseModuleName(ImportLoc, NamePath, /*IsImport*/true)) return nullptr; } @@ -2432,8 +2435,9 @@ if (HeaderUnit) Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, HeaderUnit); - else if (!Path.empty()) - Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Path); + else if (!NamePath.empty() || !Partition.empty()) + Import = Actions.ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, + NamePath, 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,13 @@ 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,11 @@ 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->isPartition(); } 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,19 @@ } } +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() && @@ -82,7 +95,8 @@ Sema::DeclGroupPtrTy Sema::ActOnModuleDecl(SourceLocation StartLoc, SourceLocation ModuleLoc, - ModuleDeclKind MDK, ModuleIdPath Path, bool IsFirstDecl) { + ModuleDeclKind MDK, ModuleIdPath NamePath, + ModuleIdPath Partition, bool IsFirstDecl) { assert((getLangOpts().ModulesTS || getLangOpts().CPlusPlusModules) && "should only have module decl in Modules TS or C++20"); @@ -151,19 +165,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(NamePath); + 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) + Diag(NamePath.front().second, diag::err_current_module_name_mismatch) + << SourceRange(NamePath.front().second, + IsPartition ? Partition.back().second + : NamePath.back().second) << getLangOpts().CurrentModule; return nullptr; } @@ -177,7 +192,7 @@ // We can't have parsed or imported a definition of this module or parsed a // module map defining it already. if (auto *M = Map.findModule(ModuleName)) { - Diag(Path[0].second, diag::err_module_redefinition) << ModuleName; + Diag(NamePath[0].second, diag::err_module_redefinition) << ModuleName; if (M->DefinitionLoc.isValid()) Diag(M->DefinitionLoc, diag::note_prev_module_definition); else if (Optional FE = M->getASTFile()) @@ -190,21 +205,35 @@ // 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; } 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. + PP.getIdentifierInfo(ModuleName), NamePath[0].second); + 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; } @@ -221,7 +250,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. @@ -302,27 +333,63 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, SourceLocation ImportLoc, - ModuleIdPath Path) { - // Flatten the module path for a Modules TS module name. + ModuleIdPath NamePath, + ModuleIdPath Partition) { + + bool IsPartition = !Partition.empty(); + bool Cxx20Mode = getLangOpts().CPlusPlusModules || getLangOpts().ModulesTS; + assert ((!IsPartition || Cxx20Mode) && + "partition seen in non-C++20 code?"); + assert ((!IsPartition || NamePath.empty()) && + "trying to import a fully qualified partition name?"); + + // For a C++20/Modules TS module name, flatten into a single identifier + // with the source location of the first component. std::pair ModuleNameLoc; - if (getLangOpts().ModulesTS) { + + if (IsPartition) { std::string ModuleName; - for (auto &Piece : Path) { - if (!ModuleName.empty()) - ModuleName += "."; - ModuleName += Piece.first->getName(); + if (ModuleScopes.empty() || + ModuleScopes.back().Module->Kind == Module::GlobalModuleFragment) { + // This is an attempt to import a partition without naming the module to + // which it belongs. + ModuleName = ":"; + ModuleName += stringFromPath(Partition); + Diag(ImportLoc, diag::err_module_partition_import_global) << ModuleName; + return true; } - ModuleNameLoc = {PP.getIdentifierInfo(ModuleName), Path[0].second}; - Path = ModuleIdPath(ModuleNameLoc); + 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) { + std::string ModuleName = stringFromPath(NamePath); + ModuleNameLoc = {PP.getIdentifierInfo(ModuleName), NamePath[0].second}; + NamePath = ModuleIdPath(ModuleNameLoc); } + Module::NameVisibilityKind Vis = IsPartition ? Module::Hidden + : Module::AllVisible; Module *Mod = - getModuleLoader().loadModule(ImportLoc, Path, Module::AllVisible, - /*IsInclusionDirective=*/false); + getModuleLoader().loadModule(ImportLoc, + IsPartition ? Partition : NamePath, + Vis, /*IsInclusionDirective=*/false); if (!Mod) return true; - return ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Mod, Path); + return ActOnModuleImport(StartLoc, ExportLoc, ImportLoc, Mod, + IsPartition ? Partition : NamePath); } /// Determine whether \p D is lexically within an export-declaration. @@ -336,7 +403,7 @@ DeclResult Sema::ActOnModuleImport(SourceLocation StartLoc, SourceLocation ExportLoc, SourceLocation ImportLoc, - Module *Mod, ModuleIdPath Path) { + Module *Mod, ModuleIdPath NamePath) { VisibleModules.setVisible(Mod, ImportLoc); checkModuleImportContext(*this, Mod, ImportLoc, CurContext); @@ -356,23 +423,30 @@ << Mod->getFullModuleName() << getLangOpts().CurrentModule; } +// CXX/module/module.interface/p2.cpp +// assert ((!getLangOpts().CPlusPlusModules || !Mod->Parent) && +// "unexpected module hierarchy in C++20 mode"); 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 (NamePath.empty()) { + // 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(NamePath[0].second); + } else { + Module *ModCheck = Mod; + for (unsigned I = 0, N = NamePath.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(NamePath[I].second); } } @@ -398,6 +472,11 @@ // 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; @@ -435,6 +514,13 @@ 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) { @@ -730,8 +816,9 @@ // Enter the scope of the global module. ModuleScopes.push_back({BeginLoc, GlobalModule, /*ModuleInterface=*/false, + /*IsPartition=*/false, /*ImplicitGlobalModuleFragment=*/IsImplicit, - /*VisibleModuleSet*/{}}); + /*OuterVisibleModules=*/{}}); VisibleModules.setVisible(GlobalModule, BeginLoc); return GlobalModule; 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-10-1-ex1.cpp b/clang/test/Modules/cxx20-10-1-ex1.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Modules/cxx20-10-1-ex1.cpp @@ -0,0 +1,50 @@ +// The example in the standard is not in required build order. +// revised here + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=1 -x c++ %s \ +// RUN: -o %t/A_Internals.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=2 -x c++ %s \ +// RUN: -fmodule-file=%t/A_Internals.pcm -o %t/A_Foo.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=3 -x c++ %s \ +// RUN: -fmodule-file=%t/A_Foo.pcm -o %t/A.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-obj -D TU=4 -x c++ %s \ +// RUN: -fmodule-file=%t/A.pcm -o %t/ex1.o + +// expected-no-diagnostics + +#if TU == 1 + +module A:Internals; +int bar(); + +#elif TU == 2 + +export module A:Foo; + +import :Internals; + +export int foo() { return 2 * (bar() + 1); } + +#elif TU == 3 + +export module A; +export import :Foo; +export int baz(); + +#elif TU == 4 +module A; + +import :Internals; + +int bar() { return baz() - 10; } +int baz() { return 30; } + +#else +#error "no TU set" +#endif diff --git a/clang/test/Modules/cxx20-10-1-ex2.cpp b/clang/test/Modules/cxx20-10-1-ex2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Modules/cxx20-10-1-ex2.cpp @@ -0,0 +1,58 @@ + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=1 -x c++ %s \ +// RUN: -o %t/B_Y.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=2 -x c++ %s \ +// RUN: -fmodule-file=%t/B_Y.pcm -o %t/B.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=3 -x c++ %s \ +// RUN: -o %t/B_X1.pcm -verify + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=4 -x c++ %s \ +// RUN: -fmodule-file=%t/B.pcm -o %t/B_X2.pcm + +// RUN: %clang_cc1 -std=c++20 -emit-obj -D TU=5 -x c++ %s \ +// RUN: -fmodule-file=%t/B.pcm -o %t/b_tu5.s + +// RUN: %clang_cc1 -std=c++20 -S -D TU=6 -x c++ %s \ +// RUN: -fmodule-file=%t/B.pcm -o %t/b_tu6.s -verify + +// RUN: %clang_cc1 -std=c++20 -emit-module-interface -D TU=7 -x c++ %s \ +// RUN: -fmodule-file=%t/B_X2.pcm -o %t/B_X3.pcm -verify + +#if TU == 1 + module B:Y; + int y(); +// expected-no-diagnostics +#elif TU == 2 + export module B; + import :Y; + int n = y(); +// expected-no-diagnostics +#elif TU == 3 + module B:X1; // does not implicitly import B + int &a = n; // expected-error {{use of undeclared identifier }} +#elif TU == 4 + module B:X2; // does not implicitly import B + import B; + int &b = n; // OK +// expected-no-diagnostics +#elif TU == 5 + module B; // implicitly imports B + int &c = n; // OK +// expected-no-diagnostics +#elif TU == 6 + import B; + // error, n is module-local and this is not a module. + int &c = n; // expected-error {{use of undeclared identifier}} +#elif TU == 7 + module B:X3; // does not implicitly import B + import :X2; // X2 is an implementation so exports nothing. + // error: n not visible here. + int &c = n; // expected-error {{use of undeclared identifier }} +#else +#error "no TU set" +#endif diff --git a/clang/test/Modules/cxx20-module-partition-diagnostics-a.cpp b/clang/test/Modules/cxx20-module-partition-diagnostics-a.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Modules/cxx20-module-partition-diagnostics-a.cpp @@ -0,0 +1,18 @@ +// Module Partition diagnostics + +// RUN: %clang_cc1 -std=c++20 -S -D TU=1 -x c++ %s -o /dev/null -verify + +// RUN: %clang_cc1 -std=c++20 -S -D TU=2 -x c++ %s -o /dev/null -verify + +#if TU == 1 + +import :B; // expected-error {{module partition ':B' cannot be imported into the global module}} + +#elif TU == 2 +module; // expected-error {{missing 'module' declaration at end of global module fragment introduced here}} + +import :Part; // expected-error {{module partition ':Part' cannot be imported into the global module}} + +#else +#error "no TU set" +#endif