mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 21:52:27 +03:00
Pretending WithWarnings isException (#9840)
This commit is contained in:
parent
2af217c3e6
commit
6902f5e0b3
@ -291,10 +291,14 @@ public class BinaryDispatchTest extends TestBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertContains(String expected, String actual) {
|
static void assertContains(String expected, String actual) {
|
||||||
if (actual.contains(expected)) {
|
assertContains("Expecting", expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertContains(String msg, String expected, String actual) {
|
||||||
|
if (actual != null && actual.contains(expected)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fail("Expecting " + expected + " in " + actual);
|
fail(msg + " " + expected + " in " + actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import org.junit.Test;
|
|||||||
public class MetaIsATest extends TestBase {
|
public class MetaIsATest extends TestBase {
|
||||||
private static Context ctx;
|
private static Context ctx;
|
||||||
private static Value isACheck;
|
private static Value isACheck;
|
||||||
|
private static Value warningCheck;
|
||||||
private static ValuesGenerator generator;
|
private static ValuesGenerator generator;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
@ -30,9 +31,10 @@ public class MetaIsATest extends TestBase {
|
|||||||
Source.newBuilder(
|
Source.newBuilder(
|
||||||
"enso",
|
"enso",
|
||||||
"""
|
"""
|
||||||
import Standard.Base.Meta
|
from Standard.Base import Meta, Warning
|
||||||
|
|
||||||
check x y = Meta.is_a x y
|
check x y = Meta.is_a x y
|
||||||
|
check_warning x = Warning.has_warnings x
|
||||||
""",
|
""",
|
||||||
"check.enso")
|
"check.enso")
|
||||||
.uri(uri)
|
.uri(uri)
|
||||||
@ -40,6 +42,7 @@ public class MetaIsATest extends TestBase {
|
|||||||
|
|
||||||
var module = ctx.eval(src);
|
var module = ctx.eval(src);
|
||||||
isACheck = module.invokeMember("eval_expression", "check");
|
isACheck = module.invokeMember("eval_expression", "check");
|
||||||
|
warningCheck = module.invokeMember("eval_expression", "check_warning");
|
||||||
assertTrue("it is a function", isACheck.canExecute());
|
assertTrue("it is a function", isACheck.canExecute());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,9 +286,22 @@ public class MetaIsATest extends TestBase {
|
|||||||
Value caseOf, Value v, Value t, StringBuilder f, ValuesGenerator g) {
|
Value caseOf, Value v, Value t, StringBuilder f, ValuesGenerator g) {
|
||||||
var test = caseOf.execute(v);
|
var test = caseOf.execute(v);
|
||||||
if (test.isException()) {
|
if (test.isException()) {
|
||||||
|
// if a generated value isException (from interop point of view)
|
||||||
|
// then it is either DataflowError or Warning
|
||||||
|
var vMeta = v.getMetaObject();
|
||||||
|
if (g.typeError().equals(vMeta)) {
|
||||||
assertEquals("DataFlowError in", g.typeError(), v.getMetaObject());
|
assertEquals("DataFlowError in", g.typeError(), v.getMetaObject());
|
||||||
assertEquals("DataFlowError out", g.typeError(), test.getMetaObject());
|
assertEquals("DataFlowError out", g.typeError(), test.getMetaObject());
|
||||||
|
// end the test here as DataflowError doesn't represent a value
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
// check if Warning.has_warnings is true
|
||||||
|
var wv = warningCheck.execute(v);
|
||||||
|
var wTest = warningCheck.execute(test);
|
||||||
|
assertTrue("Warning in", wv.asBoolean());
|
||||||
|
assertTrue("Warning out", wTest.asBoolean());
|
||||||
|
// but continue with the rest of the test, as Warning still represents a value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assertTrue("Expecting 0 or 1 result: " + test + " for " + v, test.isNumber());
|
assertTrue("Expecting 0 or 1 result: " + test + " for " + v, test.isNumber());
|
||||||
var testBool = test.asInt() == 1;
|
var testBool = test.asInt() == 1;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.enso.interpreter.test;
|
package org.enso.interpreter.test;
|
||||||
|
|
||||||
|
import static org.enso.interpreter.test.BinaryDispatchTest.assertContains;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import org.enso.common.LanguageInfo;
|
import org.enso.common.LanguageInfo;
|
||||||
@ -10,6 +12,8 @@ import org.enso.interpreter.runtime.error.Warning;
|
|||||||
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
||||||
import org.enso.interpreter.runtime.error.WithWarnings;
|
import org.enso.interpreter.runtime.error.WithWarnings;
|
||||||
import org.graalvm.polyglot.Context;
|
import org.graalvm.polyglot.Context;
|
||||||
|
import org.graalvm.polyglot.PolyglotException;
|
||||||
|
import org.graalvm.polyglot.Value;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
@ -18,16 +22,28 @@ import org.junit.Test;
|
|||||||
public class WarningsTest extends TestBase {
|
public class WarningsTest extends TestBase {
|
||||||
|
|
||||||
private static Context ctx;
|
private static Context ctx;
|
||||||
|
private static ValuesGenerator generator;
|
||||||
|
private static Value wrap;
|
||||||
private static EnsoContext ensoContext;
|
private static EnsoContext ensoContext;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void initEnsoContext() {
|
public static void initEnsoContext() {
|
||||||
ctx = createDefaultContext();
|
ctx = createDefaultContext();
|
||||||
|
generator = ValuesGenerator.create(ctx, ValuesGenerator.Language.ENSO);
|
||||||
ensoContext =
|
ensoContext =
|
||||||
(EnsoContext)
|
(EnsoContext)
|
||||||
ctx.getBindings(LanguageInfo.ID)
|
ctx.getBindings(LanguageInfo.ID)
|
||||||
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
|
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
|
||||||
.asHostObject();
|
.asHostObject();
|
||||||
|
var module =
|
||||||
|
ctx.eval(
|
||||||
|
"enso",
|
||||||
|
"""
|
||||||
|
from Standard.Base import Warning
|
||||||
|
|
||||||
|
wrap msg value = Warning.attach msg value
|
||||||
|
""");
|
||||||
|
wrap = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "wrap");
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
@ -65,4 +81,75 @@ public class WarningsTest extends TestBase {
|
|||||||
}
|
}
|
||||||
fail("One shall not be created WithWarnings without any warnings " + without);
|
fail("One shall not be created WithWarnings without any warnings " + without);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void warningIsAnException() {
|
||||||
|
var warning42 = wrap.execute("warn:1", 42);
|
||||||
|
var warningHi = wrap.execute("warn:2", "Hi");
|
||||||
|
|
||||||
|
assertTrue("value is a number", warning42.isNumber());
|
||||||
|
assertTrue("value is Int", warning42.fitsInInt());
|
||||||
|
assertEquals(42, warning42.asInt());
|
||||||
|
|
||||||
|
assertTrue("value2 is a text", warningHi.isString());
|
||||||
|
assertTrue("value2 not a number", warning42.isNumber());
|
||||||
|
assertEquals("Hi", warningHi.asString());
|
||||||
|
|
||||||
|
assertTrue("value1 with warning is also an exception", warning42.isException());
|
||||||
|
assertTrue("value2 with warning is also an exception", warningHi.isException());
|
||||||
|
|
||||||
|
try {
|
||||||
|
warning42.throwException();
|
||||||
|
fail("Shouldn't reach here");
|
||||||
|
} catch (PolyglotException ex) {
|
||||||
|
assertEquals("warn:1", ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
var warningMulti = wrap.execute("warn:3", warning42);
|
||||||
|
assertTrue("multi value is a number", warningMulti.isNumber());
|
||||||
|
assertTrue("multi value is Int", warningMulti.fitsInInt());
|
||||||
|
assertEquals(42, warningMulti.asInt());
|
||||||
|
|
||||||
|
assertTrue("multi vlaue with warning is also an exception", warningMulti.isException());
|
||||||
|
|
||||||
|
try {
|
||||||
|
warningMulti.throwException();
|
||||||
|
fail("Shouldn't reach here");
|
||||||
|
} catch (PolyglotException ex) {
|
||||||
|
assertContains("warn:1", ex.getMessage());
|
||||||
|
assertContains("warn:3", ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void allWarningsAreExceptions() throws Exception {
|
||||||
|
for (var v : generator.allValues()) {
|
||||||
|
if (v.isNull() || v.isBoolean()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
assertWarningsForAType(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertWarningsForAType(Value v) {
|
||||||
|
var type = v.getMetaObject();
|
||||||
|
|
||||||
|
var warning1 = wrap.execute("warn:once", v);
|
||||||
|
var warning2 = wrap.execute("warn:twice", warning1);
|
||||||
|
|
||||||
|
var warningType = warning2.getMetaObject();
|
||||||
|
assertEquals("Types without and with warnings are the same", type, warningType);
|
||||||
|
assertTrue("It is an exception. Type: " + type, warning2.isException());
|
||||||
|
try {
|
||||||
|
warning2.throwException();
|
||||||
|
} catch (PolyglotException ex) {
|
||||||
|
if (ex.getMessage() == null) {
|
||||||
|
assertEquals(generator.typeError(), type);
|
||||||
|
assertEquals(generator.typeError(), warningType);
|
||||||
|
} else {
|
||||||
|
assertContains("Warning found for " + type, "warn:once", ex.getMessage());
|
||||||
|
assertContains("Warning found for " + type, "warn:twice", ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,10 @@ public abstract class InteropMethodCallNode extends Node {
|
|||||||
return InteropMethodCallNodeGen.create();
|
return InteropMethodCallNodeGen.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InteropMethodCallNode getUncached() {
|
||||||
|
return InteropMethodCallNodeGen.getUncached();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the method with given state and arguments.
|
* Calls the method with given state and arguments.
|
||||||
*
|
*
|
||||||
|
@ -27,6 +27,7 @@ import org.enso.polyglot.common_utils.Core_Text_Utils;
|
|||||||
@ExportLibrary(TypesLibrary.class)
|
@ExportLibrary(TypesLibrary.class)
|
||||||
public final class Text implements EnsoObject {
|
public final class Text implements EnsoObject {
|
||||||
private static final Lock LOCK = new ReentrantLock();
|
private static final Lock LOCK = new ReentrantLock();
|
||||||
|
private static final Text EMPTY = new Text("");
|
||||||
private volatile Object contents;
|
private volatile Object contents;
|
||||||
private volatile int length = -1;
|
private volatile int length = -1;
|
||||||
private volatile FcdNormalized fcdNormalized = FcdNormalized.UNKNOWN;
|
private volatile FcdNormalized fcdNormalized = FcdNormalized.UNKNOWN;
|
||||||
@ -98,6 +99,10 @@ public final class Text implements EnsoObject {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Text empty() {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps a string in an instance of Text.
|
* Wraps a string in an instance of Text.
|
||||||
*
|
*
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package org.enso.interpreter.runtime.error;
|
package org.enso.interpreter.runtime.error;
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives;
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
|
import com.oracle.truffle.api.dsl.Bind;
|
||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
|
import com.oracle.truffle.api.interop.ArityException;
|
||||||
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
import com.oracle.truffle.api.interop.TruffleObject;
|
import com.oracle.truffle.api.interop.TruffleObject;
|
||||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||||
import com.oracle.truffle.api.library.CachedLibrary;
|
import com.oracle.truffle.api.library.CachedLibrary;
|
||||||
@ -10,21 +13,43 @@ import com.oracle.truffle.api.library.ExportMessage;
|
|||||||
import com.oracle.truffle.api.library.Message;
|
import com.oracle.truffle.api.library.Message;
|
||||||
import com.oracle.truffle.api.library.ReflectionLibrary;
|
import com.oracle.truffle.api.library.ReflectionLibrary;
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
|
import org.enso.interpreter.node.callable.InteropMethodCallNode;
|
||||||
import org.enso.interpreter.runtime.EnsoContext;
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
|
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||||
import org.enso.interpreter.runtime.data.ArrayRope;
|
import org.enso.interpreter.runtime.data.ArrayRope;
|
||||||
import org.enso.interpreter.runtime.data.EnsoObject;
|
import org.enso.interpreter.runtime.data.EnsoObject;
|
||||||
import org.enso.interpreter.runtime.data.Type;
|
import org.enso.interpreter.runtime.data.Type;
|
||||||
|
import org.enso.interpreter.runtime.data.text.Text;
|
||||||
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
|
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
|
||||||
|
import org.enso.interpreter.runtime.state.State;
|
||||||
import org.graalvm.collections.EconomicSet;
|
import org.graalvm.collections.EconomicSet;
|
||||||
import org.graalvm.collections.Equivalence;
|
import org.graalvm.collections.Equivalence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a typical Enso <em>value with warnings</em>. As much of care as possible is taken to
|
||||||
|
* delegate all operations to the underlaying {@code value}. Warnings are considered {@link
|
||||||
|
* InteropLibrary#isException exceptional values} - e.g. one can check for them in Java polyglot
|
||||||
|
* code as:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* Value value = ...;
|
||||||
|
* if (value.fitsInLong() && value.isException()) {
|
||||||
|
* // probably an Integer with a warning
|
||||||
|
* try {
|
||||||
|
* warningMulti.throwException();
|
||||||
|
* } catch (PolyglotException ex) {
|
||||||
|
* System.out.println("Warnings attached to " + value.asLong() + " are " + ex.getMessage());
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
@ExportLibrary(TypesLibrary.class)
|
@ExportLibrary(TypesLibrary.class)
|
||||||
@ExportLibrary(WarningsLibrary.class)
|
@ExportLibrary(WarningsLibrary.class)
|
||||||
@ExportLibrary(ReflectionLibrary.class)
|
@ExportLibrary(ReflectionLibrary.class)
|
||||||
|
@ExportLibrary(value = InteropLibrary.class, delegateTo = "value")
|
||||||
public final class WithWarnings implements EnsoObject {
|
public final class WithWarnings implements EnsoObject {
|
||||||
|
final Object value;
|
||||||
private final EconomicSet<Warning> warnings;
|
private final EconomicSet<Warning> warnings;
|
||||||
private final Object value;
|
|
||||||
|
|
||||||
private final boolean limitReached;
|
private final boolean limitReached;
|
||||||
private final int maxWarnings;
|
private final int maxWarnings;
|
||||||
@ -191,6 +216,29 @@ public final class WithWarnings implements EnsoObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CompilerDirectives.TruffleBoundary
|
||||||
|
private PanicException asException(Node where) {
|
||||||
|
var rawWarn = this.getWarnings(where, false, WarningsLibrary.getUncached());
|
||||||
|
var ctx = EnsoContext.get(where);
|
||||||
|
var scopeOfAny = ctx.getBuiltins().any().getDefinitionScope();
|
||||||
|
var toText = UnresolvedSymbol.build("to_text", scopeOfAny);
|
||||||
|
var node = InteropMethodCallNode.getUncached();
|
||||||
|
var state = State.create(ctx);
|
||||||
|
|
||||||
|
var text = Text.empty();
|
||||||
|
for (var w : rawWarn) {
|
||||||
|
try {
|
||||||
|
var wText = node.execute(toText, state, new Object[] {w});
|
||||||
|
if (wText instanceof Text t) {
|
||||||
|
text = text.add(t);
|
||||||
|
}
|
||||||
|
} catch (ArityException e) {
|
||||||
|
throw ctx.raiseAssertionPanic(where, null, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new PanicException(text, where);
|
||||||
|
}
|
||||||
|
|
||||||
@ExportMessage
|
@ExportMessage
|
||||||
Object send(Message message, Object[] args, @CachedLibrary(limit = "3") ReflectionLibrary lib)
|
Object send(Message message, Object[] args, @CachedLibrary(limit = "3") ReflectionLibrary lib)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@ -250,6 +298,16 @@ public final class WithWarnings implements EnsoObject {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportMessage
|
||||||
|
boolean isException() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportMessage
|
||||||
|
RuntimeException throwException(@Bind("$node") Node node) throws UnsupportedMessageException {
|
||||||
|
throw asException(node);
|
||||||
|
}
|
||||||
|
|
||||||
public static class WarningEquivalence extends Equivalence {
|
public static class WarningEquivalence extends Equivalence {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
Reference in New Issue
Block a user