diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso index 5a40a8f801..55e4500640 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso @@ -1,5 +1,5 @@ import Standard.Base.Data.Vector -from Standard.Base.Data.Index_Sub_Range import First +from Standard.Base.Data.Index_Sub_Range import First, Last import Standard.Base.Polyglot import Standard.Base.Nothing from Standard.Base.Runtime.Extensions import Source_Location, Source_Location_Data @@ -22,9 +22,10 @@ get_stack_trace : Vector.Vector Stack_Trace_Element get_stack_trace = prim_stack = primitive_get_stack_trace stack_with_prims = Vector.from_polyglot_array prim_stack - stack = stack_with_prims.map wrap_primitive_stack_trace_element - # drop this frame and the one from `Runtime.primitive_get_stack_trace` - stack.drop (First 2) + # (First 2) drops the `Runtime.primitive_get_stack_trace` frame and this one + # (Last 1) drops the `org.graalvm.polyglot.Value.execute` frame + stack = stack_with_prims.drop (First 2) . drop (Last 1) + stack.map wrap_primitive_stack_trace_element ## ADVANCED @@ -82,11 +83,7 @@ no_inline_with_arg function arg = @Builtin_Method "Runtime.no_inline_with_arg" wrap_primitive_stack_trace_element el = loc = if Polyglot.has_source_location el then Source_Location_Data (Polyglot.get_source_location el) else Nothing name = Polyglot.get_executable_name el - Stack_Trace_Element_Data name loc - -# TODO Dubious constructor export -from project.Runtime.Stack_Trace_Element import all -from project.Runtime.Stack_Trace_Element export all + Stack_Trace_Element.Stack_Trace_Element_Data name loc ## ADVANCED UNSTABLE diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso index 49ca3eef19..c89999e8a7 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso @@ -1,6 +1,6 @@ from Standard.Base import all from Standard.Base.Data.Index_Sub_Range import While -from Standard.Base.Runtime import Stack_Trace_Element_Data +from Standard.Base.Runtime import Stack_Trace_Element ## A representation of a dataflow warning attached to a value. @Builtin_Type @@ -40,7 +40,7 @@ type Warning loc = case Polyglot.has_source_location r of False -> Nothing True -> Source_Location_Data (Polyglot.get_source_location r) - Stack_Trace_Element_Data (Polyglot.get_executable_name r) loc + Stack_Trace_Element.Stack_Trace_Element_Data (Polyglot.get_executable_name r) loc ## PRIVATE diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index e03b985fcc..bcea7a2782 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -3583,4 +3583,114 @@ class RuntimeServerTest Api.Response(requestId, Api.GetTypeGraphResponse(expectedGraph)) ) } + + it should "send updates for values annotated with warning" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val idX = metadata.addItem(46, 71) + val idY = metadata.addItem(126, 5) + val idRes = metadata.addItem(136, 12) + val code = + """from Standard.Base import all + | + |main = + | x = Warning.attach_with_stacktrace 42 "y" Runtime.primitive_get_stack_trace + | y = x + 1 + | IO.println y + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // open file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents)) + ) + context.receiveNone shouldEqual None + + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 5 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idX, ConstantsGen.INTEGER), + TestMessages.update(contextId, idY, ConstantsGen.INTEGER), + TestMessages.update(contextId, idRes, ConstantsGen.NOTHING), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("43") + } + + it should "send updates for values in array annotated with warning" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val idMain = metadata.addItem(37, 79) + val code = + """from Standard.Base import all + | + |main = + | [Warning.attach_with_stacktrace "x" "y" Runtime.primitive_get_stack_trace] + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // open file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents)) + ) + context.receiveNone shouldEqual None + + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idMain, ConstantsGen.VECTOR), + context.executionComplete(contextId) + ) + } + } diff --git a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index 577b4cb946..a0d3c0adf5 100644 --- a/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -17,6 +17,7 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import java.io.{ByteArrayOutputStream, File} +import java.nio.charset.StandardCharsets import java.nio.file.{Files, Path, Paths} import java.util.UUID @@ -2510,4 +2511,271 @@ class RuntimeVisualizationsTest data3.sameElements("52".getBytes) shouldBe true context.consumeOut shouldEqual List("encoding...") } + + it should "emit visualisation update for values annotated with warnings" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualisationId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idMain = metadata.addItem(37, 76) + + val code = + """from Standard.Base import all + | + |main = + | Warning.attach_with_stacktrace 42 "y" Runtime.primitive_get_stack_trace + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents)) + ) + context.receiveNone shouldEqual None + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), + context.executionComplete(contextId) + ) + + // attach visualisation + context.send( + Api.Request( + requestId, + Api.AttachVisualisation( + visualisationId, + idMain, + Api.VisualisationConfiguration( + contextId, + Api.VisualisationExpression.Text( + "Enso_Test.Test.Main", + "x -> x.to_text" + ) + ) + ) + ) + ) + val attachVisualisationResponses = + context.receiveNIgnoreExpressionUpdates(3) + attachVisualisationResponses should contain allOf ( + Api.Response(requestId, Api.VisualisationAttached()), + context.executionComplete(contextId) + ) + val Some(data) = attachVisualisationResponses.collectFirst { + case Api.Response( + None, + Api.VisualisationUpdate( + Api.VisualisationContext( + `visualisationId`, + `contextId`, + `idMain` + ), + data + ) + ) => + data + } + new String(data, StandardCharsets.UTF_8) shouldEqual "42" + } + + it should "emit visualisation update for values in array annotated with warnings" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualisationId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idMain = metadata.addItem(37, 78) + + val code = + """from Standard.Base import all + | + |main = + | [Warning.attach_with_stacktrace 42 "y" Runtime.primitive_get_stack_trace] + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents)) + ) + context.receiveNone shouldEqual None + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idMain, ConstantsGen.VECTOR), + context.executionComplete(contextId) + ) + + // attach visualisation + context.send( + Api.Request( + requestId, + Api.AttachVisualisation( + visualisationId, + idMain, + Api.VisualisationConfiguration( + contextId, + Api.VisualisationExpression.Text( + "Enso_Test.Test.Main", + "x -> x.to_text" + ) + ) + ) + ) + ) + val attachVisualisationResponses = + context.receiveNIgnoreExpressionUpdates(3) + attachVisualisationResponses should contain allOf ( + Api.Response(requestId, Api.VisualisationAttached()), + context.executionComplete(contextId) + ) + val Some(data) = attachVisualisationResponses.collectFirst { + case Api.Response( + None, + Api.VisualisationUpdate( + Api.VisualisationContext( + `visualisationId`, + `contextId`, + `idMain` + ), + data + ) + ) => + data + } + new String(data, StandardCharsets.UTF_8) shouldEqual "[42]" + } + + it should "emit visualisation update for values in atom annotated with warnings" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualisationId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idX = metadata.addItem(81, 71) + val idRes = metadata.addItem(157, 20) + + val code = + """from Standard.Base import all + | + |type Newtype + | Mk_Newtype value + | + |main = + | x = Warning.attach_with_stacktrace 42 "x" Runtime.primitive_get_stack_trace + | Newtype.Mk_Newtype x + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(Api.OpenFileNotification(mainFile, contents)) + ) + context.receiveNone shouldEqual None + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idX, ConstantsGen.INTEGER), + TestMessages.update(contextId, idRes, s"$moduleName.Newtype.Mk_Newtype"), + context.executionComplete(contextId) + ) + + // attach visualisation + context.send( + Api.Request( + requestId, + Api.AttachVisualisation( + visualisationId, + idRes, + Api.VisualisationConfiguration( + contextId, + Api.VisualisationExpression.Text( + "Enso_Test.Test.Main", + "x -> x.to_text" + ) + ) + ) + ) + ) + val attachVisualisationResponses = + context.receiveNIgnoreExpressionUpdates(3) + attachVisualisationResponses should contain allOf ( + Api.Response(requestId, Api.VisualisationAttached()), + context.executionComplete(contextId) + ) + val Some(data) = attachVisualisationResponses.collectFirst { + case Api.Response( + None, + Api.VisualisationUpdate( + Api.VisualisationContext( + `visualisationId`, + `contextId`, + `idRes` + ), + data + ) + ) => + data + } + new String(data, StandardCharsets.UTF_8) shouldEqual "(Mk_Newtype 42)" + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java index 4ddc0725db..aaed02ddbd 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/type/Types.java @@ -10,10 +10,7 @@ import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.*; import org.enso.interpreter.runtime.data.text.Text; -import org.enso.interpreter.runtime.error.DataflowError; -import org.enso.interpreter.runtime.error.PanicException; -import org.enso.interpreter.runtime.error.PanicSentinel; -import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.*; import org.enso.interpreter.runtime.number.EnsoBigInteger; import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.polyglot.data.TypeGraph; @@ -157,6 +154,10 @@ public class Types { return ConstantsGen.PANIC; } else if (TypesGen.isPanicSentinel(value)) { return ConstantsGen.PANIC; + } else if (TypesGen.isWarning(value)) { + return ConstantsGen.WARNING; + } else if (value instanceof WithWarnings) { + return getName(((WithWarnings) value).getValue()); } else { return null; } diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 091a2f735f..8e3a48a7ae 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -1,10 +1,5 @@ package org.enso.interpreter.instrument.job -import java.io.File -import java.util.function.Consumer -import java.util.logging.Level -import java.util.UUID - import cats.implicits._ import com.oracle.truffle.api.exception.AbstractTruffleException import org.enso.interpreter.instrument.IdExecutionService.{ @@ -18,29 +13,27 @@ import org.enso.interpreter.instrument.execution.{ RuntimeContext } import org.enso.interpreter.instrument.profiling.ExecutionTime -import org.enso.interpreter.instrument.{ - InstrumentFrame, - MethodCallsCache, - RuntimeCache, - UpdatesSynchronizationState, - Visualisation -} +import org.enso.interpreter.instrument._ import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall -import org.enso.interpreter.runtime.data.text.Text -import org.enso.interpreter.runtime.error.{DataflowError, PanicSentinel} import org.enso.interpreter.runtime.`type`.Types import org.enso.interpreter.runtime.control.ThreadInterruptedException -import org.enso.interpreter.service.error.{ - MethodNotFoundException, - ModuleNotFoundForExpressionIdException, - ServiceException, - TypeNotFoundException, - VisualisationException +import org.enso.interpreter.runtime.data.text.Text +import org.enso.interpreter.runtime.error.{ + DataflowError, + PanicSentinel, + WithWarnings } +import org.enso.interpreter.service.error._ import org.enso.polyglot.LanguageInfo import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api.ContextId +import java.io.File +import java.nio.charset.StandardCharsets +import java.util.UUID +import java.util.function.Consumer +import java.util.logging.Level + import scala.jdk.OptionConverters._ /** Provides support for executing Enso code. Adds convenient methods to @@ -473,20 +466,7 @@ object ProgramExecutionSupport { expressionValue +: visualisation.arguments: _* ) } - .flatMap { - case text: String => - Right(text.getBytes("UTF-8")) - case text: Text => - Right(text.toString.getBytes("UTF-8")) - case bytes: Array[Byte] => - Right(bytes) - case other => - Left( - new VisualisationException( - s"Cannot encode ${other.getClass} to byte array" - ) - ) - } + .flatMap(visualizationResultToBytes) val result = errorOrVisualisationData match { case Left(_: ThreadInterruptedException) => ctx.executionService.getLogger.log( @@ -539,6 +519,33 @@ object ProgramExecutionSupport { } } + /** Convert the result of Enso visualization function to a byte array. + * + * @param value the result of Enso visualization function + * @return either a byte array representing the visualization result or an + * error + */ + @scala.annotation.tailrec + private def visualizationResultToBytes( + value: AnyRef + ): Either[VisualisationException, Array[Byte]] = + value match { + case text: String => + Right(text.getBytes(StandardCharsets.UTF_8)) + case text: Text => + Right(text.toString.getBytes(StandardCharsets.UTF_8)) + case bytes: Array[Byte] => + Right(bytes) + case withWarnings: WithWarnings => + visualizationResultToBytes(withWarnings.getValue) + case other => + Left( + new VisualisationException( + s"Cannot encode ${other.getClass} to byte array." + ) + ) + } + /** Extract method pointer information from the expression value. * * @param value the expression value. diff --git a/test/Tests/src/Runtime/Stack_Traces_Spec.enso b/test/Tests/src/Runtime/Stack_Traces_Spec.enso index 71f5ee108c..87b302d859 100644 --- a/test/Tests/src/Runtime/Stack_Traces_Spec.enso +++ b/test/Tests/src/Runtime/Stack_Traces_Spec.enso @@ -18,4 +18,3 @@ spec = Test.group "Stack traces" <| stack.take (First 5) . map .name . should_equal names file = enso_project.root / 'src' / 'Runtime' / 'Stack_Traces_Spec.enso' stack.take (First 5) . map (.source_location >> .file) . each (_.should_equal file) - diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso index 132c8add76..daa2cd183c 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Main.enso @@ -25,5 +25,11 @@ from project.Data.Vector export Vector import project.Polyglot from project.Polyglot export all -export project.Polyglot.Java import project.Polyglot.Java +export project.Polyglot.Java + +import project.Runtime +export project.Runtime + +import project.Warning +export project.Warning diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso new file mode 100644 index 0000000000..b011d4d8d4 --- /dev/null +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Runtime.enso @@ -0,0 +1 @@ +primitive_get_stack_trace = @Builtin_Method "Runtime.primitive_get_stack_trace" diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso new file mode 100644 index 0000000000..6c27352f71 --- /dev/null +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Warning.enso @@ -0,0 +1,7 @@ +@Builtin_Type +type Warning + value self = @Builtin_Method "Warning.value" + + origin self = @Builtin_Method "Warning.origin" + +attach_with_stacktrace value warning origin = @Builtin_Method "Warning.attach_with_stacktrace"