diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -168,6 +168,8 @@ /// some C++ module. bool isGlobalModule() const { return Kind == GlobalModuleFragment; } + bool isPrivateModule() const { return Kind == PrivateModuleFragment; } + private: /// The submodules of this module, indexed by name. std::vector SubModules; @@ -536,10 +538,23 @@ /// Get the primary module interface name from a partition. StringRef getPrimaryModuleInterfaceName() const { + // Technically, global module fragment belongs to global module. And global + // module has no name: [module.unit]p6: + // The global module has no name, no module interface unit, and is not + // introduced by any module-declaration. + // + // is the default name showed in module map. + if (isGlobalModule()) + return ""; + if (isModulePartition()) { auto pos = Name.find(':'); return StringRef(Name.data(), pos); } + + if (isPrivateModule()) + return getTopLevelModuleName(); + return Name; } 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 @@ -2239,7 +2239,7 @@ /// Namespace definitions that we will export when they finish. llvm::SmallPtrSet DeferredExportedNamespaces; - /// Get the module whose scope we are currently within. + /// Get the module unit whose scope we are currently within. Module *getCurrentModule() const { return ModuleScopes.empty() ? nullptr : ModuleScopes.back().Module; } @@ -2257,6 +2257,11 @@ VisibleModuleSet VisibleModules; + /// Cache for module units which is usable for current module. + llvm::DenseSet UsableModuleUnitsCache; + + bool isUsableModule(const Module *M); + public: /// Get the module owning an entity. Module *getOwningModule(const Decl *Entity) { 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 @@ -1558,17 +1558,39 @@ return LookupModulesCache; } -/// Determine whether the module M is part of the current module from the -/// perspective of a module-private visibility check. -static bool isInCurrentModule(const Module *M, const LangOptions &LangOpts) { +/// Determine if we could use all the declarations in the module. +bool Sema::isUsableModule(const Module *M) { + assert(M && "We shouldn't check nullness for module here"); + // Return quickly if we cached the result. + if (UsableModuleUnitsCache.count(M)) + return true; + // 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::ModulePartitionInterface || - M->Kind == Module::ModulePartitionImplementation; + // parsing, then M should be the global module fragment in current TU. So it + // should be usable. + // [module.global.frag]p1: + // 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->isGlobalModule() && !M->Parent) || + // If M is the private module fragment, it is usable only if it is within + // the current module unit. And it must be the current parsing module unit + // if it is within the current module unit according to the grammar of the + // private module fragment. + // NOTE: This is covered by the following condition. The intention of the + // check is to avoid string comparison as much as possible. + (M->isPrivateModule() && M == getCurrentModule()) || + // The module unit which is in the same module with the current module + // unit is usable. + // + // FIXME: Here we judge if they are in the same module by comparing the + // string. Is there any better solution? + (M->getPrimaryModuleInterfaceName() == + llvm::StringRef(getLangOpts().CurrentModule).split(':').first)) { + UsableModuleUnitsCache.insert(M); + return true; + } + + return false; } bool Sema::hasVisibleMergedDefinition(NamedDecl *Def) { @@ -1580,7 +1602,7 @@ bool Sema::hasMergedDefinitionInCurrentModule(NamedDecl *Def) { for (const Module *Merged : Context.getModulesWithMergedDefinition(Def)) - if (isInCurrentModule(Merged, getLangOpts())) + if (isUsableModule(Merged)) return true; return false; } @@ -1762,7 +1784,7 @@ // means it is part of the current module. For any other query, that means it // is in our visible module set. if (ModulePrivate) { - if (isInCurrentModule(M, getLangOpts())) + if (isUsableModule(M)) return true; else if (M->Kind == Module::ModuleKind::ModulePartitionImplementation && isModuleDirectlyImported(M)) diff --git a/clang/test/CXX/module/module.import/p2.cpp b/clang/test/CXX/module/module.import/p2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/module/module.import/p2.cpp @@ -0,0 +1,73 @@ +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// RUN: %clang_cc1 -std=c++20 %t/impl.cppm -emit-module-interface -o %t/M-impl.pcm +// RUN: %clang_cc1 -std=c++20 %t/M.cppm -emit-module-interface -fprebuilt-module-path=%t -o %t/M.pcm +// RUN: %clang_cc1 -std=c++20 %t/Use.cpp -fprebuilt-module-path=%t -verify -fsyntax-only +// RUN: %clang_cc1 -std=c++20 %t/UseInPartA.cppm -fprebuilt-module-path=%t -verify -fsyntax-only +// RUN: %clang_cc1 -std=c++20 %t/UseInAnotherModule.cppm -fprebuilt-module-path=%t -verify -fsyntax-only +// RUN: %clang_cc1 -std=c++20 %t/Private.cppm -emit-module-interface -fprebuilt-module-path=%t -o %t/A.pcm +// RUN: %clang_cc1 -std=c++20 %t/TryUseFromPrivate.cpp -fprebuilt-module-path=%t -verify -fsyntax-only +// RUN: %clang_cc1 -std=c++20 %t/Global.cppm -emit-module-interface -fprebuilt-module-path=%t -o %t/C.pcm +// RUN: %clang_cc1 -std=c++20 %t/UseGlobal.cpp -fprebuilt-module-path=%t -verify -fsyntax-only + +//--- impl.cppm +module M:impl; +class A {}; + +//--- M.cppm +export module M; +import :impl; +export A f(); + +//--- Use.cpp +import M; +void test() { + A a; // expected-error {{unknown type name 'A'}} +} + +//--- UseInPartA.cppm +// expected-no-diagnostics +export module M:partA; +import :impl; +void test() { + A a; +} + +//--- UseInAnotherModule.cppm +export module B; +import M; +void test() { + A a; // expected-error {{unknown type name 'A'}} +} + +//--- Private.cppm +export module A; +module :private; + +class A {}; +void test() { + A a; +} + +//--- TryUseFromPrivate.cpp + +import A; +void test() { + A a; // expected-error {{unknown type name 'A'}} +} + +//--- Global.cppm +module; +class A{}; +export module C; +void test() { + A a; +} + +//--- UseGlobal.cpp +import C; +void test() { + A a; // expected-error {{'A' must be declared before it is used}} + // expected-note@Global.cppm:2 {{declaration here is not visible}} +} diff --git a/clang/test/Modules/cxx20-10-1-ex2.cpp b/clang/test/Modules/cxx20-10-1-ex2.cpp --- a/clang/test/Modules/cxx20-10-1-ex2.cpp +++ b/clang/test/Modules/cxx20-10-1-ex2.cpp @@ -56,7 +56,14 @@ int &c = n; // expected-error {{use of undeclared identifier}} //--- std10-1-ex2-tu7.cpp +// expected-no-diagnostics 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 }} +import :X2; // X2 is an implementation unit import B. +// According to [module.import]p7: +// Additionally, when a module-import-declaration in a module unit of some +// module M imports another module unit U of M, it also imports all +// translation units imported by non-exported module-import-declarations in +// the module unit purview of U. +// +// So B is imported in B:X3 due to B:X2 imported B. So n is visible here. +int &c = n;