Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix rendering of redefined Conversion error #8245

Merged
merged 19 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ private List<Definition> translateModuleSymbolImpl(Tree inputAst, List<Definitio
methodRef,
args,
body,
getIdentifiedLocation(inputAst, 0, 1, null),
getIdentifiedLocation(inputAst, 0, 0, null),
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
meta(), diag()
);
yield join(binding, appendTo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ object Redefined {
}

/** An error representing the redefinition of a conversion in a given
* module. This is also known as a method overload.
* module
*
* @param targetType the name of the atom the conversion was being
* redefined on
Expand Down Expand Up @@ -195,7 +195,7 @@ object Redefined {

/** @inheritdoc */
override def message(source: Source): String =
s"Method overloads are not supported: ${targetType.map(_.name + ".").getOrElse("")}from " +
s"Ambiguous conversion: ${targetType.map(_.name + ".").getOrElse("")}from " +
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this error message may be clearer - conversions work a bit differently than method overloads - they do overload the from method, just with different types of that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I accidentaly do something like this:

method x y = x + y
method x = x

Would the error message say "Ambiguous conversion" in that case as well? Isn't that misleading? What about trying to distinguish between those two cases - "Ambiguous conversion" VS "Method overload"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do distinguish these cases already.

The error message I have edited was only relevant for conversions, method overloads have a separate error class - I assume the error message for conversions was copy-pasted from overloads and that's why it used to say the same thing.

Copy link
Member Author

@radeusgd radeusgd Nov 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can see here a test verifying that the behaviour is exactly as you'd expect it to be.

the[InterpreterException] thrownBy eval(code) should have message
"Compilation aborted due to errors."
val diagnostics = consumeOut
diagnostics
.filterNot(isDiagnosticLine)
.toSet shouldEqual Set(
"Test:4:1: error: Method overloads are not supported: Nothing.foo is defined multiple times in this module."
)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the above test was for member methods - so just to be comprehensive I added a test case like the one you mentioned:
2a37e54

s"${sourceType.showCode()} is defined multiple times in this module."

override def diagnosticKeys(): Array[Any] = targetType
Expand Down Expand Up @@ -230,7 +230,7 @@ object Redefined {

/** @inheritdoc */
override def showCode(indent: Int): String =
s"(Redefined (Conversion ${targetType.map(_.showCode() + ".").getOrElse("")}from $sourceType))"
s"(Redefined (Conversion ${targetType.map(_.showCode() + ".").getOrElse("")}from ${sourceType.showCode()}))"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small irrelevant fix - before printing the redefined conversion as code was leaking a lot of information by using toString instead of showCode for the sourceType. With that it is more compact and closer to what I think showCode is supposed to be.

}

/** An error representing the redefinition of a method in a given module.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class ReplTest
result.toString shouldEqual "Error in method `to_text` of [Bar 1]: Expected Text but got 42"
}
inside(executor.evaluate("C.Baz 1")) { case Right(result) =>
result.toString shouldEqual "Error in method `to_text` of [Baz 1]: Expected Text but got C.to_text[Test:18:1-29]"
result.toString shouldEqual "Error in method `to_text` of [Baz 1]: Expected Text but got C.to_text[Test:18:1-28]"
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
}
inside(executor.evaluate("Pattern.compile 'foo'")) {
case Right(result) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
package org.enso.interpreter.test;

import static org.junit.Assert.assertEquals;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

public class ConversionMethodTests extends TestBase {
private static Context ctx;

private static final ByteArrayOutputStream out = new ByteArrayOutputStream();

@BeforeClass
public static void initCtx() {
ctx = createDefaultContext();
ctx = createDefaultContext(out);
}

@AfterClass
public static void disposeCtx() {
ctx.close();
}

@Before
public void resetOutput() {
out.reset();
}

private String getStdOut() {
return out.toString(StandardCharsets.UTF_8);
}

@Test
public void testSimpleConversion() {
String src = """
Expand Down Expand Up @@ -101,4 +119,26 @@ public void testDispatchOnJSDateTime() {
Value res = evalModule(ctx, src);
assertEquals(7, res.asInt());
}

@Test
public void testAmbiguousConversionStrictUnused() {
String src = """
type Foo
Mk_Foo data
type Bar
Mk_Bar x

Foo.from (that:Bar) = Foo.Mk_Foo that.x+100
Foo.from (that:Bar) = Foo.Mk_Foo that.x+1000

main = 42
""";
try {
Value res = evalModule(ctx, src);
fail("Expected an exception, but got " + res);
} catch (Exception e) {
assertEquals("Compilation aborted due to errors.", e.getMessage());
MatcherAssert.assertThat(getStdOut(), Matchers.containsString("Unnamed:7:1: error: Ambiguous conversion: Foo.from Bar is defined multiple times in this module."));
radeusgd marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.enso.interpreter.test;

import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.Value;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class NonStrictConversionMethodTests extends TestBase {
private static Context nonStrictCtx;

@BeforeClass
public static void initCtx() {
nonStrictCtx = createNonStrictContext();
}

protected static Context createNonStrictContext() {
var context =
defaultContextBuilder().option(RuntimeOptions.STRICT_ERRORS, "false").build();
final Map<String, Language> langs = context.getEngine().getLanguages();
assertNotNull("Enso found: " + langs, langs.get("enso"));
return context;
}

@AfterClass
public static void disposeCtx() {
nonStrictCtx.close();
}

@Test
public void testAmbiguousConversion() {
String src = """
type Foo
Mk_Foo data
type Bar
Mk_Bar x

Foo.from (that:Bar) = Foo.Mk_Foo that.x+100
Foo.from (that:Bar) = Foo.Mk_Foo that.x+1000

main = 42
""";
Value res = evalModule(nonStrictCtx, src);
assertEquals(42, res.asInt());
}

@Test
public void testAmbiguousConversionUsage() {
// In non-strict mode, the conversion declarations will have errors attached to the IR, but the overall operation
// will simply not see the second conversion and succeed with the first one.
String src = """
type Foo
Mk_Foo data
type Bar
Mk_Bar x

Foo.from (that:Bar) = Foo.Mk_Foo that.x+100
Foo.from (that:Bar) = Foo.Mk_Foo that.x+1000

main = (Foo.from (Bar.Mk_Bar 42)) . data
""";

Value res = evalModule(nonStrictCtx, src);
assertEquals(142, res.asInt());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -96,16 +96,22 @@ class OverloadsResolutionTest extends CompilerTest {
}

"raise an error if there are multiple definitions with the same source type" in {
val ir =
val sourceCode =
"""Unit.from (that : Integer) = undefined
|Unit.from (that : Boolean) = undefined
|Unit.from (that : Boolean) = undefined
|""".stripMargin.preprocessModule.resolve
|""".stripMargin
val ir = sourceCode.preprocessModule.resolve

ir.bindings.length shouldEqual 3
ir.bindings.head shouldBe a[definition.Method.Conversion]
ir.bindings(1) shouldBe a[definition.Method.Conversion]
ir.bindings(2) shouldBe an[errors.Redefined.Conversion]

val errLoc = ir.bindings(2).location().get
val code = sourceCode.substring(errLoc.start, errLoc.end)
// The code should be the whole line, but without ending newlines.
code shouldEqual "Unit.from (that : Boolean) = undefined"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,56 @@ class OverloadsResolutionErrorTest extends InterpreterTest {
)
}

"result in a compiler error for clashing from conversions" in {
val code =
"""type Foo
| Mk_Foo data
|type Bar
| Mk_Bar x
|
|Foo.from (that : Bar) = Foo.Mk_Foo that.x+100
|Foo.from (that : Bar) = Foo.Mk_Foo that.x+200
|""".stripMargin.linesIterator.mkString("\n")

the[InterpreterException] thrownBy eval(code) should have message
"Compilation aborted due to errors."

val diagnostics = consumeOut
diagnostics should have length 3
val line0 =
"Test:7:1: error: Ambiguous conversion: Foo.from Bar is defined multiple times in this module."
val line1 = " 7 | Foo.from (that : Bar) = Foo.Mk_Foo that.x+200"
val line2 = " | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
diagnostics shouldEqual List(line0, line1, line2)
}

"render the compiler error correctly for clashing multiline from conversions" in {
val code =
"""type Foo
| Mk_Foo data
|type Bar
| Mk_Bar x
|
|Foo.from (that : Bar) =
| y = that.x+100
| Foo.Mk_Foo y
|Foo.from (that : Bar) =
| y = that.x+200
| Foo.Mk_Foo y
|""".stripMargin.linesIterator.mkString("\n")

the[InterpreterException] thrownBy eval(code) should have message
"Compilation aborted due to errors."

val diagnostics = consumeOut
diagnostics should have length 4
val line0 =
"Test:[9:1-11:16]: error: Ambiguous conversion: Foo.from Bar is defined multiple times in this module."
val line1 = " 9 | Foo.from (that : Bar) ="
val line2 = " 10 | y = that.x+200"
val line3 = " 11 | Foo.Mk_Foo y"
diagnostics shouldEqual List(line0, line1, line2, line3)
}

}
}
Loading