Submodules can be private (#8581)

This commit is contained in:
Pavel Marek 2023-12-19 19:13:44 +01:00 committed by GitHub
parent 277dfb1991
commit 4cb2439890
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 69 additions and 54 deletions

View File

@ -1,3 +1,5 @@
private
import project.Any.Any
import project.Data.Array.Array
import project.Data.Maybe.Maybe

View File

@ -58,11 +58,11 @@ methods and constructors.
Modules can be specified as private. Private modules cannot be imported from
other projects. Private modules can be imported from the same project.
A hierarchy of submodules cannot mix public and private modules. In other words,
if a module is public, its whole subtree must be public as well. For example,
having a public module `A` and private submodule `A.B` is forbidden and shall be
reported as an error during compilation. But having a private module `A` as well
as private module `A.B` is OK.
A hierarchy of submodules can mix public and private modules. By _hierarchy_, we
mean a parent-child relationship between modules. It does not make sense to
create a public submodule of a private module and export it, but it is allowed.
Note that this is because of current limitations of the implementation, this
might be more strict in the future.
### Types

View File

@ -65,6 +65,7 @@ public final class PrivateModuleAnalysis implements IRPass {
List<Import> importErrors = new ArrayList<>();
List<Export> exportErrors = new ArrayList<>();
var isCurrentModulePrivate = moduleIr.isPrivate();
var isCurrentModuleSynthetic = moduleContext.isSynthetic();
// Ensure that imported modules from a different project are not private.
bindingsMap
@ -97,38 +98,21 @@ public final class PrivateModuleAnalysis implements IRPass {
ImportExport.apply$default$4()));
}
// Ensure that private modules are not exported and that the hierarchy of submodules
// does not mix public and private modules.
// Ensure that private modules are not exported
bindingsMap
.getDirectlyExportedModules()
.foreach(
expModule -> {
var expModuleRef = expModule.target().module().unsafeAsModule("should succeed");
if (expModuleRef.isPrivate()) {
if (expModuleRef.isPrivate() && !isCurrentModuleSynthetic) {
var associatedExportIR = findExportIRByName(moduleIr, expModuleRef.getName());
assert associatedExportIR.isDefined();
if (isSubmoduleName(moduleContext.getName(), expModuleRef.getName())) {
var haveSameVisibility = isCurrentModulePrivate == expModuleRef.isPrivate();
if (!haveSameVisibility) {
exportErrors.add(
ImportExport.apply(
associatedExportIR.get(),
new ImportExport.SubmoduleVisibilityMismatch(
moduleContext.getName().toString(),
expModuleRef.getName().toString(),
isCurrentModulePrivate ? "private" : "public",
expModuleRef.isPrivate() ? "private" : "public"),
ImportExport.apply$default$3(),
ImportExport.apply$default$4()));
}
} else {
exportErrors.add(
ImportExport.apply(
associatedExportIR.get(),
new ImportExport.ExportPrivateModule(expModuleRef.getName().toString()),
ImportExport.apply$default$3(),
ImportExport.apply$default$4()));
}
exportErrors.add(
ImportExport.apply(
associatedExportIR.get(),
new ImportExport.ExportPrivateModule(expModuleRef.getName().toString()),
ImportExport.apply$default$3(),
ImportExport.apply$default$4()));
}
return null;
});
@ -152,14 +136,6 @@ public final class PrivateModuleAnalysis implements IRPass {
moduleIr.id());
}
private boolean isSubmoduleName(QualifiedName parentModName, QualifiedName subModName) {
if (subModName.getParent().isDefined()) {
return parentModName.item().equals(subModName.getParent().get().item());
} else {
return false;
}
}
@Override
public Expression runExpression(Expression ir, InlineContext inlineContext) {
return ir;

View File

@ -1,7 +1,5 @@
import project.Sub
export project.Sub
# Fails at compile time - cannot mix private and public modules in a module subtree.
main =
42

View File

@ -0,0 +1,7 @@
name: Test_Private_Modules_6
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>"
prefer-local-libraries: true

View File

@ -0,0 +1,5 @@
import project.Sub.Priv
main =
Priv.foo

View File

@ -0,0 +1,6 @@
# This is a private module, that is under a synthetic "Sub" module.
# By default, synthetic modules are public.
private
foo = 42

View File

@ -0,0 +1,7 @@
name: Test_Private_Modules_7
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>"
prefer-local-libraries: true

View File

@ -0,0 +1,6 @@
import project.Sub.Pub
export project.Sub.Pub
main =
Pub.foo

View File

@ -0,0 +1,4 @@
# This is a private parent module
private

View File

@ -0,0 +1,2 @@
foo = 42

View File

@ -226,7 +226,7 @@ class ImportsTest extends PackageTest {
).toString shouldEqual "42"
}
"Private modules" should "be able to import non-private stuff" in {
"Private modules" should "be able to import non-private stuff from different project" in {
evalTestProject(
"Test_Private_Modules_2"
).toString shouldEqual "(Pub_Mod_Type.Value 42)"
@ -243,13 +243,10 @@ class ImportsTest extends PackageTest {
)
}
"Private modules" should "not be able to mix private and public submodules" in {
val e = the[InterpreterException] thrownBy evalTestProject(
"Private modules" should "be able to mix private and public submodules" in {
evalTestProject(
"Test_Private_Modules_4"
)
e.getMessage() should include(
"Cannot export submodule 'local.Test_Private_Modules_4.Sub.Priv_SubMod' of module 'local.Test_Private_Modules_4.Sub'"
)
) shouldEqual 42
}
"Private module" should "be able to have only private submodules" in {
@ -258,17 +255,22 @@ class ImportsTest extends PackageTest {
) shouldEqual 42
}
"Private modules" should "be able to mix private and public submodules when private checks are disabled" in {
evalTestProject(
"Test_Private_Modules_4",
Map(RuntimeOptions.DISABLE_PRIVATE_CHECK -> "true")
) shouldEqual 42
}
"Private modules" should "be able to import private modules from different project when private checks are disabled" in {
evalTestProject(
"Test_Private_Modules_3",
Map(RuntimeOptions.DISABLE_PRIVATE_CHECK -> "true")
) shouldEqual "Success"
}
"Private modules" should "be able to have a private submodule under a public synthetic module" in {
evalTestProject(
"Test_Private_Modules_6"
) shouldEqual 42
}
"Private modules" should "be able to have a public submodule under a private module" ignore {
evalTestProject(
"Test_Private_Modules_7"
) shouldEqual 42
}
}