From b5f110617e1b96c118c6483bddaf982c484c8945 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 28 Nov 2024 19:00:42 +0100 Subject: [PATCH] More IR mini passes (#11501) Mega passes converted to minipasses in `Passes.globalTypingPasses` group: https://github.com/enso-org/enso/blob/4e6837f9dad4b8baaf1140774126348c662c5388/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala#L41-L63 Are: - [MethodDefinitions](https://github.com/enso-org/enso/blob/a3321fee972bcb2b9f3292286a97708988fc07d9/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/resolve/MethodDefinitions.java) - [ImportSymbolAnalysis](https://github.com/enso-org/enso/blob/8d9db8fe01b70b2992103c446cac31bc266c10ff/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.java) - [AmbiguousImportsAnalysis](https://github.com/enso-org/enso/blob/8d9db8fe01b70b2992103c446cac31bc266c10ff/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.java) - [PrivateModuleAnalysis](https://github.com/enso-org/enso/blob/8d9db8fe01b70b2992103c446cac31bc266c10ff/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java) - [PrivateConstructorAnalysis](https://github.com/enso-org/enso/blob/303c9fe545c7968d57810e39499c40da357075f0/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateConstructorAnalysis.java) All of these mini passes are ordered after each other. So now, in `Passes.globalTypingPasses` group, there are **8** consecutive minipasses. --- build.sbt | 19 +- .../src/main/java/module-info.java | 1 + .../enso/compiler/MetadataInteropHelpers.java | 13 +- .../enso/compiler/pass/ChainedMiniPass.java | 30 +- .../enso/compiler/pass/IRProcessingPass.java | 7 +- .../org/enso/compiler/pass/MiniIRPass.java | 9 +- .../enso/compiler/pass/MiniPassFactory.java | 5 +- .../enso/compiler/pass/MiniPassTraverser.java | 6 +- .../analyse/AmbiguousImportsAnalysis.java | 354 ++++++++++++++ .../pass/analyse/ImportSymbolAnalysis.java | 210 +++++++++ .../pass/analyse/PassPersistance.java | 4 +- .../analyse/PrivateConstructorAnalysis.java | 84 ++-- .../pass/analyse/PrivateModuleAnalysis.java | 198 ++++---- .../pass/resolve/MethodDefinitions.java | 366 ++++++++++++++ .../scala/org/enso/compiler/Compiler.scala | 1 + .../main/scala/org/enso/compiler/Passes.scala | 6 +- .../org/enso/compiler/data/BindingsMap.scala | 2 +- .../org/enso/compiler/pass/PassManager.scala | 229 +++++---- .../analyse/AmbiguousImportsAnalysis.scala | 445 ------------------ .../pass/analyse/BindingAnalysis.scala | 6 +- .../pass/analyse/ImportSymbolAnalysis.scala | 227 --------- .../pass/resolve/FullyQualifiedNames.scala | 5 +- .../compiler/pass/resolve/GlobalNames.scala | 5 +- .../pass/resolve/MethodDefinitions.scala | 281 ----------- .../enso/compiler/pass/resolve/Patterns.scala | 5 +- .../compiler/pass/resolve/TypeNames.scala | 5 +- .../test/pass/MiniPassTraverserTest.java | 128 +++++ .../compiler/test/pass/MockExpression.java | 124 +++++ .../enso/compiler/test/pass/MockMiniPass.java | 65 +++ .../instrument/job/ExecuteExpressionJob.java | 2 +- .../compiler/pass/PassManagerTestUtils.java | 11 + .../compiler/test/CompilerTestSetup.scala | 17 +- .../test/SerializationManagerTest.java | 2 +- .../enso/compiler/test/TypeInferenceTest.java | 3 +- .../enso/interpreter/runtime/ModuleTest.java | 2 +- .../test/meta/EnsoProjectTest.java | 2 +- .../org/enso/compiler/test/CompilerTest.scala | 48 +- .../org/enso/compiler/test/MiniPassTest.scala | 12 +- .../org/enso/compiler/test/PassesTest.scala | 6 +- .../pass/analyse/PrivateModifierTest.scala | 13 +- .../test/pass/resolve/GlobalNamesTest.scala | 4 +- .../pass/resolve/MethodDefinitionsTest.scala | 39 +- .../org/enso/compiler/core/Implicits.scala | 15 + .../compiler/core/ir/DefinitionArgument.scala | 6 + .../org/enso/compiler/core/ir/Function.scala | 6 + .../org/enso/compiler/core/ir/Module.scala | 13 + .../org/enso/compiler/core/ir/Name.scala | 6 + .../suggestions/SuggestionBuilder.scala | 10 +- .../runtime/TruffleCompilerContext.java | 8 +- .../runtime/scope/TopLevelScope.java | 2 +- .../interpreter/runtime/IrToTruffle.scala | 72 +-- .../src/main/java/module-info.java | 2 + .../enso/scala/wrapper}/ScalaConversions.java | 2 +- 53 files changed, 1869 insertions(+), 1274 deletions(-) create mode 100644 engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.java create mode 100644 engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.java create mode 100644 engine/runtime-compiler/src/main/java/org/enso/compiler/pass/resolve/MethodDefinitions.java delete mode 100644 engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala delete mode 100644 engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala delete mode 100644 engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala create mode 100644 engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MiniPassTraverserTest.java create mode 100644 engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockExpression.java create mode 100644 engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockMiniPass.java create mode 100644 engine/runtime-instrument-common/src/test/java/org/enso/compiler/pass/PassManagerTestUtils.java rename {engine/runtime/src/main/java/org/enso/interpreter/util => lib/java/scala-libs-wrapper/src/main/java/org/enso/scala/wrapper}/ScalaConversions.java (98%) diff --git a/build.sbt b/build.sbt index 42fef0f9e36e..e588843a702f 100644 --- a/build.sbt +++ b/build.sbt @@ -2949,6 +2949,16 @@ lazy val `runtime-integration-tests` = (`runtime` / javaModuleName).value -> Seq(javaSrcDir, testClassesDir) ) }, + Test / addOpens := { + val compilerModName = (`runtime-compiler` / javaModuleName).value + // In the tests, we access a private field of org.enso.compiler.pass.PassManager via reflection. + Map( + compilerModName + "/org.enso.compiler.pass" -> Seq( + (`runtime` / javaModuleName).value, + "ALL-UNNAMED" + ) + ) + }, // runtime-integration-tests does not have module descriptor on its own, so we have // to explicitly add some modules to the resolution. Test / addModules := Seq( @@ -3232,7 +3242,8 @@ lazy val `runtime-compiler` = "org.yaml" % "snakeyaml" % snakeyamlVersion % Test, "org.jline" % "jline" % jlineVersion % Test, "com.typesafe" % "config" % typesafeConfigVersion % Test, - "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % Test + "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % Test, + "org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test ), Compile / moduleDependencies ++= Seq( "org.slf4j" % "slf4j-api" % slf4jVersion, @@ -3243,6 +3254,7 @@ lazy val `runtime-compiler` = (`pkg` / Compile / exportedModule).value, (`runtime-parser` / Compile / exportedModule).value, (`syntax-rust-definition` / Compile / exportedModule).value, + (`scala-libs-wrapper` / Compile / exportedModule).value, (`persistance` / Compile / exportedModule).value, (`editions` / Compile / exportedModule).value ), @@ -3271,9 +3283,14 @@ lazy val `runtime-compiler` = javaModuleName.value ), Test / patchModules := { + // Patch test-classes into the runtime module. This is standard way to deal with the + // split package problem in unit tests. For example, Maven's surefire plugin does this. val testClassDir = (Test / productDirectories).value.head + // Patching with sources is useful for compilation, patching with compiled classes for runtime. + val javaSrcDir = (Test / javaSource).value Map( javaModuleName.value -> Seq( + javaSrcDir, testClassDir ) ) diff --git a/engine/runtime-compiler/src/main/java/module-info.java b/engine/runtime-compiler/src/main/java/module-info.java index 17cd6c98e308..e081f014144a 100644 --- a/engine/runtime-compiler/src/main/java/module-info.java +++ b/engine/runtime-compiler/src/main/java/module-info.java @@ -8,6 +8,7 @@ requires org.enso.runtime.parser; requires static org.enso.persistance; requires org.enso.syntax; + requires org.enso.scala.wrapper; requires org.openide.util.lookup.RELEASE180; requires org.slf4j; diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java index 72f75289790b..4b7f270df95d 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/MetadataInteropHelpers.java @@ -1,8 +1,9 @@ package org.enso.compiler; import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.MetadataStorage.MetadataPair; import org.enso.compiler.core.ir.ProcessingPass; -import org.enso.compiler.pass.IRPass; +import org.enso.compiler.pass.IRProcessingPass; import scala.Option; /** @@ -11,7 +12,7 @@ *

This encapsulates the friction of interop between Scala and Java types. */ public final class MetadataInteropHelpers { - public static T getMetadataOrNull(IR ir, IRPass pass, Class expectedType) { + public static T getMetadataOrNull(IR ir, IRProcessingPass pass, Class expectedType) { Option option = ir.passData().get(pass); if (option.isDefined()) { try { @@ -30,7 +31,7 @@ public static T getMetadataOrNull(IR ir, IRPass pass, Class expectedType) } } - public static T getMetadata(IR ir, IRPass pass, Class expectedType) { + public static T getMetadata(IR ir, IRProcessingPass pass, Class expectedType) { T metadataOrNull = getMetadataOrNull(ir, pass, expectedType); if (metadataOrNull == null) { throw new IllegalStateException("Missing expected " + pass + " metadata for " + ir + "."); @@ -39,5 +40,11 @@ public static T getMetadata(IR ir, IRPass pass, Class expectedType) { return metadataOrNull; } + public static void updateMetadata( + IR ir, T pass, ProcessingPass.Metadata metadata) { + var metaPair = new MetadataPair<>(pass, metadata); + ir.passData().update(metaPair); + } + private MetadataInteropHelpers() {} } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/ChainedMiniPass.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/ChainedMiniPass.java index 1fceeecbcdd2..3b8888f00ab5 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/ChainedMiniPass.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/ChainedMiniPass.java @@ -1,8 +1,8 @@ package org.enso.compiler.pass; -import java.util.Objects; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.Module; /** Utility class for chaining mini passes together. */ final class ChainedMiniPass extends MiniIRPass { @@ -18,34 +18,46 @@ static MiniIRPass chain(MiniIRPass firstPass, MiniIRPass secondPass) { if (firstPass == null) { return secondPass; } + if (secondPass == null) { + return firstPass; + } return new ChainedMiniPass(firstPass, secondPass); } @Override public MiniIRPass prepare(IR parent, Expression current) { - var first = firstPass.prepare(parent, current); - var second = secondPass.prepare(parent, current); - if (first == firstPass && second == secondPass) { + var firstPrepared = firstPass == null ? null : firstPass.prepare(parent, current); + var secondPrepared = secondPass == null ? null : secondPass.prepare(parent, current); + if (firstPrepared == firstPass && secondPrepared == secondPass) { return this; } else { - return new ChainedMiniPass(first, second); + return chain(firstPrepared, secondPrepared); } } @Override public Expression transformExpression(Expression ir) { - var fstIr = firstPass.transformExpression(ir); - var sndIr = secondPass.transformExpression(fstIr); + var fstIr = firstPass == null ? ir : firstPass.transformExpression(ir); + var sndIr = secondPass == null ? fstIr : secondPass.transformExpression(fstIr); return sndIr; } + @Override + public Module transformModule(Module moduleIr) { + var firstIr = firstPass == null ? moduleIr : firstPass.transformModule(moduleIr); + var secondIr = secondPass == null ? firstIr : secondPass.transformModule(firstIr); + return secondIr; + } + @Override public boolean checkPostCondition(IR ir) { - return firstPass.checkPostCondition(ir) && secondPass.checkPostCondition(ir); + var firstCheck = firstPass == null || firstPass.checkPostCondition(ir); + var secondCheck = secondPass == null || secondPass.checkPostCondition(ir); + return firstCheck && secondCheck; } @Override public String toString() { - return Objects.toString(firstPass) + ":" + Objects.toString(secondPass); + return "{" + firstPass + " + " + secondPass + "}"; } } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/IRProcessingPass.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/IRProcessingPass.java index 2d38422d488f..8de4d16b8c5f 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/IRProcessingPass.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/IRProcessingPass.java @@ -11,6 +11,11 @@ public interface IRProcessingPass extends ProcessingPass { /** The passes that this pass depends _directly_ on to run. */ public Seq precursorPasses(); - /** The passes that are invalidated by running this pass. */ + /** + * The passes that are invalidated by running this pass. + * + *

If {@code P1} invalidates {@code P2}, and {@code P1} is a precursor of {@code P2}, then + * {@code P1} must finish before {@code P2} starts. + */ public Seq invalidatedPasses(); } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniIRPass.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniIRPass.java index af921af41cca..1ee06dd4217b 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniIRPass.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniIRPass.java @@ -57,7 +57,9 @@ public abstract class MiniIRPass { * * @param parent the the parent of the edge * @param child the child expression element to be be processed. - * @return an instance of the pass to process the child's element subtree + * @return an instance of the pass to process the child's element subtree. If null is returned, + * the subtree of the child element is not processed, including {@code child} (i.e. {@code + * child} is not processed as well). */ public MiniIRPass prepare(IR parent, Expression child) { return this; @@ -103,8 +105,9 @@ public String toString() { * Combines two mini IR passes into one that delegates to both of them. * * @param first first mini pass (can be {@code null}) - * @param second second mini pass - * @return a combined pass that calls both non-{@code null} of the provided passes + * @param second second mini pass (can be {@code null}) + * @return a combined pass that calls both non-{@code null} of the provided passes. {@code null} + * if both provided passes are {@code null}. */ public static MiniIRPass combine(MiniIRPass first, MiniIRPass second) { return ChainedMiniPass.chain(first, second); diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassFactory.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassFactory.java index 8c98d1d87bf4..8a57b8ccc2e1 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassFactory.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassFactory.java @@ -16,7 +16,8 @@ public interface MiniPassFactory extends IRProcessingPass { * a module. * * @param moduleContext A mini pass can optionally save reference to this module context. - * @return May return {@code null} if module compilation is not supported. + * @return May return {@code null} if module compilation is not supported, or if the compilation + * for the given {@code moduleContext} should be skipped. */ MiniIRPass createForModuleCompilation(ModuleContext moduleContext); @@ -25,7 +26,7 @@ public interface MiniPassFactory extends IRProcessingPass { * an inline compilation. * * @param inlineContext A mini pass can optionally save reference to this inline context. - * @return Must not return {@code null}. Inline compilation should always be supported. + * @return May return {@code null} if the inline compilation is not supported. */ MiniIRPass createForInlineCompilation(InlineContext inlineContext); } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassTraverser.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassTraverser.java index 4243b0fce642..3fde07bb8553 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassTraverser.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassTraverser.java @@ -71,7 +71,7 @@ static IR compileDeep(IR root, MiniIRPass miniPass) { * @param queue queue to put objects in * @param ir IR to process * @param miniPass process with this mini pass - * @return {@code true} if the has been modified with new tries to process first + * @return {@code true} if the {@code queue} has been modified with new tries to process first */ private static List enqueueSubExpressions( Collection queue, IR ir, MiniIRPass miniPass) { @@ -81,7 +81,9 @@ private static List enqueueSubExpressions( (ch) -> { var preparedMiniPass = miniPass.prepare(ir, ch); childExpressions.add(ch); - queue.add(new MiniPassTraverser(preparedMiniPass, childExpressions, i[0]++)); + if (preparedMiniPass != null) { + queue.add(new MiniPassTraverser(preparedMiniPass, childExpressions, i[0]++)); + } return ch; }); return childExpressions; diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.java new file mode 100644 index 000000000000..591d13f8b776 --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.java @@ -0,0 +1,354 @@ +package org.enso.compiler.pass.analyse; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.enso.scala.wrapper.ScalaConversions; +import org.enso.compiler.context.InlineContext; +import org.enso.compiler.context.ModuleContext; +import org.enso.compiler.core.CompilerError; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.MetadataStorage; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.Name.Literal; +import org.enso.compiler.core.ir.Warning; +import org.enso.compiler.core.ir.expression.errors.ImportExport; +import org.enso.compiler.core.ir.module.scope.Import; +import org.enso.compiler.core.ir.module.scope.imports.Polyglot; +import org.enso.compiler.data.BindingsMap; +import org.enso.compiler.data.BindingsMap.ImportTarget; +import org.enso.compiler.data.BindingsMap.ResolvedName; +import org.enso.compiler.pass.IRProcessingPass; +import org.enso.compiler.pass.MiniIRPass; +import org.enso.compiler.pass.MiniPassFactory; +import scala.collection.immutable.Seq; +import scala.jdk.javaapi.CollectionConverters; + +/** + * A pass that checks for ambiguous and duplicated symbols from imports. A duplicated import is an + * import of a symbol that has already been imported and refers to the same object (entity). On the + * other hand, an ambiguous import is an import of a symbol that has already been imported but + * refers to a different object. For every duplicated import, a warning is attached to the IR, and + * for every ambiguous import, the IR is replaced with an error. To identify an object, this pass + * uses physical path of the object instead of the object itself. + * + *

One import IR can be replaced with multiple error IRs. This is the case for {@code from ... + * import ...} import statements. + * + *

The original import is saved in the error and warning so that the user can see from which + * location the symbol was originally imported. + * + *

Also iterates polyglot imports. + * + *

All synthetic imports and exports, as well as synthetic modules are ignored by this pass. + * + *

This pass does not alter any metadata. + */ +public final class AmbiguousImportsAnalysis implements MiniPassFactory { + public static final AmbiguousImportsAnalysis INSTANCE = new AmbiguousImportsAnalysis(); + + private AmbiguousImportsAnalysis() {} + + @Override + public Seq precursorPasses() { + List passes = + List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis.INSTANCE); + return ScalaConversions.seq(passes); + } + + @Override + public Seq invalidatedPasses() { + return ScalaConversions.seq(List.of()); + } + + @Override + public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) { + if (moduleContext.isSynthetic()) { + return null; + } + return new Mini(moduleContext.bindingsAnalysis()); + } + + @Override + public MiniIRPass createForInlineCompilation(InlineContext inlineContext) { + return null; + } + + + private static final class Mini extends MiniIRPass { + private final EncounteredSymbols encounteredSymbols = new EncounteredSymbols(); + private final BindingsMap bindingsMap; + + private Mini(BindingsMap bindingsMap) { + assert bindingsMap != null; + this.bindingsMap = bindingsMap; + } + + @Override + public MiniIRPass prepare(IR parent, Expression child) { + // return null - do not traverse any children of the root - we just + // need to transform the module IR. + return null; + } + + @Override + public Expression transformExpression(Expression expr) { + throw new IllegalStateException("Should not be called - prepare returns null"); + } + + @Override + public Module transformModule(Module moduleIr) { + var newImports = new ArrayList(); + moduleIr.imports().foreach(imp -> { + var errs = analyseAmbiguousSymbols(imp); + if (!errs.isEmpty()) { + newImports.addAll(errs); + } else { + newImports.add(imp); + } + return null; + }); + return moduleIr.copy( + CollectionConverters.asScala(newImports).toList(), + moduleIr.exports(), + moduleIr.bindings(), + moduleIr.isPrivate(), + moduleIr.location(), + moduleIr.passData(), + moduleIr.diagnostics(), + moduleIr.id()); + } + + /** + * @param imp Current import to analyse. May attach warnings. + * @return List of collected errors. Potentially empty. Not null + */ + private List analyseAmbiguousSymbols(Import imp) { + var errorsForImport = new ArrayList(); + + switch (imp) { + // Import multiple symbols + case Import.Module impMod when impMod.onlyNames().isDefined() && !impMod.isSynthetic() -> { + for (var symbol : CollectionConverters.asJava(impMod.onlyNames().get())) { + var symbolName = symbol.name(); + for (var importTarget : getImportTargets(impMod)) { + var resolution = importTarget.resolveExportedSymbol(symbolName); + if (resolution.isLeft()) { + throw new CompilerError( + "Unreachable: (should have been resolved in previous passes): " + resolution); + } + var resolvedNames = resolution.toOption().get(); + for (var resolvedName : CollectionConverters.asJava(resolvedNames)) { + var symbolPath = resolvedName.qualifiedName().toString(); + tryAddEncounteredSymbol(impMod, symbolName, symbolPath, resolvedName, errorsForImport); + } + } + } + } + + // Import all symbols + case Import.Module impMod when impMod.isAll() && !impMod.isSynthetic() -> { + var importTargets = getImportTargets(impMod); + // Names of the symbols that are exported by a module or a type referred to via importTarget + var exportedSymbolNames = importTargets.stream() + .flatMap(target -> { + var expSymbols = target.exportedSymbols().keySet().toList(); + return CollectionConverters.asJava(expSymbols).stream(); + }); + List symbolsToIterate; + if (impMod.hiddenNames().isDefined()) { + var hiddenNames = impMod.hiddenNames().get().map(Literal::name); + symbolsToIterate = exportedSymbolNames + .filter(exportedSymName -> !hiddenNames.contains(exportedSymName)) + .toList(); + } else { + symbolsToIterate = exportedSymbolNames.toList(); + } + for (var symbolName : symbolsToIterate) { + for (var importTarget : importTargets) { + var resolution = importTarget.resolveExportedSymbol(symbolName); + if (resolution.isLeft()) { + throw new CompilerError( + "Unreachable: (should have been resolved in previous passes): " + resolution); + } + var resolvedNames = resolution.toOption().get(); + if (resolvedNames.size() > 1) { + // If the symbolName is resolved to multiple objects, we ignore it. + continue; + } + var resolvedName = resolvedNames.head(); + var symbolPath = resolvedName.qualifiedName().toString(); + tryAddEncounteredSymbol(impMod, symbolName, symbolPath, resolvedName, errorsForImport); + } + } + } + + // Import a renamed symbol + case Import.Module impMod when impMod.rename().isDefined() -> { + var symbolPath = impMod.name().name(); + var symbolName = impMod.rename().get().name(); + tryAddEncounteredSymbol(impMod, symbolName, symbolPath, null, errorsForImport); + } + + // Import one symbol + case Import.Module impMod when !impMod.isSynthetic() -> { + var symbolPath = impMod.name().name(); + var symbolName = impMod.name().parts().last().name(); + tryAddEncounteredSymbol(impMod, symbolName, symbolPath, null, errorsForImport); + } + + case Polyglot polyglotImp -> { + var symbolName = polyglotImp.rename().getOrElse(() -> polyglotImp.entity().getVisibleName()); + String symbolPath; + if (polyglotImp.entity() instanceof Polyglot.Java javaEntity) { + symbolPath = javaEntity.packageName() + "." + javaEntity.className(); + } else { + throw new IllegalStateException("Unsupported polyglot entity: " + polyglotImp.entity()); + } + tryAddEncounteredSymbol(polyglotImp, symbolName, symbolPath, null, errorsForImport); + } + + default -> {} + } + return errorsForImport; + } + + /** + * Tries to add the encountered symbol to the encountered symbols map. If it is already contained + * in the map, checks whether the underlying entity path is the same as the original entity path. + * Based on that, either attaches a warning for a duplicated import, or returns an {@link ImportExport}. + * + * @param currentImport Currently iterated import + * @param symbolName Name of the symbol that is about to be processed + * @param symbolPath physical path of the symbol that is about to be processed + * @param errors Into this list, a potential error is appended. + */ + private void tryAddEncounteredSymbol( + Import currentImport, + String symbolName, + String symbolPath, + ResolvedName resolvedName, + List errors) { + if (!encounteredSymbols.containsSymbol(symbolName)) { + encounteredSymbols.addSymbol(currentImport, symbolName, symbolPath, resolvedName); + } else { + var encounteredFullName = encounteredSymbols.getPathForSymbol(symbolName); + var originalImport = encounteredSymbols.getOriginalImportForSymbol(symbolName); + if (symbolPath.equals(encounteredFullName)) { + // symbolName is already imported with the same symbolPath --> attach warning. + var warn = createWarningForDuplicatedImport(originalImport, currentImport, symbolName); + currentImport.getDiagnostics().add(warn); + } else { + // There is an encountered symbol with different physical path than symbolPath. + var resolution = encounteredSymbols.getResolvedNameForSymbol(symbolName); + if (resolution instanceof BindingsMap.ResolvedMethod resMethod && + resMethod.methodName().equals(symbolName)) { + // This is a valid ambiguous case - in previously encountered import, the symbol was resolved + // to either an extension, static, or conversion method. + return; + } else { + var error = createErrorForAmbiguousImport( + originalImport, + encounteredFullName, + currentImport, + symbolName, + symbolPath); + errors.add(error); + } + } + } + } + + private static Warning createWarningForDuplicatedImport( + Import originalImport, + Import duplicatingImport, + String duplicatedSymbol + ) { + return new Warning.DuplicatedImport( + duplicatingImport.identifiedLocation(), + originalImport, + duplicatedSymbol + ); + } + + private ImportExport createErrorForAmbiguousImport( + Import originalImport, + String originalSymbolPath, + Import duplicatingImport, + String ambiguousSymbol, + String ambiguousSymbolPath) { + return ImportExport.apply( + duplicatingImport, + new ImportExport.AmbiguousImport( + originalImport, + originalSymbolPath, + ambiguousSymbol, + ambiguousSymbolPath + ), + new MetadataStorage() + ); + } + + private List getImportTargets(Import imp) { + var found = bindingsMap.resolvedImports().find(resImp -> resImp.importDef() == imp); + if (found.isDefined()) { + return CollectionConverters.asJava(found.get().targets()); + } else { + return List.of(); + } + } + } + + /** @param symbolPath Fully qualified name of the symbol, i.e., its physical path. + * @param resolvedName The optional resolved name of the symbol. + * @param originalImport The import IR from which the symbol was originally imported. + * i.e. the first encountered import IR that imports the symbol. + */ + private record SymbolTarget( + String symbolPath, + ResolvedName resolvedName, + Import originalImport + ) { + private SymbolTarget { + assert symbolPath != null; + assert originalImport != null; + } + } + + + /** For every encountered symbol name, we keep track of the original import from which it was imported, + * along with the entity path. The entity path is vital to decide whether an imported symbol is duplicated + * or ambiguous. + * Note that there are some exceptions that are allowed to be ambiguous, like extension methods. + */ + private static final class EncounteredSymbols { + private final Map symbols = new HashMap<>(); + + boolean containsSymbol(String symbolName) { + return symbols.containsKey(symbolName); + } + + void addSymbol(Import imp, String symbol, String symbolPath, ResolvedName resolvedName) { + symbols.put(symbol, new SymbolTarget(symbolPath, resolvedName, imp)); + } + + String getPathForSymbol(String symbol) { + return symbols.get(symbol).symbolPath; + } + + ResolvedName getResolvedNameForSymbol(String symbol) { + return symbols.get(symbol).resolvedName; + } + + Import getOriginalImportForSymbol(String symbol) { + var val = symbols.get(symbol); + if (val != null) { + return val.originalImport; + } else { + return null; + } + } + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.java new file mode 100644 index 000000000000..ce7fc568b71e --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.java @@ -0,0 +1,210 @@ +package org.enso.compiler.pass.analyse; + +import java.util.ArrayList; +import java.util.List; +import org.enso.compiler.context.InlineContext; +import org.enso.compiler.context.ModuleContext; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.MetadataStorage; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.Name; +import org.enso.compiler.core.ir.expression.errors.ImportExport; +import org.enso.compiler.core.ir.module.scope.Import; +import org.enso.compiler.data.BindingsMap; +import org.enso.compiler.pass.IRProcessingPass; +import org.enso.compiler.pass.MiniIRPass; +import org.enso.compiler.pass.MiniPassFactory; +import org.enso.compiler.pass.desugar.GenerateMethodBodies$; +import org.enso.scala.wrapper.ScalaConversions; +import scala.collection.immutable.Seq; +import scala.jdk.javaapi.CollectionConverters; + +/** + * Performs analysis of `from ... import sym1, sym2, ...` statements - checks that all the symbols + * imported from the module can be resolved, i.e., exists. In case of unresolved symbols, replaces + * the IR import with {@link org.enso.compiler.core.ir.expression.errors.ImportExport}. Reports only + * the first unresolved symbol. + */ +public final class ImportSymbolAnalysis implements MiniPassFactory { + public static final ImportSymbolAnalysis INSTANCE = new ImportSymbolAnalysis(); + + private ImportSymbolAnalysis() {} + + @Override + public Seq precursorPasses() { + List passes = + List.of(BindingAnalysis$.MODULE$, GenerateMethodBodies$.MODULE$); + return ScalaConversions.seq(passes); + } + + @Override + public Seq invalidatedPasses() { + return ScalaConversions.seq(List.of()); + } + + @Override + public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) { + var bindingsMap = moduleContext.bindingsAnalysis(); + assert bindingsMap != null; + return new Mini(bindingsMap); + } + + @Override + public MiniIRPass createForInlineCompilation(InlineContext inlineContext) { + // Does not make sense for inline compilation. + return null; + } + + private static final class Mini extends MiniIRPass { + private final BindingsMap bindingsMap; + + private Mini(BindingsMap bindingsMap) { + this.bindingsMap = bindingsMap; + } + + @Override + public Module transformModule(Module moduleIr) { + var newImports = new ArrayList(); + for (var imp : CollectionConverters.asJava(moduleIr.imports())) { + if (imp instanceof Import.Module modImp) { + var encounteredErrors = analyseSymbolsFromImport(modImp); + if (encounteredErrors != null) { + newImports.addAll(encounteredErrors); + continue; + } + } + newImports.add(imp); + } + return moduleIr.copy( + CollectionConverters.asScala(newImports).toList(), + moduleIr.exports(), + moduleIr.bindings(), + moduleIr.isPrivate(), + moduleIr.location(), + moduleIr.passData(), + moduleIr.diagnostics(), + moduleIr.id()); + } + + @Override + public MiniIRPass prepare(IR parent, Expression child) { + // return null - do not traverse any children of the root - we just + // need to transform the module IR. + return null; + } + + @Override + public Expression transformExpression(Expression expr) { + throw new IllegalStateException("Should not be called - prepare returns null"); + } + + /** Returns list of encountered errors, or null. */ + private List analyseSymbolsFromImport(Import.Module imp) { + if (imp.onlyNames().isDefined()) { + var resolvedImport = + bindingsMap.resolvedImports().find(resImp -> resImp.importDef() == imp); + if (resolvedImport.isEmpty()) { + return null; + } + var onlyNames = imp.onlyNames().get(); + var importedTargets = resolvedImport.get().targets(); + var unresolvedSymbols = + importedTargets.flatMap( + importedTarget -> onlyNames.filterNot(nm -> isSymbolResolved(importedTarget, nm))); + if (unresolvedSymbols.nonEmpty()) { + scala.collection.immutable.List errs = + unresolvedSymbols.map( + unresolvedSym -> + createErrorForUnresolvedSymbol(imp, importedTargets.head(), unresolvedSym)); + return CollectionConverters.asJava(errs); + } + } + + // Importing symbols from methods is not allowed. The following code checks that if the + // import is importing all from a method, an error is reported. + if (imp.isAll() && !imp.isSynthetic()) { + var resolvedImport = + bindingsMap.resolvedImports().find(resImp -> resImp.importDef() == imp); + if (resolvedImport.isEmpty()) { + return null; + } + var importedTargets = resolvedImport.get().targets(); + var encounteredErrors = new ArrayList(); + for (var importedTarget : CollectionConverters.asJava(importedTargets)) { + switch (importedTarget) { + case BindingsMap.ResolvedModuleMethod resModMethod -> { + encounteredErrors.add( + createImportFromMethodError( + imp, + resModMethod.module().getName().toString(), + resModMethod.method().name())); + } + case BindingsMap.ResolvedExtensionMethod extMethod -> { + var staticMethod = extMethod.staticMethod(); + encounteredErrors.add( + createImportFromMethodError( + imp, + extMethod.module().getName().createChild(staticMethod.tpName()).toString(), + staticMethod.methodName())); + } + case BindingsMap.ResolvedConversionMethod resConvMethod -> { + var convMethod = resConvMethod.conversionMethod(); + var module = resConvMethod.module(); + encounteredErrors.add( + createImportFromMethodError( + imp, + module.getName().createChild(convMethod.targetTpName()).toString(), + convMethod.methodName())); + } + default -> {} + } + } + if (!encounteredErrors.isEmpty()) { + return encounteredErrors; + } + } + return null; + } + + private static boolean isSymbolResolved( + BindingsMap.ImportTarget importTarget, Name.Literal symbol) { + return importTarget.findExportedSymbolsFor(symbol.name()).nonEmpty(); + } + + private static ImportExport createErrorForUnresolvedSymbol( + Import imp, BindingsMap.ImportTarget importTarget, Name.Literal unresolvedSymbol) { + ImportExport.Reason errorReason = + switch (importTarget) { + case BindingsMap.ResolvedModule resMod -> new ImportExport.SymbolDoesNotExist( + unresolvedSymbol.name(), resMod.module().getName().toString()); + case BindingsMap.ResolvedType resType -> new ImportExport.NoSuchConstructor( + resType.tp().name(), unresolvedSymbol.name()); + case BindingsMap.ResolvedConstructor resCons -> new ImportExport.NoSuchConstructor( + resCons.cons().name(), unresolvedSymbol.name()); + case BindingsMap.ResolvedModuleMethod resMethod -> new ImportExport.NoSuchModuleMethod( + resMethod.method().name(), unresolvedSymbol.name()); + case BindingsMap.ResolvedExtensionMethod extMethod -> new ImportExport + .NoSuchStaticMethod( + extMethod.module().getName().toString(), + extMethod.staticMethod().tpName(), + unresolvedSymbol.name()); + case BindingsMap.ResolvedConversionMethod convMethod -> new ImportExport + .NoSuchConversionMethod( + convMethod.module().getName().toString(), + convMethod.conversionMethod().targetTpName(), + convMethod.conversionMethod().sourceTpName()); + default -> throw new IllegalStateException("Unexpected value: " + importTarget); + }; + return new ImportExport(imp, errorReason, new MetadataStorage()); + } + + private static ImportExport createImportFromMethodError( + Import imp, String moduleName, String methodName) { + return new ImportExport( + imp, + new ImportExport.IllegalImportFromMethod(moduleName, methodName), + new MetadataStorage()); + } + } +} diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java index 8d758ce72e57..7de99f29b66e 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java @@ -16,7 +16,7 @@ import org.enso.compiler.pass.resolve.IgnoredBindings; import org.enso.compiler.pass.resolve.IgnoredBindings$; import org.enso.compiler.pass.resolve.MethodCalls$; -import org.enso.compiler.pass.resolve.MethodDefinitions$; +import org.enso.compiler.pass.resolve.MethodDefinitions; import org.enso.compiler.pass.resolve.ModuleAnnotations; import org.enso.compiler.pass.resolve.ModuleAnnotations$; import org.enso.compiler.pass.resolve.Patterns$; @@ -54,7 +54,7 @@ @Persistable(clazz = ModuleAnnotations$.class, id = 1212) @Persistable(clazz = GatherDiagnostics$.class, id = 1213) @Persistable(clazz = MethodCalls$.class, id = 1214) -@Persistable(clazz = MethodDefinitions$.class, id = 1215) +@Persistable(clazz = MethodDefinitions.class, id = 1215) @Persistable(clazz = GenericAnnotations$.class, id = 1216) @Persistable(clazz = ExpressionAnnotations$.class, id = 1217) @Persistable(clazz = FullyQualifiedNames$.class, id = 1218) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateConstructorAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateConstructorAnalysis.java index 443f65d59492..7765ec628d2d 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateConstructorAnalysis.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateConstructorAnalysis.java @@ -9,16 +9,19 @@ import org.enso.compiler.core.ir.expression.errors.Syntax; import org.enso.compiler.core.ir.expression.errors.Syntax.InconsistentConstructorVisibility$; import org.enso.compiler.core.ir.module.scope.Definition; -import org.enso.compiler.pass.IRPass; import org.enso.compiler.pass.IRProcessingPass; +import org.enso.compiler.pass.MiniIRPass; +import org.enso.compiler.pass.MiniPassFactory; import scala.collection.immutable.Seq; import scala.jdk.javaapi.CollectionConverters; /** * Ensures that all type definitions have either all constructors public, or all constructors * private. + * + *

Does not support inline compilation. */ -public final class PrivateConstructorAnalysis implements IRPass { +public final class PrivateConstructorAnalysis implements MiniPassFactory { public static final PrivateConstructorAnalysis INSTANCE = new PrivateConstructorAnalysis(); private PrivateConstructorAnalysis() {} @@ -36,47 +39,50 @@ public Seq invalidatedPasses() { return (scala.collection.immutable.List) obj; } - @Override - public Module runModule(Module ir, ModuleContext moduleContext) { - var newBindings = - ir.bindings() - .map( - binding -> { - if (binding instanceof Definition.Type type) { - var partitions = type.members().partition(Definition.Data::isPrivate); - var privateCtorsCnt = partitions._1.size(); - var publicCtorsCnt = partitions._2.size(); - var ctorsCnt = type.members().size(); - if (!(privateCtorsCnt == ctorsCnt || publicCtorsCnt == ctorsCnt)) { - assert type.location().isDefined(); - return new Syntax( - type.location().get(), - InconsistentConstructorVisibility$.MODULE$, - type.passData(), - type.diagnostics()); - } - } - return binding; - }); - return ir.copy( - ir.copy$default$1(), - ir.copy$default$2(), - newBindings, - ir.copy$default$4(), - ir.copy$default$5(), - ir.copy$default$6(), - ir.copy$default$7(), - ir.copy$default$8()); + public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) { + return new Mini(); } - /** Not supported on a single expression. */ @Override - public Expression runExpression(Expression ir, InlineContext inlineContext) { - return ir; + public MiniIRPass createForInlineCompilation(InlineContext inlineContext) { + return null; } - @Override - public T updateMetadataInDuplicate(T sourceIr, T copyOfIr) { - return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr); + private static final class Mini extends MiniIRPass { + @Override + public Expression transformExpression(Expression expr) { + throw new IllegalStateException("Should not be called - prepare returns null"); + } + + @Override + public MiniIRPass prepare(IR parent, Expression child) { + return null; + } + + @Override + public Module transformModule(Module moduleIr) { + var newBindings = + moduleIr + .bindings() + .map( + binding -> { + if (binding instanceof Definition.Type type) { + var partitions = type.members().partition(Definition.Data::isPrivate); + var privateCtorsCnt = partitions._1.size(); + var publicCtorsCnt = partitions._2.size(); + var ctorsCnt = type.members().size(); + if (!(privateCtorsCnt == ctorsCnt || publicCtorsCnt == ctorsCnt)) { + assert type.location().isDefined(); + return new Syntax( + type.location().get(), + InconsistentConstructorVisibility$.MODULE$, + type.passData(), + type.diagnostics()); + } + } + return binding; + }); + return moduleIr.copyWithBindings(newBindings); + } } } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java index 8b772a3a0f7a..ed7ac9885295 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateModuleAnalysis.java @@ -6,13 +6,16 @@ import org.enso.compiler.context.ModuleContext; import org.enso.compiler.core.IR; import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.MetadataStorage; import org.enso.compiler.core.ir.Module; import org.enso.compiler.core.ir.expression.errors.ImportExport; import org.enso.compiler.core.ir.module.scope.Export; import org.enso.compiler.core.ir.module.scope.Import; import org.enso.compiler.data.BindingsMap; -import org.enso.compiler.pass.IRPass; import org.enso.compiler.pass.IRProcessingPass; +import org.enso.compiler.pass.MiniIRPass; +import org.enso.compiler.pass.MiniPassFactory; +import org.enso.pkg.Package; import org.enso.pkg.QualifiedName; import scala.Option; import scala.collection.immutable.Seq; @@ -29,7 +32,7 @@ * * Inserts errors into imports/exports IRs if the above conditions are violated. */ -public final class PrivateModuleAnalysis implements IRPass { +public final class PrivateModuleAnalysis implements MiniPassFactory { public static final PrivateModuleAnalysis INSTANCE = new PrivateModuleAnalysis(); private PrivateModuleAnalysis() {} @@ -37,7 +40,7 @@ private PrivateModuleAnalysis() {} @Override public Seq precursorPasses() { List passes = - List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis$.MODULE$); + List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis.INSTANCE); return CollectionConverters.asScala(passes).toList(); } @@ -49,89 +52,117 @@ public Seq invalidatedPasses() { } @Override - public Module runModule(Module moduleIr, ModuleContext moduleContext) { - var bindingsMap = (BindingsMap) moduleIr.passData().get(BindingAnalysis$.MODULE$).get(); - var currentPackage = moduleContext.getPackage(); - List importErrors = new ArrayList<>(); - List exportErrors = new ArrayList<>(); - var isCurrentModulePrivate = moduleIr.isPrivate(); - var isCurrentModuleSynthetic = moduleContext.isSynthetic(); - - // Ensure that imported modules from a different project are not private. - bindingsMap - .resolvedImports() - .foreach( - resolvedImp -> { - var importedTargets = resolvedImp.targets(); - importedTargets.foreach( - importedTarget -> { - var importedModule = importedTarget.module().unsafeAsModule("should succeed"); - var importedModuleName = importedModule.getName().toString(); - var importedModulePackage = importedModule.getPackage(); - if (currentPackage != null - && !currentPackage.equals(importedModulePackage) - && importedModule.isPrivate()) { - importErrors.add( - ImportExport.apply( - resolvedImp.importDef(), - new ImportExport.ImportPrivateModule(importedModuleName), - ImportExport.apply$default$3())); - } - return null; - }); - return null; - }); + public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) { + return new Mini( + moduleContext.bindingsAnalysis(), + moduleContext.getName().toString(), + moduleContext.getPackage(), + moduleContext.isSynthetic()); + } + + @Override + public MiniIRPass createForInlineCompilation(InlineContext inlineContext) { + return null; + } - // Ensure that no symbols are exported from a private module. - if (isCurrentModulePrivate && containsExport(moduleIr)) { - exportErrors.add( - ImportExport.apply( - moduleIr.exports().apply(0), - new ImportExport.ExportSymbolsFromPrivateModule(moduleContext.getName().toString()), - ImportExport.apply$default$3())); + private static final class Mini extends MiniIRPass { + private final BindingsMap bindingsMap; + private final String moduleName; + private final Package currentPackage; + private final boolean isSynthetic; + + private Mini( + BindingsMap bindingsMap, + String moduleName, + Package currentPackage, + boolean isSynthetic) { + assert bindingsMap != null; + assert moduleName != null; + this.moduleName = moduleName; + this.currentPackage = currentPackage; + this.isSynthetic = isSynthetic; + this.bindingsMap = bindingsMap; } - // Ensure that private modules are not exported - bindingsMap - .getDirectlyExportedModules() - .foreach( - expModule -> { - var expModuleRef = expModule.module().module().unsafeAsModule("should succeed"); - if (expModuleRef.isPrivate() && !isCurrentModuleSynthetic) { - var associatedExportIR = findExportIRByName(moduleIr, expModuleRef.getName()); - assert associatedExportIR.isDefined(); - exportErrors.add( - ImportExport.apply( - associatedExportIR.get(), - new ImportExport.ExportPrivateModule(expModuleRef.getName().toString()), - ImportExport.apply$default$3())); - } - return null; - }); + @Override + public MiniIRPass prepare(IR parent, Expression child) { + return null; + } - scala.collection.immutable.List convertedImports = - importErrors.isEmpty() - ? moduleIr.imports() - : CollectionConverters.asScala(importErrors).toList(); - scala.collection.immutable.List convertedExports = - exportErrors.isEmpty() - ? moduleIr.exports() - : CollectionConverters.asScala(exportErrors).toList(); - - return moduleIr.copy( - convertedImports, - convertedExports, - moduleIr.copy$default$3(), - moduleIr.copy$default$4(), - moduleIr.copy$default$5(), - moduleIr.copy$default$6(), - moduleIr.copy$default$7(), - moduleIr.copy$default$8()); - } + @Override + public Expression transformExpression(Expression expr) { + throw new IllegalStateException("Should not be called - prepare returns null"); + } - @Override - public Expression runExpression(Expression ir, InlineContext inlineContext) { - return ir; + @Override + public Module transformModule(Module moduleIr) { + List importErrors = new ArrayList<>(); + List exportErrors = new ArrayList<>(); + var isCurrentModulePrivate = moduleIr.isPrivate(); + + // Ensure that imported modules from a different project are not private. + bindingsMap + .resolvedImports() + .foreach( + resolvedImp -> { + var importedTargets = resolvedImp.targets(); + importedTargets.foreach( + importedTarget -> { + var importedModule = importedTarget.module().unsafeAsModule("should succeed"); + var importedModuleName = importedModule.getName().toString(); + var importedModulePackage = importedModule.getPackage(); + if (currentPackage != null + && !currentPackage.equals(importedModulePackage) + && importedModule.isPrivate()) { + importErrors.add( + ImportExport.apply( + resolvedImp.importDef(), + new ImportExport.ImportPrivateModule(importedModuleName), + new MetadataStorage())); + } + return null; + }); + return null; + }); + + // Ensure that no symbols are exported from a private module. + if (isCurrentModulePrivate && containsExport(moduleIr)) { + exportErrors.add( + ImportExport.apply( + moduleIr.exports().apply(0), + new ImportExport.ExportSymbolsFromPrivateModule(moduleName), + new MetadataStorage())); + } + + // Ensure that private modules are not exported + bindingsMap + .getDirectlyExportedModules() + .foreach( + expModule -> { + var expModuleRef = expModule.module().module().unsafeAsModule("should succeed"); + if (expModuleRef.isPrivate() && !isSynthetic) { + var associatedExportIR = findExportIRByName(moduleIr, expModuleRef.getName()); + assert associatedExportIR.isDefined(); + exportErrors.add( + ImportExport.apply( + associatedExportIR.get(), + new ImportExport.ExportPrivateModule(expModuleRef.getName().toString()), + new MetadataStorage())); + } + return null; + }); + + scala.collection.immutable.List convertedImports = + importErrors.isEmpty() + ? moduleIr.imports() + : CollectionConverters.asScala(importErrors).toList(); + scala.collection.immutable.List convertedExports = + exportErrors.isEmpty() + ? moduleIr.exports() + : CollectionConverters.asScala(exportErrors).toList(); + + return moduleIr.copyWithImportsAndExports(convertedImports, convertedExports); + } } /** Returns true iff the given Module's IR contains an export that is not synthetic. */ @@ -164,9 +195,4 @@ private static Option findExportIRByName(Module moduleIr, QualifiedName return null; }); } - - @Override - public T updateMetadataInDuplicate(T sourceIr, T copyOfIr) { - return IRPass.super.updateMetadataInDuplicate(sourceIr, copyOfIr); - } } diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/resolve/MethodDefinitions.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/resolve/MethodDefinitions.java new file mode 100644 index 000000000000..3264faf4610f --- /dev/null +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/resolve/MethodDefinitions.java @@ -0,0 +1,366 @@ +package org.enso.compiler.pass.resolve; + +import java.util.ArrayList; +import org.enso.compiler.MetadataInteropHelpers; +import org.enso.compiler.context.InlineContext; +import org.enso.compiler.context.ModuleContext; +import org.enso.compiler.core.CompilerError; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.DefinitionArgument; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.Function; +import org.enso.compiler.core.ir.MetadataStorage; +import org.enso.compiler.core.ir.Module; +import org.enso.compiler.core.ir.Name; +import org.enso.compiler.core.ir.expression.errors.Conversion; +import org.enso.compiler.core.ir.expression.errors.Conversion.UnsupportedSourceType$; +import org.enso.compiler.core.ir.module.scope.Definition; +import org.enso.compiler.core.ir.module.scope.definition.Method; +import org.enso.compiler.data.BindingsMap; +import org.enso.compiler.data.BindingsMap.Resolution; +import org.enso.compiler.data.BindingsMap.ResolvedConstructor; +import org.enso.compiler.data.BindingsMap.ResolvedConversionMethod; +import org.enso.compiler.data.BindingsMap.ResolvedExtensionMethod; +import org.enso.compiler.data.BindingsMap.ResolvedModule; +import org.enso.compiler.data.BindingsMap.ResolvedModuleMethod; +import org.enso.compiler.data.BindingsMap.ResolvedPolyglotField; +import org.enso.compiler.data.BindingsMap.ResolvedPolyglotSymbol; +import org.enso.compiler.data.BindingsMap.ResolvedType; +import org.enso.compiler.data.BindingsMap.Type; +import org.enso.compiler.pass.IRProcessingPass; +import org.enso.compiler.pass.MiniIRPass; +import org.enso.compiler.pass.MiniPassFactory; +import org.enso.compiler.pass.analyse.BindingAnalysis$; +import org.enso.compiler.pass.desugar.ComplexType$; +import org.enso.compiler.pass.desugar.FunctionBinding$; +import org.enso.compiler.pass.desugar.GenerateMethodBodies$; +import scala.Option; +import scala.collection.immutable.List; +import scala.collection.immutable.Seq; +import scala.jdk.javaapi.CollectionConverters; + +/** + * Resolves the correct {@code self} argument type for method definitions and stores the resolution + * in the method's metadata. + * + *

Metadata type is {@link BindingsMap.Resolution} + */ +public final class MethodDefinitions implements MiniPassFactory { + private MethodDefinitions() {} + + public static final MethodDefinitions INSTANCE = new MethodDefinitions(); + + @Override + public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) { + var bindingsMap = moduleContext.bindingsAnalysis(); + assert bindingsMap != null; + return new Mini(bindingsMap); + } + + @Override + public MiniIRPass createForInlineCompilation(InlineContext inlineContext) { + return null; + } + + @Override + public Seq precursorPasses() { + java.util.List passes = + java.util.List.of( + ComplexType$.MODULE$, + FunctionBinding$.MODULE$, + GenerateMethodBodies$.MODULE$, + BindingAnalysis$.MODULE$); + return CollectionConverters.asScala(passes).toList(); + } + + @Override + @SuppressWarnings("unchecked") + public Seq invalidatedPasses() { + Object obj = scala.collection.immutable.Nil$.MODULE$; + return (scala.collection.immutable.List) obj; + } + + private static List list(T item) { + return CollectionConverters.asScala(java.util.List.of(item)).toList(); + } + + private static boolean computeIsStatic(IR body) { + return Method.Explicit$.MODULE$.computeIsStatic(body); + } + + private static final class Mini extends MiniIRPass { + private final BindingsMap bindingsMap; + + private Mini(BindingsMap bindingsMap) { + this.bindingsMap = bindingsMap; + } + + @Override + public Expression transformExpression(Expression expr) { + throw new IllegalStateException("unreachable - prepare returns null."); + } + + @Override + public MiniIRPass prepare(IR parent, Expression child) { + // Supports only transformModule + return null; + } + + @Override + public Module transformModule(Module moduleIr) { + var newDefs = + moduleIr + .bindings() + .map( + def -> { + if (def instanceof Method method) { + var methodRef = method.methodReference(); + Option resolvedTypeRef = + methodRef.typePointer().map(tp -> resolveType(tp, bindingsMap)); + var resolvedMethodRef = methodRef.copyWithTypePointer(resolvedTypeRef); + + return switch (method) { + case Method.Explicit explicitMethod -> { + var isStatic = computeIsStatic(explicitMethod.body()); + var resolvedMethod = + explicitMethod.copy( + resolvedMethodRef, + explicitMethod.body(), + isStatic, + explicitMethod.isPrivate(), + explicitMethod.isStaticWrapperForInstanceMethod(), + explicitMethod.location(), + explicitMethod.passData(), + explicitMethod.diagnostics(), + explicitMethod.id()); + yield resolvedMethod; + } + case Method.Conversion conversionMethod -> { + var sourceTypeExpr = conversionMethod.sourceTypeName(); + Name resolvedName = + switch (sourceTypeExpr) { + case Name name -> resolveType(name, bindingsMap); + default -> new Conversion( + sourceTypeExpr, + UnsupportedSourceType$.MODULE$, + new MetadataStorage()); + }; + var resolvedMethod = + conversionMethod.copy( + resolvedMethodRef, + resolvedName, + conversionMethod.body(), + conversionMethod.location(), + conversionMethod.passData(), + conversionMethod.diagnostics(), + conversionMethod.id()); + yield resolvedMethod; + } + default -> throw new CompilerError( + "Unexpected method type in MethodDefinitions pass."); + }; + } else { + return def; + } + }); + + java.util.List withStaticAliases = new ArrayList<>(); + for (var def : CollectionConverters.asJava(newDefs)) { + withStaticAliases.add(def); + if (def instanceof Method.Explicit method && !method.isStatic()) { + var staticAlias = generateStaticAliasMethod(method); + if (staticAlias != null) { + withStaticAliases.add(staticAlias); + } + } + } + + return moduleIr.copyWithBindings(CollectionConverters.asScala(withStaticAliases).toList()); + } + + /** + * Returns null if there is no suitable static alias method that can be generated for the given + * {@code method}. + * + * @param method Non-static method from which a static alias method is generated. + * @return Static alias method for the given {@code method} or null. + */ + private Method.Explicit generateStaticAliasMethod(Method.Explicit method) { + assert !method.isStatic(); + var typePointer = method.methodReference().typePointer(); + if (typePointer.isEmpty()) { + return null; + } + var resolution = + MetadataInteropHelpers.getMetadataOrNull(typePointer.get(), INSTANCE, Resolution.class); + if (resolution == null) { + return null; + } + if (resolution.target() instanceof ResolvedType resType + && canGenerateStaticWrappers(resType.tp())) { + assert method.body() instanceof Function.Lambda; + var dup = method.duplicate(true, true, true, false); + // This is the self argument that will receive the `SelfType.type` value upon dispatch, it + // is + // added to avoid modifying the dispatch mechanism. + var syntheticModuleSelfArg = + new DefinitionArgument.Specified( + new Name.Self(null, true, new MetadataStorage()), + Option.empty(), + Option.empty(), + false, + null, + new MetadataStorage()); + var newBody = + new Function.Lambda( + // This is the synthetic Self argument that gets the static module + list(syntheticModuleSelfArg), + // Here we add the type ascription ensuring that the 'proper' self argument only + // accepts _instances_ of the type (or triggers conversions) + addTypeAscriptionToSelfArgument(dup.body()), + null, + true, + new MetadataStorage()); + // The actual `self` argument that is referenced inside of method body is the second one in + // the lambda. + // This is the argument that will hold the actual instance of the object we are calling on, + // e.g. `My_Type.method instance`. + // We add a type check to it to ensure only `instance` of `My_Type` can be passed to it. + var staticMethod = + dup.copy( + dup.methodReference(), + newBody, + true, + dup.isPrivate(), + true, + dup.location(), + dup.passData(), + dup.diagnostics(), + dup.id()); + return staticMethod; + } + return null; + } + + private static Expression addTypeAscriptionToSelfArgument(Expression methodBody) { + if (methodBody instanceof Function.Lambda lambda) { + if (lambda.arguments().isEmpty()) { + throw new CompilerError( + "MethodDefinitions pass: expected at least one argument (self) in the method, but got" + + " none."); + } + var firstArg = lambda.arguments().head(); + if (firstArg instanceof DefinitionArgument.Specified selfArg + && selfArg.name() instanceof Name.Self) { + var selfType = new Name.SelfType(selfArg.identifiedLocation(), new MetadataStorage()); + var newSelfArg = selfArg.copyWithAscribedType(selfType); + return lambdaWithNewSelfArg(lambda, newSelfArg); + } else { + throw new CompilerError( + "MethodDefinitions pass: expected the first argument to be `self`, but got " + + firstArg); + } + } else { + throw new CompilerError( + "Unexpected body type " + methodBody + " in MethodDefinitions pass."); + } + } + + private static Function.Lambda lambdaWithNewSelfArg( + Function.Lambda lambda, DefinitionArgument newSelfArg) { + var args = new ArrayList<>(CollectionConverters.asJava(lambda.arguments())); + assert !args.isEmpty(); + args.set(0, newSelfArg); + var newArgs = CollectionConverters.asScala(args).toList(); + return lambda.copyWithArguments(newArgs); + } + + // Generate static wrappers for + // 1. Types having at least one type constructor + // 2. All builtin types except for Nothing. Nothing's eigentype is Nothing and not Nothing.type, + // would lead to overriding conflicts. + // TODO: Remove the hardcoded type once Enso's annotations can define parameters. + private static boolean canGenerateStaticWrappers(Type tp) { + return tp.members().nonEmpty() || (tp.builtinType() && !"Nothing".equals(tp.name())); + } + + private Name resolveType(Name typePointer, BindingsMap availableSymbolsMap) { + if (typePointer instanceof Name.Qualified || typePointer instanceof Name.Literal) { + var items = + switch (typePointer) { + case Name.Qualified qualName -> qualName.parts().map(Name::name); + case Name.Literal lit -> list(lit.name()); + default -> throw new CompilerError("Impossible to reach"); + }; + + var resolvedItemsOpt = availableSymbolsMap.resolveQualifiedName(items); + if (resolvedItemsOpt.isLeft()) { + var err = resolvedItemsOpt.swap().toOption().get(); + return new org.enso.compiler.core.ir.expression.errors.Resolution( + typePointer, + new org.enso.compiler.core.ir.expression.errors.Resolution.ResolverError(err), + new MetadataStorage()); + } + var resolvedItems = resolvedItemsOpt.toOption().get(); + assert resolvedItems.size() == 1 : "Expected a single resolution"; + switch (resolvedItems.head()) { + case ResolvedConstructor ignored -> { + return new org.enso.compiler.core.ir.expression.errors.Resolution( + typePointer, + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedConstructor( + "a method definition target"), + new MetadataStorage()); + } + case ResolvedModule resMod -> { + MetadataInteropHelpers.updateMetadata(typePointer, INSTANCE, new Resolution(resMod)); + return typePointer; + } + case ResolvedType resType -> { + MetadataInteropHelpers.updateMetadata(typePointer, INSTANCE, new Resolution(resType)); + return typePointer; + } + case ResolvedPolyglotSymbol ignored -> { + return new org.enso.compiler.core.ir.expression.errors.Resolution( + typePointer, + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedPolyglot( + "a method definition target"), + new MetadataStorage()); + } + case ResolvedPolyglotField ignored -> { + return new org.enso.compiler.core.ir.expression.errors.Resolution( + typePointer, + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedPolyglot( + "a method definition target"), + new MetadataStorage()); + } + case ResolvedModuleMethod ignored -> { + return new org.enso.compiler.core.ir.expression.errors.Resolution( + typePointer, + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedMethod( + "a method definition target"), + new MetadataStorage()); + } + case ResolvedExtensionMethod ignored -> { + return new org.enso.compiler.core.ir.expression.errors.Resolution( + typePointer, + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedMethod( + "a static method definition target"), + new MetadataStorage()); + } + case ResolvedConversionMethod ignored -> { + return new org.enso.compiler.core.ir.expression.errors.Resolution( + typePointer, + new org.enso.compiler.core.ir.expression.errors.Resolution.UnexpectedMethod( + "a conversion method definition target"), + new MetadataStorage()); + } + default -> throw new IllegalStateException("Unexpected value: " + resolvedItems.head()); + } + } else if (typePointer instanceof org.enso.compiler.core.ir.expression.errors.Resolution) { + return typePointer; + } else { + throw new CompilerError("Unexpected kind of name for method reference"); + } + } + } +} diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala index e7d0db8505c1..8df72d828b26 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala @@ -606,6 +606,7 @@ class Compiler( expr else injectSyntheticModuleExports(expr, module.getDirectModulesRefs) + context.updateModule(module, _.ir(exprWithModuleExports)) val discoveredModule = recognizeBindings(exprWithModuleExports, moduleContext) if (context.wasLoadedFromCache(module)) { diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala index acdf7361e1e6..d9f75cba68eb 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala @@ -40,12 +40,12 @@ class Passes(config: CompilerConfig) { val globalTypingPasses = new PassGroup( List( - MethodDefinitions, + MethodDefinitions.INSTANCE, SectionsToBinOp.INSTANCE, OperatorToFunction, LambdaShorthandToLambda, - ImportSymbolAnalysis, - AmbiguousImportsAnalysis + ImportSymbolAnalysis.INSTANCE, + AmbiguousImportsAnalysis.INSTANCE ) ++ (if (config.privateCheckEnabled) { List( PrivateModuleAnalysis.INSTANCE, diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala index dbac0802ec34..9c8753aad043 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -875,7 +875,7 @@ object BindingsMap { case method: ir.module.scope.definition.Method.Explicit => method.methodReference.methodName.name == this.method.name && method.methodReference.typePointer .forall( - _.getMetadata(MethodDefinitions) + _.getMetadata(MethodDefinitions.INSTANCE) .contains(Resolution(ResolvedModule(module))) ) case _ => false diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala index 5bb0732e5734..9c643deeb200 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/PassManager.scala @@ -1,9 +1,13 @@ package org.enso.compiler.pass +import org.enso.common.{Asserts, CompilationStage} import org.slf4j.LoggerFactory import org.enso.compiler.context.{InlineContext, ModuleContext} import org.enso.compiler.core.ir.{Expression, Module} -import org.enso.compiler.core.CompilerError +import org.enso.compiler.core.{CompilerError, IR} +import org.enso.compiler.pass.analyse.BindingAnalysis + +import scala.collection.mutable.ListBuffer // TODO [AA] In the future, the pass ordering should be _computed_ from the list // of available passes, rather than just verified. @@ -18,7 +22,7 @@ import org.enso.compiler.core.CompilerError */ //noinspection DuplicatedCode class PassManager( - passes: List[PassGroup], + protected val passes: List[PassGroup], passConfiguration: PassConfiguration ) { private val logger = LoggerFactory.getLogger(classOf[PassManager]) @@ -57,21 +61,6 @@ class PassManager( passes } - /** Executes all pass groups on the [[Module]]. - * - * @param ir the module to execute the compiler passes on - * @param moduleContext the module context in which the passes are executed - * @return the result of executing `passGroup` on `ir` - */ - def runPassesOnModule( - ir: Module, - moduleContext: ModuleContext - ): Module = { - passes.foldLeft(ir)((ir, group) => - runPassesOnModule(ir, moduleContext, group) - ) - } - /** Executes the provided `passGroup` on the [[Module]]. * * @param ir the module to execute the compiler passes on @@ -84,74 +73,31 @@ class PassManager( moduleContext: ModuleContext, passGroup: PassGroup ): Module = { + Asserts.assertInJvm(validateConsistency(ir, moduleContext)) + if (!passes.contains(passGroup)) { throw new CompilerError("Cannot run an unvalidated pass group.") } - val newContext = - moduleContext.copy(passConfiguration = Some(passConfiguration)) - - val passesWithIndex = passGroup.passes.zipWithIndex - logger.debug( "runPassesOnModule[{}@{}]", moduleContext.getName(), moduleContext.module.getCompilationStage() ) - var pendingMiniPasses: List[MiniPassFactory] = List() - def flushMiniPass(in: Module): Module = { - if (pendingMiniPasses.nonEmpty) { - val miniPasses = pendingMiniPasses.map(factory => - factory.createForModuleCompilation(newContext) - ) - val combinedPass = miniPasses.fold(null)(MiniIRPass.combine) - logger.trace(" flushing pending mini pass: {}", combinedPass) - pendingMiniPasses = List() - MiniIRPass.compile(classOf[Module], in, combinedPass) - } else { - in - } - } - val res = passesWithIndex.foldLeft(ir) { - case (intermediateIR, (pass, index)) => { - pass match { - case miniFactory: MiniPassFactory => - logger.trace( - " mini collected: {}", - pass - ) - val combiningPreventedBy = pendingMiniPasses.find { p => - p.invalidatedPasses.contains(miniFactory) - } - val irForRemainingMiniPasses = if (combiningPreventedBy.isDefined) { - logger.trace( - " pass {} forces flush before {}", - combiningPreventedBy.orNull, - miniFactory - ) - flushMiniPass(intermediateIR) - } else { - intermediateIR - } - pendingMiniPasses = pendingMiniPasses.appended(miniFactory) - irForRemainingMiniPasses - case megaPass: IRPass => - // TODO [AA, MK] This is a possible race condition. - passConfiguration - .get(megaPass) - .foreach(c => - c.shouldWriteToContext = isLastRunOf(index, megaPass, passGroup) - ) - val flushedIR = flushMiniPass(intermediateIR) - logger.trace( - " mega running: {}", - megaPass - ) - megaPass.runModule(flushedIR, newContext) - } - } - } - flushMiniPass(res) + + val newContext = + moduleContext.copy(passConfiguration = Some(passConfiguration)) + + runPasses[Module, ModuleContext]( + ir, + newContext, + passGroup, + createMiniPass = + (factory, ctx) => factory.createForModuleCompilation(ctx), + miniPassCompile = (miniPass, ir) => + MiniIRPass.compile[Module](classOf[Module], ir, miniPass), + megaPassCompile = (megaPass, ir, ctx) => megaPass.runModule(ir, ctx) + ) } /** Executes all passes on the [[Expression]]. @@ -188,15 +134,80 @@ class PassManager( val newContext = inlineContext.copy(passConfiguration = Some(passConfiguration)) - val passesWithIndex = passGroup.passes.zipWithIndex + runPasses[Expression, InlineContext]( + ir, + newContext, + passGroup, + createMiniPass = + (factory, ctx) => factory.createForInlineCompilation(ctx), + miniPassCompile = (miniPass, ir) => + MiniIRPass.compile[Expression](classOf[Expression], ir, miniPass), + megaPassCompile = (megaPass, ir, ctx) => megaPass.runExpression(ir, ctx) + ) + } + + /** Runs all the passes in the given `passGroup` on `ir` with `context`. + * @param createMiniPass Function that creates a minipass. + * @param miniPassCompile Function that compiles IR with mini pass. + * @param megaPassCompile Function that compiles IR with mega pass. + * @tparam IRType Type of the [[IR]] that is being compiled. + * @tparam ContextType Type of the context for the compilation. + * Either [[ModuleContext]] or [[InlineContext]] + * @return Compiled IR. Might be the same reference as `ir` if no compilation was done. + */ + private def runPasses[IRType <: IR, ContextType]( + ir: IRType, + context: ContextType, + passGroup: PassGroup, + createMiniPass: (MiniPassFactory, ContextType) => MiniIRPass, + miniPassCompile: (MiniIRPass, IRType) => IRType, + megaPassCompile: (IRPass, IRType, ContextType) => IRType + ): IRType = { + val pendingMiniPasses: ListBuffer[MiniPassFactory] = ListBuffer() - passesWithIndex.foldLeft(ir) { - case (intermediateIR, (pass, index)) => { + def flushMiniPasses(in: IRType): IRType = { + if (pendingMiniPasses.nonEmpty) { + val miniPasses = + pendingMiniPasses.map(factory => createMiniPass(factory, context)) + val combinedPass = miniPasses.fold(null)(MiniIRPass.combine) + pendingMiniPasses.clear() + if (combinedPass != null) { + logger.trace(" flushing pending mini pass: {}", combinedPass) + miniPassCompile(combinedPass, in) + } else { + in + } + } else { + in + } + } + val passesWithIndex = passGroup.passes.zipWithIndex + val res = passesWithIndex.foldLeft(ir) { + case (intermediateIR, (pass, index)) => pass match { case miniFactory: MiniPassFactory => - val miniPass = miniFactory.createForInlineCompilation(newContext) - MiniIRPass.compile(classOf[Expression], intermediateIR, miniPass) + logger.trace( + " mini collected: {}", + pass + ) + val combiningPreventedByOpt = pendingMiniPasses.find { p => + p.invalidatedPasses.contains(miniFactory) + } + val irForRemainingMiniPasses = combiningPreventedByOpt match { + case Some(combiningPreventedBy) => + logger.trace( + " pass {} forces flush before (invalidates) {}", + combiningPreventedBy, + miniFactory + ) + flushMiniPasses(intermediateIR) + case None => + intermediateIR + } + pendingMiniPasses.addOne(miniFactory) + irForRemainingMiniPasses + case megaPass: IRPass => // TODO [AA, MK] This is a possible race condition. passConfiguration @@ -204,11 +215,16 @@ class PassManager( .foreach(c => c.shouldWriteToContext = isLastRunOf(index, megaPass, passGroup) ) - megaPass.runExpression(intermediateIR, newContext) + val flushedIR = flushMiniPasses(intermediateIR) + logger.trace( + " mega running: {}", + megaPass + ) + megaPassCompile(megaPass, flushedIR, context) } - - } } + + flushMiniPasses(res) } /** Determines whether the run at index `indexOfPassInGroup` is the last run @@ -231,6 +247,51 @@ class PassManager( ix - totalLength == indexOfPassInGroup } + /** Validates consistency between the IR accessible via `moduleContext` and `ir`. + * There is no way to enforce this consistency statically. + * Should be called only iff assertions are enabled. + * @return true if they are consistent, otherwise throws [[AssertionError]]. + */ + private def validateConsistency( + ir: Module, + moduleContext: ModuleContext + ): Boolean = { + def hex(obj: Object): String = { + if (obj != null) { + val hexStr = Integer.toHexString(System.identityHashCode(obj)) + val className = obj.getClass.getSimpleName + s"$className@${hexStr}" + } else { + "null" + } + } + + if ( + moduleContext.module.getCompilationStage.isAtLeast( + CompilationStage.AFTER_PARSING + ) + ) { + if (!(moduleContext.module.getIr eq ir)) { + throw new AssertionError( + "Mismatch of IR between ModuleContext and IR in module '" + moduleContext + .getName() + "'. " + + s"IR from moduleContext: ${hex(moduleContext.module.getIr)}, IR from module: ${hex(ir)}" + ) + } + val bmFromCtx = moduleContext.bindingsAnalysis() + val bmFromMeta = ir.passData.get(BindingAnalysis) + if (bmFromMeta.isDefined || bmFromCtx != null) { + Asserts.assertInJvm( + bmFromCtx eq bmFromMeta.get, + s"BindingsMap mismatch between ModuleContext and IR in module '" + + moduleContext.getName() + "'. " + + s"BindingsMap from moduleContext: ${hex(bmFromCtx)}, BindingsMap from IR: ${hex(bmFromMeta.get)}" + ) + } + } + true + } + /** Updates the metadata in a copy of the IR when updating that metadata * requires global state. * diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala deleted file mode 100644 index c27c211e806a..000000000000 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala +++ /dev/null @@ -1,445 +0,0 @@ -package org.enso.compiler.pass.analyse - -import org.enso.compiler.context.{InlineContext, ModuleContext} -import org.enso.compiler.core.Implicits.{AsDiagnostics, AsMetadata} -import org.enso.compiler.core.ir.{Expression, Module, Warning} -import org.enso.compiler.core.ir.expression.errors -import org.enso.compiler.core.ir.module.scope.Import -import org.enso.compiler.core.ir.module.scope.imports -import org.enso.compiler.data.BindingsMap -import org.enso.compiler.core.CompilerError -import org.enso.compiler.data.BindingsMap.ResolvedName -import org.enso.compiler.pass.IRPass - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer - -/** A pass that checks for ambiguous and duplicated symbols from imports. - * A duplicated import is an import of a symbol that has already been imported and refers to the - * same object (entity). On the other hand, an ambiguous import is an import of a symbol that has already - * been imported but refers to a different object. For every duplicated import, a warning is attached - * to the IR, and for every ambiguous import, the IR is replaced with an error. - * To identify an object, this pass uses physical path of the object instead of the object itself. - * - * One import IR can be replaced with multiple error IRs. This is the case for `from ... import ...` - * import statements. - * - * The original import is saved in the error and warning so that the user can see from which location - * the symbol was originally imported. - * - * Also iterates polyglot imports. - * - * All synthetic imports and exports, as well as synthetic modules are ignored by this pass. - * - * This pass does not alter any metadata. - */ -case object AmbiguousImportsAnalysis extends IRPass { - - override type Metadata = IRPass.Metadata.Empty - - override type Config = IRPass.Configuration.Default - - override lazy val precursorPasses: Seq[IRPass] = - Seq(BindingAnalysis, ImportSymbolAnalysis) - - override lazy val invalidatedPasses: Seq[IRPass] = - Seq() - - /** @inheritdoc - */ - override def runExpression( - ir: Expression, - inlineContext: InlineContext - ): Expression = ir - - /** @inheritdoc - */ - override def runModule( - ir: Module, - moduleContext: ModuleContext - ): Module = { - if (moduleContext.isSynthetic()) { - ir - } else { - val bindingMap = ir.unsafeGetMetadata( - BindingAnalysis, - "BindingMap should already be present" - ) - val encounteredSymbols = new EncounteredSymbols() - ir.copy( - imports = ir.imports.flatMap(imp => { - analyseAmbiguousSymbols( - imp, - bindingMap, - encounteredSymbols - ).fold(identity, imp => List(imp)) - }) - ) - } - } - - /** Analyses ambiguous symbols in the given import. - * @param imp current import - * @param bindingMap binding map of the current module - * @param encounteredSymbols already encountered symbols - * @return A list of errors, if any encountered, or an import IR, potentially with some - * warnings attached. - */ - private def analyseAmbiguousSymbols( - imp: Import, - bindingMap: BindingsMap, - encounteredSymbols: EncounteredSymbols - ): Either[List[errors.ImportExport], Import] = { - imp match { - // Import multiple symbols - case moduleImport @ Import.Module( - _, - _, - _, - Some(onlyNames), - _, - _, - false, - _ - ) => - getImportTargets(moduleImport, bindingMap) match { - case Some(importTargets) => - val encounteredErrors: ListBuffer[errors.ImportExport] = - ListBuffer() - onlyNames.foreach { symbol => - val symbolName = symbol.name - importTargets.foreach { importTarget => - importTarget.resolveExportedSymbol(symbolName) match { - case Right(resolvedNames) => - resolvedNames.foreach { resolvedName => - val symbolPath = resolvedName.qualifiedName.toString - tryAddEncounteredSymbol( - encounteredSymbols, - moduleImport, - symbolName, - symbolPath, - Some(resolvedName) - ) match { - case Left(error) => - encounteredErrors += error - case Right(_) => () - } - } - case Left(resolutionError) => - throw new CompilerError( - s"Unreachable: (should have been resolved in previous passes) $resolutionError" - ) - } - } - } - if (encounteredErrors.nonEmpty) { - Left(encounteredErrors.toList) - } else { - Right(moduleImport) - } - - case None => - Right(moduleImport) - } - - // Import all symbols - case moduleImport @ Import.Module( - _, - _, - true, - _, - hiddenNames, - _, - false, - _ - ) => - getImportTargets(moduleImport, bindingMap) match { - case Some(importTargets) => - // Names of the symbols that are exported by a module or a type referred to via importTarget - val exportedSymbolNames: List[String] = - importTargets.flatMap(_.exportedSymbols.keySet.toList) - val symbolsToIterate = hiddenNames match { - case None => exportedSymbolNames - case Some(hiddenNamesLiterals) => - val hiddenNames = hiddenNamesLiterals.map(_.name) - exportedSymbolNames.filterNot(hiddenNames.contains) - } - val encounteredErrors: ListBuffer[errors.ImportExport] = - ListBuffer() - symbolsToIterate.foreach { symbolName => - importTargets.foreach { importTarget => - importTarget.resolveExportedSymbol(symbolName) match { - case Left(resolutionError) => - throw new CompilerError( - s"Unreachable: (should have been resolved in previous passes) $resolutionError" - ) - case Right(List(resolvedName)) => - tryAddEncounteredSymbol( - encounteredSymbols, - moduleImport, - symbolName, - resolvedName.qualifiedName.toString, - Some(resolvedName) - ) match { - case Left(error) => - encounteredErrors += error - case Right(_) => () - } - // If the symbolName is resolved to multiple objects, we ignore it. - case Right(_) => () - } - } - } - if (encounteredErrors.nonEmpty) { - Left(encounteredErrors.toList) - } else { - Right(moduleImport) - } - - case None => - Right(moduleImport) - } - - // Import a renamed symbol - case moduleImport @ Import.Module( - importPath, - Some(rename), - _, - _, - _, - _, - false, - _ - ) => - val symbolPath = importPath.name - tryAddEncounteredSymbol( - encounteredSymbols, - moduleImport, - rename.name, - symbolPath, - None - ) match { - case Left(error) => Left(List(error)) - case Right(imp) => Right(imp) - } - - // Import one symbol - case moduleImport @ Import.Module( - importPath, - _, - _, - _, - _, - _, - false, - _ - ) => - tryAddEncounteredSymbol( - encounteredSymbols, - moduleImport, - importPath.parts.last.name, - importPath.name, - None - ) match { - case Left(err) => Left(List(err)) - case Right(imp) => Right(imp) - } - - // Polyglot import - case polyImport @ imports.Polyglot(entity, rename, _, _) => - val symbolName = rename.getOrElse(entity.getVisibleName) - val symbolPath = entity match { - case imports.Polyglot.Java(packageName, className) => - packageName + "." + className - } - tryAddEncounteredSymbol( - encounteredSymbols, - polyImport, - symbolName, - symbolPath, - None - ) match { - case Left(err) => Left(List(err)) - case Right(imp) => Right(imp) - } - - case _ => Right(imp) - } - } - - private def getImportTargets( - imp: Import, - bindingMap: BindingsMap - ): Option[List[BindingsMap.ImportTarget]] = { - bindingMap.resolvedImports.find(_.importDef == imp) match { - case Some(resolvedImport) => - Some(resolvedImport.targets) - case None => - None - } - } - - /** Tries to add the encountered symbol to the encountered symbols map. If it is already contained - * in the map, checks whether the underlying entity path is the same as the original entity path. - * Based on that, either attaches a warning for a duplicated import, or returns an [[errors.ImportExport]]. - * - * @param encounteredSymbols Encountered symbols in the current module - * @param currentImport Currently iterated import - * @param symbolName Name of the symbol that is about to be processed - * @param symbolPath physical path of the symbol that is about to be processed - * @return - */ - private def tryAddEncounteredSymbol( - encounteredSymbols: EncounteredSymbols, - currentImport: Import, - symbolName: String, - symbolPath: String, - resolvedName: Option[ResolvedName] - ): Either[errors.ImportExport, Import] = { - if (encounteredSymbols.containsSymbol(symbolName)) { - val encounteredFullName = - encounteredSymbols.getPathForSymbol(symbolName) - val originalImport = - encounteredSymbols.getOriginalImportForSymbol(symbolName).get - if (symbolPath == encounteredFullName) { - val warn = - createWarningForDuplicatedImport( - originalImport, - currentImport, - symbolName - ) - Right(currentImport.addDiagnostic(warn)) - } else { - // The symbol was encountered before and the physical path is different. - val ambiguousImpErr = createErrorForAmbiguousImport( - originalImport, - encounteredFullName, - currentImport, - symbolName, - symbolPath - ) - encounteredSymbols.getResolvedNameForSymbol(symbolName) match { - case Some(resolvedMethod: BindingsMap.ResolvedMethod) - if resolvedMethod.methodName == symbolName => - // This is a valid ambiguous case - in previously encountered import, the symbol was resolved - // to either an extension, static, or conversion method. - Right(currentImport) - case _ => - Left(ambiguousImpErr) - } - } - } else { - encounteredSymbols.addSymbol( - currentImport, - symbolName, - symbolPath, - resolvedName - ) - Right(currentImport) - } - } - - private def createErrorForAmbiguousImport( - originalImport: Import, - originalSymbolPath: String, - duplicatingImport: Import, - ambiguousSymbol: String, - ambiguousSymbolPath: String - ): errors.ImportExport = { - errors.ImportExport( - duplicatingImport, - errors.ImportExport.AmbiguousImport( - originalImport, - originalSymbolPath, - ambiguousSymbol, - ambiguousSymbolPath - ) - ) - } - - private def createWarningForDuplicatedImport( - originalImport: Import, - duplicatingImport: Import, - duplicatedSymbol: String - ): Warning = { - Warning.DuplicatedImport( - duplicatingImport.identifiedLocation(), - originalImport, - duplicatedSymbol - ) - } - - /** @param symbolPath Fully qualified name of the symbol, i.e., its physical path. - * @param resolvedName The optinal resolved name of the symbol. - * @param originalImport The import IR from which the symbol was originally imported. - * i.e. the first encountered import IR that imports the symbol. - */ - private case class SymbolTarget( - symbolPath: String, - resolvedName: Option[ResolvedName], - originalImport: Import - ) - - /** For every encountered symbol name, we keep track of the original import from which it was imported, - * along with the entity path. The entity path is vital to decide whether an imported symbol is duplicated - * or ambiguous. - * Note that there are some exceptions that are allowed to be ambiguous, like extension methods. - */ - private class EncounteredSymbols( - private val encounteredSymbols: mutable.Map[ - String, - SymbolTarget - ] = mutable.HashMap.empty - ) { - - def containsSymbol(symbolName: String): Boolean = { - encounteredSymbols.contains(symbolName) - } - - /** @param imp Import where the symbol is located - */ - def addSymbol( - imp: Import, - symbol: String, - symbolPath: String, - resolvedName: Option[ResolvedName] - ): Unit = { - encounteredSymbols.put( - symbol, - SymbolTarget(symbolPath, resolvedName, imp) - ) - } - - /** Returns the entity path for the symbol. - */ - def getPathForSymbol( - symbol: String - ): String = { - encounteredSymbols.get(symbol) match { - case Some(symbolTarget) => - symbolTarget.symbolPath - case None => - throw new IllegalStateException("unreachable") - } - } - - def getResolvedNameForSymbol( - symbol: String - ): Option[ResolvedName] = { - encounteredSymbols.get(symbol) match { - case Some(symbolTarget) => - symbolTarget.resolvedName - case None => - throw new IllegalStateException("unreachable") - } - } - - /** Returns the original import from which the symbol was imported, if any. - */ - def getOriginalImportForSymbol( - symbol: String - ): Option[Import] = { - encounteredSymbols.get(symbol).map(_.originalImport) - } - } -} diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala index a49fab7d2a30..913c9a5b00ca 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala @@ -13,7 +13,7 @@ import org.enso.compiler.data.BindingsMap.{ ExtensionMethod, ModuleMethod } -import org.enso.compiler.pass.IRPass +import org.enso.compiler.pass.{IRPass, IRProcessingPass} import org.enso.compiler.pass.desugar.{ ComplexType, FunctionBinding, @@ -40,8 +40,8 @@ case object BindingAnalysis extends IRPass { Seq(ComplexType, FunctionBinding, GenerateMethodBodies) /** The passes that are invalidated by running this pass. */ - override lazy val invalidatedPasses: Seq[IRPass] = - Seq(MethodDefinitions, Patterns) + override lazy val invalidatedPasses: Seq[IRProcessingPass] = + Seq(MethodDefinitions.INSTANCE, Patterns) /** Executes the pass on the provided `ir`, and returns a possibly transformed * or annotated version of `ir`. diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala deleted file mode 100644 index 2f390b5cd039..000000000000 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/ImportSymbolAnalysis.scala +++ /dev/null @@ -1,227 +0,0 @@ -package org.enso.compiler.pass.analyse - -import org.enso.compiler.context.{InlineContext, ModuleContext} -import org.enso.compiler.core.Implicits.AsMetadata -import org.enso.compiler.core.ir.{Expression, Module, Name} -import org.enso.compiler.core.ir.module.scope.Import -import org.enso.compiler.core.ir.expression.errors -import org.enso.compiler.data.BindingsMap -import org.enso.compiler.pass.IRPass -import org.enso.compiler.pass.desugar.GenerateMethodBodies - -/** Performs analysis of `from ... import sym1, sym2, ...` statements - checks that all - * the symbols imported from the module can be resolved, i.e., exists. - * In case of unresolved symbols, replaces the IR import with [[errors.ImportExport]]. - * Reports only the first unresolved symbol. - */ -case object ImportSymbolAnalysis extends IRPass { - - override type Metadata = BindingsMap - - override type Config = IRPass.Configuration.Default - - override lazy val precursorPasses: Seq[IRPass] = - Seq(BindingAnalysis, GenerateMethodBodies) - - override lazy val invalidatedPasses: Seq[IRPass] = - Seq() - - /** @inheritdoc - */ - override def runModule( - ir: Module, - moduleContext: ModuleContext - ): Module = { - val bindingMap = ir.unsafeGetMetadata( - BindingAnalysis, - "BindingMap should already be present" - ) - ir.copy( - imports = ir.imports.flatMap(analyseSymbolsFromImport(_, bindingMap)) - ) - } - - /** @inheritdoc - */ - override def runExpression( - ir: Expression, - inlineContext: InlineContext - ): Expression = ir - - /** @return May return multiple [[errors.ImportExport]] in case of multiple unresolved symbols. - */ - private def analyseSymbolsFromImport( - imp: Import, - bindingMap: BindingsMap - ): List[Import] = { - imp match { - case imp @ Import.Module( - _, - _, - _, - Some(onlyNames), - _, - _, - _, - _ - ) => - bindingMap.resolvedImports.find(_.importDef == imp) match { - case Some(resolvedImport) => - val importedTargets = resolvedImport.targets - val unresolvedSymbols = importedTargets.flatMap { importedTarget => - onlyNames.filterNot(isSymbolResolved(importedTarget, _)) - } - if (unresolvedSymbols.nonEmpty) { - unresolvedSymbols - .map( - createErrorForUnresolvedSymbol( - imp, - importedTargets.head, - _ - ) - ) - } else { - List(imp) - } - case None => List(imp) - } - // Importing symbols from methods is not allowed. The following code checks that if the - // import is importing all from a method, an error is reported. - case imp @ Import.Module( - _, - _, - isAll, - _, - _, - _, - isSynthetic, - _ - ) if isAll && !isSynthetic => - bindingMap.resolvedImports.find(_.importDef == imp) match { - case Some(resolvedImport) => - val importedTargets = resolvedImport.targets - val errors = importedTargets.flatMap { importedTarget => - importedTarget match { - case BindingsMap.ResolvedModuleMethod(module, method) => - val err = createImportFromMethodError( - imp, - module.getName.toString, - method.name - ) - Some(err) - case BindingsMap.ResolvedExtensionMethod( - module, - staticMethod - ) => - val err = createImportFromMethodError( - imp, - module.getName.createChild(staticMethod.tpName).toString, - staticMethod.methodName - ) - Some(err) - case BindingsMap.ResolvedConversionMethod( - module, - conversionMethod - ) => - val err = createImportFromMethodError( - imp, - module.getName - .createChild(conversionMethod.targetTpName) - .toString, - conversionMethod.methodName - ) - Some(err) - case _ => None - } - } - if (errors.nonEmpty) { - errors - } else { - List(imp) - } - case None => List(imp) - } - case _ => List(imp) - } - } - - private def createImportFromMethodError( - imp: Import, - moduleName: String, - methodName: String - ): errors.ImportExport = { - errors.ImportExport( - imp, - errors.ImportExport.IllegalImportFromMethod( - moduleName, - methodName - ) - ) - } - - private def createErrorForUnresolvedSymbol( - imp: Import, - importTarget: BindingsMap.ImportTarget, - unresolvedSymbol: Name.Literal - ): errors.ImportExport = { - importTarget match { - case BindingsMap.ResolvedModule(module) => - errors.ImportExport( - imp, - errors.ImportExport.SymbolDoesNotExist( - unresolvedSymbol.name, - module.getName.toString - ) - ) - case BindingsMap.ResolvedType(_, tp) => - errors.ImportExport( - imp, - errors.ImportExport.NoSuchConstructor( - tp.name, - unresolvedSymbol.name - ) - ) - case BindingsMap.ResolvedConstructor(_, cons) => - errors.ImportExport( - imp, - errors.ImportExport.NoSuchConstructor( - cons.name, - unresolvedSymbol.name - ) - ) - case BindingsMap.ResolvedModuleMethod(_, method) => - errors.ImportExport( - imp, - errors.ImportExport.NoSuchModuleMethod( - method.name, - unresolvedSymbol.name - ) - ) - case BindingsMap.ResolvedExtensionMethod(mod, staticMethod) => - errors.ImportExport( - imp, - errors.ImportExport.NoSuchStaticMethod( - moduleName = mod.getName.toString, - typeName = staticMethod.tpName, - methodName = unresolvedSymbol.name - ) - ) - case BindingsMap.ResolvedConversionMethod(mod, conversionMethod) => - errors.ImportExport( - imp, - errors.ImportExport.NoSuchConversionMethod( - moduleName = mod.getName.toString, - targetTypeName = conversionMethod.targetTpName, - sourceTypeName = conversionMethod.sourceTpName - ) - ) - } - } - - private def isSymbolResolved( - importTarget: BindingsMap.ImportTarget, - symbol: Name.Literal - ): Boolean = { - importTarget.findExportedSymbolsFor(symbol.name).nonEmpty - } -} diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala index 39543d6e4fd5..fb1bf1f0bcbe 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala @@ -181,7 +181,10 @@ case object FullyQualifiedNames extends IRPass { case asc: Type.Ascription => asc case method: definition.Method => val resolution = method.methodReference.typePointer.flatMap( - _.getMetadata(MethodDefinitions) + _.getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) ) method.mapExpressions( processExpression( diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala index 706709209c4d..e95ea3f79e8b 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala @@ -115,7 +115,10 @@ case object GlobalNames extends IRPass { case asc: Type.Ascription => asc case method: definition.Method => val resolution = method.methodReference.typePointer.flatMap( - _.getMetadata(MethodDefinitions) + _.getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) ) method.mapExpressions( processExpression(_, bindings, List(), freshNameSupply, resolution) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala deleted file mode 100644 index 7537cab6f2c3..000000000000 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala +++ /dev/null @@ -1,281 +0,0 @@ -package org.enso.compiler.pass.resolve - -import org.enso.compiler.context.{InlineContext, ModuleContext} -import org.enso.compiler.core.Implicits.AsMetadata -import org.enso.compiler.core.ir.{ - DefinitionArgument, - Expression, - Function, - Module, - Name -} -import org.enso.compiler.core.ir.expression.errors -import org.enso.compiler.core.ir.module.scope.definition -import org.enso.compiler.core.ir.MetadataStorage.MetadataPair -import org.enso.compiler.data.BindingsMap -import org.enso.compiler.data.BindingsMap.{Resolution, ResolvedType, Type} -import org.enso.compiler.core.CompilerError -import org.enso.compiler.pass.IRPass -import org.enso.compiler.pass.analyse.BindingAnalysis -import org.enso.compiler.pass.desugar.{ - ComplexType, - FunctionBinding, - GenerateMethodBodies -} - -/** Resolves the correct `self` argument type for method definitions and stores - * the resolution in the method's metadata. - */ -case object MethodDefinitions extends IRPass { - - override type Metadata = BindingsMap.Resolution - - override type Config = IRPass.Configuration.Default - - override lazy val precursorPasses: Seq[IRPass] = - List(ComplexType, FunctionBinding, GenerateMethodBodies, BindingAnalysis) - - override lazy val invalidatedPasses: Seq[IRPass] = List() - - /** 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: Module, - moduleContext: ModuleContext - ): Module = { - val availableSymbolsMap = ir.unsafeGetMetadata( - BindingAnalysis, - "MethodDefinitionResolution is being run before BindingResolution" - ) - val newDefs = ir.bindings.map { - case method: definition.Method => - val methodRef = method.methodReference - val resolvedTypeRef = - methodRef.typePointer.map(resolveType(_, availableSymbolsMap)) - val resolvedMethodRef = methodRef.copy(typePointer = resolvedTypeRef) - - method match { - case method: definition.Method.Explicit => - val resolvedMethod = - method.copy( - methodReference = resolvedMethodRef, - isStatic = - definition.Method.Explicit.computeIsStatic(method.body) - ) - resolvedMethod - case method: definition.Method.Conversion => - val sourceTypeExpr = method.sourceTypeName - - val resolvedName: Name = sourceTypeExpr match { - case name: Name => resolveType(name, availableSymbolsMap) - case _ => - errors.Conversion( - sourceTypeExpr, - errors.Conversion.UnsupportedSourceType - ) - } - - val resolvedMethod = method.copy( - methodReference = resolvedMethodRef, - sourceTypeName = resolvedName - ) - resolvedMethod - - case _ => - throw new CompilerError( - "Unexpected method type in MethodDefinitions pass." - ) - } - case other => other - } - - val withStaticAliases = newDefs.flatMap { - case method: definition.Method.Explicit if !method.isStatic => - method.methodReference.typePointer.flatMap( - _.getMetadata(this) - ) match { - case Some(Resolution(ResolvedType(_, tp))) - if canGenerateStaticWrappers(tp) => - org.enso.common.Asserts - .assertInJvm(method.body.isInstanceOf[Function.Lambda]) - val dup = method.duplicate() - // This is the self argument that will receive the `SelfType.type` value upon dispatch, it is added to avoid modifying the dispatch mechanism. - val syntheticModuleSelfArg = DefinitionArgument.Specified( - Name.Self(identifiedLocation = null, synthetic = true), - None, - None, - suspended = false, - identifiedLocation = null - ) - - // The actual `self` argument that is referenced inside of method body is the second one in the lambda. - // This is the argument that will hold the actual instance of the object we are calling on, e.g. `My_Type.method instance`. - // We add a type check to it to ensure only `instance` of `My_Type` can be passed to it. - val static = dup.copy( - body = new Function.Lambda( - // This is the synthetic Self argument that gets the static module - List(syntheticModuleSelfArg), - // Here we add the type ascription ensuring that the 'proper' self argument only accepts _instances_ of the type (or triggers conversions) - addTypeAscriptionToSelfArgument(dup.body), - identifiedLocation = null - ), - isStaticWrapperForInstanceMethod = true, - isStatic = true - ) - List(method, static) - case _ => - List(method) - } - - case other => List(other) - } - - ir.copy(bindings = withStaticAliases) - } - - private def addTypeAscriptionToSelfArgument( - methodBody: Expression - ): Expression = methodBody match { - case lambda: Function.Lambda => - val newArguments = lambda.arguments match { - case (selfArg @ DefinitionArgument.Specified( - Name.Self(_, _, _), - _, - _, - _, - _, - _ - )) :: rest => - val selfType = - Name.SelfType(identifiedLocation = selfArg.identifiedLocation) - selfArg.copy(ascribedType = Some(selfType)) :: rest - case other :: _ => - throw new CompilerError( - s"MethodDefinitions pass: expected the first argument to be `self`, but got $other" - ) - case Nil => - throw new CompilerError( - s"MethodDefinitions pass: expected at least one argument (self) in the method, but got none." - ) - } - lambda.copy(arguments = newArguments) - case other => - throw new CompilerError( - s"Unexpected body type $other in MethodDefinitions pass." - ) - } - - // Generate static wrappers for - // 1. Types having at least one type constructor - // 2. All builtin types except for Nothing. Nothing's eigentype is Nothing and not Nothing.type, - // would lead to overriding conflicts. - // TODO: Remove the hardcoded type once Enso's annotations can define parameters. - private def canGenerateStaticWrappers(tp: Type): Boolean = - tp.members.nonEmpty || (tp.builtinType && (tp.name != "Nothing")) - - private def resolveType( - typePointer: Name, - availableSymbolsMap: BindingsMap - ): Name = { - typePointer match { - case _: Name.Qualified | _: Name.Literal => - val items = typePointer match { - case qualName: Name.Qualified => qualName.parts.map(_.name) - case literal: Name.Literal => List(literal.name) - case _ => - throw new CompilerError("Impossible to reach.") - } - availableSymbolsMap.resolveQualifiedName(items) match { - case Left(err) => - errors.Resolution( - typePointer, - errors.Resolution.ResolverError(err) - ) - case Right(resolvedItems) => - org.enso.common.Asserts.assertInJvm( - resolvedItems.size == 1, - "Expected a single resolution" - ) - resolvedItems.head match { - case _: BindingsMap.ResolvedConstructor => - errors.Resolution( - typePointer, - errors.Resolution.UnexpectedConstructor( - "a method definition target" - ) - ) - case value: BindingsMap.ResolvedModule => - typePointer.updateMetadata( - new MetadataPair(this, BindingsMap.Resolution(value)) - ) - case value: BindingsMap.ResolvedType => - typePointer.updateMetadata( - new MetadataPair(this, BindingsMap.Resolution(value)) - ) - case _: BindingsMap.ResolvedPolyglotSymbol => - errors.Resolution( - typePointer, - errors.Resolution.UnexpectedPolyglot( - "a method definition target" - ) - ) - case _: BindingsMap.ResolvedPolyglotField => - errors.Resolution( - typePointer, - errors.Resolution.UnexpectedPolyglot( - "a method definition target" - ) - ) - case _: BindingsMap.ResolvedModuleMethod => - errors.Resolution( - typePointer, - errors.Resolution.UnexpectedMethod( - "a method definition target" - ) - ) - case _: BindingsMap.ResolvedExtensionMethod => - errors.Resolution( - typePointer, - errors.Resolution.UnexpectedMethod( - "a static method definition target" - ) - ) - case _: BindingsMap.ResolvedConversionMethod => - errors.Resolution( - typePointer, - errors.Resolution.UnexpectedMethod( - "a conversion method definition target" - ) - ) - } - } - case tp: errors.Resolution => tp - case _ => - throw new CompilerError( - "Unexpected kind of name for method reference" - ) - } - } - - /** 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: Expression, - inlineContext: InlineContext - ): Expression = ir - -} diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala index e2914b67881a..840c93d69beb 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala @@ -69,7 +69,10 @@ object Patterns extends IRPass { case method: definition.Method.Explicit => val resolution = method.methodReference.typePointer .flatMap( - _.getMetadata(MethodDefinitions) + _.getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) ) .map(_.target) val newBody = doExpression(method.body, bindings, resolution) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala index 0b675f973b82..04aaa046d574 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala @@ -96,7 +96,10 @@ case object TypeNames extends IRPass { def fromMethodReference(m: Name.MethodReference): SelfTypeInfo = m.typePointer match { case Some(p) => - p.getMetadata(MethodDefinitions) match { + p.getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) match { case Some(resolution) => resolution.target match { case typ: BindingsMap.ResolvedType => diff --git a/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MiniPassTraverserTest.java b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MiniPassTraverserTest.java new file mode 100644 index 000000000000..f05e6b71e68c --- /dev/null +++ b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MiniPassTraverserTest.java @@ -0,0 +1,128 @@ +package org.enso.compiler.test.pass; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.util.List; +import org.enso.compiler.pass.MiniIRPass; +import org.junit.Test; + +public class MiniPassTraverserTest { + @Test + public void traversesOneExpression() { + var expr = new MockExpression(false); + var miniPass = MockMiniPass.builder().build(); + MiniIRPass.compile(MockExpression.class, expr, miniPass); + assertThat( + "Prepare is called only for trees with depth > 1", expr.isPreparedByAny(), is(false)); + assertThat(expr.isTransformedByAny(), is(true)); + } + + @Test + public void traversesExpressionWithOneChild() { + var parentExpr = new MockExpression(false); + var childExpr = new MockExpression(true); + parentExpr.addChild(childExpr); + var miniPass = MockMiniPass.builder().build(); + MiniIRPass.compile(MockExpression.class, parentExpr, miniPass); + assertThat( + "Prepare must be called on a child expression", childExpr.isPreparedByAny(), is(true)); + assertThat(childExpr.isTransformedByAny(), is(true)); + assertThat(parentExpr.isTransformedByAny(), is(true)); + } + + @Test + public void traversesExpressionWithManyChildren() { + var parentExpr = new MockExpression(false); + var children = List.of(new MockExpression(true), new MockExpression(true)); + children.forEach(parentExpr::addChild); + var miniPass = MockMiniPass.builder().build(); + MiniIRPass.compile(MockExpression.class, parentExpr, miniPass); + for (var ch : children) { + assertThat("Prepare must be called on a child expression", ch.isPreparedByAny(), is(true)); + assertThat(ch.isTransformedByAny(), is(true)); + } + assertThat(parentExpr.isTransformedByAny(), is(true)); + } + + @Test + public void stopTraversingWhenPrepareReturnsNull() { + var e1 = new MockExpression(false); + var e2 = new MockExpression(true); + var e3 = new MockExpression(true); + e1.addChild(e2); + e2.addChild(e3); + // Should stop traversing when e3 is encountered. + // Should only process e1 and e2, not e3 + var miniPass = MockMiniPass.builder().stopExpr(e3).build(); + MiniIRPass.compile(MockExpression.class, e1, miniPass); + assertThat("e3 should not be processed", e3.isPreparedByAny(), is(false)); + assertThat("e3 should not be processed", e3.isTransformedByAny(), is(false)); + assertThat("e2 should still be processed", e2.isPreparedByAny(), is(true)); + assertThat("e2 should still be processed", e2.isTransformedByAny(), is(true)); + } + + @Test + public void chainedMiniPass_TraversesSingleExpression() { + var parentExpr = new MockExpression(false); + var childExpr = new MockExpression(true); + parentExpr.addChild(childExpr); + var miniPass1 = MockMiniPass.builder().build(); + var miniPass2 = MockMiniPass.builder().build(); + var chainedPass = MiniIRPass.combine(miniPass1, miniPass2); + MiniIRPass.compile(MockExpression.class, parentExpr, chainedPass); + assertThat( + "Child expression is transformed by both passes", + childExpr.isTransformedBy(miniPass1), + is(true)); + assertThat( + "Child expression is transformed by both passes", + childExpr.isTransformedBy(miniPass2), + is(true)); + assertThat( + "Child expression is prepared by both passes", childExpr.isPreparedBy(miniPass1), is(true)); + assertThat( + "Child expression is prepared by both passes", childExpr.isPreparedBy(miniPass2), is(true)); + } + + @Test + public void chainedMiniPass_StopsTraversingWhenPrepareReturnsNull() { + var e1 = new MockExpression(false); + var e2 = new MockExpression(true); + var e3 = new MockExpression(true); + e1.addChild(e2); + e2.addChild(e3); + // miniPass1 stops traversing on e2. + var miniPass1 = MockMiniPass.builder().stopExpr(e3).build(); + // miniPass2 traverses everything. + var miniPass2 = MockMiniPass.builder().build(); + var chainedPass = MiniIRPass.combine(miniPass1, miniPass2); + MiniIRPass.compile(MockExpression.class, e1, chainedPass); + assertThat("e3 should be prepared only by miniPass2", e3.isPreparedBy(miniPass2), is(true)); + assertThat( + "e3 should be transformed only by miniPass2", e3.isTransformedBy(miniPass2), is(true)); + assertThat("e3 must not be transformed by miniPass1", e3.isTransformedBy(miniPass1), is(false)); + assertThat( + "e2 should still be transformed by miniPass1", e2.isTransformedBy(miniPass1), is(true)); + } + + @Test + public void chainedMiniPass_StopsTraversingWhenPrepareFromBothPassesReturnNull() { + var e1 = new MockExpression(false); + var e2 = new MockExpression(true); + var e3 = new MockExpression(true); + e1.addChild(e2); + e2.addChild(e3); + // Both mini passes process just e1. + var miniPass1 = MockMiniPass.builder().stopExpr(e2).build(); + var miniPass2 = MockMiniPass.builder().stopExpr(e2).build(); + var chainedPass = MiniIRPass.combine(miniPass1, miniPass2); + MiniIRPass.compile(MockExpression.class, e1, chainedPass); + assertThat("e3 should not be prepared by any pass", e3.isPreparedByAny(), is(false)); + assertThat("e3 should not be transformed by any pass", e3.isTransformedByAny(), is(false)); + assertThat("e2 should not be prepared by any pass", e2.isPreparedByAny(), is(false)); + assertThat("e2 should not be transformed by any pass", e2.isTransformedByAny(), is(false)); + assertThat("e1 should be processed by both passes", e1.isTransformedBy(miniPass1), is(true)); + assertThat("e1 should be processed by both passes", e1.isTransformedBy(miniPass2), is(true)); + } +} diff --git a/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockExpression.java b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockExpression.java new file mode 100644 index 000000000000..244e02eeba16 --- /dev/null +++ b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockExpression.java @@ -0,0 +1,124 @@ +package org.enso.compiler.test.pass; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import org.enso.compiler.core.IR; +import org.enso.compiler.core.Identifier; +import org.enso.compiler.core.ir.DiagnosticStorage; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.core.ir.IdentifiedLocation; +import org.enso.compiler.core.ir.MetadataStorage; +import scala.Option; +import scala.PartialFunction; +import scala.jdk.javaapi.CollectionConverters; + +final class MockExpression implements Expression { + private final Set transformedBy = new HashSet<>(); + private final Set preparedBy = new HashSet<>(); + private final boolean hasParent; + private List exprChildren = new ArrayList<>(); + + MockExpression(boolean hasParent) { + this.hasParent = hasParent; + } + + boolean isTransformedBy(MockMiniPass pass) { + return transformedBy.contains(pass); + } + + boolean isTransformedByAny() { + return !transformedBy.isEmpty(); + } + + boolean isPreparedBy(MockMiniPass pass) { + return preparedBy.contains(pass); + } + + boolean isPreparedByAny() { + return !preparedBy.isEmpty(); + } + + void setTransformedByPass(MockMiniPass pass) { + transformedBy.add(pass); + } + + boolean hasParent() { + return hasParent; + } + + void addChild(MockExpression child) { + exprChildren.add(child); + } + + void setPreparedBy(MockMiniPass pass) { + preparedBy.add(pass); + } + + @Override + public Expression transformExpressions(PartialFunction fn) { + return this; + } + + @Override + public Expression mapExpressions(Function fn) { + for (var child : exprChildren) { + fn.apply(child); + } + return this; + } + + @Override + public scala.collection.immutable.List children() { + var lst = CollectionConverters.asScala(exprChildren).toList(); + var ret = lst.map(item -> (IR) item); + return ret; + } + + @Override + public @Identifier UUID getId() { + return null; + } + + @Override + public DiagnosticStorage diagnostics() { + return null; + } + + @Override + public DiagnosticStorage getDiagnostics() { + return null; + } + + @Override + public MetadataStorage passData() { + return null; + } + + @Override + public IdentifiedLocation identifiedLocation() { + return null; + } + + @Override + public Expression setLocation(Option location) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public Expression duplicate( + boolean keepLocations, + boolean keepMetadata, + boolean keepDiagnostics, + boolean keepIdentifiers) { + throw new UnsupportedOperationException("unimplemented"); + } + + @Override + public String showCode(int indent) { + throw new UnsupportedOperationException("unimplemented"); + } +} diff --git a/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockMiniPass.java b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockMiniPass.java new file mode 100644 index 000000000000..e0792a9bc27c --- /dev/null +++ b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/MockMiniPass.java @@ -0,0 +1,65 @@ +package org.enso.compiler.test.pass; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.enso.compiler.core.IR; +import org.enso.compiler.core.ir.Expression; +import org.enso.compiler.pass.MiniIRPass; + +final class MockMiniPass extends MiniIRPass { + private final MockExpression stopExpr; + + /** + * @param stopExpr When encountered this expression, {@code prepare} method will return null to + * signal that the traversal should stop. Can be null. + */ + private MockMiniPass(MockExpression stopExpr) { + this.stopExpr = stopExpr; + } + + static Builder builder() { + return new Builder(); + } + + @Override + public Expression transformExpression(Expression expr) { + if (expr instanceof MockExpression mockExpr) { + if (mockExpr.hasParent()) { + assertThat( + "Prepare must be called on an expression with a parent", + mockExpr.isPreparedBy(this), + is(true)); + } + assertThat( + "Transform is called just once by one pass", mockExpr.isTransformedBy(this), is(false)); + mockExpr.setTransformedByPass(this); + } + return expr; + } + + @Override + public MiniIRPass prepare(IR parent, Expression child) { + if (stopExpr == child) { + return null; + } + if (child instanceof MockExpression mockExpr) { + assertThat("Prepare is called just once by one pass", mockExpr.isPreparedBy(this), is(false)); + mockExpr.setPreparedBy(this); + } + return this; + } + + static final class Builder { + private MockExpression stopExpr; + + Builder stopExpr(MockExpression stopExpr) { + this.stopExpr = stopExpr; + return this; + } + + MockMiniPass build() { + return new MockMiniPass(stopExpr); + } + } +} diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/job/ExecuteExpressionJob.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/job/ExecuteExpressionJob.java index 31db700f7a88..220c25cf53d1 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/job/ExecuteExpressionJob.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/job/ExecuteExpressionJob.java @@ -4,7 +4,7 @@ import org.enso.interpreter.instrument.OneshotExpression; import org.enso.interpreter.instrument.execution.Executable; import org.enso.interpreter.instrument.execution.RuntimeContext; -import org.enso.interpreter.util.ScalaConversions; +import org.enso.scala.wrapper.ScalaConversions; /** The job that schedules the execution of the expression. */ public class ExecuteExpressionJob extends Job implements UniqueJob { diff --git a/engine/runtime-instrument-common/src/test/java/org/enso/compiler/pass/PassManagerTestUtils.java b/engine/runtime-instrument-common/src/test/java/org/enso/compiler/pass/PassManagerTestUtils.java new file mode 100644 index 000000000000..3e0e0314b2e9 --- /dev/null +++ b/engine/runtime-instrument-common/src/test/java/org/enso/compiler/pass/PassManagerTestUtils.java @@ -0,0 +1,11 @@ +package org.enso.compiler.pass; + +import scala.collection.immutable.List; + +public final class PassManagerTestUtils { + private PassManagerTestUtils() {} + + public static List getPasses(PassManager passManager) { + return passManager.passes(); + } +} diff --git a/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala b/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala index 01c42dc4683a..216260e9eca1 100644 --- a/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala +++ b/engine/runtime-instrument-common/src/test/scala/org/enso/compiler/test/CompilerTestSetup.scala @@ -8,7 +8,11 @@ import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.data.BindingsMap.ModuleReference import org.enso.compiler.data.{BindingsMap, CompilerConfig} import org.enso.compiler.pass.analyse.BindingAnalysis -import org.enso.compiler.pass.{PassConfiguration, PassManager} +import org.enso.compiler.pass.{ + PassConfiguration, + PassManager, + PassManagerTestUtils +} import org.enso.interpreter.runtime import org.enso.interpreter.runtime.ModuleTestUtils import org.enso.compiler.context.LocalScope @@ -38,7 +42,16 @@ trait CompilerTestSetup { passManager: PassManager, moduleContext: ModuleContext ): Module = { - passManager.runPassesOnModule(ir, moduleContext) + val passGroups = PassManagerTestUtils.getPasses(passManager) + val runtimeMod = runtime.Module.fromCompilerModule(moduleContext.module) + passGroups.foldLeft(ir)((curIr, group) => { + // Before a PassGroup is run on a module, we need to explicitly set the + // IR on the runtime module, as the pass manager will not do this for us. + // This is to ensure consistency between the curIr and IR stored in moduleContext + ModuleTestUtils.unsafeSetIr(runtimeMod, curIr) + val newIr = passManager.runPassesOnModule(curIr, moduleContext, group) + newIr + }) } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java index 8c29b11ddaf1..bb41f3d6a129 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/SerializationManagerTest.java @@ -15,10 +15,10 @@ import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.util.TruffleFileSystem; import org.enso.interpreter.test.InterpreterContext; -import org.enso.interpreter.util.ScalaConversions; import org.enso.pkg.Package; import org.enso.pkg.PackageManager; import org.enso.polyglot.Suggestion; +import org.enso.scala.wrapper.ScalaConversions; import org.junit.After; import org.junit.Assert; import org.junit.Before; diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java index 038ae28e76d5..33aa8b37b74f 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/test/TypeInferenceTest.java @@ -1184,7 +1184,8 @@ public CompilerConfig defaultConfig() { ModuleContext moduleContext = compilerRunner.buildModuleContext( moduleName, Option.apply(new FreshNameSupply()), Option.empty(), compilerConfig, false); - Module processedModule = passManager.runPassesOnModule(rawModule, moduleContext); + Module processedModule = + compilerRunner.runPassesOnModule(rawModule, passManager, moduleContext); return processedModule; } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ModuleTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ModuleTest.java index ee710fb4e2cb..c37652ab4795 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ModuleTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/runtime/ModuleTest.java @@ -1,6 +1,6 @@ package org.enso.interpreter.runtime; -import static org.enso.interpreter.util.ScalaConversions.nil; +import static org.enso.scala.wrapper.ScalaConversions.nil; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/EnsoProjectTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/EnsoProjectTest.java index 7ba0eb5a1317..30a915f5e0ab 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/EnsoProjectTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/meta/EnsoProjectTest.java @@ -9,9 +9,9 @@ import java.util.Set; import org.enso.common.LanguageInfo; import org.enso.common.RuntimeOptions; -import org.enso.interpreter.util.ScalaConversions; import org.enso.pkg.QualifiedName; import org.enso.polyglot.PolyglotContext; +import org.enso.scala.wrapper.ScalaConversions; import org.enso.test.utils.ContextUtils; import org.enso.test.utils.ProjectUtils; import org.enso.test.utils.SourceModule; diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala index b7bd14a235c0..a81def60f616 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/CompilerTest.scala @@ -16,7 +16,7 @@ import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.data.BindingsMap.ModuleReference import org.enso.compiler.data.{BindingsMap, CompilerConfig} import org.enso.compiler.pass.analyse.BindingAnalysis -import org.enso.compiler.pass.{PassConfiguration, PassManager} +import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpecLike import org.enso.interpreter.runtime @@ -31,6 +31,20 @@ trait CompilerTest extends AnyWordSpecLike with Matchers with CompilerRunner trait CompilerRunner { // === IR Utilities ========================================================= + /** Utility method to access private field of [[PassManager]]. We cannot use + * a class in the same package because patching up the JPMS modules would + * be too complex. Let's just hack it via reflection here. + * @return + */ + protected def getPassesViaReflection( + passManager: PassManager + ): List[PassGroup] = { + val passesField = passManager.getClass.getDeclaredField("passes") + passesField.setAccessible(true) + val passes = passesField.get(passManager) + passes.asInstanceOf[List[PassGroup]] + } + /** Provides an extension method allowing the running of a specified list of * passes on the provided IR. * @@ -48,8 +62,38 @@ trait CompilerRunner { passManager: PassManager, moduleContext: ModuleContext ): Module = { - passManager.runPassesOnModule(ir, moduleContext) + val passGroups = getPassesViaReflection(passManager) + val runtimeMod = runtime.Module.fromCompilerModule(moduleContext.module) + passGroups.foldLeft(ir)((curIr, group) => { + // Before a PassGroup is run on a module, we need to explicitly set the + // IR on the runtime module, as the pass manager will not do this for us. + // This is to ensure consistency between the curIr and IR stored in moduleContext + ModuleTestUtils.unsafeSetIr(runtimeMod, curIr) + val newIr = passManager.runPassesOnModule(curIr, moduleContext, group) + newIr + }) } + + def runPasses( + passManager: PassManager, + passGroup: PassGroup, + moduleContext: ModuleContext + ): Module = { + val runtimeMod = runtime.Module.fromCompilerModule(moduleContext.module) + ModuleTestUtils.unsafeSetIr(runtimeMod, ir) + val newIr = passManager.runPassesOnModule(ir, moduleContext, passGroup) + newIr + } + } + + /** Wrapper for the implicit method. Callable from Java. + */ + protected def runPassesOnModule( + ir: Module, + passManager: PassManager, + moduleContext: ModuleContext + ): Module = { + ir.runPasses(passManager, moduleContext) } /** Provides an extensions to work with diagnostic information attached to IR. diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/MiniPassTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/MiniPassTest.scala index d26dcd80ca9a..4b4c2203a5b2 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/MiniPassTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/MiniPassTest.scala @@ -84,9 +84,8 @@ trait MiniPassTest extends CompilerTest { source: String, moduleCtx: ModuleContext ): Module = { - val module = parseModule(source) - val preprocessedModule = - megaPassManager.runPassesOnModule(module, moduleCtx) + val module = parseModule(source) + val preprocessedModule = module.runPasses(megaPassManager, moduleCtx) megaPass.runModule(preprocessedModule, moduleCtx) } @@ -94,10 +93,9 @@ trait MiniPassTest extends CompilerTest { source: String, moduleCtx: ModuleContext ): Module = { - val module = parseModule(source) - val miniPass = miniPassFactory.createForModuleCompilation(moduleCtx) - val preprocessedModule = - megaPassManager.runPassesOnModule(module, moduleCtx) + val module = parseModule(source) + val miniPass = miniPassFactory.createForModuleCompilation(moduleCtx) + val preprocessedModule = module.runPasses(megaPassManager, moduleCtx) MiniIRPass.compile(classOf[Module], preprocessedModule, miniPass) } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala index 2ef5cf27e8e7..4f117401b4d7 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/PassesTest.scala @@ -60,12 +60,12 @@ class PassesTest extends CompilerTest { GenerateMethodBodies, BindingAnalysis, ModuleNameConflicts, - MethodDefinitions, + MethodDefinitions.INSTANCE, SectionsToBinOp.INSTANCE, OperatorToFunction, LambdaShorthandToLambda, - ImportSymbolAnalysis, - AmbiguousImportsAnalysis, + ImportSymbolAnalysis.INSTANCE, + AmbiguousImportsAnalysis.INSTANCE, PrivateModuleAnalysis.INSTANCE, PrivateConstructorAnalysis.INSTANCE, ShadowedPatternFields, diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/PrivateModifierTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/PrivateModifierTest.scala index fb8af0bf9c76..6ee32f8c2772 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/PrivateModifierTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/PrivateModifierTest.scala @@ -30,15 +30,12 @@ class PrivateModifierTest extends CompilerTest { */ implicit class AnalyseModule(ir: Module) { - /** Runs alias analysis on a module. - * - * @return [[ir]], with attached aliasing information - */ def analyse: Module = { - PrivateConstructorAnalysis.INSTANCE.runModule( - ir, - buildModuleContext(passConfiguration = Some(passConfig)) - ) + val miniPass = + PrivateConstructorAnalysis.INSTANCE.createForModuleCompilation( + buildModuleContext(passConfiguration = Some(passConfig)) + ) + miniPass.transformModule(ir) } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala index f16c70bea1ee..c79fd0998d74 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala @@ -85,11 +85,11 @@ class GlobalNamesTest extends CompilerTest { | |""".stripMargin val parsed = EnsoParser.compile(code) - val moduleMapped = passManager.runPassesOnModule(parsed, ctx, group1) + val moduleMapped = parsed.runPasses(passManager, group1, ctx) ModuleTestUtils.unsafeSetIr(both._2, moduleMapped) new ExportsResolution(null).run(List(both._2.asCompilerModule())) - val allPrecursors = passManager.runPassesOnModule(moduleMapped, ctx, group2) + val allPrecursors = moduleMapped.runPasses(passManager, group2, ctx) val ir = allPrecursors.analyse val bodyExprs = ir diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala index aefe1db7f663..b6f19462701e 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/MethodDefinitionsTest.scala @@ -9,7 +9,12 @@ import org.enso.compiler.core.ir.expression.errors import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap.Type import org.enso.compiler.pass.resolve.MethodDefinitions -import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} +import org.enso.compiler.pass.{ + MiniIRPass, + PassConfiguration, + PassGroup, + PassManager +} import org.enso.compiler.test.CompilerTest class MethodDefinitionsTest extends CompilerTest { @@ -24,7 +29,7 @@ class MethodDefinitionsTest extends CompilerTest { val passes = new Passes(defaultConfig) val precursorPasses: PassGroup = - passes.getPrecursors(MethodDefinitions).get + passes.getPrecursors(MethodDefinitions.INSTANCE).get val passConfiguration: PassConfiguration = PassConfiguration() @@ -43,7 +48,9 @@ class MethodDefinitionsTest extends CompilerTest { * @return [[ir]], with tail call analysis metadata attached */ def analyse(implicit context: ModuleContext): Module = { - MethodDefinitions.runModule(ir, context) + val miniPass = + MethodDefinitions.INSTANCE.createForModuleCompilation(context) + MiniIRPass.compile(classOf[Module], ir, miniPass) } } @@ -78,7 +85,10 @@ class MethodDefinitionsTest extends CompilerTest { .methodReference .typePointer .get - .getMetadata(MethodDefinitions) shouldEqual Some( + .getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) shouldEqual Some( BindingsMap.Resolution( BindingsMap.ResolvedType( ctx.moduleReference(), @@ -96,7 +106,10 @@ class MethodDefinitionsTest extends CompilerTest { .methodReference .typePointer .get - .getMetadata(MethodDefinitions) shouldEqual Some( + .getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) shouldEqual Some( BindingsMap.Resolution( BindingsMap.ResolvedModule(ctx.moduleReference()) ) @@ -112,7 +125,8 @@ class MethodDefinitionsTest extends CompilerTest { .bindings(6) .asInstanceOf[definition.Method.Conversion] conv1.methodReference.typePointer.get.getMetadata( - MethodDefinitions + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] ) shouldEqual Some( BindingsMap.Resolution( BindingsMap.ResolvedType( @@ -121,7 +135,10 @@ class MethodDefinitionsTest extends CompilerTest { ) ) ) - conv1.sourceTypeName.getMetadata(MethodDefinitions) shouldEqual Some( + conv1.sourceTypeName.getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) shouldEqual Some( BindingsMap.Resolution( BindingsMap.ResolvedType( ctx.moduleReference(), @@ -134,7 +151,8 @@ class MethodDefinitionsTest extends CompilerTest { .bindings(7) .asInstanceOf[definition.Method.Conversion] conv2.methodReference.typePointer.get.getMetadata( - MethodDefinitions + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] ) shouldEqual Some( BindingsMap.Resolution( BindingsMap.ResolvedType( @@ -149,7 +167,10 @@ class MethodDefinitionsTest extends CompilerTest { .bindings(8) .asInstanceOf[definition.Method.Conversion] conv3.methodReference.typePointer.get shouldBe an[errors.Resolution] - conv3.sourceTypeName.getMetadata(MethodDefinitions) shouldEqual Some( + conv3.sourceTypeName.getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) shouldEqual Some( BindingsMap.Resolution( BindingsMap.ResolvedType( ctx.moduleReference(), diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/Implicits.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/Implicits.scala index 702c835d7619..827fe4540820 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/Implicits.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/Implicits.scala @@ -3,6 +3,8 @@ package org.enso.compiler.core import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.core.ir.{Diagnostic, ProcessingPass} +import scala.annotation.unused + object Implicits { /** This class adds an extension method to control how the pass data element @@ -91,6 +93,19 @@ object Implicits { ir.passData.get(pass).asInstanceOf[Option[pass.Metadata]] } + /** Getting metadata from passes that are implemented in Java and thus have no + * overriden type alias for the metadata types. + * @return + */ + def getMetadata[MetaType <: ProcessingPass.Metadata]( + pass: ProcessingPass, + @unused + expectedMetaType: Class[MetaType] + ): Option[MetaType] = { + val meta = ir.passData.get(pass) + meta.asInstanceOf[Option[MetaType]] + } + /** Unsafely gets the metadata for the specified pass, if it exists. * * @param pass the pass to get metadata for diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DefinitionArgument.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DefinitionArgument.scala index fd820cd62a72..765dd45d52ff 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DefinitionArgument.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/DefinitionArgument.scala @@ -141,6 +141,12 @@ object DefinitionArgument { } else this } + def copyWithAscribedType( + ascribedType: Expression + ): Specified = { + copy(ascribedType = Some(ascribedType)) + } + override def withName(ir: Name): DefinitionArgument = copy(name = ir) /** @inheritdoc */ diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Function.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Function.scala index 021e0c1b57cb..826c2fc2faba 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Function.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Function.scala @@ -135,6 +135,12 @@ object Function { } else this } + def copyWithArguments( + arguments: List[DefinitionArgument] + ): Lambda = { + copy(arguments = arguments) + } + /** @inheritdoc */ override def duplicate( keepLocations: Boolean = true, diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Module.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Module.scala index b0e95f373572..a0bd539fd773 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Module.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Module.scala @@ -99,6 +99,19 @@ final case class Module( } else this } + def copyWithImportsAndExports( + imports: List[Import], + exports: List[Export] + ) = { + copy(imports, exports) + } + + def copyWithBindings( + bindings: List[Definition] + ) = { + copy(bindings = bindings) + } + /** @inheritdoc */ override def duplicate( keepLocations: Boolean = true, diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Name.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Name.scala index c4be29a77458..6509ae2179ee 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Name.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/Name.scala @@ -93,6 +93,12 @@ object Name { } else this } + def copyWithTypePointer( + typePointer: Option[Name] + ) = { + copy(typePointer = typePointer) + } + /** @inheritdoc */ override def duplicate( keepLocations: Boolean = true, diff --git a/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/SuggestionBuilder.scala b/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/SuggestionBuilder.scala index 06c71db33068..a9b4758eab8b 100644 --- a/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/SuggestionBuilder.scala +++ b/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/SuggestionBuilder.scala @@ -131,7 +131,10 @@ final class SuggestionBuilder[A: IndexedSource]( val (selfTypeOpt, isStatic) = typePtr match { case Some(typePtr) => val selfType = typePtr - .getMetadata(MethodDefinitions) + .getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) .map(_.target.qualifiedName) selfType -> m.isStatic case None => @@ -167,7 +170,10 @@ final class SuggestionBuilder[A: IndexedSource]( ) if !conversionMeth.isPrivate => val selfType = typePtr.flatMap { typePointer => typePointer - .getMetadata(MethodDefinitions) + .getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) .map(_.target.qualifiedName) } val conversion = buildConversion( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/TruffleCompilerContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/TruffleCompilerContext.java index cfed0237247f..f0c7d5069f39 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/TruffleCompilerContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/TruffleCompilerContext.java @@ -1,7 +1,7 @@ package org.enso.interpreter.runtime; -import static org.enso.interpreter.util.ScalaConversions.cons; -import static org.enso.interpreter.util.ScalaConversions.nil; +import static org.enso.scala.wrapper.ScalaConversions.cons; +import static org.enso.scala.wrapper.ScalaConversions.nil; import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.TruffleLogger; @@ -791,7 +791,9 @@ public BindingsMap getBindingsMap() { var meta = module.getIr().passData(); var pass = meta.get(BindingAnalysis$.MODULE$); emitIOException(); - return (BindingsMap) pass.get(); + if (pass.isDefined()) { + return (BindingsMap) pass.get(); + } } catch (IOException ex) { var logger = TruffleLogger.getLogger(LanguageInfo.ID, org.enso.interpreter.runtime.Module.class); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java index 444cf84032bf..79452f8043f8 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/TopLevelScope.java @@ -23,9 +23,9 @@ import org.enso.interpreter.runtime.data.EnsoObject; import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; import org.enso.interpreter.runtime.type.Types; -import org.enso.interpreter.util.ScalaConversions; import org.enso.pkg.Package; import org.enso.pkg.QualifiedName; +import org.enso.scala.wrapper.ScalaConversions; /** Represents the top scope of Enso execution, containing all the importable modules. */ @ExportLibrary(InteropLibrary.class) diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index d071f6e07c94..1ad5b986a45f 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -724,7 +724,10 @@ class IrToTruffle( Some(scopeAssociatedType) case Some(tpePointer) => tpePointer - .getMetadata(MethodDefinitions) + .getMetadata( + MethodDefinitions.INSTANCE, + classOf[BindingsMap.Resolution] + ) .map { res => res.target match { case binding @ BindingsMap.ResolvedType(_, _) => @@ -945,38 +948,40 @@ class IrToTruffle( } private def getTypeResolution(expr: IR): Option[Type] = - expr.getMetadata(MethodDefinitions).map { res => - res.target match { - case binding @ BindingsMap.ResolvedType(_, _) => - asType(binding) - case BindingsMap.ResolvedModule(module) => - asAssociatedType(module.unsafeAsModule()) - case BindingsMap.ResolvedConstructor(_, _) => - throw new CompilerError( - "Impossible here, should be caught by MethodDefinitions pass." - ) - case BindingsMap.ResolvedPolyglotSymbol(_, _) => - throw new CompilerError( - "Impossible polyglot symbol, should be caught by MethodDefinitions pass." - ) - case BindingsMap.ResolvedPolyglotField(_, _) => - throw new CompilerError( - "Impossible polyglot field, should be caught by MethodDefinitions pass." - ) - case _: BindingsMap.ResolvedModuleMethod => - throw new CompilerError( - "Impossible module method here, should be caught by MethodDefinitions pass." - ) - case _: BindingsMap.ResolvedExtensionMethod => - throw new CompilerError( - "Impossible static method here, should be caught by MethodDefinitions pass." - ) - case _: BindingsMap.ResolvedConversionMethod => - throw new CompilerError( - "Impossible conversion method here, should be caught by MethodDefinitions pass." - ) + expr + .getMetadata(MethodDefinitions.INSTANCE, classOf[BindingsMap.Resolution]) + .map { res => + res.target match { + case binding @ BindingsMap.ResolvedType(_, _) => + asType(binding) + case BindingsMap.ResolvedModule(module) => + asAssociatedType(module.unsafeAsModule()) + case BindingsMap.ResolvedConstructor(_, _) => + throw new CompilerError( + "Impossible here, should be caught by MethodDefinitions pass." + ) + case BindingsMap.ResolvedPolyglotSymbol(_, _) => + throw new CompilerError( + "Impossible polyglot symbol, should be caught by MethodDefinitions pass." + ) + case BindingsMap.ResolvedPolyglotField(_, _) => + throw new CompilerError( + "Impossible polyglot field, should be caught by MethodDefinitions pass." + ) + case _: BindingsMap.ResolvedModuleMethod => + throw new CompilerError( + "Impossible module method here, should be caught by MethodDefinitions pass." + ) + case _: BindingsMap.ResolvedExtensionMethod => + throw new CompilerError( + "Impossible static method here, should be caught by MethodDefinitions pass." + ) + case _: BindingsMap.ResolvedConversionMethod => + throw new CompilerError( + "Impossible conversion method here, should be caught by MethodDefinitions pass." + ) + } } - } private def getTailStatus( expression: Expression @@ -1137,7 +1142,8 @@ class IrToTruffle( ) org.enso.common.Asserts.assertInJvm( fun != null, - s"exported symbol (static method) `${staticMethod.name}` needs to be registered first in the module " + s"exported symbol (static method) `${staticMethod.name}` on type '${eigenTp.getName}' " + + s"needs to be registered first in the module '${actualModule.getName.toString}'." ) scopeBuilder.registerMethod( scopeAssociatedType, diff --git a/lib/java/scala-libs-wrapper/src/main/java/module-info.java b/lib/java/scala-libs-wrapper/src/main/java/module-info.java index 8d56d1d2d699..fecf3f974905 100644 --- a/lib/java/scala-libs-wrapper/src/main/java/module-info.java +++ b/lib/java/scala-libs-wrapper/src/main/java/module-info.java @@ -4,6 +4,8 @@ requires org.jline; requires org.slf4j; + exports org.enso.scala.wrapper; + // "org.typelevel" % ("cats-core_" + scalaVer) % "2.10.0", exports cats; exports cats.arrow; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/util/ScalaConversions.java b/lib/java/scala-libs-wrapper/src/main/java/org/enso/scala/wrapper/ScalaConversions.java similarity index 98% rename from engine/runtime/src/main/java/org/enso/interpreter/util/ScalaConversions.java rename to lib/java/scala-libs-wrapper/src/main/java/org/enso/scala/wrapper/ScalaConversions.java index aa21c1662039..a2020178888f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/util/ScalaConversions.java +++ b/lib/java/scala-libs-wrapper/src/main/java/org/enso/scala/wrapper/ScalaConversions.java @@ -1,4 +1,4 @@ -package org.enso.interpreter.util; +package org.enso.scala.wrapper; import java.util.List; import java.util.Optional;