diff --git a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso index 357ceb8eef8..3a71b3c32e8 100644 --- a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso +++ b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Warnings.enso @@ -12,8 +12,4 @@ process_to_json_text : Any -> Text process_to_json_text value = warnings = Warning.get_all value text = warnings.map w->w.value.to_display_text - json = text.to_json - - ## Workaround so that the JS String is converted to a Text - https://www.pivotaltracker.com/story/show/184061302 - "" + json + text.to_json diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 9ef7aeab913..94ae0e50a71 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -351,9 +351,14 @@ An information about the computed value. type ExpressionUpdatePayload = Value | DatafalowError | Panic | Pending; /** - * An empty payload. Indicates that the expression was computed to a value. + * Indicates that the expression was computed to a value. */ -interface Value {} +interface Value { + /** + * Information about attached warnings. + */ + warnings?: Warnings; +} /** * Indicates that the expression was computed to an error. @@ -395,6 +400,23 @@ interface Pending { */ progress?: Number; } + +/** + * Information about warnings associated with the value. + */ +interface Warnings { + /** + * The number of attached warnings. + */ + count: number; + + /** + * If the value has a single warning attached, this field contains textual + * representation of the attached warning. In general, warning values should + * be obtained by attaching an appropriate visualization to a value. + */ + value?: string; +} ``` ### `VisualisationConfiguration` diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala index c08d93a4515..095e1a888c8 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextEventsListener.scala @@ -63,7 +63,7 @@ final class ContextEventsListener( override def receive: Receive = withState(Set(), Vector()) - def withState( + private def withState( oneshotVisualisations: Set[Api.VisualisationContext], expressionUpdates: Vector[Api.ExpressionUpdate] ): Receive = { @@ -244,8 +244,10 @@ final class ContextEventsListener( payload: Api.ExpressionUpdate.Payload ): ContextRegistryProtocol.ExpressionUpdate.Payload = payload match { - case Api.ExpressionUpdate.Payload.Value() => - ContextRegistryProtocol.ExpressionUpdate.Payload.Value + case Api.ExpressionUpdate.Payload.Value(warnings) => + ContextRegistryProtocol.ExpressionUpdate.Payload.Value( + warnings.map(toProtocolWarnings) + ) case Api.ExpressionUpdate.Payload.Pending(m, p) => ContextRegistryProtocol.ExpressionUpdate.Payload.Pending(m, p) @@ -258,6 +260,17 @@ final class ContextEventsListener( .Panic(message, trace) } + /** Convert the runtime warnings to the context registry protocol + * representation + * + * @param payload the warnings payload + */ + private def toProtocolWarnings( + payload: Api.ExpressionUpdate.Payload.Value.Warnings + ): ContextRegistryProtocol.ExpressionUpdate.Payload.Value.Warnings = + ContextRegistryProtocol.ExpressionUpdate.Payload.Value + .Warnings(payload.count, payload.warning) + /** Convert the runtime profiling info to the context registry protocol * representation. * diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 62e36e9daa6..2be57de8460 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -174,11 +174,23 @@ object ContextRegistryProtocol { sealed trait Payload object Payload { - /** An information about computed expression. */ - case object Value extends Payload + /** An information about computed expression. + * + * @param warnings information about attached warnings. + */ + case class Value(warnings: Option[Value.Warnings]) extends Payload + object Value { + + /** Information about warnings associated with the value. + * + * @param count the number of attached warnings + * @param value textual representation of the attached warning + */ + case class Warnings(count: Int, value: Option[String]) + } case class Pending(message: Option[String], progress: Option[Double]) - extends Payload; + extends Payload /** Indicates that the expression was computed to an error. * @@ -191,10 +203,7 @@ object ContextRegistryProtocol { * @param message the error message * @param trace the stack trace */ - case class Panic( - message: String, - trace: Seq[UUID] - ) extends Payload + case class Panic(message: String, trace: Seq[UUID]) extends Payload private object CodecField { @@ -215,8 +224,10 @@ object ContextRegistryProtocol { implicit val encoder: Encoder[Payload] = Encoder.instance[Payload] { - case Payload.Value => - Json.obj(CodecField.Type -> PayloadType.Value.asJson) + case m: Payload.Value => + Encoder[Payload.Value] + .apply(m) + .deepMerge(Json.obj(CodecField.Type -> PayloadType.Value.asJson)) case m: Payload.DataflowError => Encoder[Payload.DataflowError] @@ -244,7 +255,7 @@ object ContextRegistryProtocol { Decoder.instance { cursor => cursor.downField(CodecField.Type).as[String].flatMap { case PayloadType.Value => - Right(Payload.Value) + Decoder[Payload.Value].tryDecode(cursor) case PayloadType.DataflowError => Decoder[Payload.DataflowError].tryDecode(cursor) diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala index 414fc89b758..e9eab24ed4b 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/runtime/ContextEventsListenerSpec.scala @@ -104,7 +104,7 @@ class ContextEventsListenerSpec Some(suggestionIds(1).get), Vector(), false, - ContextRegistryProtocol.ExpressionUpdate.Payload.Value + ContextRegistryProtocol.ExpressionUpdate.Payload.Value(None) ) ) ) @@ -244,7 +244,7 @@ class ContextEventsListenerSpec None, Vector(), false, - ContextRegistryProtocol.ExpressionUpdate.Payload.Value + ContextRegistryProtocol.ExpressionUpdate.Payload.Value(None) ), ContextRegistryProtocol.ExpressionUpdate( Suggestions.local.externalId.get, @@ -252,7 +252,7 @@ class ContextEventsListenerSpec None, Vector(), false, - ContextRegistryProtocol.ExpressionUpdate.Payload.Value + ContextRegistryProtocol.ExpressionUpdate.Payload.Value(None) ) ) ) diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 0466cefe317..78f08201476 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -385,10 +385,22 @@ object Runtime { sealed trait Payload object Payload { - /** An empty payload. Indicates that the expression was computed to a - * value. + /** Indicates that the expression was computed to a value. + * + * @param warnings information about attached warnings. */ - case class Value() extends Payload + case class Value(warnings: Option[Value.Warnings] = None) + extends Payload + + object Value { + + /** Information about warnings associated with the value. + * + * @param count the number of attached warnings. + * @param warning textual representation of the attached warning. + */ + case class Warnings(count: Int, warning: Option[String]) + } /** TBD */ 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 75dba75626e..c9d023f7721 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 @@ -219,7 +219,7 @@ public class IncrementalUpdatesTest { assertSameElements(context.receiveNIgnorePendingExpressionUpdates(4, 10, emptySet()), Response(requestId, new Runtime$Api$PushContextResponse(contextId)), TestMessages.update(contextId, mainFoo, exprType, new Runtime$Api$MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "foo")), - TestMessages.update(contextId, mainRes, ConstantsGen.NOTHING, false), + TestMessages.update(contextId, mainRes, ConstantsGen.NOTHING), context.executionComplete(contextId) ); assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut()); @@ -235,8 +235,8 @@ public class IncrementalUpdatesTest { ); assertSameElements(context.receiveNIgnorePendingExpressionUpdates(4, 10, emptySet()), Response(requestId, new Runtime$Api$PushContextResponse(contextId)), - TestMessages.update(contextId, fooX, exprType, false), - TestMessages.update(contextId, fooRes, exprType, false), + TestMessages.update(contextId, fooX, exprType), + TestMessages.update(contextId, fooRes, exprType), context.executionComplete(contextId) ); assertEquals(List.newBuilder().addOne(originalOutput), context.consumeOut()); 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 c3c250abe3b..a2237d874f6 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 @@ -3832,6 +3832,103 @@ class RuntimeServerTest val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + val idX1 = metadata.addItem(47, 14) + val idX2 = metadata.addItem(71, 32) + val idX3 = metadata.addItem(113, 32) + + val code = + """from Standard.Base import all + | + |main = + | x1 = attach "x" "y" + | x2 = attach "x" (My_Warning.Value 42) + | x3 = attach "x" (My_Warning.Value x2) + | [x1, x2, x3] + | + |type My_Warning + | Value reason + | + |attach value warning = + | Warning.attach_with_stacktrace value warning 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.SetModuleSourcesNotification(mainFile, contents)) + ) + context.receiveNone shouldEqual None + + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 5 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages + .update( + contextId, + idX1, + ConstantsGen.TEXT, + methodPointer = + Some(Api.MethodPointer(moduleName, moduleName, "attach")), + payload = Api.ExpressionUpdate.Payload.Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + ) + ), + TestMessages + .update( + contextId, + idX2, + ConstantsGen.TEXT, + methodPointer = + Some(Api.MethodPointer(moduleName, moduleName, "attach")), + payload = Api.ExpressionUpdate.Payload.Value( + Some( + Api.ExpressionUpdate.Payload.Value + .Warnings(1, Some("(My_Warning.Value 42)")) + ) + ) + ), + TestMessages + .update( + contextId, + idX3, + ConstantsGen.TEXT, + methodPointer = + Some(Api.MethodPointer(moduleName, moduleName, "attach")), + payload = Api.ExpressionUpdate.Payload + .Value(Some(Api.ExpressionUpdate.Payload.Value.Warnings(2, None))) + ), + context.executionComplete(contextId) + ) + } + + it should "send updates for expressions 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) @@ -3877,9 +3974,33 @@ class RuntimeServerTest 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), + TestMessages + .update( + contextId, + idX, + ConstantsGen.INTEGER, + payload = Api.ExpressionUpdate.Payload.Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + ) + ), + TestMessages + .update( + contextId, + idY, + ConstantsGen.INTEGER, + payload = Api.ExpressionUpdate.Payload.Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + ) + ), + TestMessages + .update( + contextId, + idRes, + ConstantsGen.NOTHING, + payload = Api.ExpressionUpdate.Payload.Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + ) + ), context.executionComplete(contextId) ) context.consumeOut shouldEqual List("43") diff --git a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index cf59fbb2205..1bbfd0b701c 100644 --- a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala +++ b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -2847,7 +2847,14 @@ class RuntimeVisualizationsTest 3 ) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), + TestMessages.update( + contextId, + idMain, + ConstantsGen.INTEGER, + payload = Api.ExpressionUpdate.Payload.Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'y'"))) + ) + ), context.executionComplete(contextId) ) @@ -3035,13 +3042,25 @@ class RuntimeVisualizationsTest contextId, idX, ConstantsGen.INTEGER, - Api.MethodPointer( - warningModuleName.toString, - warningTypeName.toString + ".type", - "attach" + methodPointer = Some( + Api.MethodPointer( + warningModuleName.toString, + warningTypeName.toString + ".type", + "attach" + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'x'"))) + ) + ), + TestMessages.update( + contextId, + idRes, + s"$moduleName.Newtype", + payload = Api.ExpressionUpdate.Payload.Value( + Some(Api.ExpressionUpdate.Payload.Value.Warnings(1, Some("'x'"))) ) ), - TestMessages.update(contextId, idRes, s"$moduleName.Newtype"), context.executionComplete(contextId) ) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java index fc41a8e91f1..ea1536cd23e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/WithWarnings.java @@ -67,8 +67,9 @@ public final class WithWarnings implements TruffleObject { return allWarnings; } - public ArrayRope collectWarnings() { - return warnings; + /** @return the number of warnings. */ + public int getWarningsCount() { + return warnings.size(); } public ArrayRope getReassignedWarnings(Node location) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java b/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java index c25bf563792..dd50f28e8a9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java @@ -185,7 +185,7 @@ public class ExecutionService { * defined in. * * @param moduleName the module where the method is defined. - * @param consName the name of the constructor the method is defined on. + * @param typeName the name of the type the method is defined on. * @param methodName the method name. * @param cache the precomputed expression values. * @param methodCallsCache the storage tracking the executed method calls. @@ -245,6 +245,21 @@ public class ExecutionService { } } + /** + * Converts the provided object to a readable representation. + * + * @param receiver the object to convert. + * @return the textual representation of the object. + */ + public String toDisplayString(Object receiver) { + try { + return interopLibrary.asString(interopLibrary.toDisplayString(receiver)); + } catch (UnsupportedMessageException ignored) { + CompilerDirectives.shouldNotReachHere("Message support already checked."); + } + return null; + } + /** * Calls a function with the given argument. * 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 10a6d10ae3c..42ebc7c90ae 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 @@ -17,7 +17,11 @@ import org.enso.interpreter.instrument._ import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall import org.enso.interpreter.runtime.`type`.Types import org.enso.interpreter.runtime.control.ThreadInterruptedException -import org.enso.interpreter.runtime.error.{DataflowError, PanicSentinel} +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 @@ -282,7 +286,7 @@ object ProgramExecutionSupport { * @param ctx the runtime context * @return the API message describing the error */ - def getExecutionOutcome( + private def getExecutionOutcome( t: Throwable )(implicit ctx: RuntimeContext): Option[Api.ExecutionResult] = getDiagnosticOutcome.orElse(getFailureOutcome).lift(t) @@ -310,7 +314,7 @@ object ProgramExecutionSupport { } /** Extract information about the failure from the provided exception. */ - def getFailureOutcome(implicit + private def getFailureOutcome(implicit ctx: RuntimeContext ): PartialFunction[Throwable, Api.ExecutionResult.Failure] = { case ex: TypeNotFoundException => @@ -373,6 +377,21 @@ object ProgramExecutionSupport { Api.ExpressionUpdate.Payload.DataflowError( ErrorResolver.getStackTrace(error).flatMap(_.expressionId) ) + case withWarnings: WithWarnings => + val warningsCount = withWarnings.getWarningsCount + val warning = + if (warningsCount == 1) { + val warnings = withWarnings.getWarningsArray(null) + Option(ctx.executionService.toDisplayString(warnings(0).getValue)) + } else { + None + } + Api.ExpressionUpdate.Payload.Value( + Some( + Api.ExpressionUpdate.Payload.Value + .Warnings(warningsCount, warning) + ) + ) case _ => Api.ExpressionUpdate.Payload.Value() } diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala index d2e479dd056..77396af5d88 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/TestMessages.scala @@ -39,14 +39,12 @@ object TestMessages { * @param contextId an identifier of the context * @param expressionId an identifier of the expression * @param expressionType a type of the expression - * @param fromCache whether or not the value for this expression came * @return the expression update response */ def update( contextId: UUID, expressionId: UUID, - expressionType: String, - fromCache: Boolean = false + expressionType: String ): Api.Response = Api.Response( Api.ExpressionUpdates( @@ -57,13 +55,47 @@ object TestMessages { Some(expressionType), None, Vector(Api.ProfilingInfo.ExecutionTime(0)), - fromCache, + false, Api.ExpressionUpdate.Payload.Value() ) ) ) ) + /** Create an update response. + * + * @param contextId an identifier of the context + * @param expressionId an identifier of the expression + * @param expressionType a type of the expression + * @param fromCache whether or not the value for this expression came + * @param methodPointer method pointer + * @param payload the update payload + * @return the expression update response + */ + def update( + contextId: UUID, + expressionId: UUID, + expressionType: String, + fromCache: Boolean = false, + methodPointer: Option[Api.MethodPointer] = None, + payload: Api.ExpressionUpdate.Payload = Api.ExpressionUpdate.Payload.Value() + ): Api.Response = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + expressionId, + Some(expressionType), + methodPointer, + Vector(Api.ProfilingInfo.ExecutionTime(0)), + fromCache, + payload + ) + ) + ) + ) + /** Create an update response. * * @param contextId an identifier of the context