diff --git a/engine/runtime-with-instruments/src/test/java/org/enso/interpreter/test/instrument/IncrementalUpdatesTest.java b/engine/runtime-with-instruments/src/test/java/org/enso/interpreter/test/instrument/IncrementalUpdatesTest.java index 0a3eeabd52c..71142360da8 100644 --- a/engine/runtime-with-instruments/src/test/java/org/enso/interpreter/test/instrument/IncrementalUpdatesTest.java +++ b/engine/runtime-with-instruments/src/test/java/org/enso/interpreter/test/instrument/IncrementalUpdatesTest.java @@ -16,7 +16,7 @@ import org.enso.polyglot.runtime.Runtime$Api$BackgroundJobsStartedNotification; import org.enso.polyglot.runtime.Runtime$Api$CreateContextRequest; import org.enso.polyglot.runtime.Runtime$Api$CreateContextResponse; import org.enso.polyglot.runtime.Runtime$Api$EditFileNotification; -import org.enso.polyglot.runtime.Runtime$Api$ExecutionFailed; +import org.enso.polyglot.runtime.Runtime$Api$ExecutionComplete; import org.enso.polyglot.runtime.Runtime$Api$ExpressionUpdates; import org.enso.polyglot.runtime.Runtime$Api$InitializedNotification; import org.enso.polyglot.runtime.Runtime$Api$MethodCall; @@ -107,8 +107,11 @@ public class IncrementalUpdatesTest { @Test public void sendNotANumberChange() { - var failed = sendUpdatesWhenFunctionBodyIsChangedBySettingValue("4", ConstantsGen.INTEGER, "4", "x", null, LiteralNode.class); - assertTrue("Execution failed: " + failed, failed.head().payload() instanceof Runtime$Api$ExecutionFailed); + var result = sendUpdatesWhenFunctionBodyIsChangedBySettingValue("4", ConstantsGen.INTEGER, "4", "x", null, LiteralNode.class); + assertTrue("Execution succeeds: " + result, result.head().payload() instanceof Runtime$Api$ExecutionComplete); + assertEquals("Error is printed as a result", + List.newBuilder().addOne("(Error: Uninitialized value)"), context.consumeOut() + ); } private static String extractPositions(String code, String chars, Map beginAndLength) { diff --git a/engine/runtime-with-polyglot/src/test/java/org/enso/interpreter/test/InsightForEnsoTest.java b/engine/runtime-with-polyglot/src/test/java/org/enso/interpreter/test/InsightForEnsoTest.java new file mode 100644 index 00000000000..13b23de04f2 --- /dev/null +++ b/engine/runtime-with-polyglot/src/test/java/org/enso/interpreter/test/InsightForEnsoTest.java @@ -0,0 +1,94 @@ +package org.enso.interpreter.test; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.nio.file.Paths; +import java.util.Map; +import java.util.function.Function; +import org.enso.polyglot.RuntimeOptions; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Language; +import org.graalvm.polyglot.Source; +import org.junit.After; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Before; +import org.junit.Test; + +public class InsightForEnsoTest { + private Context ctx; + private AutoCloseable insightHandle; + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + @Before + public void initContext() throws Exception { + this.ctx = Context.newBuilder() + .allowExperimentalOptions(true) + .option( + RuntimeOptions.LANGUAGE_HOME_OVERRIDE, + Paths.get("../../distribution/component").toFile().getAbsolutePath() + ) + .logHandler(OutputStream.nullOutputStream()) + .allowExperimentalOptions(true) + .allowIO(true) + .out(out) + .allowAllAccess(true) + .build(); + + var engine = ctx.getEngine(); + Map langs = engine.getLanguages(); + assertNotNull("Enso found: " + langs, langs.get("enso")); + + @SuppressWarnings("unchecked") + var fn = (Function) engine.getInstruments().get("insight").lookup(Function.class); + assertNotNull(fn); + + var insightScript = Source.newBuilder("js", """ + insight.on('enter', (ctx, frame) => { + print(`${ctx.name} at ${ctx.source.name}:${ctx.line}:`); + let dump = ""; + for (let p in frame) { + dump += ` ${p}=${frame[p]}`; + } + print(dump); + }, { + roots : true + }); + """, "trace.js").build(); + this.insightHandle = fn.apply(insightScript); + } + + @After + public void disposeContext() throws Exception { + this.insightHandle.close(); + this.ctx.close(); + } + + @Test + public void computeFactorial() throws Exception { + var code = Source.newBuilder("enso", """ + fac n = + acc n v = if n <= 1 then v else + @Tail_Call acc n-1 n*v + + acc n 1 + """, "factorial.enso").build(); + + var m = ctx.eval(code); + var fac = m.invokeMember("eval_expression", "fac"); + var res = fac.execute(5); + assertEquals(120, res.asInt()); + + var msgs = out.toString(); + assertNotEquals("Step one: " + msgs, -1, msgs.indexOf("n=5 v=1 acc=function")); + assertNotEquals("Step two: " + msgs, -1, msgs.indexOf("n=4 v=5 acc=function")); + assertNotEquals("3rd step: " + msgs, -1, msgs.indexOf("n=3 v=20 acc=function")); + assertNotEquals("4th step: " + msgs, -1, msgs.indexOf("n=2 v=60 acc=function")); + + assertNotEquals( + "Uninitialized variables are seen as JavaScript null: " + msgs, + -1, msgs.indexOf("n=null v=null acc=function") + ); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index dec1c0cdade..d9c33e29380 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -9,10 +9,7 @@ import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; -import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Deque; import java.util.List; import java.util.Optional; import java.util.UUID; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java index 53621ff7684..790f6462ac8 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Builtins.java @@ -1,8 +1,14 @@ package org.enso.interpreter.runtime.builtin; import com.oracle.truffle.api.CompilerDirectives; - +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -10,8 +16,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; - - +import java.util.stream.Collectors; import org.enso.compiler.Passes; import org.enso.compiler.context.FreshNameSupply; import org.enso.compiler.exception.CompilerError; @@ -19,16 +24,20 @@ import org.enso.compiler.phase.BuiltinsIrBuilder; import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.dsl.TypeProcessor; import org.enso.interpreter.dsl.model.MethodDefinition; +import org.enso.interpreter.node.expression.builtin.Any; import org.enso.interpreter.node.expression.builtin.Boolean; -import org.enso.interpreter.node.expression.builtin.*; +import org.enso.interpreter.node.expression.builtin.Builtin; +import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; +import org.enso.interpreter.node.expression.builtin.Nothing; +import org.enso.interpreter.node.expression.builtin.Polyglot; import org.enso.interpreter.node.expression.builtin.debug.Debug; import org.enso.interpreter.node.expression.builtin.error.CaughtPanic; import org.enso.interpreter.node.expression.builtin.error.Warning; +import org.enso.interpreter.node.expression.builtin.immutable.Vector; import org.enso.interpreter.node.expression.builtin.io.File; import org.enso.interpreter.node.expression.builtin.meta.ProjectDescription; import org.enso.interpreter.node.expression.builtin.mutable.Array; import org.enso.interpreter.node.expression.builtin.mutable.Ref; -import org.enso.interpreter.node.expression.builtin.immutable.Vector; import org.enso.interpreter.node.expression.builtin.ordering.Comparable; import org.enso.interpreter.node.expression.builtin.ordering.DefaultComparator; import org.enso.interpreter.node.expression.builtin.ordering.Ordering; @@ -37,24 +46,18 @@ import org.enso.interpreter.node.expression.builtin.runtime.Context; import org.enso.interpreter.node.expression.builtin.text.Text; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.Module; +import org.enso.interpreter.runtime.builtin.Error; +import org.enso.interpreter.runtime.builtin.Number; +import org.enso.interpreter.runtime.builtin.System; +import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.Type; -import org.enso.interpreter.runtime.callable.atom.Atom; import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.type.TypesFromProxy; import org.enso.pkg.QualifiedName; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.util.stream.Collectors; - /** Container class for static predefined atoms, methods, and their containing scope. */ -public class Builtins { +public final class Builtins { private static final List> loadedBuiltinConstructors; private static final Map loadedBuiltinMethods; @@ -133,12 +136,9 @@ public class Builtins { builtinMethodNodes = readBuiltinMethodsMetadata(loadedBuiltinMethods, scope); registerBuiltinMethods(scope, language); - error = new Error(this, context); ordering = getBuiltinType(Ordering.class); comparable = getBuiltinType(Comparable.class); defaultComparator = getBuiltinType(DefaultComparator.class); - system = new System(this); - number = new Number(this); bool = this.getBuiltinType(Boolean.class); contexts = this.getBuiltinType(Context.class); @@ -161,8 +161,12 @@ public class Builtins { duration = builtins.get(org.enso.interpreter.node.expression.builtin.date.Duration.class); timeOfDay = builtins.get(org.enso.interpreter.node.expression.builtin.date.TimeOfDay.class); timeZone = builtins.get(org.enso.interpreter.node.expression.builtin.date.TimeZone.class); - special = new Special(language); warning = builtins.get(Warning.class); + + error = new Error(this, context); + system = new System(this); + number = new Number(this); + special = new Special(language); } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java index 5addeb626b9..bcf6709cda9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/builtin/Error.java @@ -15,7 +15,7 @@ import org.enso.interpreter.runtime.data.text.Text; import static com.oracle.truffle.api.CompilerDirectives.transferToInterpreterAndInvalidate; /** Container for builtin Error types */ -public class Error { +public final class Error { private final EnsoContext context; private final SyntaxError syntaxError; private final TypeError typeError; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java index e008e54b5ef..de64f4562fc 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java @@ -9,6 +9,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.nodes.Node; +import java.util.Objects; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; @@ -21,7 +22,10 @@ import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(TypesLibrary.class) -public class DataflowError extends AbstractTruffleException { +public final class DataflowError extends AbstractTruffleException { + /** Signals (local) values that haven't yet been initialized */ + public static final DataflowError UNINITIALIZED = new DataflowError(null, (Node) null); + private final Object payload; /** @@ -34,6 +38,7 @@ public class DataflowError extends AbstractTruffleException { * @return a new dataflow error */ public static DataflowError withoutTrace(Object payload, Node location) { + assert payload != null; DataflowError result = new DataflowError(payload, location); TruffleStackTrace.fillIn(result); return result; @@ -50,6 +55,7 @@ public class DataflowError extends AbstractTruffleException { * @return a new dataflow error */ public static DataflowError withTrace(Object payload, AbstractTruffleException prototype) { + assert payload != null; return new DataflowError(payload, prototype); } @@ -69,7 +75,7 @@ public class DataflowError extends AbstractTruffleException { * @return the payload object */ public Object getPayload() { - return payload; + return payload != null ? payload : "Uninitialized value"; } /** @@ -78,18 +84,17 @@ public class DataflowError extends AbstractTruffleException { * @return a string representation of this object */ @Override + @TruffleBoundary public String toString() { - return "Error:" + getPayload().toString(); + return "Error:" + Objects.toString(getPayload()); } @ExportMessage @TruffleBoundary - public String toDisplayString( - boolean allowSideEffects, - @CachedLibrary(limit = "3") InteropLibrary displays, - @CachedLibrary(limit = "3") InteropLibrary strings) { + public String toDisplayString(boolean allowSideEffects) { try { - return "(Error: " + strings.asString(displays.toDisplayString(payload)) + ")"; + var iop = InteropLibrary.getUncached(); + return "(Error: " + iop.asString(iop.toDisplayString(getPayload())) + ")"; } catch (UnsupportedMessageException e) { return "Error"; } @@ -110,6 +115,11 @@ public class DataflowError extends AbstractTruffleException { return true; } + @ExportMessage + boolean isNull() { + return payload == null; + } + @ExportMessage RuntimeException throwException() throws UnsupportedMessageException { return this; diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala index 65e83c42090..27942b4ddc1 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala @@ -8,6 +8,7 @@ import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{ Scope => AliasScope } import org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis} +import org.enso.interpreter.runtime.error.DataflowError import org.enso.interpreter.runtime.scope.LocalScope.{ internalSlots, monadicStateSlotName @@ -162,6 +163,7 @@ class LocalScope( ) assert(localFrameSlotIdxs(definition.id) == returnedFrameIdx) } + descriptorBuilder.defaultValue(DataflowError.UNINITIALIZED) val frameDescriptor = descriptorBuilder.build() assert( internalSlots.length + localFrameSlotIdxs.size == frameDescriptor.getNumberOfSlots diff --git a/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java b/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java index 08f424e3559..85fd720aee7 100644 --- a/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java +++ b/engine/runtime/src/test/java/org/enso/compiler/ExecCompilerTest.java @@ -6,9 +6,12 @@ import org.enso.polyglot.RuntimeOptions; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.PolyglotException; import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import org.junit.BeforeClass; import org.junit.Test; -import static org.junit.Assert.*; public class ExecCompilerTest { private static Context ctx; @@ -66,6 +69,37 @@ public class ExecCompilerTest { } } + @Test + public void testSelfAssignment() throws Exception { + var module = ctx.eval("enso", """ + from Standard.Base.Errors.Common import all + run value = + meta1 = meta1 + meta1 + """); + var run = module.invokeMember("eval_expression", "run"); + var error = run.execute(-1); + assertTrue("We get an error value back", error.isException()); + assertTrue("The error value also represents null", error.isNull()); + assertEquals("(Error: Uninitialized value)", error.toString()); + } + + @Test + public void testRecursiveDefinition() throws Exception { + var module = ctx.eval("enso", """ + from Standard.Base import all + + run prefix = + op = if False then 42 else prefix+op + op + """); + var run = module.invokeMember("eval_expression", "run"); + var error = run.execute("Nope: "); + assertTrue("We get an error value back", error.isException()); + assertTrue("The error value also represents null", error.isNull()); + assertEquals("(Error: Uninitialized value)", error.toString()); + } + @Test public void testInvalidEnsoProjectRef() throws Exception { var module =