Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple exports of the same module #3897

Merged
merged 3 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,14 +438,15 @@
- [Fix performance of method calls on polyglot arrays][3781]
- [Improved support for static and non-static builtins][3791]
- [Missing foreign language generates proper Enso error][3798]
- [Connecting IGV 4 Enso with Engine sources][3810]
- [Made Vector performance to be on par with Array][3811]
- [Introduced IO Permission Contexts][3828]
- [Accept Array-like object seamlessly in builtins][3817]
- [Initialize Builtins at Native Image build time][3821]
- [Add the `Self` keyword referring to current type][3844]
- [Split Atom suggestion entry to Type and Constructor][3835]
- [Connecting IGV 4 Enso with Engine sources][3810]
- [Add the `Self` keyword referring to current type][3844]
- [Support VCS for projects in Language Server][3851]
- [Support multiple exports of the same module][3897]

[3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248
Expand Down Expand Up @@ -502,14 +503,15 @@
[3781]: https://github.com/enso-org/enso/pull/3781
[3791]: https://github.com/enso-org/enso/pull/3791
[3798]: https://github.com/enso-org/enso/pull/3798
[3810]: https://github.com/enso-org/enso/pull/3810
[3811]: https://github.com/enso-org/enso/pull/3811
[3828]: https://github.com/enso-org/enso/pull/3828
[3817]: https://github.com/enso-org/enso/pull/3817
[3821]: https://github.com/enso-org/enso/pull/3821
[3844]: https://github.com/enso-org/enso/pull/3844
[3828]: https://github.com/enso-org/enso/pull/3828
[3835]: https://github.com/enso-org/enso/pull/3835
[3810]: https://github.com/enso-org/enso/pull/3810
[3844]: https://github.com/enso-org/enso/pull/3844
[3851]: https://github.com/enso-org/enso/pull/3851
[3897]: https://github.com/enso-org/enso/pull/3897

# Enso 2.0.0-alpha.18 (2021-10-12)

Expand Down
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2100,8 +2100,8 @@ buildStdLib := Def.inputTaskDyn {
val cmd: String = allStdBits.parsed
val root: File = engineDistributionRoot.value
// Ensure that a complete distribution was built at least once.
// Becasuse of `if` in the sbt task definition and usage of `streams.value` one has to
// delegate to another task defintion (sbt restriction).
// Because of `if` in the sbt task definition and usage of `streams.value` one has to
// delegate to another task definition (sbt restriction).
if ((root / "manifest.yaml").exists) {
pkgStdLibInternal.toTask(cmd)
} else buildEngineDistribution
Expand Down
23 changes: 21 additions & 2 deletions engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ class Compiler(
*/
def runImportsResolution(module: Module): List[Module] = {
initialize()
importResolver.mapImports(module)
try {
importResolver.mapImports(module)
} catch {
case e: ImportResolver.HiddenNamesConflict => reportExportConflicts(e)
}
}

/** Processes the provided language sources, registering any bindings in the
Expand Down Expand Up @@ -368,7 +372,12 @@ class Compiler(
}

private def runImportsAndExportsResolution(module: Module): List[Module] = {
val importedModules = importResolver.mapImports(module)
val importedModules =
try {
importResolver.mapImports(module)
} catch {
case e: ImportResolver.HiddenNamesConflict => reportExportConflicts(e)
}

val requiredModules =
try { new ExportsResolution().run(importedModules) }
Expand Down Expand Up @@ -818,6 +827,16 @@ class Compiler(
}
}

private def reportExportConflicts(exception: Throwable): Nothing = {
if (context.isStrictErrors) {
context.getOut.println("Compiler encountered errors:")
context.getOut.println(exception.getMessage)
throw new CompilationAbortedException
} else {
throw exception
}
}

/** Report the errors encountered when initializing the package repository.
*
* @param err the package repository error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,46 +255,48 @@ case class BindingsMap(
* as and any further symbol restrictions.
*/
def getDirectlyExportedModules: List[ExportedModule] =
resolvedImports.collect { case ResolvedImport(_, Some(exp), mod) =>
val hidingEnsoProject =
SymbolRestriction.Hiding(Set(Generated.ensoProjectMethodName))
val restriction = if (exp.isAll) {
val definedRestriction = if (exp.onlyNames.isDefined) {
resolvedImports.collect { case ResolvedImport(_, exports, mod) =>
exports.map { exp =>
val hidingEnsoProject =
SymbolRestriction.Hiding(Set(Generated.ensoProjectMethodName))
val restriction = if (exp.isAll) {
val definedRestriction = if (exp.onlyNames.isDefined) {
SymbolRestriction.Only(
exp.onlyNames.get
.map(name =>
SymbolRestriction
.AllowedResolution(name.name.toLowerCase, None)
)
.toSet
)
} else if (exp.hiddenNames.isDefined) {
SymbolRestriction.Hiding(
exp.hiddenNames.get.map(_.name.toLowerCase).toSet
)
} else {
SymbolRestriction.All
}
SymbolRestriction.Intersect(
List(hidingEnsoProject, definedRestriction)
)
} else {
SymbolRestriction.Only(
exp.onlyNames.get
.map(name =>
SymbolRestriction
.AllowedResolution(name.name.toLowerCase, None)
Set(
SymbolRestriction.AllowedResolution(
exp.getSimpleName.name.toLowerCase,
Some(mod)
)
.toSet
)
} else if (exp.hiddenNames.isDefined) {
SymbolRestriction.Hiding(
exp.hiddenNames.get.map(_.name.toLowerCase).toSet
)
)
}
val rename = if (!exp.isAll) {
Some(exp.getSimpleName.name)
} else {
SymbolRestriction.All
None
}
SymbolRestriction.Intersect(
List(hidingEnsoProject, definedRestriction)
)
} else {
SymbolRestriction.Only(
Set(
SymbolRestriction.AllowedResolution(
exp.getSimpleName.name.toLowerCase,
Some(mod)
)
)
)
ExportedModule(mod, rename, restriction)
}
val rename = if (!exp.isAll) {
Some(exp.getSimpleName.name)
} else {
None
}
ExportedModule(mod, rename, restriction)
}
}.flatten
}

object BindingsMap {
Expand Down Expand Up @@ -675,7 +677,7 @@ object BindingsMap {
*/
case class ResolvedImport(
importDef: IR.Module.Scope.Import.Module,
exports: Option[IR.Module.Scope.Export.Module],
exports: List[IR.Module.Scope.Export.Module],
target: ImportTarget
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import scala.collection.mutable
* @param compiler the compiler instance for the compiling context.
*/
class ImportResolver(compiler: Compiler) {
import ImportResolver._

/** Runs the import mapping logic.
*
Expand Down Expand Up @@ -121,8 +122,65 @@ class ImportResolver(compiler: Compiler) {
): (IR.Module.Scope.Import, Option[BindingsMap.ResolvedImport]) = {
val impName = imp.name.name
val exp = module.exports
.collect { case ex: Export.Module => ex }
.find(_.name.name == impName)
.collect { case ex: Export.Module if ex.name.name == impName => ex }
val fromAllExports = exp.filter(_.isAll)
fromAllExports match {
case _ :: _ :: _ =>
// Detect potential conflicts when importing all and hiding names for the exports of the same module
val unqualifiedImports = fromAllExports.collect {
case e if e.onlyNames.isEmpty => e
}
val qualifiedImports = fromAllExports.collect {
case IR.Module.Scope.Export.Module(
_,
_,
_,
Some(onlyNames),
_,
_,
_,
_,
_
) =>
onlyNames.map(_.name)
}
val importsWithHiddenNames = fromAllExports.collect {
case e @ IR.Module.Scope.Export.Module(
_,
_,
_,
_,
Some(hiddenNames),
_,
_,
_,
_
) =>
(e, hiddenNames)
}
importsWithHiddenNames.foreach { case (e, hidden) =>
val unqualifiedConflicts = unqualifiedImports.filter(_ != e)
if (unqualifiedConflicts.nonEmpty) {
throw HiddenNamesShadowUnqualifiedExport(
e.name.name,
hidden.map(_.name)
)
}

val qualifiedConflicts =
qualifiedImports
.filter(_ != e)
.flatten
.intersect(hidden.map(_.name))
if (qualifiedConflicts.nonEmpty) {
throw HiddenNamesShadowQualifiedExport(
e.name.name,
qualifiedConflicts
)
}
}
case _ =>
}
val libraryName = imp.name.parts match {
case namespace :: name :: _ =>
LibraryName(namespace.name, name.name)
Expand Down Expand Up @@ -175,3 +233,37 @@ class ImportResolver(compiler: Compiler) {
}
}
}

object ImportResolver {
trait HiddenNamesConflict {
def getMessage(): String
}

private case class HiddenNamesShadowUnqualifiedExport(
name: String,
hiddenNames: List[String]
) extends Exception(
s"""Hidden '${hiddenNames.mkString(",")}' name${if (
hiddenNames.size == 1
) ""
else
"s"} of the export module ${name} conflict${if (hiddenNames.size == 1)
"s"
else
""} with the unqualified export"""
)
with HiddenNamesConflict

private case class HiddenNamesShadowQualifiedExport(
name: String,
conflict: List[String]
) extends Exception(
s"""Hidden '${conflict.mkString(",")}' name${if (conflict.size == 1) ""
else
"s"} of the exported module ${name} conflict${if (conflict.size == 1)
"s"
else
""} with the qualified export"""
)
with HiddenNamesConflict
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Test_Multiple_Conflicting_Exports_1
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo = 42
bar = "z"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import project.F1
from project.F1 import foo
export project.F1
from project.F1 export foo
from project.F1 export all hiding foo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from Standard.Base import IO
from project.F2 import all

main =
IO.println F1.bar
IO.println foo

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Test_Multiple_Conflicting_Exports_2
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo = 42
bar = "z"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import project.F1
from project.F1 import foo
export project.F1

from project.F1 export all
from project.F1 export all hiding bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from Standard.Base import IO
from project.F2 import all

main =
IO.println F1.bar
IO.println foo

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: Test_Multiple_Exports
license: APLv2
enso-version: default
version: "0.0.1"
author: "Enso Team <contact@enso.org>"
maintainer: "Enso Team <contact@enso.org>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo = 42
bar = "z"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import project.F1
from project.F1 import foo
export project.F1
from project.F1 export foo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from Standard.Base import IO
from project.F2 import all

main =
IO.println F1.bar
IO.println foo
0
Loading