mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 13:02:07 +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) {
|
||||
if (actual.contains(expected)) {
|
||||
static void assertContains(String expected, String actual) {
|
||||
assertContains("Expecting", expected, actual);
|
||||
}
|
||||
|
||||
static void assertContains(String msg, String expected, String actual) {
|
||||
if (actual != null && actual.contains(expected)) {
|
||||
return;
|
||||
}
|
||||
fail("Expecting " + expected + " in " + actual);
|
||||
fail(msg + " " + expected + " in " + actual);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import org.junit.Test;
|
||||
public class MetaIsATest extends TestBase {
|
||||
private static Context ctx;
|
||||
private static Value isACheck;
|
||||
private static Value warningCheck;
|
||||
private static ValuesGenerator generator;
|
||||
|
||||
@BeforeClass
|
||||
@ -30,9 +31,10 @@ public class MetaIsATest extends TestBase {
|
||||
Source.newBuilder(
|
||||
"enso",
|
||||
"""
|
||||
import Standard.Base.Meta
|
||||
from Standard.Base import Meta, Warning
|
||||
|
||||
check x y = Meta.is_a x y
|
||||
check_warning x = Warning.has_warnings x
|
||||
""",
|
||||
"check.enso")
|
||||
.uri(uri)
|
||||
@ -40,6 +42,7 @@ public class MetaIsATest extends TestBase {
|
||||
|
||||
var module = ctx.eval(src);
|
||||
isACheck = module.invokeMember("eval_expression", "check");
|
||||
warningCheck = module.invokeMember("eval_expression", "check_warning");
|
||||
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) {
|
||||
var test = caseOf.execute(v);
|
||||
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 out", g.typeError(), test.getMetaObject());
|
||||
// end the test here as DataflowError doesn't represent a value
|
||||
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());
|
||||
var testBool = test.asInt() == 1;
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.enso.interpreter.test;
|
||||
|
||||
import static org.enso.interpreter.test.BinaryDispatchTest.assertContains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
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.WithWarnings;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.PolyglotException;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
@ -18,16 +22,28 @@ import org.junit.Test;
|
||||
public class WarningsTest extends TestBase {
|
||||
|
||||
private static Context ctx;
|
||||
private static ValuesGenerator generator;
|
||||
private static Value wrap;
|
||||
private static EnsoContext ensoContext;
|
||||
|
||||
@BeforeClass
|
||||
public static void initEnsoContext() {
|
||||
ctx = createDefaultContext();
|
||||
generator = ValuesGenerator.create(ctx, ValuesGenerator.Language.ENSO);
|
||||
ensoContext =
|
||||
(EnsoContext)
|
||||
ctx.getBindings(LanguageInfo.ID)
|
||||
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
|
||||
.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
|
||||
@ -65,4 +81,75 @@ public class WarningsTest extends TestBase {
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
public static InteropMethodCallNode getUncached() {
|
||||
return InteropMethodCallNodeGen.getUncached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the method with given state and arguments.
|
||||
*
|
||||
|
@ -27,6 +27,7 @@ import org.enso.polyglot.common_utils.Core_Text_Utils;
|
||||
@ExportLibrary(TypesLibrary.class)
|
||||
public final class Text implements EnsoObject {
|
||||
private static final Lock LOCK = new ReentrantLock();
|
||||
private static final Text EMPTY = new Text("");
|
||||
private volatile Object contents;
|
||||
private volatile int length = -1;
|
||||
private volatile FcdNormalized fcdNormalized = FcdNormalized.UNKNOWN;
|
||||
@ -98,6 +99,10 @@ public final class Text implements EnsoObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Text empty() {
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a string in an instance of Text.
|
||||
*
|
||||
|
@ -1,7 +1,10 @@
|
||||
package org.enso.interpreter.runtime.error;
|
||||
|
||||
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.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
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.ReflectionLibrary;
|
||||
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.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.data.ArrayRope;
|
||||
import org.enso.interpreter.runtime.data.EnsoObject;
|
||||
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.state.State;
|
||||
import org.graalvm.collections.EconomicSet;
|
||||
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(WarningsLibrary.class)
|
||||
@ExportLibrary(ReflectionLibrary.class)
|
||||
@ExportLibrary(value = InteropLibrary.class, delegateTo = "value")
|
||||
public final class WithWarnings implements EnsoObject {
|
||||
|
||||
final Object value;
|
||||
private final EconomicSet<Warning> warnings;
|
||||
private final Object value;
|
||||
|
||||
private final boolean limitReached;
|
||||
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
|
||||
Object send(Message message, Object[] args, @CachedLibrary(limit = "3") ReflectionLibrary lib)
|
||||
throws Exception {
|
||||
@ -250,6 +298,16 @@ public final class WithWarnings implements EnsoObject {
|
||||
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 {
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user