Conflicting extension methods result in compilation failure (#9844)

Fixes the non-deterministic method resolution in cases when the method is defined multiple times in imported modules.
This commit is contained in:
Pavel Marek 2024-05-22 13:49:23 +02:00 committed by GitHub
parent 202a05011b
commit 202f7e1a49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 514 additions and 72 deletions

View File

@ -162,6 +162,9 @@ public class IRDumper {
var body = explicitMethodIr.body();
createIRGraph(body);
createEdge(explicitMethodIr, body, "body");
var methodRef = explicitMethodIr.methodReference();
createIRGraph(methodRef);
createEdge(explicitMethodIr, methodRef, "methodReference");
}
case Method.Conversion conversionMethod -> {
var bldr =
@ -171,6 +174,9 @@ public class IRDumper {
var body = conversionMethod.body();
createIRGraph(body);
createEdge(conversionMethod, body, "body");
var methodRef = conversionMethod.methodReference();
createIRGraph(methodRef);
createEdge(conversionMethod, methodRef, "methodReference");
}
case Method.Binding binding -> {
var bldr = GraphVizNode.Builder.fromIr(binding);
@ -183,6 +189,9 @@ public class IRDumper {
var body = binding.body();
createIRGraph(body);
createEdge(binding, body, "body");
var methodRef = binding.methodReference();
createIRGraph(methodRef);
createEdge(binding, methodRef, "methodReference");
}
case Definition.Type type -> {
var typeNode =
@ -331,6 +340,17 @@ public class IRDumper {
var literalNode = bldr.build();
addNode(literalNode);
}
case Name.MethodReference methodRef -> {
var bldr = GraphVizNode.Builder.fromIr(methodRef);
bldr.addLabelLine("methodName: " + methodRef.methodName().name());
if (methodRef.typePointer().isDefined()) {
bldr.addLabelLine("typePointer: " + methodRef.typePointer().get().name());
} else {
bldr.addLabelLine("typePointer: null");
}
var methodRefNode = bldr.build();
addNode(methodRefNode);
}
default -> {
var node = GraphVizNode.Builder.fromIr(expression).build();
addNode(node);

View File

@ -0,0 +1,327 @@
package org.enso.interpreter.test;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Set;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.PolyglotContext;
import org.enso.polyglot.RuntimeOptions;
import org.enso.polyglot.TopScope;
import org.graalvm.polyglot.PolyglotException;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
/**
* Shadowing identifiers from imported modules is not a compilation error, neither runtime error.
* But we need to make sure that the shadowing is deterministic and that the shadowing is not
* affecting the resolution of extension methods. In other words, we need to make sure that the
* method resolution is deterministic.
*/
public class ExtensionMethodResolutionTest extends TestBase {
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();
private static final Matcher<String> methodsOverloadErrorMessageMatcher =
allOf(
containsString("Method overloads are not supported"),
containsString("defined multiple times"));
@Test
public void twoExtensionMethodsWithSameNameInOneModuleShouldFail() throws IOException {
var src = """
type T
T.foo x = x
T.foo x y = x + y
""";
testProjectCompilationFailure(src, methodsOverloadErrorMessageMatcher);
}
@Test
public void extensionMethodAndNormalMethodConflictInOneModule() throws IOException {
var src = """
type T
foo x = x
T.foo x y = x + y
""";
testProjectCompilationFailure(src, methodsOverloadErrorMessageMatcher);
}
@Test
public void firstResolutionIsInTypesScope() throws IOException {
var xMod =
new SourceModule(
QualifiedName.fromString("X"),
"""
type T
T.foo = "X"
""");
var yMod =
new SourceModule(
QualifiedName.fromString("Y"),
"""
from project.X import T
T.foo = "Y"
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.X import T
from project.Y import all
T.foo = "Main"
main = T.foo
""");
var projDir = createProject("Proj", Set.of(xMod, yMod, mainMod), tempFolder);
testProjectRun(
projDir,
res -> {
assertThat(
"Method should be first resolved in the types module scope", res.asString(), is("X"));
});
}
@Test
public void secondResolutionIsInCurrentModuleScope() throws IOException {
var xMod =
new SourceModule(QualifiedName.fromString("X"), """
type T
""");
var yMod =
new SourceModule(
QualifiedName.fromString("Y"),
"""
from project.X import T
T.foo = "Y"
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.X import T
from project.Y import all
T.foo = "Main"
main = T.foo
""");
var projDir = createProject("Proj", Set.of(xMod, yMod, mainMod), tempFolder);
testProjectRun(
projDir,
res -> {
assertThat(
"Method should be secondly resolved in the current scope",
res.asString(),
is("Main"));
});
}
@Test
public void resolutionFromImportedModulesIsDeterministic1() throws IOException {
var xMod =
new SourceModule(QualifiedName.fromString("X"), """
type T
""");
var yMod =
new SourceModule(
QualifiedName.fromString("Y"),
"""
from project.X import T
T.foo = "Y"
""");
var zMod =
new SourceModule(
QualifiedName.fromString("Z"),
"""
from project.X import T
T.foo = "Z"
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.X import T
from project.Y import all
from project.Z import all
main = T.foo
""");
var projDir = createProject("Proj", Set.of(xMod, yMod, zMod, mainMod), tempFolder);
testProjectRun(
projDir,
res -> {
assertThat(
"Method resolution from imported modules should be deterministic "
+ "(Y module is imported first)",
res.asString(),
is("Y"));
});
}
@Test
public void resolutionFromImportedModulesIsDeterministic2() throws IOException {
var xMod =
new SourceModule(QualifiedName.fromString("X"), """
type T
""");
var yMod =
new SourceModule(
QualifiedName.fromString("Y"),
"""
from project.X import T
T.foo = "Y"
""");
var zMod =
new SourceModule(
QualifiedName.fromString("Z"),
"""
from project.X import T
T.foo = "Z"
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.X import T
from project.Z import all
from project.Y import all
main = T.foo
""");
var projDir = createProject("Proj", Set.of(xMod, yMod, zMod, mainMod), tempFolder);
testProjectRun(
projDir,
res -> {
assertThat(
"Method resolution from imported modules should be deterministic "
+ "(Z module is imported first)",
res.asString(),
is("Z"));
});
}
@Test
public void sameMethodInShadowedType() throws IOException {
var mod =
new SourceModule(
QualifiedName.fromString("Mod"),
"""
type T
method = 42
""");
// This is type-shadowing, which is allowed.
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.Mod import T
type T
method = 23
main =
T.method
""");
var projDir = createProject("Proj", Set.of(mod, mainMod), tempFolder);
testProjectRun(
projDir,
res -> {
assertThat(res.isNumber(), is(true));
assertThat(res.asInt(), is(23));
});
}
@Test
public void sameExtensionMethodInDifferentTypes() throws IOException {
var mod =
new SourceModule(
QualifiedName.fromString("Mod"),
"""
type T
T.method = 42
""");
// main module imports just the `Mod` and not the type - it should succeed.
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
import project.Mod
type T
T.method = 23
main =
T.method
""");
var projDir = createProject("Proj", Set.of(mod, mainMod), tempFolder);
testProjectRun(
projDir,
res -> {
assertThat(res.isNumber(), is(true));
assertThat(res.asInt(), is(23));
});
}
@Test
public void sameExtensionMethodInDifferentTypesInThreeModules() throws IOException {
var mod2 =
new SourceModule(
QualifiedName.fromString("Mod2"), """
# An empty module
""");
// The type T defined in mod1 and mainMod have exactly the same location on purpose.
var mod1 =
new SourceModule(
QualifiedName.fromString("Mod1"),
"""
import project.Mod2
type T
T.method = 1
""");
// main module imports just the `Mod` and not the type - it should succeed.
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
import project.Mod1
type T
T.method = 2
main =
T.method
""");
var projDir = createProject("Proj", Set.of(mod2, mod1, mainMod), tempFolder);
testProjectRun(
projDir,
res -> {
assertThat(res.isNumber(), is(true));
assertThat(res.asInt(), is(2));
});
}
private void testProjectCompilationFailure(String mainSrc, Matcher<String> errorMessageMatcher)
throws IOException {
var projDir = createProject("Proj", mainSrc, tempFolder);
testProjectCompilationFailure(projDir, errorMessageMatcher);
}
private void testProjectCompilationFailure(
Path mainProjDir, Matcher<String> errorMessageMatcher) {
var out = new ByteArrayOutputStream();
try (var ctx =
defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, mainProjDir.toAbsolutePath().toString())
.option(RuntimeOptions.STRICT_ERRORS, "true")
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
.out(out)
.err(out)
.build()) {
var polyCtx = new PolyglotContext(ctx);
TopScope topScope = polyCtx.getTopScope();
try {
topScope.compile(true);
fail("Expected compilation error: " + out);
} catch (PolyglotException e) {
assertThat(e.isSyntaxError(), is(true));
assertThat(out.toString(), errorMessageMatcher);
}
}
}
}

View File

@ -9,8 +9,6 @@ import static org.junit.Assert.fail;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.enso.interpreter.util.ScalaConversions;
import org.enso.polyglot.PolyglotContext;
@ -48,7 +46,7 @@ public class PrivateAccessTest extends TestBase {
private Cons data
main = My_Type.Cons 42
""";
var projDir = createProject("My_Project", mainSrc);
var projDir = createProject("My_Project", mainSrc, tempFolder);
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
try (var ctx =
defaultContextBuilder()
@ -71,7 +69,7 @@ public class PrivateAccessTest extends TestBase {
type My_Type
private Cons data
""";
var projDir = createProject("My_Project", mainSrc);
var projDir = createProject("My_Project", mainSrc, tempFolder);
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
try (var ctx =
defaultContextBuilder()
@ -94,7 +92,7 @@ public class PrivateAccessTest extends TestBase {
main =
My_Type.Cons 42
""";
var projDir = createProject("My_Project", mainSrc);
var projDir = createProject("My_Project", mainSrc, tempFolder);
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
try (var ctx =
defaultContextBuilder()
@ -128,7 +126,7 @@ public class PrivateAccessTest extends TestBase {
My_Type.Cons x -> x
_ -> 0
""";
var projDir = createProject("My_Project", mainSrc);
var projDir = createProject("My_Project", mainSrc, tempFolder);
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
try (var ctx =
defaultContextBuilder()
@ -153,7 +151,7 @@ public class PrivateAccessTest extends TestBase {
private Cons data
create x = My_Type.Cons x
""";
createProject("Lib", libSrc);
createProject("Lib", libSrc, tempFolder);
var projSrc =
"""
from local.Lib import My_Type
@ -162,7 +160,7 @@ public class PrivateAccessTest extends TestBase {
case obj of
My_Type.Cons x -> x
""";
var projDir = createProject("Proj", projSrc);
var projDir = createProject("Proj", projSrc, tempFolder);
var out = new ByteArrayOutputStream();
try (var ctx =
defaultContextBuilder()
@ -186,33 +184,4 @@ public class PrivateAccessTest extends TestBase {
}
}
}
/**
* Creates temporary project directory structure with a given main source content. No need to
* clean it up, as it is managed by JUnit TemporaryFolder rule. Note that we need to create a
* project, otherwise the private stuff won't work.
*
* @param projName Name of the project (as defined in package.yaml).
* @param mainSrc Main.enso source content
* @return Path to the newly created directly structure - a project directory.
*/
private Path createProject(String projName, String mainSrc) throws IOException {
var projDir = tempFolder.newFolder(projName);
assert projDir.exists();
var projYaml =
"""
name: %s
version: 0.0.1
prefer-local-libraries: true
""".formatted(projName);
var yamlPath = projDir.toPath().resolve("package.yaml");
Files.writeString(yamlPath, projYaml);
assert yamlPath.toFile().exists();
var srcDir = tempFolder.newFolder(projName, "src");
assert srcDir.exists();
var mainSrcPath = srcDir.toPath().resolve("Main.enso");
Files.writeString(mainSrcPath, mainSrc);
assert mainSrcPath.toFile().exists();
return projDir.toPath();
}
}

View File

@ -9,10 +9,16 @@ import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import org.enso.common.LanguageInfo;
@ -20,6 +26,8 @@ import org.enso.common.MethodNames.Module;
import org.enso.common.MethodNames.TopScope;
import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.PolyglotContext;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Language;
@ -27,6 +35,7 @@ import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.IOAccess;
import org.graalvm.polyglot.proxy.ProxyExecutable;
import org.junit.rules.TemporaryFolder;
public abstract class TestBase {
protected static Context createDefaultContext() {
@ -157,6 +166,92 @@ public abstract class TestBase {
return module.invokeMember(Module.EVAL_EXPRESSION, methodName);
}
/**
* Creates temporary project directory structure with a given main source content. No need to
* clean it up, as it is managed by JUnit TemporaryFolder rule. Note that we need to create a
* project, otherwise the private stuff won't work.
*
* @param projName Name of the project (as defined in package.yaml).
* @param mainSrc Main.enso source content
* @param tempFolder Temporary folder from JUnit rule.
* @return Path to the newly created directly structure - a project directory.
*/
protected static Path createProject(String projName, String mainSrc, TemporaryFolder tempFolder)
throws IOException {
var modules = Set.of(new SourceModule(QualifiedName.fromString("Main"), mainSrc));
return createProject(projName, modules, tempFolder);
}
/**
* Creates a temporary project directory structure with all the given modules and their content.
* Creates also the package descriptor. The created project directory structure is eligible for
* running via {@code enso --run <projDir>}.
*
* @param projName Name of the project
* @param modules Set of modules. Must contain `Main` module.
* @param tempFolder Temporary folder. From jUnit rule.
* @return Path to the root directory of the project.
*/
protected static Path createProject(
String projName, Set<SourceModule> modules, TemporaryFolder tempFolder) throws IOException {
var projDir = tempFolder.newFolder(projName);
assert projDir.exists();
var projYaml =
"""
name: %s
version: 0.0.1
prefer-local-libraries: true
""".formatted(projName);
var yamlPath = projDir.toPath().resolve("package.yaml");
Files.writeString(yamlPath, projYaml);
assert yamlPath.toFile().exists();
var srcDir = tempFolder.newFolder(projName, "src");
var srcDirPath = srcDir.toPath();
assert srcDir.exists();
boolean mainModuleFound = false;
for (var module : modules) {
var relativePath = String.join(File.pathSeparator, module.name.pathAsJava());
var modDirPath = srcDirPath.resolve(relativePath);
modDirPath.toFile().mkdirs();
var modPath = modDirPath.resolve(module.name.item() + ".enso");
Files.writeString(modPath, module.code);
if (module.name.equals(QualifiedName.fromString("Main"))) {
mainModuleFound = true;
}
}
assert mainModuleFound;
return projDir.toPath();
}
/**
* Tests running the project located in the given {@code projDir}. Is equal to running {@code enso
* --run <projDir>}.
*
* @param projDir Root directory of the project.
* @param resultConsumer Any action that is to be evaluated on the result of running the {@code
* main} method
*/
protected void testProjectRun(Path projDir, Consumer<Value> resultConsumer) {
assert projDir.toFile().exists() && projDir.toFile().isDirectory();
try (var ctx =
defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
.option(RuntimeOptions.STRICT_ERRORS, "true")
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
.build()) {
var polyCtx = new PolyglotContext(ctx);
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
var mainMod = polyCtx.evalModule(mainSrcPath.toFile());
var assocMainModType = mainMod.getAssociatedType();
var mainMethod = mainMod.getMethod(assocMainModType, "main").get();
var res = mainMethod.execute();
resultConsumer.accept(res);
}
}
/** A simple structure corresponding to an Enso module. */
public record SourceModule(QualifiedName name, String code) {}
/**
* An artificial RootNode. Used for tests of nodes that need to be adopted. Just create this root
* node inside a context, all the other nodes, and insert them via {@link

View File

@ -73,8 +73,8 @@ class OverloadsResolutionTest extends CompilerTest {
val redef1 = ir.bindings(1).asInstanceOf[errors.Redefined.Method]
val redef2 = ir.bindings(2).asInstanceOf[errors.Redefined.Method]
redef1.atomName.get.name shouldEqual atomName
redef2.atomName.get.name shouldEqual atomName
redef1.typeName.get.name shouldEqual atomName
redef2.typeName.get.name shouldEqual atomName
redef1.methodName.name shouldEqual methodName
redef2.methodName.name shouldEqual methodName

View File

@ -236,7 +236,7 @@ object Redefined {
/** An error representing the redefinition of a method in a given module.
* This is also known as a method overload.
*
* @param atomName the name of the atom the method was being redefined on
* @param typeName the name of the type the method was being redefined on
* @param methodName the method name being redefined on `atomName`
* @param location the location in the source to which this error
* corresponds
@ -244,7 +244,7 @@ object Redefined {
* @param diagnostics any diagnostics associated with this error.
*/
sealed case class Method(
atomName: Option[Name],
typeName: Option[Name],
methodName: Name,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = new MetadataStorage(),
@ -267,7 +267,7 @@ object Redefined {
* @return a copy of `this`, updated with the specified values
*/
def copy(
atomName: Option[Name] = atomName,
atomName: Option[Name] = typeName,
methodName: Name = methodName,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
@ -288,7 +288,7 @@ object Redefined {
keepIdentifiers: Boolean = false
): Method =
copy(
atomName = atomName.map(
atomName = typeName.map(
_.duplicate(
keepLocations,
keepMetadata,
@ -317,11 +317,11 @@ object Redefined {
/** @inheritdoc */
override def message(source: (IdentifiedLocation => String)): String =
s"Method overloads are not supported: ${atomName.map(_.name + ".").getOrElse("")}" +
s"Method overloads are not supported: ${typeName.map(_.name + ".").getOrElse("")}" +
s"${methodName.name} is defined multiple times in this module."
override def diagnosticKeys(): Array[Any] = {
atomName
typeName
.map(_.name :: methodName.name :: Nil)
.getOrElse(methodName.name :: Nil)
.toArray
@ -336,7 +336,7 @@ object Redefined {
override def toString: String =
s"""
|Error.Redefined.Method(
|atomName = $atomName,
|atomName = $typeName,
|methodName = $methodName,
|location = $location,
|passData = ${this.showPassData},
@ -347,13 +347,13 @@ object Redefined {
/** @inheritdoc */
override def children: List[IR] =
atomName
typeName
.map(_ :: methodName :: Nil)
.getOrElse(methodName :: Nil)
/** @inheritdoc */
override def showCode(indent: Int): String =
s"(Redefined (Method ${atomName.map(_.showCode() + ".").getOrElse("")}$methodName))"
s"(Redefined (Method ${typeName.map(_.showCode() + ".").getOrElse("")}$methodName))"
}
/** An error representing the redefinition of a method in a given module,

View File

@ -0,0 +1,32 @@
package org.enso.interpreter.runtime.error;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.nodes.Node;
import java.util.List;
import java.util.Objects;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.Type;
public class AmbiguousMethodException extends AbstractTruffleException {
public AmbiguousMethodException(
Node location, Type type, String methodName, List<Function> candidates) {
super(constructMessage(type, methodName, candidates), location);
}
private static String constructMessage(Type type, String methodName, List<Function> candidates) {
Objects.requireNonNull(type);
Objects.requireNonNull(methodName);
Objects.requireNonNull(candidates);
if (candidates.size() < 2) {
throw new IllegalArgumentException("candidates list must contain at least two functions");
}
var candidateNames = candidates.stream().map(f -> f.toString(false)).toList();
var sb = new StringBuilder();
sb.append("Ambiguous method call '");
sb.append(type.getName()).append(".").append(methodName);
sb.append("': ");
sb.append("definition candidates are: ");
sb.append(candidateNames);
return sb.toString();
}
}

View File

@ -4,14 +4,13 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.enso.interpreter.runtime.Module;
@ -47,12 +46,12 @@ public final class ModuleScope implements EnsoObject {
* @param module the module related to the newly created scope.
*/
public ModuleScope(Module module) {
this.polyglotSymbols = new HashMap<>();
this.types = new HashMap<>();
this.methods = new ConcurrentHashMap<>();
this.conversions = new ConcurrentHashMap<>();
this.imports = new HashSet<>();
this.exports = new HashSet<>();
this.polyglotSymbols = new LinkedHashMap<>();
this.types = new LinkedHashMap<>();
this.methods = new LinkedHashMap<>();
this.conversions = new LinkedHashMap<>();
this.imports = new LinkedHashSet<>();
this.exports = new LinkedHashSet<>();
this.module = module;
this.associatedType = Type.createSingleton(module.getName().item(), this, null, false, false);
}
@ -110,14 +109,14 @@ public final class ModuleScope implements EnsoObject {
*/
private Map<String, Supplier<Function>> ensureMethodMapFor(Type type) {
Type tpeKey = type == null ? noTypeKey : type;
return methods.computeIfAbsent(tpeKey, k -> new HashMap<>());
return methods.computeIfAbsent(tpeKey, k -> new LinkedHashMap<>());
}
private Map<String, Supplier<Function>> getMethodMapFor(Type type) {
Type tpeKey = type == null ? noTypeKey : type;
Map<String, Supplier<Function>> result = methods.get(tpeKey);
if (result == null) {
return new HashMap<>();
return new LinkedHashMap<>();
}
return result;
}
@ -167,13 +166,13 @@ public final class ModuleScope implements EnsoObject {
* @return a list containing all the defined conversions in definition order
*/
private Map<Type, Function> ensureConversionsFor(Type type) {
return conversions.computeIfAbsent(type, k -> new HashMap<>());
return conversions.computeIfAbsent(type, k -> new LinkedHashMap<>());
}
private Map<Type, Function> getConversionsFor(Type type) {
var result = conversions.get(type);
if (result == null) {
return new HashMap<>();
return new LinkedHashMap<>();
}
return result;
}
@ -394,11 +393,11 @@ public final class ModuleScope implements EnsoObject {
}
public void reset() {
imports = new HashSet<>();
exports = new HashSet<>();
methods = new HashMap<>();
conversions = new HashMap<>();
polyglotSymbols = new HashMap<>();
imports = new LinkedHashSet<>();
exports = new LinkedHashSet<>();
methods = new LinkedHashMap<>();
conversions = new LinkedHashMap<>();
polyglotSymbols = new LinkedHashMap<>();
}
/**
@ -408,12 +407,12 @@ public final class ModuleScope implements EnsoObject {
* @return a copy of this scope modulo the requested types
*/
public ModuleScope withTypes(List<String> typeNames) {
Map<String, Object> polyglotSymbols = new HashMap<>(this.polyglotSymbols);
Map<String, Type> requestedTypes = new HashMap<>(this.types);
Map<Type, Map<String, Supplier<Function>>> methods = new ConcurrentHashMap<>();
Map<Type, Map<Type, Function>> conversions = new ConcurrentHashMap<>();
Set<ModuleScope> imports = new HashSet<>(this.imports);
Set<ModuleScope> exports = new HashSet<>(this.exports);
Map<String, Object> polyglotSymbols = new LinkedHashMap<>(this.polyglotSymbols);
Map<String, Type> requestedTypes = new LinkedHashMap<>(this.types);
Map<Type, Map<String, Supplier<Function>>> methods = new LinkedHashMap<>();
Map<Type, Map<Type, Function>> conversions = new LinkedHashMap<>();
Set<ModuleScope> imports = new LinkedHashSet<>(this.imports);
Set<ModuleScope> exports = new LinkedHashSet<>(this.exports);
this.types
.entrySet()
.forEach(