Implement imports and exports for Main.enso (#1098)

This commit is contained in:
Marcin Kostrzewa 2020-08-21 17:30:13 +02:00 committed by GitHub
parent 8e764f957b
commit 5b6ce5b31f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 197 additions and 9 deletions

View File

@ -1,3 +1,7 @@
import Builtins
from Builtins export Nil, Cons
## PRIVATE
A helper for the `map` function.

View File

@ -0,0 +1,3 @@
import Base.List
from Base.List export Nil, Cons

View File

@ -34,7 +34,7 @@ import org.enso.pkg.QualifiedName;
/** Container class for static predefined atoms, methods, and their containing scope. */
public class Builtins {
public static final String MODULE_NAME = "Builtins";
public static final String MODULE_NAME = "Builtins.Main";
/** Container for method names needed outside this class. */
public static class MethodNames {
@ -66,7 +66,7 @@ public class Builtins {
public Builtins(Context context) {
Language language = context.getLanguage();
module = Module.empty(QualifiedName.simpleName(MODULE_NAME));
module = Module.empty(QualifiedName.fromString(MODULE_NAME).get());
scope = module.compileScope(context);
unit = new AtomConstructor("Unit", scope).initializeFields();
any = new AtomConstructor("Any", scope).initializeFields();

View File

@ -22,6 +22,7 @@ class Passes(passes: Option[List[PassGroup]] = None) {
val moduleDiscoveryPasses = new PassGroup(
List(
DocumentationComments,
MainImportAndExport,
ComplexType,
FunctionBinding,
GenerateMethodBodies,

View File

@ -95,7 +95,7 @@ case class BindingsMap(
private def handleAmbiguity(
candidates: List[ResolvedName]
): Either[ResolutionError, ResolvedName] = {
candidates match {
candidates.distinct match {
case List() => Left(ResolutionNotFound)
case List(it) => Right(it)
case items => Left(ResolutionAmbiguous(items))

View File

@ -0,0 +1,86 @@
package org.enso.compiler.pass.desugar
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.IRPass
/**
* Desugars imports and exports mentioning only the project name to refer
* to the `Main` module of the mentioned project instead.
*/
case object MainImportAndExport extends IRPass {
/** The type of the metadata object that the pass writes to the IR. */
override type Metadata = IRPass.Metadata.Empty
/** The type of configuration for the pass. */
override type Config = IRPass.Configuration.Default
/** The passes that this pass depends _directly_ on to run. */
override val precursorPasses: Seq[IRPass] = Seq()
/** The passes that are invalidated by running this pass. */
override val invalidatedPasses: Seq[IRPass] = Seq()
private val mainModuleName =
IR.Name.Literal("Main", isReferent = true, location = None)
/** Executes the pass on the provided `ir`, and returns a possibly transformed
* or annotated version of `ir`.
*
* @param ir the Enso IR to process
* @param moduleContext a context object that contains the information needed
* to process a module
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runModule(
ir: IR.Module,
moduleContext: ModuleContext
): IR.Module = {
val newImports = ir.imports.map {
case i: IR.Module.Scope.Import.Module =>
val parts = i.name.parts
if (parts.length == 1) {
i.copy(
name = i.name.copy(parts = parts :+ mainModuleName),
rename =
computeRename(i.rename, parts.head.asInstanceOf[IR.Name.Literal])
)
} else { i }
case other => other
}
val newExports = ir.exports.map { ex =>
val parts = ex.name.parts
if (parts.length == 1) {
ex.copy(
name = ex.name.copy(parts = parts :+ mainModuleName),
rename =
computeRename(ex.rename, parts.head.asInstanceOf[IR.Name.Literal])
)
} else {
ex
}
}
ir.copy(imports = newImports, exports = newExports)
}
/** Executes the pass on the provided `ir`, and returns a possibly transformed
* or annotated version of `ir` in an inline context.
*
* @param ir the Enso IR to process
* @param inlineContext a context object that contains the information needed
* for inline evaluation
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runExpression(
ir: IR.Expression,
inlineContext: InlineContext
): IR.Expression = ir
private def computeRename(
originalRename: Option[IR.Name.Literal],
qualName: IR.Name.Literal
): Some[IR.Name.Literal] = Some(originalRename.getOrElse(qualName))
}

View File

@ -64,10 +64,13 @@ class ImportResolver(compiler: Compiler) {
IR.Module.Scope.Import
.Module(
IR.Name.Qualified(
List(IR.Name.Literal("Builtins", isReferent = true, None)),
List(
IR.Name.Literal("Builtins", isReferent = true, None),
IR.Name.Literal("Main", isReferent = true, None)
),
None
),
None,
Some(IR.Name.Literal("Builtins", isReferent = true, None)),
isAll = true,
None,
None,
@ -82,9 +85,9 @@ class ImportResolver(compiler: Compiler) {
current.unsafeSetCompilationStage(
Module.CompilationStage.AFTER_IMPORT_RESOLUTION
)
seen += current
stack = importedModules.map(_.module) ++ stack
stack = currentLocal.resolvedImports.map(_.module) ++ stack
}
seen += current
}
seen.toList
}

View File

@ -41,6 +41,7 @@ class PassesTest extends CompilerTest {
passes.getPrecursors(AliasAnalysis).map(_.passes) shouldEqual Some(
List(
DocumentationComments,
MainImportAndExport,
ComplexType,
FunctionBinding,
GenerateMethodBodies,

View File

@ -0,0 +1,90 @@
package org.enso.compiler.test.pass.desugar
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.desugar.MainImportAndExport
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
class MainImportAndExportTest extends CompilerTest {
// === Test Setup ===========================================================
def mkModuleContext: ModuleContext =
buildModuleContext(
freshNameSupply = Some(new FreshNameSupply)
)
val passes = new Passes
val precursorPasses: PassGroup =
passes.getPrecursors(MainImportAndExport).get
val passConfiguration: PassConfiguration = PassConfiguration()
implicit val passManager: PassManager =
new PassManager(List(precursorPasses), passConfiguration)
/** Adds an extension method to analyse an Enso module.
*
* @param ir the ir to analyse
*/
implicit class AnalyseModule(ir: IR.Module) {
/** Performs tail call analysis on [[ir]].
*
* @param context the module context in which analysis takes place
* @return [[ir]], with tail call analysis metadata attached
*/
def analyse(implicit context: ModuleContext) = {
MainImportAndExport.runModule(ir, context)
}
}
// === The Tests ============================================================
"Main import and export desugaring" should {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|import Foo
|import Foo as Bar
|from Foo import Bar, Baz
|from Foo as Bar import Baz, Spam
|
|export Foo
|export Foo as Bar
|from Foo export Bar, Baz
|from Foo as Bar export all
|
|import Foo.Bar
|export Foo.Bar
|""".stripMargin.preprocessModule.analyse
"desugar project name imports correctly" in {
ir.imports.take(4).map(_.showCode()) shouldEqual List(
"import Foo.Main as Foo",
"import Foo.Main as Bar",
"from Foo.Main as Foo import Bar, Baz",
"from Foo.Main as Bar import Baz, Spam"
)
}
"desugar project name exports correctly" in {
ir.exports.take(4).map(_.showCode()) shouldEqual List(
"export Foo.Main as Foo",
"export Foo.Main as Bar",
"from Foo.Main as Foo export Bar, Baz",
"from Foo.Main as Bar export all"
)
}
"leave module imports and exports untouched" in {
ir.imports.last.showCode() shouldEqual "import Foo.Bar"
ir.exports.last.showCode() shouldEqual "export Foo.Bar"
}
}
}

View File

@ -1,8 +1,8 @@
import Base.Test
import Base.List
from Base import all
spec = describe "List" <|
l = Cons 1 <| Cons 2 <| Cons 3 <| Nil
l = Base.Cons 1 <| Base.Cons 2 <| Base.Cons 3 <| Base.Nil
it "should have properly defined length" <|
l.length.should_equal 3
it "should have well defined length when empty" <|