mirror of
https://github.com/enso-org/enso.git
synced 2025-01-02 11:27:57 +03:00
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:
parent
202a05011b
commit
202f7e1a49
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user