forked from OSchip/llvm-project
[C++20] [Modules] Judge current module correctly
Now the implementation would accept following code: ``` //--- impl.cppm module M:impl; class A {}; //--- M.cppm export module M; import :impl; //--- Use.cpp import M; void test() { A a; // Expected error. A is not visible here. } ``` which is clearly wrong. The root cause is the implementation of `isInCurrentModule` would return true if the module is a partition! So in the above example, although Use.cpp is not a module unit, `isInCurrentModule ` would still return true when the compiler tries to see if the owning module of `A` is the current module. I believe this is an oversight. This patch tries to fix this problem. Reviewed By: iains Differential Revision: https://reviews.llvm.org/D123837
This commit is contained in:
parent
483efc9ad0
commit
ce2257d69f
|
@ -168,6 +168,8 @@ public:
|
|||
/// 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<Module *> SubModules;
|
||||
|
@ -536,10 +538,23 @@ public:
|
|||
|
||||
/// 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.
|
||||
//
|
||||
// <global> is the default name showed in module map.
|
||||
if (isGlobalModule())
|
||||
return "<global>";
|
||||
|
||||
if (isModulePartition()) {
|
||||
auto pos = Name.find(':');
|
||||
return StringRef(Name.data(), pos);
|
||||
}
|
||||
|
||||
if (isPrivateModule())
|
||||
return getTopLevelModuleName();
|
||||
|
||||
return Name;
|
||||
}
|
||||
|
||||
|
|
|
@ -2239,7 +2239,7 @@ private:
|
|||
/// Namespace definitions that we will export when they finish.
|
||||
llvm::SmallPtrSet<const NamespaceDecl*, 8> 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 @@ private:
|
|||
|
||||
VisibleModuleSet VisibleModules;
|
||||
|
||||
/// Cache for module units which is usable for current module.
|
||||
llvm::DenseSet<const Module *> UsableModuleUnitsCache;
|
||||
|
||||
bool isUsableModule(const Module *M);
|
||||
|
||||
public:
|
||||
/// Get the module owning an entity.
|
||||
Module *getOwningModule(const Decl *Entity) {
|
||||
|
|
|
@ -1558,17 +1558,39 @@ llvm::DenseSet<Module*> &Sema::getLookupModules() {
|
|||
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::hasVisibleMergedDefinition(NamedDecl *Def) {
|
|||
|
||||
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 @@ bool Sema::isModuleVisible(const Module *M, bool ModulePrivate) {
|
|||
// 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))
|
||||
|
|
|
@ -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}}
|
||||
}
|
|
@ -56,7 +56,14 @@ import B;
|
|||
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;
|
||||
|
|
Loading…
Reference in New Issue