Fix Warnings visualization (#3754)

Changelog
- fix reporting of runtime type for values annotated with warning
- fix visualizations of values annotated with warnings
- fix `Runtime.get_stack_trace` failure in interactive mode
This commit is contained in:
Dmitry Bushev 2022-10-04 20:27:13 +03:00 committed by GitHub
parent ff987212ce
commit f6b5438e9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 447 additions and 51 deletions

View File

@ -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<Function>.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

View File

@ -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

View File

@ -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)
)
}
}

View File

@ -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)"
}
}

View File

@ -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;
}

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -0,0 +1 @@
primitive_get_stack_trace = @Builtin_Method "Runtime.primitive_get_stack_trace"

View File

@ -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"