From 5b6ce5b31f9ca612d4401c3e98e2dd36b9645902 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Fri, 21 Aug 2020 17:30:13 +0200 Subject: [PATCH] Implement imports and exports for Main.enso (#1098) --- distribution/std-lib/Base/src/List.enso | 4 + distribution/std-lib/Base/src/Main.enso | 3 + .../interpreter/runtime/builtin/Builtins.java | 4 +- .../main/scala/org/enso/compiler/Passes.scala | 1 + .../org/enso/compiler/data/BindingsMap.scala | 2 +- .../pass/desugar/MainImportAndExport.scala | 86 ++++++++++++++++++ .../enso/compiler/phase/ImportResolver.scala | 11 ++- .../org/enso/compiler/test/PassesTest.scala | 1 + .../desugar/MainImportAndExportTest.scala | 90 +++++++++++++++++++ test/Test/src/List_Spec.enso | 4 +- 10 files changed, 197 insertions(+), 9 deletions(-) create mode 100644 distribution/std-lib/Base/src/Main.enso create mode 100644 engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/MainImportAndExport.scala create mode 100644 engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/MainImportAndExportTest.scala diff --git a/distribution/std-lib/Base/src/List.enso b/distribution/std-lib/Base/src/List.enso index d68986e46b..8b3a41e565 100644 --- a/distribution/std-lib/Base/src/List.enso +++ b/distribution/std-lib/Base/src/List.enso @@ -1,3 +1,7 @@ +import Builtins + +from Builtins export Nil, Cons + ## PRIVATE A helper for the `map` function. diff --git a/distribution/std-lib/Base/src/Main.enso b/distribution/std-lib/Base/src/Main.enso new file mode 100644 index 0000000000..7a9537db16 --- /dev/null +++ b/distribution/std-lib/Base/src/Main.enso @@ -0,0 +1,3 @@ +import Base.List + +from Base.List export Nil, Cons diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index e83394a464..e2e4bcf6a5 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -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(); diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala index 695a0c5920..295d32ee2b 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala @@ -22,6 +22,7 @@ class Passes(passes: Option[List[PassGroup]] = None) { val moduleDiscoveryPasses = new PassGroup( List( DocumentationComments, + MainImportAndExport, ComplexType, FunctionBinding, GenerateMethodBodies, diff --git a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala index d078b2d249..c3a6d5b55a 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -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)) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/MainImportAndExport.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/MainImportAndExport.scala new file mode 100644 index 0000000000..c0ba7fdd33 --- /dev/null +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/MainImportAndExport.scala @@ -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)) +} diff --git a/engine/runtime/src/main/scala/org/enso/compiler/phase/ImportResolver.scala b/engine/runtime/src/main/scala/org/enso/compiler/phase/ImportResolver.scala index e42bab3072..2158748c5c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/phase/ImportResolver.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/phase/ImportResolver.scala @@ -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 } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/PassesTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/PassesTest.scala index ac1fb0815e..8c1e024986 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/PassesTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/PassesTest.scala @@ -41,6 +41,7 @@ class PassesTest extends CompilerTest { passes.getPrecursors(AliasAnalysis).map(_.passes) shouldEqual Some( List( DocumentationComments, + MainImportAndExport, ComplexType, FunctionBinding, GenerateMethodBodies, diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/MainImportAndExportTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/MainImportAndExportTest.scala new file mode 100644 index 0000000000..b6ffad2fe4 --- /dev/null +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/MainImportAndExportTest.scala @@ -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" + } + + } +} diff --git a/test/Test/src/List_Spec.enso b/test/Test/src/List_Spec.enso index fe95414dce..db3cc11ab1 100644 --- a/test/Test/src/List_Spec.enso +++ b/test/Test/src/List_Spec.enso @@ -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" <|