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 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.Polyglot
import Standard.Base.Nothing import Standard.Base.Nothing
from Standard.Base.Runtime.Extensions import Source_Location, Source_Location_Data 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 = get_stack_trace =
prim_stack = primitive_get_stack_trace prim_stack = primitive_get_stack_trace
stack_with_prims = Vector.from_polyglot_array prim_stack stack_with_prims = Vector.from_polyglot_array prim_stack
stack = stack_with_prims.map wrap_primitive_stack_trace_element # (First 2) drops the `Runtime.primitive_get_stack_trace` frame and this one
# drop this frame and the one from `Runtime.primitive_get_stack_trace` # (Last 1) drops the `org.graalvm.polyglot.Value<Function>.execute` frame
stack.drop (First 2) stack = stack_with_prims.drop (First 2) . drop (Last 1)
stack.map wrap_primitive_stack_trace_element
## ADVANCED ## ADVANCED
@ -82,11 +83,7 @@ no_inline_with_arg function arg = @Builtin_Method "Runtime.no_inline_with_arg"
wrap_primitive_stack_trace_element el = wrap_primitive_stack_trace_element el =
loc = if Polyglot.has_source_location el then Source_Location_Data (Polyglot.get_source_location el) else Nothing loc = if Polyglot.has_source_location el then Source_Location_Data (Polyglot.get_source_location el) else Nothing
name = Polyglot.get_executable_name el name = Polyglot.get_executable_name el
Stack_Trace_Element_Data name loc Stack_Trace_Element.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
## ADVANCED ## ADVANCED
UNSTABLE UNSTABLE

View File

@ -1,6 +1,6 @@
from Standard.Base import all from Standard.Base import all
from Standard.Base.Data.Index_Sub_Range import While 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. ## A representation of a dataflow warning attached to a value.
@Builtin_Type @Builtin_Type
@ -40,7 +40,7 @@ type Warning
loc = case Polyglot.has_source_location r of loc = case Polyglot.has_source_location r of
False -> Nothing False -> Nothing
True -> Source_Location_Data (Polyglot.get_source_location r) 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 ## PRIVATE

View File

@ -3583,4 +3583,114 @@ class RuntimeServerTest
Api.Response(requestId, Api.GetTypeGraphResponse(expectedGraph)) 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 org.scalatest.matchers.should.Matchers
import java.io.{ByteArrayOutputStream, File} import java.io.{ByteArrayOutputStream, File}
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path, Paths} import java.nio.file.{Files, Path, Paths}
import java.util.UUID import java.util.UUID
@ -2510,4 +2511,271 @@ class RuntimeVisualizationsTest
data3.sameElements("52".getBytes) shouldBe true data3.sameElements("52".getBytes) shouldBe true
context.consumeOut shouldEqual List("encoding...") 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.callable.function.Function;
import org.enso.interpreter.runtime.data.*; import org.enso.interpreter.runtime.data.*;
import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.error.*;
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.number.EnsoBigInteger; import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.polyglot.data.TypeGraph; import org.enso.polyglot.data.TypeGraph;
@ -157,6 +154,10 @@ public class Types {
return ConstantsGen.PANIC; return ConstantsGen.PANIC;
} else if (TypesGen.isPanicSentinel(value)) { } else if (TypesGen.isPanicSentinel(value)) {
return ConstantsGen.PANIC; return ConstantsGen.PANIC;
} else if (TypesGen.isWarning(value)) {
return ConstantsGen.WARNING;
} else if (value instanceof WithWarnings) {
return getName(((WithWarnings) value).getValue());
} else { } else {
return null; return null;
} }

View File

@ -1,10 +1,5 @@
package org.enso.interpreter.instrument.job 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 cats.implicits._
import com.oracle.truffle.api.exception.AbstractTruffleException import com.oracle.truffle.api.exception.AbstractTruffleException
import org.enso.interpreter.instrument.IdExecutionService.{ import org.enso.interpreter.instrument.IdExecutionService.{
@ -18,29 +13,27 @@ import org.enso.interpreter.instrument.execution.{
RuntimeContext RuntimeContext
} }
import org.enso.interpreter.instrument.profiling.ExecutionTime import org.enso.interpreter.instrument.profiling.ExecutionTime
import org.enso.interpreter.instrument.{ import org.enso.interpreter.instrument._
InstrumentFrame,
MethodCallsCache,
RuntimeCache,
UpdatesSynchronizationState,
Visualisation
}
import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall 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.`type`.Types
import org.enso.interpreter.runtime.control.ThreadInterruptedException import org.enso.interpreter.runtime.control.ThreadInterruptedException
import org.enso.interpreter.service.error.{ import org.enso.interpreter.runtime.data.text.Text
MethodNotFoundException, import org.enso.interpreter.runtime.error.{
ModuleNotFoundForExpressionIdException, DataflowError,
ServiceException, PanicSentinel,
TypeNotFoundException, WithWarnings
VisualisationException
} }
import org.enso.interpreter.service.error._
import org.enso.polyglot.LanguageInfo import org.enso.polyglot.LanguageInfo
import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.runtime.Runtime.Api.ContextId 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._ import scala.jdk.OptionConverters._
/** Provides support for executing Enso code. Adds convenient methods to /** Provides support for executing Enso code. Adds convenient methods to
@ -473,20 +466,7 @@ object ProgramExecutionSupport {
expressionValue +: visualisation.arguments: _* expressionValue +: visualisation.arguments: _*
) )
} }
.flatMap { .flatMap(visualizationResultToBytes)
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"
)
)
}
val result = errorOrVisualisationData match { val result = errorOrVisualisationData match {
case Left(_: ThreadInterruptedException) => case Left(_: ThreadInterruptedException) =>
ctx.executionService.getLogger.log( 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. /** Extract method pointer information from the expression value.
* *
* @param value 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 stack.take (First 5) . map .name . should_equal names
file = enso_project.root / 'src' / 'Runtime' / 'Stack_Traces_Spec.enso' file = enso_project.root / 'src' / 'Runtime' / 'Stack_Traces_Spec.enso'
stack.take (First 5) . map (.source_location >> .file) . each (_.should_equal file) 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 import project.Polyglot
from project.Polyglot export all from project.Polyglot export all
export project.Polyglot.Java
import 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"