From 108b2308d45afa739211e738d520694c01888032 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 19 Sep 2024 11:45:52 +0100 Subject: [PATCH 01/24] DRAFT: execution config --- .../polyglot/debugger/IdExecutionService.java | 11 + .../org/enso/polyglot/runtime/Runtime.scala | 14 +- .../instrument/ExecutionConfig.java | 56 ++ .../service/ExecutionCallbacks.java | 32 +- .../interpreter/service/ExecutionService.java | 12 + .../command/RecomputeContextCmd.scala | 12 +- .../instrument/job/ExecuteJob.scala | 28 +- .../job/ProgramExecutionSupport.scala | 20 +- .../id/execution/IdExecutionInstrument.java | 17 + .../test/instrument/RuntimeServerTest.scala | 533 +++++++++++++++++- .../RuntimeSuggestionUpdatesTest.scala | 5 +- .../RuntimeVisualizationsTest.scala | 20 +- .../expression/builtin/meta/Instrumentor.java | 7 + .../runtime/state/ExecutionEnvironment.java | 2 +- .../lib/Standard/Base/0.0.0-dev/src/Main.enso | 1 + .../Standard/Base/0.0.0-dev/src/Runtime.enso | 4 +- 16 files changed, 728 insertions(+), 46 deletions(-) create mode 100644 engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java index 10db1f0a2c5c..bf9dff12ce72 100644 --- a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java @@ -4,6 +4,8 @@ import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.nodes.RootNode; + import java.util.UUID; public interface IdExecutionService { @@ -21,6 +23,11 @@ public abstract class Info { */ public abstract Object getResult(); + /** + * @return the root of this node. + */ + public abstract RootNode getRootNode(); + /** * @return {@code true} when the result is panic, {@code false} otherwise. */ @@ -69,6 +76,10 @@ public interface Callbacks { * the execution and return the value as a result. */ Object onFunctionReturn(Info info); + + void setExecutionEnvironment(Info info); + + void resetExecutionEnvironment(Info info); } /** 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 46e235759f6d..549d50c8d40e 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 @@ -691,6 +691,16 @@ object Runtime { } } + /** The configuration of how to execute the expression. + * + * @param expressionId the expression identifier + * @param executionEnvironment the execution environment for the expression + */ + sealed case class ExpressionConfig( + expressionId: ExpressionId, + executionEnvironment: Option[ExecutionEnvironment] + ) + /** The notification about the execution status. * * @param contextId the context's id @@ -914,12 +924,14 @@ object Runtime { * @param expressions the selector specifying which expressions should be * recomputed. * @param executionEnvironment the environment used for execution + * @param expressionConfigs execution configurations for selected expressions */ @named("recomputeContextRequest") final case class RecomputeContextRequest( contextId: ContextId, expressions: Option[InvalidatedExpressions], - executionEnvironment: Option[ExecutionEnvironment] + executionEnvironment: Option[ExecutionEnvironment], + expressionConfigs: Seq[ExpressionConfig] ) extends ApiRequest /** A response sent from the server upon handling the diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java new file mode 100644 index 000000000000..0719b04181d1 --- /dev/null +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java @@ -0,0 +1,56 @@ +package org.enso.interpreter.instrument; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.enso.interpreter.runtime.state.ExecutionEnvironment; +import org.enso.polyglot.runtime.Runtime$Api$ExecutionEnvironment; +import org.enso.polyglot.runtime.Runtime$Api$ExpressionConfig; +import scala.Option; +import scala.collection.immutable.Seq; + +/** + * The program execution config. + * + * @param executionEnvironment the global execution environment of the program + * @param expressionConfigs execution configs for each expression + */ +public record ExecutionConfig( + ExecutionEnvironment executionEnvironment, Map expressionConfigs) { + + public static ExecutionConfig empty() { + return new ExecutionConfig(null, Collections.emptyMap()); + } + + @SuppressWarnings("unchecked") + public static ExecutionConfig create( + Object executionEnvironmentOption1, Object expressionConfigs1) { + Map expressionConfigsBuilder = new HashMap<>(); + Option executionEnvironmentOption = + (Option) executionEnvironmentOption1; + Seq expressionConfigs = + (Seq) expressionConfigs1; + + expressionConfigs.foreach( + expressionConfig -> { + expressionConfig + .executionEnvironment() + .foreach( + executionEnvironment -> { + expressionConfigsBuilder.put( + expressionConfig.expressionId(), + ExecutionEnvironment.forName(executionEnvironment.name())); + return null; + }); + return null; + }); + + ExecutionEnvironment executionEnvironment = + executionEnvironmentOption + .map(env -> ExecutionEnvironment.forName(env.name())) + .getOrElse(() -> null); + + return new ExecutionConfig(executionEnvironment, expressionConfigsBuilder); + } +} diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index 27972daa52b6..a4c048a7715f 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -13,9 +13,11 @@ import org.enso.interpreter.instrument.profiling.ExecutionTime; import org.enso.interpreter.instrument.profiling.ProfilingInfo; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; +import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; +import org.enso.interpreter.runtime.state.ExecutionEnvironment; import org.enso.interpreter.runtime.type.Constants; import org.enso.interpreter.service.ExecutionService.ExpressionCall; import org.enso.interpreter.service.ExecutionService.ExpressionValue; @@ -31,21 +33,27 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { private final MethodCallsCache methodCallsCache; private final UpdatesSynchronizationState syncState; private final Map calls = new HashMap<>(); + private final Map expressionConfigs; private final Consumer onCachedCallback; private final Consumer onComputedCallback; private final Consumer functionCallCallback; private final Consumer onExecutedVisualizationCallback; + private ExecutionEnvironment originalExecutionEnvironment = null; + /** * Creates callbacks instance. * + * @param visualizationHolder the holder of all visualizations attached to an execution context. + * @param nextExecutionItem the next item scheduled for execution. * @param cache the precomputed expression values. * @param methodCallsCache the storage tracking the executed updateCachedResult calls. * @param syncState the synchronization state of runtime updates. - * @param nextExecutionItem the next item scheduled for execution. - * @param functionCallCallback the consumer of function call events. + * @param expressionConfigs the execution config for each expression. * @param onComputedCallback the consumer of the computed value events. * @param onCachedCallback the consumer of the cached value events. + * @param functionCallCallback the consumer of function call events. + * @param onExecutedVisualizationCallback the consumer of an executed visualization result. */ ExecutionCallbacks( VisualizationHolder visualizationHolder, @@ -53,6 +61,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { RuntimeCache cache, MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, + Map expressionConfigs, Consumer onCachedCallback, Consumer onComputedCallback, Consumer functionCallCallback, @@ -62,6 +71,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { this.cache = cache; this.methodCallsCache = methodCallsCache; this.syncState = syncState; + this.expressionConfigs = expressionConfigs; this.onCachedCallback = onCachedCallback; this.onComputedCallback = onComputedCallback; this.functionCallCallback = functionCallCallback; @@ -141,6 +151,24 @@ public Object onFunctionReturn(IdExecutionService.Info info) { return null; } + @Override + public void setExecutionEnvironment(IdExecutionService.Info info) { + ExecutionEnvironment nodeEnvironment = expressionConfigs.get(info.getId()); + if (nodeEnvironment != null && originalExecutionEnvironment == null) { + EnsoContext context = EnsoContext.get(info.getRootNode()); + originalExecutionEnvironment = context.getExecutionEnvironment(); + context.setExecutionEnvironment(nodeEnvironment); + } + } + + @Override + public void resetExecutionEnvironment(IdExecutionService.Info info) { + if (originalExecutionEnvironment != null) { + EnsoContext.get(info.getRootNode()).setExecutionEnvironment(originalExecutionEnvironment); + originalExecutionEnvironment = null; + } + } + @CompilerDirectives.TruffleBoundary private void callOnComputedCallback(ExpressionValue expressionValue) { onComputedCallback.accept(expressionValue); diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java index c24bde869267..6b32a0d65087 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java @@ -20,6 +20,8 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.Collections; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -48,6 +50,7 @@ import org.enso.interpreter.runtime.instrument.NotificationHandler; import org.enso.interpreter.runtime.instrument.Timer; import org.enso.interpreter.runtime.scope.ModuleScope; +import org.enso.interpreter.runtime.state.ExecutionEnvironment; import org.enso.interpreter.runtime.state.State; import org.enso.interpreter.service.error.FailedToApplyEditsException; import org.enso.interpreter.service.error.MethodNotFoundException; @@ -157,9 +160,11 @@ public void initializeLanguageServerConnection(Endpoint endpoint) { * @param methodCallsCache the storage tracking the executed method calls. * @param syncState the synchronization state of runtime updates. * @param nextExecutionItem the next item scheduled for execution. + * @param expressionConfigs the execution config for each expression. * @param funCallCallback the consumer for function call events. * @param onComputedCallback the consumer of the computed value events. * @param onCachedCallback the consumer of the cached value events. + * @param onExecutedVisualizationCallback the consumer of an executed visualization result. */ public void execute( VisualizationHolder visualizationHolder, @@ -169,6 +174,7 @@ public void execute( MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, + Map expressionConfigs, Consumer funCallCallback, Consumer onComputedCallback, Consumer onCachedCallback, @@ -188,6 +194,7 @@ public void execute( cache, methodCallsCache, syncState, + expressionConfigs, onCachedCallback, onComputedCallback, funCallCallback, @@ -220,9 +227,11 @@ public void execute( * @param methodCallsCache the storage tracking the executed method calls. * @param syncState the synchronization state of runtime updates. * @param nextExecutionItem the next item scheduled for execution. + * @param expressionConfigs the execution config for each expression. * @param funCallCallback the consumer for function call events. * @param onComputedCallback the consumer of the computed value events. * @param onCachedCallback the consumer of the cached value events. + * @param onExecutedVisualizationCallback the consumer of an executed visualization result. */ public void execute( String moduleName, @@ -233,6 +242,7 @@ public void execute( MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, + Map expressionConfigs, Consumer funCallCallback, Consumer onComputedCallback, Consumer onCachedCallback, @@ -255,6 +265,7 @@ public void execute( methodCallsCache, syncState, nextExecutionItem, + expressionConfigs, funCallCallback, onComputedCallback, onCachedCallback, @@ -368,6 +379,7 @@ public Object callFunctionWithInstrument( cache, methodCallsCache, syncState, + Collections.emptyMap(), onCachedCallback, onComputedCallback, funCallCallback, diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala index b54d35bcfb90..b2f214d3cc6f 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala @@ -1,6 +1,10 @@ package org.enso.interpreter.instrument.command -import org.enso.interpreter.instrument.{CacheInvalidation, InstrumentFrame} +import org.enso.interpreter.instrument.{ + CacheInvalidation, + ExecutionConfig, + InstrumentFrame +} import org.enso.interpreter.instrument.execution.RuntimeContext import org.enso.interpreter.instrument.job.{EnsureCompiledJob, ExecuteJob} import org.enso.polyglot.runtime.Runtime.Api @@ -109,13 +113,17 @@ class RecomputeContextCmd( ): Future[Unit] = { if (isStackNonEmpty) { val stack = ctx.contextManager.getStack(request.contextId) + val executionConfig = ExecutionConfig.create( + request.executionEnvironment, + request.expressionConfigs + ) for { _ <- ctx.jobProcessor.run(EnsureCompiledJob(stack)) _ <- ctx.jobProcessor.run( new ExecuteJob( request.contextId, stack.toList, - request.executionEnvironment + executionConfig ) ) } yield () diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala index 88db0df63c85..baf665cca0d9 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala @@ -1,9 +1,8 @@ package org.enso.interpreter.instrument.job import java.util.UUID -import org.enso.interpreter.instrument.InstrumentFrame +import org.enso.interpreter.instrument.{ExecutionConfig, InstrumentFrame} import org.enso.interpreter.instrument.execution.{Executable, RuntimeContext} -import org.enso.interpreter.runtime.state.ExecutionEnvironment import org.enso.polyglot.runtime.Runtime.Api import java.util.logging.Level @@ -12,12 +11,12 @@ import java.util.logging.Level * * @param contextId an identifier of a context to execute * @param stack a call stack to execute - * @param executionEnvironment the execution environment to use + * @param executionConfig the program execution configuration */ class ExecuteJob( contextId: UUID, stack: List[InstrumentFrame], - val executionEnvironment: Option[Api.ExecutionEnvironment], + val executionConfig: ExecutionConfig, val visualizationTriggered: Boolean = false ) extends Job[Unit]( List(contextId), @@ -68,20 +67,17 @@ class ExecuteJob( ctx.locking.withReadCompilationLock( this.getClass, () => { - val context = ctx.executionService.getContext - val originalExecutionEnvironment = - executionEnvironment.map(_ => context.getExecutionEnvironment) - executionEnvironment.foreach(env => + val context = ctx.executionService.getContext + val originalExecutionEnvironment = context.getExecutionEnvironment + if (executionConfig.executionEnvironment ne null) { context.setExecutionEnvironment( - ExecutionEnvironment.forName(env.name) + executionConfig.executionEnvironment ) - ) + } val outcome = - try ProgramExecutionSupport.runProgram(contextId, stack) + try ProgramExecutionSupport.runProgram(contextId, stack, executionConfig) finally { - originalExecutionEnvironment.foreach( - context.setExecutionEnvironment - ) + context.setExecutionEnvironment(originalExecutionEnvironment) } outcome match { case Some(diagnostic: Api.ExecutionResult.Diagnostic) => @@ -134,7 +130,7 @@ object ExecuteJob { new ExecuteJob( executable.contextId, executable.stack.toList, - None, + ExecutionConfig.empty(), visualizationTriggered ) @@ -145,5 +141,5 @@ object ExecuteJob { * @return new execute job */ def apply(contextId: UUID, stack: List[InstrumentFrame]): ExecuteJob = - new ExecuteJob(contextId, stack, None) + new ExecuteJob(contextId, stack, ExecutionConfig.empty()) } diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index a655ee55f143..4a13c49f31f7 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -2,6 +2,7 @@ package org.enso.interpreter.instrument.job import com.oracle.truffle.api.exception.AbstractTruffleException import org.enso.interpreter.instrument.{ + ExecutionConfig, InstrumentFrame, MethodCallsCache, RuntimeCache, @@ -49,6 +50,7 @@ import java.io.File import java.util.UUID import java.util.function.Consumer import java.util.logging.Level + import scala.jdk.OptionConverters.RichOptional import scala.util.Try @@ -57,15 +59,17 @@ import scala.util.Try */ object ProgramExecutionSupport { - /** Runs an Enso program. + /** Runs the program. * * @param contextId an identifier of an execution context + * @param executionConfig the program execution config * @param executionFrame an execution frame * @param callStack a call stack */ @scala.annotation.tailrec final private def executeProgram( contextId: Api.ContextId, + executionConfig: ExecutionConfig, executionFrame: ExecutionFrame, callStack: List[LocalCallFrame] )(implicit ctx: RuntimeContext): Unit = { @@ -142,6 +146,7 @@ object ProgramExecutionSupport { methodCallsCache, syncState, callStack.headOption.map(_.expressionId).orNull, + executionConfig.expressionConfigs, callablesCallback, onComputedValueCallback, onCachedValueCallback, @@ -183,6 +188,7 @@ object ProgramExecutionSupport { methodCallsCache, syncState, callStack.headOption.map(_.expressionId).orNull, + executionConfig.expressionConfigs, callablesCallback, onComputedValueCallback, onCachedValueCallback, @@ -219,23 +225,25 @@ object ProgramExecutionSupport { item.cache, item.syncState ) - executeProgram(contextId, executionFrame, tail) + executeProgram(contextId, executionConfig, executionFrame, tail) case None => () } } } - /** Runs an Enso program. + /** Runs the program. * * @param contextId an identifier of an execution context * @param stack a call stack + * @param executionConfig the program execution config * @param ctx a runtime context * @return an execution result */ final def runProgram( contextId: Api.ContextId, - stack: List[InstrumentFrame] + stack: List[InstrumentFrame], + executionConfig: ExecutionConfig )(implicit ctx: RuntimeContext): Option[Api.ExecutionResult] = { val logger = ctx.executionService.getLogger logger.log(Level.FINEST, s"Run program $contextId") @@ -271,7 +279,9 @@ object ProgramExecutionSupport { Some(Api.ExecutionResult.Failure("Execution stack is empty.", None)) ) _ <- - Try(executeProgram(contextId, stackItem, localCalls)).toEither.left + Try( + executeProgram(contextId, executionConfig, stackItem, localCalls) + ).toEither.left .map(onExecutionError(stackItem.item, _)) } yield () logger.log(Level.FINEST, s"Execution finished: $executionResult") diff --git a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java index 53537b45e231..3812f60ba5b6 100644 --- a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java @@ -148,6 +148,11 @@ public Object getResult() { return result; } + @Override + public EnsoRootNode getRootNode() { + return ensoRootNode; + } + @Override public boolean isPanic() { return result instanceof AbstractTruffleException && !(result instanceof DataflowError); @@ -209,6 +214,8 @@ public void onEnter(VirtualFrame frame) { if (result != null) { throw context.createUnwind(result); } + callbacks.setExecutionEnvironment(info); + nanoTimeElapsed = timer.getTime(); } @@ -250,10 +257,20 @@ public void onReturnValue(VirtualFrame frame, Object result) { frame == null ? null : frame.materialize(), node); callbacks.updateCachedResult(info); + callbacks.resetExecutionEnvironment(info); if (info.isPanic()) { throw context.createUnwind(result); } + } else if (node instanceof ExpressionNode expressionNode) { + Info info = + new NodeInfo( + expressionNode.getId(), + result, + nanoTimeElapsed, + frame == null ? null : frame.materialize(), + node); + callbacks.resetExecutionEnvironment(info); } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 84db77279857..33da76dcc909 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -5051,7 +5051,10 @@ class RuntimeServerTest // recompute context.send( - Api.Request(requestId, Api.RecomputeContextRequest(contextId, None, None)) + Api.Request( + requestId, + Api.RecomputeContextRequest(contextId, None, None, Seq()) + ) ) context.receiveN(2) should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), @@ -5104,7 +5107,8 @@ class RuntimeServerTest Api.RecomputeContextRequest( contextId, Some(Api.InvalidatedExpressions.All()), - None + None, + Seq() ) ) ) @@ -5172,7 +5176,8 @@ class RuntimeServerTest Some( Api.InvalidatedExpressions.Expressions(Vector(context.Main.idMainZ)) ), - None + None, + Seq() ) ) ) @@ -5232,7 +5237,8 @@ class RuntimeServerTest Api.RecomputeContextRequest( contextId, Some(Api.InvalidatedExpressions.All()), - Some(Api.ExecutionEnvironment.Live()) + Some(Api.ExecutionEnvironment.Live()), + Seq() ) ) ) @@ -5256,6 +5262,512 @@ class RuntimeServerTest .name } + it should "recompute expression with expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 17, "aa") + val idIn = metadata.addItem(131, 16, "ab") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = Output.is_enabled + | in = Input.is_enabled + | IO.println out + | IO.println in + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("False", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + Some(Api.InvalidatedExpressions.All()), + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("True", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + Some(Api.InvalidatedExpressions.All()), + None, + Seq( + Api.ExpressionConfig(idIn, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("False", "True") + } + + it should "recompute recursive method call with expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 5, "aa") + val idIn = metadata.addItem(119, 16, "ab") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = fac 3 + | in = Input.is_enabled + | IO.println out + | IO.println in + | + |fac n=1 acc=1 = + | if Output.is_enabled.not then Nothing else + | if n <= 0 then acc else + | IO.println n + | @Tail_Call fac n-1 acc*n + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + moduleName, + moduleName, + "fac" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("Nothing", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + Some(Api.InvalidatedExpressions.All()), + //Some(Api.ExecutionEnvironment.Live()), + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.INTEGER, + fromCache = false, + typeChanged = true, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + moduleName, + moduleName, + "fac" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("3", "2", "1", "6", "False") + } + + it should "recompute method call returning Panic with expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 34, "aa") + val idIn = metadata.addItem(148, 16, "ab") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = Panic.catch Any (foo 42) _.payload + | in = Input.is_enabled + | IO.println out + | IO.println in + | + |foo n=Nothing = + | Output.if_enabled (Panic.throw n) + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + "Standard.Base.Errors.Common.Forbidden_Operation", + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Panic", + "Standard.Base.Panic.Panic", + "catch" + ) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List( + "(Forbidden_Operation.Error 'The Output context is disabled.')", + "False" + ) + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + Some(Api.InvalidatedExpressions.All()), + //Some(Api.ExecutionEnvironment.Live()), + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.INTEGER, + fromCache = false, + typeChanged = true, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Panic", + "Standard.Base.Panic.Panic", + "catch" + ) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("42", "False") + } + it should "return error when module not found" in { val contents = context.Main.code val mainFile = context.writeMain(context.Main.code) @@ -6667,7 +7179,10 @@ class RuntimeServerTest // recompute context.send( - Api.Request(requestId, Api.RecomputeContextRequest(contextId, None, None)) + Api.Request( + requestId, + Api.RecomputeContextRequest(contextId, None, None, Seq()) + ) ) context.receiveN(2) should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), @@ -6757,7 +7272,10 @@ class RuntimeServerTest // recompute existing stack context.send( - Api.Request(requestId, Api.RecomputeContextRequest(contextId, None, None)) + Api.Request( + requestId, + Api.RecomputeContextRequest(contextId, None, None, Seq()) + ) ) context.receiveN(2) should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), @@ -6771,7 +7289,8 @@ class RuntimeServerTest Api.RecomputeContextRequest( contextId, Some(Api.InvalidatedExpressions.All()), - None + None, + Seq() ) ) ) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala index 3f3b7f0745a3..e111b3488e34 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala @@ -1414,7 +1414,10 @@ class RuntimeSuggestionUpdatesTest // recompute context.send( - Api.Request(requestId, Api.RecomputeContextRequest(contextId, None, None)) + Api.Request( + requestId, + Api.RecomputeContextRequest(contextId, None, None, Seq()) + ) ) val updates2 = context.receiveNIgnoreExpressionUpdates(3) updates2.length shouldEqual 3 diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index 76d55ef788cb..68f1775e0fb1 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -423,7 +423,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None) + Api.RecomputeContextRequest(contextId, None, None, Seq()) ) ) @@ -551,7 +551,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None) + Api.RecomputeContextRequest(contextId, None, None, Seq()) ) ) context.receiveNIgnoreExpressionUpdates(2) should contain allOf ( @@ -570,7 +570,8 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { Vector(context.Main.idMainX) ) ), - None + None, + Seq() ) ) ) @@ -1572,7 +1573,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None) + Api.RecomputeContextRequest(contextId, None, None, Seq()) ) ) context.receiveNIgnoreExpressionUpdates( @@ -1593,7 +1594,8 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { Vector(context.Main.idMainX) ) ), - None + None, + Seq() ) ) ) @@ -2900,7 +2902,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None) + Api.RecomputeContextRequest(contextId, None, None, Seq()) ) ) @@ -3074,7 +3076,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None) + Api.RecomputeContextRequest(contextId, None, None, Seq()) ) ) @@ -3213,7 +3215,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None) + Api.RecomputeContextRequest(contextId, None, None, Seq()) ) ) @@ -3397,7 +3399,7 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers { context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None) + Api.RecomputeContextRequest(contextId, None, None, Seq()) ) ) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java index da77904cb888..4f6be3c102af 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java @@ -5,6 +5,7 @@ import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; +import java.util.UUID; import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; import org.enso.interpreter.runtime.EnsoContext; @@ -120,4 +121,10 @@ public Object onFunctionReturn(IdExecutionService.Info info) { } return null; } + + @Override + public void setExecutionEnvironment(IdExecutionService.Info info) {} + + @Override + public void resetExecutionEnvironment(IdExecutionService.Info info) {} } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/state/ExecutionEnvironment.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/state/ExecutionEnvironment.java index ab67f39bad3b..c66715c636fa 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/state/ExecutionEnvironment.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/state/ExecutionEnvironment.java @@ -105,7 +105,7 @@ public static ExecutionEnvironment forName(String name) { case DESIGN_ENVIRONMENT_NAME: return DESIGN; default: - throw new RuntimeException("Unsupported Execution Environment `" + name + "`"); + throw new IllegalArgumentException("Unsupported Execution Environment `" + name + "`"); } } } 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 247d1defdcae..dd876471a1b1 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 @@ -13,6 +13,7 @@ export project.Data.Numbers.Number export project.Data.Numbers.Integer export project.Data.Vector.Vector export project.Function.Function +export project.Nothing.Nothing export project.Polyglot.Java export project.Polyglot.Polyglot export project.Runtime 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 index a2d634309536..132df0fe48d5 100644 --- 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 @@ -23,13 +23,13 @@ type Context Output -> "Output" if_enabled : Any -> Text -> Text -> Boolean -> Any ! Forbidden_Operation - if_enabled self ~action environment="design" disabled_message="The "+self.name+" context is disabled." panic=True = + if_enabled self ~action environment=Runtime.current_execution_environment disabled_message="The "+self.name+" context is disabled." panic=True = if self.is_enabled environment then action else error = Forbidden_Operation.Error disabled_message if panic then Panic.throw error else Error.throw error is_enabled : Text -> Boolean - is_enabled self environment="design" = + is_enabled self environment=Runtime.current_execution_environment = self.is_enabled_builtin environment ## PRIVATE From d79a9fd329b5afb156d224c8dd12b7d4728010a7 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Fri, 20 Sep 2024 17:52:08 +0100 Subject: [PATCH 02/24] DRAFT: thread local --- .../SetExecutionEnvironmentCommand.java | 19 +++++++++--- .../expression/builtin/meta/Instrumentor.java | 1 - .../builtin/runtime/ContextIsEnabledNode.java | 2 +- ...untimeCurrentExecutionEnvironmentNode.java | 2 +- .../enso/interpreter/runtime/EnsoContext.java | 29 ++++++++++++------- .../runtime/error/DataflowError.java | 2 +- 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java index 8c11bcbd5667..3429e8f241e0 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java @@ -56,10 +56,21 @@ private void setExecutionEnvironment( this.getClass(), () -> { Stack stack = ctx.contextManager().getStack(contextId); - ctx.executionService() - .getContext() - .setExecutionEnvironment( - ExecutionEnvironment.forName(executionEnvironment.name())); + ctx.state() + .executionHooks() + .add( + () -> + ctx.locking() + .withWriteCompilationLock( + this.getClass(), + () -> { + ctx.executionService() + .getContext() + .setExecutionEnvironment( + ExecutionEnvironment.forName( + executionEnvironment.name())); + return null; + })); CacheInvalidation.invalidateAll(stack); ctx.jobProcessor().run(ExecuteJob.apply(contextId, stack.toList())); reply(new Runtime$Api$SetExecutionEnvironmentResponse(contextId), ctx); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java index 4f6be3c102af..33a1fa677cf5 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java @@ -5,7 +5,6 @@ import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; -import java.util.UUID; import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; import org.enso.interpreter.runtime.EnsoContext; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java index b2e74dbf2d72..b8f19174fecc 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java @@ -17,7 +17,7 @@ public class ContextIsEnabledNode extends Node { Object execute(Atom self, Object environmentName) { String envName = expectStringNode.execute(environmentName); - ExecutionEnvironment currentEnv = EnsoContext.get(this).getExecutionEnvironment(); + ExecutionEnvironment currentEnv = EnsoContext.get(this).getThreadExecutionEnvironment(); if (!currentEnv.getName().equals(envName)) { Atom error = EnsoContext.get(this) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java index 7fd13a913790..e8d63bd8b76d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java @@ -12,6 +12,6 @@ autoRegister = false) public class RuntimeCurrentExecutionEnvironmentNode extends Node { Object execute() { - return Text.create(EnsoContext.get(this).getExecutionEnvironment().getName()); + return Text.create(EnsoContext.get(this).getThreadExecutionEnvironment().getName()); } } 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 dc4dc3f4a465..f09d88291cfe 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 @@ -107,7 +107,8 @@ public final class EnsoContext { private final AtomicLong clock = new AtomicLong(); private final Shape rootStateShape = Shape.newBuilder().layout(State.Container.class).build(); - private ExecutionEnvironment executionEnvironment; + private ExecutionEnvironment contextExecutionEnvironment; + private final ThreadLocal threadExecutionEnvironment; private final int warningsLimit; @@ -143,7 +144,8 @@ public EnsoContext( getOption(RuntimeOptions.DISABLE_IR_CACHES_KEY) || isParallelismEnabled; this.isPrivateCheckDisabled = getOption(RuntimeOptions.DISABLE_PRIVATE_CHECK_KEY); this.isStaticTypeAnalysisEnabled = getOption(RuntimeOptions.ENABLE_STATIC_ANALYSIS_KEY); - this.executionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); + this.contextExecutionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); + this.threadExecutionEnvironment = ThreadLocal.withInitial(() -> contextExecutionEnvironment); this.assertionsEnabled = shouldAssertionsBeEnabled(); this.shouldWaitForPendingSerializationJobs = getOption(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS_KEY); @@ -868,12 +870,17 @@ public long nextSequenceId() { } public ExecutionEnvironment getExecutionEnvironment() { - return executionEnvironment; + return contextExecutionEnvironment; + } + + public ExecutionEnvironment getThreadExecutionEnvironment() { + return threadExecutionEnvironment.get(); } /** Set the runtime execution environment of this context. */ public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { - this.executionEnvironment = executionEnvironment; + this.contextExecutionEnvironment = executionEnvironment; + this.threadExecutionEnvironment.remove(); } /** @@ -881,11 +888,12 @@ public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { * * @param context the execution context * @param environmentName the execution environment name + * @return the execution environment version before modification */ public ExecutionEnvironment enableExecutionEnvironment(Atom context, String environmentName) { - ExecutionEnvironment original = executionEnvironment; - if (executionEnvironment.getName().equals(environmentName)) { - executionEnvironment = executionEnvironment.withContextEnabled(context); + ExecutionEnvironment original = contextExecutionEnvironment; + if (original.getName().equals(environmentName)) { + setExecutionEnvironment(original.withContextEnabled(context)); } return original; } @@ -895,11 +903,12 @@ public ExecutionEnvironment enableExecutionEnvironment(Atom context, String envi * * @param context the execution context * @param environmentName the execution environment name + * @return the execution environment version before modification */ public ExecutionEnvironment disableExecutionEnvironment(Atom context, String environmentName) { - ExecutionEnvironment original = executionEnvironment; - if (executionEnvironment.getName().equals(environmentName)) { - executionEnvironment = executionEnvironment.withContextDisabled(context); + ExecutionEnvironment original = contextExecutionEnvironment; + if (original.getName().equals(environmentName)) { + setExecutionEnvironment(original.withContextDisabled(context)); } return original; } 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 1d65eac65161..a57ddff19896 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 @@ -56,7 +56,7 @@ public static DataflowError withDefaultTrace(State state, Object payload, Node l boolean attachFullStackTrace = state == null || EnsoContext.get(location) - .getExecutionEnvironment() + .getThreadExecutionEnvironment() .hasContextEnabled("Dataflow_Stack_Trace"); if (attachFullStackTrace) { var result = new DataflowError(payload, UNLIMITED_STACK_TRACE, location); From 75a66c8eaabe30e2a94bbabc131d1f112b2c08c6 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 24 Sep 2024 16:31:54 +0100 Subject: [PATCH 03/24] DRAFT: cache invalidation --- .../command/RecomputeContextCmd.scala | 165 ++++++--- .../instrument/job/ExecuteJob.scala | 3 +- .../test/instrument/RuntimeServerTest.scala | 321 ++++++++++++++---- 3 files changed, 379 insertions(+), 110 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala index b2f214d3cc6f..6d6bb3809bba 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala @@ -1,5 +1,9 @@ package org.enso.interpreter.instrument.command +import org.enso.compiler.core.Implicits.AsMetadata +import org.enso.compiler.pass.analyse.DataflowAnalysis +import org.enso.compiler.refactoring.IRUtils +import org.enso.interpreter.instrument.command.RecomputeContextCmd.InvalidateExpressions import org.enso.interpreter.instrument.{ CacheInvalidation, ExecutionConfig, @@ -11,7 +15,6 @@ import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api.RequestId import scala.concurrent.{ExecutionContext, Future} -import scala.jdk.CollectionConverters._ /** A command that forces a recomputation of the current position. * @@ -45,56 +48,19 @@ class RecomputeContextCmd( reply(Api.EmptyStackError(request.contextId)) false } else { - val cacheInvalidationCommands = request.expressions.toSeq - .map(CacheInvalidation.Command(_)) - .map(CacheInvalidation(CacheInvalidation.StackSelector.Top, _)) - CacheInvalidation.runAll( - stack, - cacheInvalidationCommands + ctx.state.executionHooks.add( + InvalidateExpressions( + request.contextId, + request.expressions, + request.expressionConfigs + ) ) - - sendPendingUpdates(stack) reply(Api.RecomputeContextResponse(request.contextId)) true } } } - private def sendPendingUpdates( - stack: Iterable[InstrumentFrame] - )(implicit ctx: RuntimeContext): Unit = { - val invalidatedExpressions = - request.expressions - .map { - case expressions: Api.InvalidatedExpressions.Expressions => - expressions.value.toSet - case _: Api.InvalidatedExpressions.All => - stack.headOption - .map { frame => - frame.cache.getWeights.keySet().asScala.toSet - } - .getOrElse(Set()) - } - .getOrElse(Set()) - if (invalidatedExpressions.nonEmpty) { - val updates = invalidatedExpressions.collect { - case expressionId if expressionId ne null => - Api.ExpressionUpdate( - expressionId, - None, - None, - Vector.empty, - true, - false, - Api.ExpressionUpdate.Payload.Pending(None, None) - ) - } - ctx.endpoint.sendToClient( - Api.Response(Api.ExpressionUpdates(request.contextId, updates)) - ) - } - } - private def doesContextExist(implicit ctx: RuntimeContext): Boolean = { ctx.contextManager.contains(request.contextId) } @@ -133,3 +99,114 @@ class RecomputeContextCmd( } } + +object RecomputeContextCmd { + + /** Invalidate caches for the request. */ + sealed private case class InvalidateExpressions( + contextId: Api.ContextId, + expressions: Option[Api.InvalidatedExpressions], + expressionConfigs: Seq[Api.ExpressionConfig] + )(implicit ctx: RuntimeContext) + extends Runnable { + + override def run(): Unit = { + val stack = ctx.contextManager.getStack(contextId) + + val invalidationCommands = + ctx.locking.withWriteCompilationLock( + classOf[RecomputeContextCmd], + () => { + val expressionsInvalidationCommands = expressions.toSeq + .map(CacheInvalidation.Command(_)) + .map(CacheInvalidation(CacheInvalidation.StackSelector.All, _)) + val expressionConfigsDependentInvalidationCommands = + expressionConfigs + .map(_.expressionId) + .flatMap(RecomputeContextCmd.invalidateDependent) + val allInvalidationCommands = + expressionsInvalidationCommands ++ expressionConfigsDependentInvalidationCommands + + CacheInvalidation.runAll(stack, allInvalidationCommands) + + allInvalidationCommands + } + ) + + sendPendingUpdates(stack, contextId, invalidationCommands) + } + } + + /** Invalidate dependent nodes of the provided expression. + * + * @param expressionId the expression id + * @return commands to invalidate dependent nodes of the provided expression + */ + private def invalidateDependent( + expressionId: Api.ExpressionId + )(implicit ctx: RuntimeContext): Seq[CacheInvalidation] = { + val builder = Vector.newBuilder[CacheInvalidation] + ctx.executionService.getContext + .findModuleByExpressionId(expressionId) + .ifPresent { module => + module.getIr + .getMetadata(DataflowAnalysis) + .foreach { metadata => + val dependents = + IRUtils + .findByExternalId(module.getIr, expressionId) + .map { ir => + DataflowAnalysis.DependencyInfo.Type + .Static(ir.getId, ir.getExternalId) + } + .flatMap { expressionKey => + metadata.dependents.getExternal(expressionKey) + } + .fold(Set(expressionId))(_ + expressionId) + builder += CacheInvalidation( + CacheInvalidation.StackSelector.All, + CacheInvalidation.Command.InvalidateKeys(dependents) + ) + } + } + + builder.result() + } + + private def sendPendingUpdates( + stack: Iterable[InstrumentFrame], + contextId: Api.ContextId, + cacheInvalidations: Seq[CacheInvalidation] + )(implicit ctx: RuntimeContext): Unit = { + val builder = Set.newBuilder[Api.ExpressionId] + cacheInvalidations.map(_.command).foreach { + case CacheInvalidation.Command.InvalidateAll => + stack.headOption + .map { frame => + frame.cache.getWeights.keySet().forEach(builder.addOne) + } + case CacheInvalidation.Command.InvalidateKeys(expressionIds) => + builder ++= expressionIds + case _ => + } + + val invalidatedExpressions = builder.result() + if (invalidatedExpressions.nonEmpty) { + val updates = invalidatedExpressions.collect { + case expressionId if expressionId ne null => + Api.ExpressionUpdate( + expressionId, + None, + None, + Vector.empty, + true, + false, + Api.ExpressionUpdate.Payload.Pending(None, None) + ) + } + ctx.endpoint.sendToClient( + Api.Response(Api.ExpressionUpdates(contextId, updates)) + ) + } + } +} diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala index baf665cca0d9..d4a07f99b225 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala @@ -75,7 +75,8 @@ class ExecuteJob( ) } val outcome = - try ProgramExecutionSupport.runProgram(contextId, stack, executionConfig) + try ProgramExecutionSupport + .runProgram(contextId, stack, executionConfig) finally { context.setExecutionEnvironment(originalExecutionEnvironment) } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 33da76dcc909..51049513e848 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -5357,7 +5357,7 @@ class RuntimeServerTest requestId, Api.RecomputeContextRequest( contextId, - Some(Api.InvalidatedExpressions.All()), + None, None, Seq( Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) @@ -5366,7 +5366,7 @@ class RuntimeServerTest ) ) context.receiveNIgnorePendingExpressionUpdates( - 4 + 3 ) should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), TestMessages.update( @@ -5386,23 +5386,6 @@ class RuntimeServerTest ) ) ), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), context.executionComplete(contextId) ) context.consumeOut shouldEqual List("True", "False") @@ -5413,7 +5396,7 @@ class RuntimeServerTest requestId, Api.RecomputeContextRequest( contextId, - Some(Api.InvalidatedExpressions.All()), + None, None, Seq( Api.ExpressionConfig(idIn, Some(Api.ExecutionEnvironment.Live())) @@ -5422,26 +5405,9 @@ class RuntimeServerTest ) ) context.receiveNIgnorePendingExpressionUpdates( - 4 + 3 ) should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), TestMessages.update( contextId, idIn, @@ -5461,7 +5427,7 @@ class RuntimeServerTest ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("False", "True") + context.consumeOut shouldEqual List("True", "True") } it should "recompute recursive method call with expression configs" in { @@ -5565,8 +5531,7 @@ class RuntimeServerTest requestId, Api.RecomputeContextRequest( contextId, - Some(Api.InvalidatedExpressions.All()), - //Some(Api.ExecutionEnvironment.Live()), + None, None, Seq( Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) @@ -5575,7 +5540,7 @@ class RuntimeServerTest ) ) context.receiveNIgnorePendingExpressionUpdates( - 4 + 3 ) should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), TestMessages.update( @@ -5595,23 +5560,6 @@ class RuntimeServerTest ) ) ), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), context.executionComplete(contextId) ) context.consumeOut shouldEqual List("3", "2", "1", "6", "False") @@ -5717,8 +5665,7 @@ class RuntimeServerTest requestId, Api.RecomputeContextRequest( contextId, - Some(Api.InvalidatedExpressions.All()), - //Some(Api.ExecutionEnvironment.Live()), + None, None, Seq( Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) @@ -5727,15 +5674,13 @@ class RuntimeServerTest ) ) context.receiveNIgnorePendingExpressionUpdates( - 4 + 3 ) should contain theSameElementsAs Seq( Api.Response(requestId, Api.RecomputeContextResponse(contextId)), TestMessages.update( contextId, idOut, ConstantsGen.INTEGER, - fromCache = false, - typeChanged = true, methodCall = Some( Api.MethodCall( Api.MethodPointer( @@ -5746,6 +5691,235 @@ class RuntimeServerTest ) ) ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("42", "False") + } + + it should "recompute expression dropping the cache by providing empty expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 17, "aa") + val idIn = metadata.addItem(131, 16, "ab") + val idOutTxt = metadata.addItem(162, 11, "ac") + val idInTxt = metadata.addItem(187, 10, "ad") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = Output.is_enabled + | in = Input.is_enabled + | out_txt = out.to_text + | in_txt = in.to_text + | IO.println out_txt + | IO.println in_txt + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ), + TestMessages.update( + contextId, + idOutTxt, + ConstantsGen.TEXT, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ), + TestMessages.update( + contextId, + idInTxt, + ConstantsGen.TEXT, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("False", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idIn, Some(Api.ExecutionEnvironment.Live())), + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 6 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idInTxt, + ConstantsGen.TEXT, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ) + ), + TestMessages.update( + contextId, + idOutTxt, + ConstantsGen.TEXT, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("True", "True") + + // recompute dropping the caches + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idIn, None) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), TestMessages.update( contextId, idIn, @@ -5763,9 +5937,26 @@ class RuntimeServerTest ) ) ), + TestMessages.update( + contextId, + idInTxt, + ConstantsGen.TEXT, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ) + ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("42", "False") + context.consumeOut shouldEqual List("True", "False") } it should "return error when module not found" in { From 3994fe3765f03f07f39423b4110fd1ca471cbd76 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 25 Sep 2024 17:00:42 +0100 Subject: [PATCH 04/24] refactor: RuntimeRecomputeTest --- .../instrument/RuntimeRecomputeTest.scala | 1056 +++ .../test/instrument/RuntimeServerTest.scala | 6488 +++++++---------- 2 files changed, 3827 insertions(+), 3717 deletions(-) create mode 100644 engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala new file mode 100644 index 000000000000..47816084bb8e --- /dev/null +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala @@ -0,0 +1,1056 @@ +package org.enso.interpreter.test.instrument + +import org.apache.commons.io.output.TeeOutputStream +import org.enso.common.{LanguageInfo, MethodNames, RuntimeOptions} +import org.enso.interpreter.runtime.EnsoContext +import org.enso.interpreter.runtime.`type`.ConstantsGen +import org.enso.interpreter.test.Metadata +import org.enso.polyglot.RuntimeServerInfo +import org.enso.polyglot.runtime.Runtime.Api +import org.graalvm.polyglot.Context +import org.scalatest.BeforeAndAfterEach +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import java.io.{ByteArrayOutputStream, File} +import java.nio.file.{Files, Paths} +import java.util.UUID + +@scala.annotation.nowarn("msg=multiarg infix syntax") +class RuntimeRecomputeTest + extends AnyFlatSpec + with Matchers + with BeforeAndAfterEach { + + var context: TestContext = _ + + class TestContext(packageName: String) + extends InstrumentTestContext(packageName) + with RuntimeServerTest.TestMain { + + val out: ByteArrayOutputStream = new ByteArrayOutputStream() + val logOut: ByteArrayOutputStream = new ByteArrayOutputStream() + protected val context = + Context + .newBuilder(LanguageInfo.ID) + .allowExperimentalOptions(true) + .allowAllAccess(true) + .option(RuntimeOptions.PROJECT_ROOT, pkg.root.getAbsolutePath) + .option( + RuntimeOptions.LOG_LEVEL, + java.util.logging.Level.WARNING.getName + ) + .option(RuntimeOptions.INTERPRETER_SEQUENTIAL_COMMAND_EXECUTION, "true") + .option(RuntimeOptions.ENABLE_PROJECT_SUGGESTIONS, "false") + .option(RuntimeOptions.ENABLE_GLOBAL_SUGGESTIONS, "false") + .option(RuntimeOptions.ENABLE_EXECUTION_TIMER, "false") + .option(RuntimeOptions.STRICT_ERRORS, "false") + .option( + RuntimeOptions.DISABLE_IR_CACHES, + InstrumentTestContext.DISABLE_IR_CACHE + ) + .option(RuntimeServerInfo.ENABLE_OPTION, "true") + .option(RuntimeOptions.INTERACTIVE_MODE, "true") + .option( + RuntimeOptions.LANGUAGE_HOME_OVERRIDE, + Paths + .get("../../test/micro-distribution/component") + .toFile + .getAbsolutePath + ) + .option(RuntimeOptions.EDITION_OVERRIDE, "0.0.0-dev") + .logHandler(new TeeOutputStream(logOut, System.err)) + .out(new TeeOutputStream(out, System.err)) + .serverTransport(runtimeServerEmulator.makeServerTransport) + .build() + + lazy val languageContext = executionContext.context + .getBindings(LanguageInfo.ID) + .invokeMember(MethodNames.TopScope.LEAK_CONTEXT) + .asHostObject[EnsoContext] + + def writeMain(contents: String): File = + Files.write(pkg.mainFile.toPath, contents.getBytes).toFile + + def writeFile(file: File, contents: String): File = + Files.write(file.toPath, contents.getBytes).toFile + + def writeInSrcDir(moduleName: String, contents: String): File = { + val file = new File(pkg.sourceDir, s"$moduleName.enso") + Files.write(file.toPath, contents.getBytes).toFile + } + + def send(msg: Api.Request): Unit = runtimeServerEmulator.sendToRuntime(msg) + + def consumeOut: List[String] = { + val result = out.toString + out.reset() + result.linesIterator.toList + } + + def executionComplete(contextId: UUID): Api.Response = + Api.Response(Api.ExecutionComplete(contextId)) + } + + override protected def beforeEach(): Unit = { + context = new TestContext("Test") + context.init() + val Some(Api.Response(_, Api.InitializedNotification())) = context.receive + } + + override protected def afterEach(): Unit = { + if (context != null) { + context.close() + context.out.reset() + context = null + } + } + + it should "recompute expressions without invalidation" in { + val contents = context.Main.code + val mainFile = context.writeMain(contents) + val moduleName = "Enso_Test.Test.Main" + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + context.executionComplete(contextId) + ) + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest(contextId, None, None, Seq()) + ) + ) + context.receiveN(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + context.executionComplete(contextId) + ) + } + + it should "recompute expressions invalidating all" in { + val contents = context.Main.code + val mainFile = context.writeMain(contents) + val moduleName = "Enso_Test.Test.Main" + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + context.executionComplete(contextId) + ) + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + Some(Api.InvalidatedExpressions.All()), + None, + Seq() + ) + ) + ) + context.receiveN(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.pending( + contextId, + context.Main.idMainX, + context.Main.idMainY, + context.Main.idMainZ, + context.Main.idFooY, + context.Main.idFooZ + ), + context.Main.Update.mainX(contextId, typeChanged = false), + context.Main.Update.mainY(contextId, typeChanged = false), + context.Main.Update.mainZ(contextId, typeChanged = false), + context.executionComplete(contextId) + ) + } + + it should "recompute expressions invalidating some" in { + val contents = context.Main.code + val mainFile = context.writeMain(contents) + val moduleName = "Enso_Test.Test.Main" + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.receiveNone shouldEqual None + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + context.executionComplete(contextId) + ) + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + Some( + Api.InvalidatedExpressions.Expressions(Vector(context.Main.idMainZ)) + ), + None, + Seq() + ) + ) + ) + context.receiveN(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.pending(contextId, context.Main.idMainZ), + context.Main.Update.mainZ(contextId, typeChanged = false), + context.executionComplete(contextId) + ) + } + + it should "recompute expressions changing an execution environment" in { + val contents = context.Main.code + val mainFile = context.writeMain(contents) + val moduleName = "Enso_Test.Test.Main" + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + context.executionComplete(contextId) + ) + + // recompute + context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + .Design() + .name + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + Some(Api.InvalidatedExpressions.All()), + Some(Api.ExecutionEnvironment.Live()), + Seq() + ) + ) + ) + context.receiveN(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.pending( + contextId, + context.Main.idMainX, + context.Main.idMainY, + context.Main.idMainZ, + context.Main.idFooY, + context.Main.idFooZ + ), + context.Main.Update.mainX(contextId, typeChanged = false), + context.Main.Update.mainY(contextId, typeChanged = false), + context.Main.Update.mainZ(contextId, typeChanged = false), + context.executionComplete(contextId) + ) + context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + .Design() + .name + } + + it should "recompute expression with expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 17, "aa") + val idIn = metadata.addItem(131, 16, "ab") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = Output.is_enabled + | in = Input.is_enabled + | IO.println out + | IO.println in + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("False", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("True", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idIn, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("True", "True") + } + + it should "recompute recursive method call with expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 5, "aa") + val idIn = metadata.addItem(119, 16, "ab") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = fac 3 + | in = Input.is_enabled + | IO.println out + | IO.println in + | + |fac n=1 acc=1 = + | if Output.is_enabled.not then Nothing else + | if n <= 0 then acc else + | IO.println n + | @Tail_Call fac n-1 acc*n + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + moduleName, + moduleName, + "fac" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("Nothing", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.INTEGER, + fromCache = false, + typeChanged = true, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + moduleName, + moduleName, + "fac" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("3", "2", "1", "6", "False") + } + + it should "recompute method call returning Panic with expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 34, "aa") + val idIn = metadata.addItem(148, 16, "ab") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = Panic.catch Any (foo 42) _.payload + | in = Input.is_enabled + | IO.println out + | IO.println in + | + |foo n=Nothing = + | Output.if_enabled (Panic.throw n) + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + "Standard.Base.Errors.Common.Forbidden_Operation", + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Panic", + "Standard.Base.Panic.Panic", + "catch" + ) + ) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List( + "(Forbidden_Operation.Error 'The Output context is disabled.')", + "False" + ) + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.INTEGER, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Panic", + "Standard.Base.Panic.Panic", + "catch" + ) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("42", "False") + } + + it should "recompute expression dropping the cache by providing empty expression configs" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + val metadata = new Metadata + val idOut = metadata.addItem(104, 17, "aa") + val idIn = metadata.addItem(131, 16, "ab") + val idOutTxt = metadata.addItem(162, 11, "ac") + val idInTxt = metadata.addItem(187, 10, "ad") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + | + |main = + | out = Output.is_enabled + | in = Input.is_enabled + | out_txt = out.to_text + | in_txt = in.to_text + | IO.println out_txt + | IO.println in_txt + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + + // Create a new file + val mainFile = context.writeMain(contents) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + context.consumeOut shouldEqual List() + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ), + TestMessages.update( + contextId, + idOutTxt, + ConstantsGen.TEXT, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ), + TestMessages.update( + contextId, + idInTxt, + ConstantsGen.TEXT, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("False", "False") + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idIn, Some(Api.ExecutionEnvironment.Live())), + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 6 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idInTxt, + ConstantsGen.TEXT, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ) + ), + TestMessages.update( + contextId, + idOutTxt, + ConstantsGen.TEXT, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("True", "True") + + // recompute dropping the caches + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idIn, None) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idIn, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + TestMessages.update( + contextId, + idInTxt, + ConstantsGen.TEXT, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Any", + "Standard.Base.Any.Any", + "to_text" + ), + Vector() + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("True", "False") + } +} diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 51049513e848..5893ce32c658 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -33,7 +33,8 @@ class RuntimeServerTest var context: TestContext = _ class TestContext(packageName: String) - extends InstrumentTestContext(packageName) { + extends InstrumentTestContext(packageName) + with RuntimeServerTest.TestMain { val out: ByteArrayOutputStream = new ByteArrayOutputStream() val logOut: ByteArrayOutputStream = new ByteArrayOutputStream() @@ -113,939 +114,27 @@ class RuntimeServerTest def executionComplete(contextId: UUID): Api.Response = Api.Response(Api.ExecutionComplete(contextId)) - - // === The Tests ========================================================== - - object Main { - - val metadata = new Metadata - - val idMainX = metadata.addItem(63, 1, "aa1") - val idMainY = metadata.addItem(73, 7, "aa2") - val idMainZ = metadata.addItem(89, 5, "aa3") - val idFooY = metadata.addItem(133, 8, "ff2") - val idFooZ = metadata.addItem(150, 5, "ff3") - - def code = - metadata.appendToCode( - """ - |from Standard.Base.Data.Numbers import all - | - |main = - | x = 6 - | y = x.foo 5 - | z = y + 5 - | z - | - |Number.foo self = x -> - | y = self + 3 - | z = y * x - | z - |""".stripMargin.linesIterator.mkString("\n") - ) - - object Update { - - def mainX( - contextId: UUID, - fromCache: Boolean = false, - typeChanged: Boolean = true - ): Api.Response = - Api.Response( - Api.ExpressionUpdates( - contextId, - Set( - Api.ExpressionUpdate( - Main.idMainX, - Some(ConstantsGen.INTEGER), - None, - Vector(Api.ProfilingInfo.ExecutionTime(0)), - fromCache, - typeChanged, - Api.ExpressionUpdate.Payload.Value() - ) - ) - ) - ) - - def pendingZ(): Api.ExpressionUpdate = - Api.ExpressionUpdate( - Main.idFooZ, - None, - None, - Vector(), - true, - false, - Api.ExpressionUpdate.Payload.Pending(None, None) - ) - - def pendingY(): Api.ExpressionUpdate = - Api.ExpressionUpdate( - Main.idFooY, - None, - None, - Vector(), - true, - false, - Api.ExpressionUpdate.Payload.Pending(None, None) - ) - - def mainY( - contextId: UUID, - fromCache: Boolean = false, - typeChanged: Boolean = true - ): Api.Response = - Api.Response( - Api.ExpressionUpdates( - contextId, - Set( - Api.ExpressionUpdate( - Main.idMainY, - Some(ConstantsGen.INTEGER), - Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - ConstantsGen.NUMBER, - "foo" - ) - ) - ), - Vector(Api.ProfilingInfo.ExecutionTime(0)), - fromCache, - typeChanged, - Api.ExpressionUpdate.Payload.Value() - ) - ) - ) - ) - - def mainZ( - contextId: UUID, - fromCache: Boolean = false, - typeChanged: Boolean = true - ): Api.Response = - Api.Response( - Api.ExpressionUpdates( - contextId, - Set( - Api.ExpressionUpdate( - Main.idMainZ, - Some(ConstantsGen.INTEGER), - Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Data.Numbers", - ConstantsGen.INTEGER, - "+" - ) - ) - ), - Vector(Api.ProfilingInfo.ExecutionTime(0)), - fromCache, - typeChanged, - Api.ExpressionUpdate.Payload.Value() - ) - ) - ) - ) - - def fooY( - contextId: UUID, - fromCache: Boolean = false, - typeChanged: Boolean = true - ): Api.Response = - Api.Response( - Api.ExpressionUpdates( - contextId, - Set( - Api.ExpressionUpdate( - Main.idFooY, - Some(ConstantsGen.INTEGER), - Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Data.Numbers", - ConstantsGen.INTEGER, - "+" - ) - ) - ), - Vector(Api.ProfilingInfo.ExecutionTime(0)), - fromCache, - typeChanged, - Api.ExpressionUpdate.Payload.Value() - ) - ) - ) - ) - - def fooZ( - contextId: UUID, - fromCache: Boolean = false, - typeChanged: Boolean = true - ): Api.Response = - Api.Response( - Api.ExpressionUpdates( - contextId, - Set( - Api.ExpressionUpdate( - Main.idFooZ, - Some(ConstantsGen.INTEGER), - Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Data.Numbers", - ConstantsGen.INTEGER, - "*" - ) - ) - ), - Vector(Api.ProfilingInfo.ExecutionTime(0)), - fromCache, - typeChanged, - Api.ExpressionUpdate.Payload.Value() - ) - ) - ) - ) - } - } - - object Main2 { - - val metadata = new Metadata - val idMainY = metadata.addItem(178, 5) - val idMainZ = metadata.addItem(192, 5) - - val code = metadata.appendToCode( - """from Standard.Base import all - | - |foo = arg -> - | IO.println "I'm expensive!" - | arg + 5 - | - |bar = arg -> - | IO.println "I'm more expensive!" - | arg * 5 - | - |main = - | x = 10 - | y = foo x - | z = bar y - | z - |""".stripMargin.linesIterator.mkString("\n") - ) - - object Update { - - def mainY(contextId: UUID) = - Api.Response( - Api.ExpressionUpdates( - contextId, - Set( - Api.ExpressionUpdate( - idMainY, - Some(ConstantsGen.INTEGER), - Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main", - "foo" - ) - ) - ), - Vector(Api.ProfilingInfo.ExecutionTime(0)), - false, - true, - Api.ExpressionUpdate.Payload.Value() - ) - ) - ) - ) - - def mainZ(contextId: UUID) = - Api.Response( - Api.ExpressionUpdates( - contextId, - Set( - Api.ExpressionUpdate( - idMainZ, - Some(ConstantsGen.INTEGER), - Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main", - "bar" - ) - ) - ), - Vector(Api.ProfilingInfo.ExecutionTime(0)), - false, - true, - Api.ExpressionUpdate.Payload.Value() - ) - ) - ) - ) - } - } - - } - - override protected def beforeEach(): Unit = { - context = new TestContext("Test") - context.init() - val Some(Api.Response(_, Api.InitializedNotification())) = context.receive - } - - override protected def afterEach(): Unit = { - if (context != null) { - context.close() - context.out.reset() - context = null - } - } - - "RuntimeServer" should "push and pop functions on the stack" in { - val contents = context.Main.code - val mainFile = context.writeMain(contents) - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - - // 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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push local item on top of the empty stack - val invalidLocalItem = Api.StackItem.LocalCall(context.Main.idMainY) - context.send( - Api - .Request(requestId, Api.PushContextRequest(contextId, invalidLocalItem)) - ) - context.receive shouldEqual Some( - Api.Response(requestId, Api.InvalidStackItemError(contextId)) - ) - - // push main - val item1 = Api.StackItem.ExplicitCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "main"), - None, - Vector() - ) - context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item1)) - ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.mainX(contextId, typeChanged = true), - context.Main.Update.mainY(contextId, typeChanged = true), - context.Main.Update.mainZ(contextId, typeChanged = true), - context.executionComplete(contextId) - ) - - // push foo call - val item2 = Api.StackItem.LocalCall(context.Main.idMainY) - context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item2)) - ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.fooY(contextId), - context.Main.Update.fooZ(contextId), - context.executionComplete(contextId) - ) - - // push method pointer on top of the non-empty stack - val invalidExplicitCall = Api.StackItem.ExplicitCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "main"), - None, - Vector() - ) - context.send( - Api.Request( - requestId, - Api.PushContextRequest(contextId, invalidExplicitCall) - ) - ) - context.receive shouldEqual Some( - Api.Response(requestId, Api.InvalidStackItemError(contextId)) - ) - - // pop foo call - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)), - context.Main.Update.mainY(contextId, fromCache = true), - context.Main.Update.mainZ(contextId, fromCache = true), - context.executionComplete(contextId) - ) - - // pop main - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.PopContextResponse(contextId)) - ) - - // pop empty stack - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.EmptyStackError(contextId)) - ) - } - - it should "substitute Nothing when pushing method with unapplied arguments" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val identityResultId = metadata.addItem(13, 1, "aa") - val identityCallId = metadata.addItem(27, 8, "ab") - - val code = - """identity x = x - | - |main = - | identity - |""".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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) - ) - context.receiveN(3) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - identityCallId, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, moduleName, "identity"), - Vector(0) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, moduleName, "identity"), - Vector(0) - ) - ) - ) - ), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List() - - // push identity - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.LocalCall(identityCallId) - ) - ) - ) - context.receiveN(3) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages - .update(contextId, identityResultId, ConstantsGen.NOTHING_BUILTIN), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List() - } - - it should "push method with default arguments on top of the stack" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val idFoo = metadata.addItem(41, 6, "ffff") - - val code = - """from Standard.Base import all - | - |foo x=0 = x + 42 - | - |main = - | IO.println foo - |""".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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "foo"), - None, - Vector() - ) - ) - ) - ) - context.receiveNIgnoreStdLib(3) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idFoo, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Data.Numbers", - ConstantsGen.INTEGER, - "+" - ) - ) - ), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List() - } - - it should "push method with default arguments on the stack" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val idMain = metadata.addItem(54, 19) - val idMainFoo = metadata.addItem(70, 3) - - val code = - """from Standard.Base import all - | - |foo a=0 = a + 1 - | - |main = - | IO.println foo - |""".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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) - ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idMainFoo, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, moduleName, "foo"), - Vector(0) - ) - ), - TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("1") - - // push foo call - context.send( - Api.Request( - requestId, - Api.PushContextRequest(contextId, Api.StackItem.LocalCall(idMainFoo)) - ) - ) - context.receiveN(2) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("1") - } - - it should "send method pointer updates of methods" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val idMain = metadata.addItem(105, 120, "aa") - val idMainX = metadata.addItem(132, 9, "ab") - val idMainY = metadata.addItem(150, 3, "ac") - val idMainM = metadata.addItem(162, 8, "ad") - val idMainP = metadata.addItem(179, 5, "ae") - val idMainQ = metadata.addItem(193, 5, "af") - val idMainF = metadata.addItem(215, 9, "bb") - - val code = - """from Standard.Base import all - |import Enso_Test.Test.A - | - |type QuuxT - | Quux - | - | foo = 42 - | - |bar = 7 - | - |main = - | f a b = a + b - | x = QuuxT.foo - | y = bar - | m = A.AT.A x - | p = m.foo - | q = A.bar - | IO.println (f x+y p+q) - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) - val mainFile = context.writeMain(contents) - - val aCode = - """ - |type AT - | A un_a - | - | foo self = 11 - | - |bar = 19 - |""".stripMargin.linesIterator.mkString("\n") - val aFile = context.writeInSrcDir("A", aCode) - - // 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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - context.send(Api.Request(requestId, Api.OpenFileRequest(aFile, aCode))) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) - ) - context.receiveNIgnoreStdLib(9) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idMainX, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.QuuxT", - "foo" - ) - ) - ), - TestMessages.update( - contextId, - idMainY, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "bar") - ) - ), - TestMessages.update( - contextId, - idMainM, - "Enso_Test.Test.A.AT", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.A", "Enso_Test.Test.A.AT", "A") - ) - ), - TestMessages.update( - contextId, - idMainP, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.A", "Enso_Test.Test.A.AT", "foo") - ) - ), - TestMessages.update( - contextId, - idMainQ, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.A", "Enso_Test.Test.A", "bar") - ) - ), - TestMessages.update(contextId, idMainF, ConstantsGen.INTEGER), - TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("79") - } - - it should "send method pointer updates of constructors" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val idA = metadata.addItem(47, 3, "aa") - val idB = metadata.addItem(59, 6, "ab") - val idC = metadata.addItem(70, 7, "ac") - - val code = - """type T - | A - | B x - | C y z - | - |main = - | a = T.A - | b = T.B 42 - | T.C a b - |""".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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) - ) - context.receiveN(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idA, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") - ) - ), - TestMessages.update( - contextId, - idB, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "B") - ) - ), - TestMessages.update( - contextId, - idC, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "C") - ) - ), - context.executionComplete(contextId) - ) } - it should "send method pointer updates of simple_suspended_constructors" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val idA = metadata.addItem(52, 3, "aa") - - val code = - """type T - | A - | B x - | C y z - | - |main = - | a = test T.A - | a - | - |test ~t:T = t - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) - val mainFile = context.writeMain(contents) - - metadata.assertInCode(idA, code, "T.A") - - // 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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) - ) - context.receiveN(3) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idA, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") - ) - ), - context.executionComplete(contextId) - ) + override protected def beforeEach(): Unit = { + context = new TestContext("Test") + context.init() + val Some(Api.Response(_, Api.InitializedNotification())) = context.receive } - it should "send method pointer updates of simple_autoscoped_constructors" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val idA = metadata.addItem(52, 3, "aa") - - val code = - """type T - | A - | B x - | C y z - | - |main = - | a = test ..A - | a - | - |test t:T = t - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) - val mainFile = context.writeMain(contents) + override protected def afterEach(): Unit = { + if (context != null) { + context.close() + context.out.reset() + context = null + } + } - metadata.assertInCode(idA, code, "..A") + "RuntimeServer" should "push and pop functions on the stack" in { + val contents = context.Main.code + val mainFile = context.writeMain(contents) + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) @@ -1061,147 +150,101 @@ class RuntimeServerTest Api.Response(Some(requestId), Api.OpenFileResponse) ) + // push local item on top of the empty stack + val invalidLocalItem = Api.StackItem.LocalCall(context.Main.idMainY) + context.send( + Api + .Request(requestId, Api.PushContextRequest(contextId, invalidLocalItem)) + ) + context.receive shouldEqual Some( + Api.Response(requestId, Api.InvalidStackItemError(contextId)) + ) + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "main"), + None, + Vector() + ) context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) ) - context.receiveN(3) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idA, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") - ) - ), + context.Main.Update.mainX(contextId, typeChanged = true), + context.Main.Update.mainY(contextId, typeChanged = true), + context.Main.Update.mainZ(contextId, typeChanged = true), context.executionComplete(contextId) ) - } - - it should "send method pointer updates of autoscope constructors" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val idA = metadata.addItem(52, 3, "aa") - val idB = metadata.addItem(70, 6, "ab") - val idC = metadata.addItem(88, 7, "ac") - - val code = - """type T - | A - | B x - | C y z - | - |main = - | a = test ..A - | b = test (..B 42) - | test (..C a b) - | - |test t:T = t - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) - val mainFile = context.writeMain(contents) - - metadata.assertInCode(idA, code, "..A") - metadata.assertInCode(idB, code, "..B 42") - metadata.assertInCode(idC, code, "..C a b") - - // create context - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) - // open file + // push foo call + val item2 = Api.StackItem.LocalCall(context.Main.idMainY) context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + Api.Request(requestId, Api.PushContextRequest(contextId, item2)) ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.fooY(contextId), + context.Main.Update.fooZ(contextId), + context.executionComplete(contextId) ) - // push main + // push method pointer on top of the non-empty stack + val invalidExplicitCall = Api.StackItem.ExplicitCall( + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "main"), + None, + Vector() + ) context.send( Api.Request( requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) + Api.PushContextRequest(contextId, invalidExplicitCall) ) ) - context.receiveN(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idA, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") - ) - ), - TestMessages.update( - contextId, - idB, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "B") - ) - ), - TestMessages.update( - contextId, - idC, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "C") - ) - ), + context.receive shouldEqual Some( + Api.Response(requestId, Api.InvalidStackItemError(contextId)) + ) + + // pop foo call + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveN(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)), + context.Main.Update.mainY(contextId, fromCache = true), + context.Main.Update.mainZ(contextId, fromCache = true), context.executionComplete(contextId) ) + + // pop main + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.PopContextResponse(contextId)) + ) + + // pop empty stack + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.EmptyStackError(contextId)) + ) } - it should "send method pointer updates of when autoscope constructor changes to a value" in { + it should "substitute Nothing when pushing method with unapplied arguments" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val idA = metadata.addItem(44, 3, "aa") + val metadata = new Metadata + val identityResultId = metadata.addItem(13, 1, "aa") + val identityCallId = metadata.addItem(27, 8, "ab") val code = - """type Xyz - | - |type T - | A + """identity x = x | |main = - | a = test Xyz - | a - | - |test t:(Xyz | T) = t + | identity |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) - metadata.assertInCode(idA, code, "Xyz") - // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -1234,84 +277,61 @@ class RuntimeServerTest Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idA, - "Enso_Test.Test.Main.Xyz" - ), - context.executionComplete(contextId) - ) - - // Modify the file /Xyz/..A/ - context.send( - Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - model.TextEdit( - model.Range(model.Position(6, 13), model.Position(6, 16)), - "..A" + identityCallId, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer(moduleName, moduleName, "identity"), + Vector(0) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, moduleName, "identity"), + Vector(0) ) - ), - execute = true, - idMap = None - ) - ) - ) - - context.receiveN(3) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idA), - TestMessages.update( - contextId, - idA, - "Enso_Test.Test.Main.T", - Api.MethodCall( - Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") + ) ) ), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List() - // Modify the file /..A/Xyz/ + // push identity context.send( Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - model.TextEdit( - model.Range(model.Position(6, 13), model.Position(6, 16)), - "Xyz" - ) - ), - execute = true, - idMap = None - ) - ) - ) - - context.receiveN(3) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idA), - TestMessages.update( - contextId, - idA, - "Enso_Test.Test.Main.Xyz" - ), + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.LocalCall(identityCallId) + ) + ) + ) + context.receiveN(3) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages + .update(contextId, identityResultId, ConstantsGen.NOTHING_BUILTIN), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List() } - it should "send method pointer updates of builtin operators" in { + it should "push method with default arguments on top of the stack" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" val metadata = new Metadata - val id_x_1 = metadata.addItem(48, 5, "aa") + val idFoo = metadata.addItem(41, 6, "ffff") val code = """from Standard.Base import all + | + |foo x=0 = x + 42 | |main = - | x_1 = 3 ^ 4 - | x_1 + | IO.println foo |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -1337,7 +357,7 @@ class RuntimeServerTest Api.PushContextRequest( contextId, Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), + Api.MethodPointer(moduleName, moduleName, "foo"), None, Vector() ) @@ -1348,36 +368,37 @@ class RuntimeServerTest Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x_1, + idFoo, ConstantsGen.INTEGER, Api.MethodCall( Api.MethodPointer( "Standard.Base.Data.Numbers", - "Standard.Base.Data.Numbers.Integer", - "^" + ConstantsGen.INTEGER, + "+" ) ) ), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List() } - it should "send method pointer updates of partially applied builtin operators" in { + it should "push method with default arguments on the stack" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val id_x_1 = metadata.addItem(48, 5, "aa") - val id_x_2 = metadata.addItem(64, 7, "ab") + val metadata = new Metadata + val idMain = metadata.addItem(54, 19) + val idMainFoo = metadata.addItem(70, 3) val code = """from Standard.Base import all + | + |foo a=0 = a + 1 | |main = - | x_1 = "4" + - | x_2 = x_1 "2" - | x_2 + | IO.println foo |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -1410,56 +431,84 @@ class RuntimeServerTest ) ) ) - val textPlusMethodPointer = Api.MethodPointer( - "Standard.Base.Data.Text", - "Standard.Base.Data.Text.Text", - "+" - ) context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x_1, - ConstantsGen.FUNCTION, - methodCall = Some(Api.MethodCall(textPlusMethodPointer, Vector(1))), - payload = Api.ExpressionUpdate.Payload.Value( - None, - Some(Api.FunctionSchema(textPlusMethodPointer, Vector(1))) + idMainFoo, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, moduleName, "foo"), + Vector(0) ) ), - TestMessages.update( - contextId, - id_x_2, - ConstantsGen.TEXT, - Api.MethodCall(textPlusMethodPointer) - ), + TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("1") + + // push foo call + context.send( + Api.Request( + requestId, + Api.PushContextRequest(contextId, Api.StackItem.LocalCall(idMainFoo)) + ) + ) + context.receiveN(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("1") } - it should "send method pointer updates of partially applied constructors" in { + it should "send method pointer updates of methods" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" val metadata = new Metadata - val id_x_0 = metadata.addItem(35, 3, "aa") - val id_x_1 = metadata.addItem(49, 5, "ab") - val id_x_2 = metadata.addItem(65, 5, "ac") + val idMain = metadata.addItem(105, 120, "aa") + val idMainX = metadata.addItem(132, 9, "ab") + val idMainY = metadata.addItem(150, 3, "ac") + val idMainM = metadata.addItem(162, 8, "ad") + val idMainP = metadata.addItem(179, 5, "ae") + val idMainQ = metadata.addItem(193, 5, "af") + val idMainF = metadata.addItem(215, 9, "bb") val code = - """type T - | A x y + """from Standard.Base import all + |import Enso_Test.Test.A + | + |type QuuxT + | Quux + | + | foo = 42 + | + |bar = 7 | |main = - | x_0 = T.A - | x_1 = x_0 1 - | x_2 = x_1 2 - | x_2 + | f a b = a + b + | x = QuuxT.foo + | y = bar + | m = A.AT.A x + | p = m.foo + | q = A.bar + | IO.println (f x+y p+q) |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + val aCode = + """ + |type AT + | A un_a + | + | foo self = 11 + | + |bar = 19 + |""".stripMargin.linesIterator.mkString("\n") + val aFile = context.writeInSrcDir("A", aCode) + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -1473,6 +522,10 @@ class RuntimeServerTest context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) ) + context.send(Api.Request(requestId, Api.OpenFileRequest(aFile, aCode))) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) // push main context.send( @@ -1488,173 +541,79 @@ class RuntimeServerTest ) ) ) - context.receiveN(5) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(9) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x_0, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall(Api.MethodPointer(moduleName, s"$moduleName.T", "A")) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "A"), - Vector(0, 1) - ) + idMainX, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.QuuxT", + "foo" ) ) ), TestMessages.update( contextId, - id_x_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, s"$moduleName.T", "A"), - Vector(1) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "A"), - Vector(1) - ) - ) + idMainY, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "bar") ) ), TestMessages.update( contextId, - id_x_2, - s"$moduleName.T", - Api.MethodCall(Api.MethodPointer(moduleName, s"$moduleName.T", "A")) + idMainM, + "Enso_Test.Test.A.AT", + Api.MethodCall( + Api.MethodPointer("Enso_Test.Test.A", "Enso_Test.Test.A.AT", "A") + ) ), - context.executionComplete(contextId) - ) - } - - it should "send error updates for partially applied autoscope constructors" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val id_x_0 = metadata.addItem(40, 3, "aa") - val id_x_1 = metadata.addItem(60, 5, "ab") - - val code = - """type T - | A x y - | - |main = - | x_0 = test ..A - | x_1 = test (..A 1) - | T.A x_0 x_1 - | - |test t:T = t - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) - val mainFile = context.writeMain(contents) - - metadata.assertInCode(id_x_0, code, "..A") - metadata.assertInCode(id_x_1, code, "..A 1") - - // 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(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) + TestMessages.update( + contextId, + idMainP, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer("Enso_Test.Test.A", "Enso_Test.Test.A.AT", "foo") ) - ) - ) - context.receiveN(3) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), + ), TestMessages.update( contextId, - id_x_0, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, s"$moduleName.T", "A"), - Vector(0, 1) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "A"), - Vector(0, 1) - ) - ) + idMainQ, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer("Enso_Test.Test.A", "Enso_Test.Test.A", "bar") ) ), - Api.Response( - Api.ExecutionFailed( - contextId, - Api.ExecutionResult.Diagnostic.error( - "Type_Error.Error", - Some(mainFile), - Some(model.Range(model.Position(8, 0), model.Position(8, 12))), - None, - Vector( - Api.StackTraceElement( - "Main.test", - Some(mainFile), - Some(model.Range(model.Position(8, 0), model.Position(8, 12))), - None - ), - Api.StackTraceElement( - "Main.main", - Some(mainFile), - Some(model.Range(model.Position(4, 10), model.Position(4, 18))), - None - ) - ) - ) - ) - ) + TestMessages.update(contextId, idMainF, ConstantsGen.INTEGER), + TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), + context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("79") } - it should "send method pointer updates of partially applied static method returning a method" in { + it should "send method pointer updates of constructors" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x_1 = metadata.addItem(17, 9, "aa") - val id_x_2 = metadata.addItem(37, 5, "ab") + val metadata = new Metadata + val idA = metadata.addItem(47, 3, "aa") + val idB = metadata.addItem(59, 6, "ab") + val idC = metadata.addItem(70, 7, "ac") val code = - """main = - | x_1 = func1 1 2 - | x_2 = x_1 3 - | x_2 + """type T + | A + | B x + | C y z | - |func1 x = func2 x - |func2 x y z = x + y + z + |main = + | a = T.A + | b = T.B 42 + | T.C a b |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -1687,65 +646,61 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveN(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, moduleName, "func1") - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, moduleName, "func2"), - Vector(2) - ) - ) + idA, + "Enso_Test.Test.Main.T", + Api.MethodCall( + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") ) ), TestMessages.update( contextId, - id_x_2, - ConstantsGen.INTEGER, + idB, + "Enso_Test.Test.Main.T", Api.MethodCall( - Api - .MethodPointer(moduleName, moduleName, "func2"), - Vector() + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "B") + ) + ), + TestMessages.update( + contextId, + idC, + "Enso_Test.Test.Main.T", + Api.MethodCall( + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "C") ) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of partially applied type method returning a method" in { + it should "send method pointer updates of simple_suspended_constructors" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x_1 = metadata.addItem(17, 9, "aa") - val id_x_2 = metadata.addItem(37, 5, "ab") - val id_x_3 = metadata.addItem(53, 7, "ac") + val metadata = new Metadata + val idA = metadata.addItem(52, 3, "aa") val code = - """main = - | x_1 = T.A.func1 - | x_2 = x_1 1 - | x_3 = x_2 2 3 - | x_3 - | - |type T + """type T | A - | func1 self x = self.func2 x - | func2 self x y z = x + y + z + | B x + | C y z + | + |main = + | a = test T.A + | a + | + |test ~t:T = t |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(idA, code, "T.A") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -1774,82 +729,45 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveN(3) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(1) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(1) - ) - ) - ) - ), - TestMessages.update( - contextId, - id_x_2, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1") - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func2"), - Vector(2, 3) - ) - ) - ) - ), - TestMessages.update( - contextId, - id_x_3, - ConstantsGen.INTEGER, + idA, + "Enso_Test.Test.Main.T", Api.MethodCall( - Api.MethodPointer(moduleName, s"$moduleName.T", "func2") + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") ) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of partially applied static methods defined on type" in { + it should "send method pointer updates of simple_autoscoped_constructors" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(18, 7, "aa") - val id_x1_2 = metadata.addItem(37, 6, "ab") - val id_x1 = metadata.addItem(53, 6, "ac") + val metadata = new Metadata + val idA = metadata.addItem(52, 3, "aa") val code = - """main = - | x1_1 = T.func1 - | x1_2 = x1_1 1 - | x1 = x1_2 2 - | x1 - | - |type T + """type T | A + | B x + | C y z | - | func1 x y = x + y + |main = + | a = test ..A + | a + | + |test t:T = t |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(idA, code, "..A") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -1878,96 +796,50 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveN(3) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(0, 1) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(0, 1) - ) - ) - ) - ), - TestMessages.update( - contextId, - id_x1_2, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(1) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(1) - ) - ) - ) - ), - TestMessages.update( - contextId, - id_x1, - ConstantsGen.INTEGER, + idA, + "Enso_Test.Test.Main.T", Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ) + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") ) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of partially applied static methods without application" in { - pending + it should "send method pointer updates of autoscope constructors" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(18, 7, "aa") - val id_x1_2 = metadata.addItem(37, 4, "ab") - val id_x1 = metadata.addItem(51, 8, "ac") + val metadata = new Metadata + val idA = metadata.addItem(52, 3, "aa") + val idB = metadata.addItem(70, 6, "ab") + val idC = metadata.addItem(88, 7, "ac") val code = - """main = - | x1_1 = T.func1 - | x1_2 = x1_1 - | x1 = x1_2 1 2 - | x1 - | - |type T + """type T | A + | B x + | C y z | - | func1 x y = x + y + |main = + | a = test ..A + | b = test (..B 42) + | test (..C a b) + | + |test t:T = t |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(idA, code, "..A") + metadata.assertInCode(idB, code, "..B 42") + metadata.assertInCode(idC, code, "..C a b") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -1996,76 +868,61 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveN(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1_1, - ConstantsGen.FUNCTION_BUILTIN, + idA, + "Enso_Test.Test.Main.T", Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(0, 1) + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") ) ), TestMessages.update( contextId, - id_x1_2, - ConstantsGen.FUNCTION_BUILTIN, - // the method call is missing + idB, + "Enso_Test.Test.Main.T", Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(1, 2) + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "B") ) ), TestMessages.update( contextId, - id_x1, - ConstantsGen.INTEGER, + idC, + "Enso_Test.Test.Main.T", Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ) + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "C") ) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of partially applied static methods defined as extensions on type" in { + it should "send method pointer updates of when autoscope constructor changes to a value" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(18, 7, "aa") - val id_x1_2 = metadata.addItem(37, 6, "ab") - val id_x1 = metadata.addItem(53, 6, "ac") + val metadata = new Metadata + val idA = metadata.addItem(44, 3, "aa") val code = - """main = - | x1_1 = T.func1 - | x1_2 = x1_1 1 - | x1 = x1_2 2 - | x1 + """type Xyz | |type T | A | - |T.func1 x y = x + y + |main = + | a = test Xyz + | a + | + |test t:(Xyz | T) = t |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(idA, code, "Xyz") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -2094,92 +951,88 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveN(3) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(0, 1) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(0, 1) + idA, + "Enso_Test.Test.Main.Xyz" + ), + context.executionComplete(contextId) + ) + + // Modify the file /Xyz/..A/ + context.send( + Api.Request( + Api.EditFileNotification( + mainFile, + Seq( + model.TextEdit( + model.Range(model.Position(6, 13), model.Position(6, 16)), + "..A" ) - ) + ), + execute = true, + idMap = None ) - ), + ) + ) + + context.receiveN(3) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idA), TestMessages.update( contextId, - id_x1_2, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(1) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(1) - ) - ) + idA, + "Enso_Test.Test.Main.T", + Api.MethodCall( + Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main.T", "A") ) ), + context.executionComplete(contextId) + ) + + // Modify the file /..A/Xyz/ + context.send( + Api.Request( + Api.EditFileNotification( + mainFile, + Seq( + model.TextEdit( + model.Range(model.Position(6, 13), model.Position(6, 16)), + "Xyz" + ) + ), + execute = true, + idMap = None + ) + ) + ) + + context.receiveN(3) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idA), TestMessages.update( contextId, - id_x1, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ) - ) + idA, + "Enso_Test.Test.Main.Xyz" ), context.executionComplete(contextId) ) } - it should "send method pointer updates of partially applied methods defined on type" in { + it should "send method pointer updates of builtin operators" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(30, 7, "aa") - val id_x1_2 = metadata.addItem(49, 6, "ab") - val id_x1 = metadata.addItem(65, 6, "ac") + val metadata = new Metadata + val id_x_1 = metadata.addItem(48, 5, "aa") val code = - """main = - | a = T.A - | x1_1 = a.func1 - | x1_2 = x1_1 1 - | x1 = x1_2 2 - | x1 - | - |type T - | A + """from Standard.Base import all | - | func1 self x y = x + y + |main = + | x_1 = 3 ^ 4 + | x_1 |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2204,71 +1057,25 @@ class RuntimeServerTest requestId, Api.PushContextRequest( contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) - ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - id_x1_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(1, 2) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(1, 2) - ) - ) - ) - ), - TestMessages.update( - contextId, - id_x1_2, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(2) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(2) - ) + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() ) ) - ), + ) + ) + context.receiveNIgnoreStdLib(3) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1, + id_x_1, ConstantsGen.INTEGER, Api.MethodCall( Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" + "Standard.Base.Data.Numbers", + "Standard.Base.Data.Numbers.Integer", + "^" ) ) ), @@ -2276,28 +1083,22 @@ class RuntimeServerTest ) } - it should "send method pointer updates of partially applied methods defined as extensions on type" in { + it should "send method pointer updates of partially applied builtin operators" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(30, 7, "aa") - val id_x1_2 = metadata.addItem(49, 6, "ab") - val id_x1 = metadata.addItem(65, 6, "ac") + val metadata = new Metadata + val id_x_1 = metadata.addItem(48, 5, "aa") + val id_x_2 = metadata.addItem(64, 7, "ab") val code = - """main = - | a = T.A - | x1_1 = a.func1 - | x1_2 = x1_1 1 - | x1 = x1_2 2 - | x1 - | - |type T - | A + """from Standard.Base import all | - |T.func1 self x y = x + y + |main = + | x_1 = "4" + + | x_2 = x_1 "2" + | x_2 |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2330,92 +1131,52 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + val textPlusMethodPointer = Api.MethodPointer( + "Standard.Base.Data.Text", + "Standard.Base.Data.Text.Text", + "+" + ) + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(1, 2) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(1, 2) - ) - ) - ) - ), - TestMessages.update( - contextId, - id_x1_2, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(2) - ) - ), + id_x_1, + ConstantsGen.FUNCTION, + methodCall = Some(Api.MethodCall(textPlusMethodPointer, Vector(1))), payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(2) - ) - ) + None, + Some(Api.FunctionSchema(textPlusMethodPointer, Vector(1))) ) ), TestMessages.update( contextId, - id_x1, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ) - ) + id_x_2, + ConstantsGen.TEXT, + Api.MethodCall(textPlusMethodPointer) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of partially applied atom methods called with static notation" in { + it should "send method pointer updates of partially applied constructors" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(30, 7, "aa") - val id_x1_2 = metadata.addItem(49, 10, "ab") - val id_x1 = metadata.addItem(69, 6, "ac") + val metadata = new Metadata + val id_x_0 = metadata.addItem(35, 3, "aa") + val id_x_1 = metadata.addItem(49, 5, "ab") + val id_x_2 = metadata.addItem(65, 5, "ac") val code = - """main = - | a = T.A - | x1_1 = T.func1 - | x1_2 = x1_1 a y=2 - | x1 = x1_2 1 - | x1 - | - |type T - | A + """type T + | A x y | - | func1 self x y = x + y + |main = + | x_0 = T.A + | x_1 = x_0 1 + | x_2 = x_1 2 + | x_2 |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2448,49 +1209,38 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveN(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1_1, + id_x_0, ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), - Vector(0, 1, 2) - ) + Api.MethodCall(Api.MethodPointer(moduleName, s"$moduleName.T", "A")) ), payload = Api.ExpressionUpdate.Payload.Value( functionSchema = Some( Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), - Vector(0, 1, 2) + Api.MethodPointer(moduleName, s"$moduleName.T", "A"), + Vector(0, 1) ) ) ) ), TestMessages.update( contextId, - id_x1_2, + id_x_1, ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ), + Api.MethodPointer(moduleName, s"$moduleName.T", "A"), Vector(1) ) ), payload = Api.ExpressionUpdate.Payload.Value( functionSchema = Some( Api.FunctionSchema( - Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Api.MethodPointer(moduleName, s"$moduleName.T", "A"), Vector(1) ) ) @@ -2498,42 +1248,40 @@ class RuntimeServerTest ), TestMessages.update( contextId, - id_x1, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer( - "Enso_Test.Test.Main", - "Enso_Test.Test.Main.T", - "func1" - ) - ) + id_x_2, + s"$moduleName.T", + Api.MethodCall(Api.MethodPointer(moduleName, s"$moduleName.T", "A")) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of partially applied module methods called without module name" in { + it should "send error updates for partially applied autoscope constructors" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(18, 5, "aa") - val id_x1_2 = metadata.addItem(35, 8, "ab") - val id_x1 = metadata.addItem(53, 8, "ac") + val metadata = new Metadata + val id_x_0 = metadata.addItem(40, 3, "aa") + val id_x_1 = metadata.addItem(60, 5, "ab") val code = - """main = - | x1_1 = func1 - | x1_2 = x1_1 y=2 - | x1 = x1_2 1 3 - | x1 + """type T + | A x y | - |func1 x y z = x + y + z + |main = + | x_0 = test ..A + | x_1 = test (..A 1) + | T.A x_0 x_1 + | + |test t:T = t |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode(id_x_0, code, "..A") + metadata.assertInCode(id_x_1, code, "..A 1") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( @@ -2562,74 +1310,72 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveN(3) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1_1, + id_x_0, ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( Api.MethodCall( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 1, 2) + Api.MethodPointer(moduleName, s"$moduleName.T", "A"), + Vector(0, 1) ) ), payload = Api.ExpressionUpdate.Payload.Value( functionSchema = Some( Api.FunctionSchema( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 1, 2) + Api.MethodPointer(moduleName, s"$moduleName.T", "A"), + Vector(0, 1) ) ) ) ), - TestMessages.update( - contextId, - id_x1_2, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 2) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 2) + Api.Response( + Api.ExecutionFailed( + contextId, + Api.ExecutionResult.Diagnostic.error( + "Type_Error.Error", + Some(mainFile), + Some(model.Range(model.Position(8, 0), model.Position(8, 12))), + None, + Vector( + Api.StackTraceElement( + "Main.test", + Some(mainFile), + Some(model.Range(model.Position(8, 0), model.Position(8, 12))), + None + ), + Api.StackTraceElement( + "Main.main", + Some(mainFile), + Some(model.Range(model.Position(4, 10), model.Position(4, 18))), + None + ) ) - ) - ) - ), - TestMessages.update( - contextId, - id_x1, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "func1")) - ), - context.executionComplete(contextId) + ) + ) + ) ) } - it should "send method pointer updates of partially applied module methods called with module name" in { + it should "send method pointer updates of partially applied static method returning a method" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val id_x1_1 = metadata.addItem(18, 10, "aa") - val id_x1_2 = metadata.addItem(40, 8, "ab") - val id_x1 = metadata.addItem(58, 8, "ac") + val id_x_1 = metadata.addItem(17, 9, "aa") + val id_x_2 = metadata.addItem(37, 5, "ab") val code = """main = - | x1_1 = Main.func1 - | x1_2 = x1_1 y=2 - | x1 = x1_2 1 3 - | x1 + | x_1 = func1 1 2 + | x_2 = x_1 3 + | x_2 | - |func1 x y z = x + y + z + |func1 x = func2 x + |func2 x y z = x + y + z |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2662,70 +1408,61 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x1_1, - ConstantsGen.FUNCTION_BUILTIN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 1, 2) - ) - ), - payload = Api.ExpressionUpdate.Payload.Value( - functionSchema = Some( - Api.FunctionSchema( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 1, 2) - ) - ) - ) - ), - TestMessages.update( - contextId, - id_x1_2, + id_x_1, ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( Api.MethodCall( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 2) + Api.MethodPointer(moduleName, moduleName, "func1") ) ), payload = Api.ExpressionUpdate.Payload.Value( functionSchema = Some( Api.FunctionSchema( - Api.MethodPointer(moduleName, moduleName, "func1"), - Vector(0, 2) + Api.MethodPointer(moduleName, moduleName, "func2"), + Vector(2) ) ) ) ), TestMessages.update( contextId, - id_x1, + id_x_2, ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "func1")) + Api.MethodCall( + Api + .MethodPointer(moduleName, moduleName, "func2"), + Vector() + ) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of a builtin method" in { + it should "send method pointer updates of partially applied type method returning a method" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val id_x = metadata.addItem(46, 17, "aa") + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x_1 = metadata.addItem(17, 9, "aa") + val id_x_2 = metadata.addItem(37, 5, "ab") + val id_x_3 = metadata.addItem(53, 7, "ac") val code = - """from Standard.Base import all + """main = + | x_1 = T.A.func1 + | x_2 = x_1 1 + | x_3 = x_2 2 3 + | x_3 | - |main = - | x = "hello" + "world" - | x + |type T + | A + | func1 self x = self.func2 x + | func2 self x y z = x + y + z |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2758,40 +1495,78 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(3) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - id_x, - ConstantsGen.TEXT, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Data.Text", - "Standard.Base.Data.Text.Text", - "+" + id_x_1, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(1) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(1) + ) + ) + ) + ), + TestMessages.update( + contextId, + id_x_2, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1") ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func2"), + Vector(2, 3) + ) + ) + ) + ), + TestMessages.update( + contextId, + id_x_3, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, s"$moduleName.T", "func2") ) ), context.executionComplete(contextId) ) } - it should "send method pointer updates of a builtin method called as static" in { + it should "send method pointer updates of partially applied static methods defined on type" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val id_x = metadata.addItem(52, 25, "aa") - val id_y = metadata.addItem(86, 16, "ab") + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(18, 7, "aa") + val id_x1_2 = metadata.addItem(37, 6, "ab") + val id_x1 = metadata.addItem(53, 6, "ac") val code = - """import Standard.Base.Data.Time.Date + """main = + | x1_1 = T.func1 + | x1_2 = x1_1 1 + | x1 = x1_2 2 + | x1 | - |main = - | x = Date.new_builtin 1970 1 1 - | y = Date.Date.year x - | y + |type T + | A + | + | func1 x y = x + y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2824,92 +1599,92 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, id_x, ConstantsGen.DATE), TestMessages.update( contextId, - id_y, - ConstantsGen.INTEGER_BUILTIN, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Data.Time.Date", - "Standard.Base.Data.Time.Date.Date", - "year" + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(0, 1) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(0, 1) + ) ) ) ), - context.executionComplete(contextId) - ) - } - - it should "not send method pointer updates of a builtin method defined as static" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - val metadata = new Metadata - val id_x = metadata.addItem(52, 25, "aa") - - val code = - """import Standard.Base.Data.Time.Date - | - |main = - | x = Date.new_builtin 2022 1 1 - | 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 file - context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() + TestMessages.update( + contextId, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(1) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(1) + ) + ) + ) + ), + TestMessages.update( + contextId, + id_x1, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" ) ) - ) - ) - context.receiveNIgnoreStdLib(3) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, id_x, "Standard.Base.Data.Time.Date.Date"), + ), context.executionComplete(contextId) ) } - it should "send updates from last line" in { + it should "send method pointer updates of partially applied static methods without application" in { + pending val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val idMain = metadata.addItem(23, 12) - val idMainFoo = metadata.addItem(28, 7) + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(18, 7, "aa") + val id_x1_2 = metadata.addItem(37, 4, "ab") + val id_x1 = metadata.addItem(51, 8, "ac") val code = - """foo a b = a + b + """main = + | x1_1 = T.func1 + | x1_2 = x1_1 + | x1 = x1_2 1 2 + | x1 | - |main = - | foo 1 2 + |type T + | A + | + | func1 x y = x + y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2942,35 +1717,72 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainFoo, + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(0, 1) + ) + ), + TestMessages.update( + contextId, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, + // the method call is missing + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(1, 2) + ) + ), + TestMessages.update( + contextId, + id_x1, ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ) + ) ), - TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), context.executionComplete(contextId) ) } - it should "compute side effects correctly from last line" in { + it should "send method pointer updates of partially applied static methods defined as extensions on type" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val idMain = metadata.addItem(54, 25) - val idMainFoo = metadata.addItem(71, 7) + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(18, 7, "aa") + val id_x1_2 = metadata.addItem(37, 6, "ab") + val id_x1 = metadata.addItem(53, 6, "ac") val code = - """from Standard.Base import all + """main = + | x1_1 = T.func1 + | x1_2 = x1_1 1 + | x1 = x1_2 2 + | x1 | - |foo a b = a + b + |type T + | A | - |main = - | IO.println (foo 1 2) + |T.func1 x y = x + y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -2996,43 +1808,99 @@ class RuntimeServerTest Api.PushContextRequest( contextId, Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + Api.MethodPointer(moduleName, moduleName, "main"), None, Vector() ) ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainFoo, + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(0, 1) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(0, 1) + ) + ) + ) + ), + TestMessages.update( + contextId, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(1) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(1) + ) + ) + ) + ), + TestMessages.update( + contextId, + id_x1, ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ) + ) ), - TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("3") } - it should "run State getting the initial state" in { + it should "send method pointer updates of partially applied methods defined on type" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val idMain = metadata.addItem(73, 36) - val idMainBar = metadata.addItem(105, 3) + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(30, 7, "aa") + val id_x1_2 = metadata.addItem(49, 6, "ab") + val id_x1 = metadata.addItem(65, 6, "ac") val code = - """from Standard.Base import all - |import Standard.Base.Runtime.State + """main = + | a = T.A + | x1_1 = a.func1 + | x1_2 = x1_1 1 + | x1 = x1_2 2 + | x1 | - |main = IO.println (State.run Number 42 bar) + |type T + | A | - |bar = State.get Number + | func1 self x y = x + y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3058,58 +1926,99 @@ class RuntimeServerTest Api.PushContextRequest( contextId, Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + Api.MethodPointer(moduleName, moduleName, "main"), None, Vector() ) ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainBar, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "bar")) + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(1, 2) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(1, 2) + ) + ) + ) ), TestMessages.update( contextId, - idMain, - ConstantsGen.NOTHING, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( Api.MethodCall( Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(2) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(2) ) ) ) ), + TestMessages.update( + contextId, + id_x1, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ) + ) + ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("42") } - it should "run State setting the state" in { + it should "send method pointer updates of partially applied methods defined as extensions on type" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val idMain = metadata.addItem(73, 35) - val idMainBar = metadata.addItem(104, 3) + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(30, 7, "aa") + val id_x1_2 = metadata.addItem(49, 6, "ab") + val id_x1 = metadata.addItem(65, 6, "ac") val code = - """from Standard.Base import all - |import Standard.Base.Runtime.State + """main = + | a = T.A + | x1_1 = a.func1 + | x1_2 = x1_1 1 + | x1 = x1_2 2 + | x1 | - |main = IO.println (State.run Number 0 bar) + |type T + | A | - |bar = - | State.put Number 10 - | State.get Number + |T.func1 self x y = x + y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3142,48 +2051,92 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainBar, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "bar")) + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(1, 2) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(1, 2) + ) + ) + ) ), TestMessages.update( contextId, - idMain, - ConstantsGen.NOTHING, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( Api.MethodCall( Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(2) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(2) ) ) ) ), + TestMessages.update( + contextId, + id_x1, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ) + ) + ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("10") } - it should "send updates of a function call" in { + it should "send method pointer updates of partially applied atom methods called with static notation" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") - val idMain = metadata.addItem(23, 18) - val idMainFoo = metadata.addItem(28, 7) + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(30, 7, "aa") + val id_x1_2 = metadata.addItem(49, 10, "ab") + val id_x1 = metadata.addItem(69, 6, "ac") val code = - """foo a b = a + b + """main = + | a = T.A + | x1_1 = T.func1 + | x1_2 = x1_1 a y=2 + | x1 = x1_2 1 + | x1 | - |main = - | foo 1 2 - | 1 + |type T + | A + | + | func1 self x y = x + y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3216,44 +2169,88 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainFoo, + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(0, 1, 2) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(0, 1, 2) + ) + ) + ) + ), + TestMessages.update( + contextId, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ), + Vector(1) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, s"$moduleName.T", "func1"), + Vector(1) + ) + ) + ) + ), + TestMessages.update( + contextId, + id_x1, ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main.T", + "func1" + ) + ) ), - TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), context.executionComplete(contextId) ) } - it should "send updates when function body is changed" in { + it should "send method pointer updates of partially applied module methods called without module name" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - // foo definition - metadata.addItem(25, 22) - // foo name - metadata.addItem(25, 3) - val fooX = metadata.addItem(45, 1, "aa") - val fooRes = metadata.addItem(51, 1, "ab") - val mainFoo = metadata.addItem(69, 3, "ac") - val mainRes = metadata.addItem(77, 12, "ad") + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(18, 5, "aa") + val id_x1_2 = metadata.addItem(35, 8, "ab") + val id_x1 = metadata.addItem(53, 8, "ac") val code = - """from Standard.Base import all - | - |foo = - | x = 4 - | x + """main = + | x1_1 = func1 + | x1_2 = x1_1 y=2 + | x1 = x1_2 1 3 + | x1 | - |main = - | y = foo - | IO.println y + |func1 x y z = x + y + z |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3264,7 +2261,7 @@ class RuntimeServerTest Api.Response(requestId, Api.CreateContextResponse(contextId)) ) - // Set sources for the module + // open file context.send( Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) ) @@ -3286,130 +2283,74 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages - .update( - contextId, - mainFoo, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) - ), TestMessages.update( contextId, - mainRes, - ConstantsGen.NOTHING, + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 1, 2) ) - ) - ), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("4") - - // push foo call - context.send( - Api.Request( - requestId, - Api.PushContextRequest(contextId, Api.StackItem.LocalCall(mainFoo)) - ) - ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, fooX, ConstantsGen.INTEGER), - TestMessages.update(contextId, fooRes, ConstantsGen.INTEGER), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("4") - - // Modify the foo method - context.send( - Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 9)), - "5" - ) - ), - execute = true, - idMap = None - ) - ) - ) - context.receiveN(4) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, fooX, fooRes, mainFoo, mainRes), - TestMessages - .update(contextId, fooX, ConstantsGen.INTEGER, typeChanged = false), - TestMessages - .update(contextId, fooRes, ConstantsGen.INTEGER, typeChanged = false), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("5") - - // pop the foo call - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)), - TestMessages - .update( - contextId, - mainRes, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 1, 2) ) - ), - typeChanged = false + ) + ) + ), + TestMessages.update( + contextId, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 2) + ) ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 2) + ) + ) + ) + ), TestMessages.update( contextId, - mainFoo, + id_x1, ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")), - fromCache = false, - typeChanged = false + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "func1")) ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("5") } - it should "obey the execute parameter of edit command" in { + it should "send method pointer updates of partially applied module methods called with module name" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - // foo definition - metadata.addItem(31, 22) - // foo name - metadata.addItem(31, 3) - val mainFoo = metadata.addItem(69, 3) - val mainRes = metadata.addItem(77, 12) + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val id_x1_1 = metadata.addItem(18, 10, "aa") + val id_x1_2 = metadata.addItem(40, 8, "ab") + val id_x1 = metadata.addItem(58, 8, "ac") val code = - """from Standard.Base import all - | - |foo = - | x = 4 - | x + """main = + | x1_1 = Main.func1 + | x1_2 = x1_1 y=2 + | x1 = x1_2 1 3 + | x1 | - |main = - | y = foo - | IO.println y + |func1 x y z = x + y + z |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3420,7 +2361,7 @@ class RuntimeServerTest Api.Response(requestId, Api.CreateContextResponse(contextId)) ) - // Set sources for the module + // open file context.send( Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) ) @@ -3442,103 +2383,139 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - mainFoo, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) + id_x1_1, + ConstantsGen.FUNCTION_BUILTIN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 1, 2) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 1, 2) + ) + ) + ) ), TestMessages.update( contextId, - mainRes, - ConstantsGen.NOTHING, + id_x1_2, + ConstantsGen.FUNCTION_BUILTIN, methodCall = Some( Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 2) + ) + ), + payload = Api.ExpressionUpdate.Payload.Value( + functionSchema = Some( + Api.FunctionSchema( + Api.MethodPointer(moduleName, moduleName, "func1"), + Vector(0, 2) ) ) ) ), + TestMessages.update( + contextId, + id_x1, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "func1")) + ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("4") + } - // Modify the foo method + it should "send method pointer updates of a builtin method" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val id_x = metadata.addItem(46, 17, "aa") + + val code = + """from Standard.Base import all + | + |main = + | x = "hello" + "world" + | 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 file context.send( - Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 9)), - "5" - ) - ), - execute = false, - idMap = None - ) - ) + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) ) - context.receiveNone shouldEqual None - // Modify the foo method + // push main context.send( Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 9)), - "6" - ) - ), - execute = true, - idMap = None + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) ) ) ) - context.receiveN(4) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, mainFoo, mainRes), - TestMessages.update( - contextId, - mainFoo, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")), - fromCache = false, - typeChanged = false - ), + context.receiveNIgnoreStdLib(3) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - mainRes, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) + id_x, + ConstantsGen.TEXT, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Data.Text", + "Standard.Base.Data.Text.Text", + "+" ) - ), - typeChanged = false + ) ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("6") } - it should "send updates when the type is not changed" in { + it should "send method pointer updates of a builtin method called as static" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val idMain = context.Main.metadata.addItem(54, 46, "aaaaa") - val contents = context.Main.code - val mainFile = context.writeMain(contents) + + val metadata = new Metadata + val id_x = metadata.addItem(52, 25, "aa") + val id_y = metadata.addItem(86, 16, "ab") + + val code = + """import Standard.Base.Data.Time.Date + | + |main = + | x = Date.new_builtin 1970 1 1 + | y = Date.Date.year x + | 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))) @@ -3568,60 +2545,39 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.mainX(contextId), - context.Main.Update.mainY(contextId), - context.Main.Update.mainZ(contextId), - TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), - context.executionComplete(contextId) - ) - - // push foo call - val item2 = Api.StackItem.LocalCall(context.Main.idMainY) - context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item2)) - ) - context.receiveN(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.fooY(contextId), - context.Main.Update.fooZ(contextId), - context.executionComplete(contextId) - ) - - // pop foo call - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)), - context.Main.Update.mainY(contextId, fromCache = true), - context.Main.Update.mainZ(contextId, fromCache = true), - TestMessages - .update(contextId, idMain, ConstantsGen.INTEGER, typeChanged = false), + TestMessages.update(contextId, id_x, ConstantsGen.DATE), + TestMessages.update( + contextId, + id_y, + ConstantsGen.INTEGER_BUILTIN, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Data.Time.Date", + "Standard.Base.Data.Time.Date.Date", + "year" + ) + ) + ), context.executionComplete(contextId) ) - - // pop main - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(1) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)) - ) } - it should "send updates when the type is changed" in { + it should "not send method pointer updates of a builtin method defined as static" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val idResult = metadata.addItem(51, 4, "aae") - val idPrintln = metadata.addItem(60, 17, "aaf") - val idMain = metadata.addItem(37, 40, "aaa") + val metadata = new Metadata + val id_x = metadata.addItem(52, 25, "aa") + val code = - """from Standard.Base import all + """import Standard.Base.Data.Time.Date | |main = - | result = 1337 - | IO.println result + | x = Date.new_builtin 2022 1 1 + | x |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3654,98 +2610,27 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(3) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, idResult, ConstantsGen.INTEGER), - TestMessages.update( - contextId, - idPrintln, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) - ) - ) - ), - TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("1337") - - // Modify the file - context.send( - Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 13), model.Position(3, 17)), - "\"Hi\"" - ) - ), - execute = true, - idMap = None - ) - ) - ) - context.receiveN(5) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idResult, idPrintln, idMain), - TestMessages.update(contextId, idResult, ConstantsGen.TEXT), - TestMessages.update( - contextId, - idPrintln, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) - ) - ), - typeChanged = false - ), - TestMessages - .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), + TestMessages.update(contextId, id_x, "Standard.Base.Data.Time.Date.Date"), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("Hi") } - it should "send updates when the method pointer is changed" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - val numberTypeName = "Standard.Base.Data.Numbers.Number" + it should "send updates from last line" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val idMain = metadata.addItem(23, 12) + val idMainFoo = metadata.addItem(28, 7) - val metadata = new Metadata - val idMain = metadata.addItem(37, 34, "aaaa") - val idMainA = metadata.addItem(46, 8, "aabb") - val idMainP = metadata.addItem(59, 12, "aacc") - // pie id - metadata.addItem(89, 1, "eee") - // uwu id - metadata.addItem(87, 1, "bbb") - // hie id - metadata.addItem(95, 6, "fff") - // Number.x id - metadata.addItem(115, 1, "999") val code = - """from Standard.Base import all + """foo a b = a + b | |main = - | a = 123 + 21 - | IO.println a - | - |pie = 3 - |uwu = 7 - |hie = "hie!" - |Number.x self y = y + | foo 1 2 |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3778,207 +2663,140 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainA, + idMainFoo, ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Data.Numbers", - ConstantsGen.INTEGER, - "+" - ) - ) - ), - TestMessages.update( - contextId, - idMainP, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) - ) - ) + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) ), - TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), + TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("144") + } - // Edit s/123 + 21/1234.x 4/ - context.send( - Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 16)), - "1234.x 4" - ) - ), - execute = true, - idMap = None - ) - ) - ) - context.receiveN(5) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idMain, idMainA, idMainP), - TestMessages.update( - contextId, - idMainA, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "x")) - ), - TestMessages.update( - contextId, - idMainP, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) - ) - ), - typeChanged = false - ), - TestMessages - .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("4") + it should "compute side effects correctly from last line" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" - // Edit s/1234.x 4/1000.x 5/ - context.send( - Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 16)), - "1000.x 5" - ) - ), - execute = true, - idMap = None - ) - ) - ) - context.receiveN(5) shouldEqual Seq( - TestMessages.pending(contextId, idMain, idMainA, idMainP), - TestMessages.update( - contextId, - idMainA, - ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, numberTypeName, "x")), - fromCache = false, - typeChanged = false - ), - TestMessages.update( - contextId, - idMainP, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) - ) - ), - typeChanged = false - ), - TestMessages - .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), - context.executionComplete(contextId) + val metadata = new Metadata + val idMain = metadata.addItem(54, 25) + val idMainFoo = metadata.addItem(71, 7) + + val code = + """from Standard.Base import all + | + |foo a b = a + b + | + |main = + | IO.println (foo 1 2) + |""".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)) ) - context.consumeOut shouldEqual List("5") - // Edit s/1000.x 5/Main.pie/ + // open file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main context.send( Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 16)), - "Main.pie" - ) - ), - execute = true, - idMap = None + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) ) ) ) - context.receiveN(5) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idMain, idMainA, idMainP), + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainA, + idMainFoo, ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "pie")), - fromCache = false, - typeChanged = true - ), - TestMessages.update( - contextId, - idMainP, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) - ) - ), - typeChanged = false + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) ), - TestMessages - .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), + TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), context.executionComplete(contextId) ) context.consumeOut shouldEqual List("3") + } - // Edit s/Main.pie/Main.uwu/ + it should "run State getting the initial state" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val idMain = metadata.addItem(73, 36) + val idMainBar = metadata.addItem(105, 3) + + val code = + """from Standard.Base import all + |import Standard.Base.Runtime.State + | + |main = IO.println (State.run Number 42 bar) + | + |bar = State.get Number + |""".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(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main context.send( Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 16)), - "Main.uwu" - ) - ), - execute = true, - idMap = None + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) ) ) ) - context.receiveN(5) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idMain, idMainA, idMainP), + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainA, + idMainBar, ConstantsGen.INTEGER, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "uwu")), - fromCache = false, - typeChanged = true + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "bar")) ), TestMessages.update( contextId, - idMainP, + idMain, ConstantsGen.NOTHING, methodCall = Some( Api.MethodCall( @@ -3988,88 +2806,74 @@ class RuntimeServerTest "println" ) ) - ), - typeChanged = false + ) ), - TestMessages - .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("7") + context.consumeOut shouldEqual List("42") + } - // Edit s/Main.uwu/Main.hie/ + it should "run State setting the state" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val idMain = metadata.addItem(73, 35) + val idMainBar = metadata.addItem(104, 3) + + val code = + """from Standard.Base import all + |import Standard.Base.Runtime.State + | + |main = IO.println (State.run Number 0 bar) + | + |bar = + | State.put Number 10 + | State.get Number + |""".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.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 16)), - "Main.hie" - ) - ), - execute = true, - idMap = None - ) - ) + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) ) - context.receiveN(5) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idMain, idMainA, idMainP), - TestMessages.update( - contextId, - idMainA, - ConstantsGen.TEXT, - Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "hie")) - ), - TestMessages.update( - contextId, - idMainP, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.IO", - "Standard.Base.IO", - "println" - ) - ) - ), - typeChanged = false - ), - TestMessages - .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), - context.executionComplete(contextId) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) ) - context.consumeOut shouldEqual List("hie!") - // Edit s/Main.hie/"Hello!"/ + // push main context.send( Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 16)), - "\"Hello!\"" - ) - ), - execute = true, - idMap = None + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) ) ) ) - context.receiveN(5) should contain theSameElementsAs Seq( - TestMessages.pending(contextId, idMain, idMainA, idMainP), + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), TestMessages.update( contextId, - idMainA, - ConstantsGen.TEXT, - fromCache = false, - typeChanged = true + idMainBar, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "bar")) ), TestMessages.update( contextId, - idMainP, + idMain, ConstantsGen.NOTHING, methodCall = Some( Api.MethodCall( @@ -4079,43 +2883,28 @@ class RuntimeServerTest "println" ) ) - ), - typeChanged = false - ), - TestMessages - .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), + ) + ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("Hello!") + context.consumeOut shouldEqual List("10") } - it should "send updates for overloaded functions" in { + it should "send updates of a function call" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val idMain = metadata.addItem(122, 87, "aaaa") - val id1 = metadata.addItem(131, 15, "aad1") - val id2 = metadata.addItem(151, 18, "aad2") - val id3 = metadata.addItem(174, 15, "aad3") - // Note that Nothing.Nothing is on purpose. - // If not provided the full name it will resolve the expression Nothing to a Nothing module. - // Similarly Text.Text. That in turn will mismatch the expectations for method types which actually - // return proper types. + val metadata = new Metadata("import Standard.Base.Data.Numbers\n\n") + val idMain = metadata.addItem(23, 18) + val idMainFoo = metadata.addItem(28, 7) + val code = - """from Standard.Base.Data.Numbers import Number - |from Standard.Base.Data.Text import all - |import Standard.Base.Nothing + """foo a b = a + b | |main = - | x = 15.overloaded 1 - | "foo".overloaded 2 - | 10.overloaded x - | Nothing.Nothing - | - |Text.overloaded self arg = arg + 1 - |Number.overloaded self arg = arg + 2 + | foo 1 2 + | 1 |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -4148,227 +2937,200 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), - TestMessages.update( - contextId, - id1, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") - ) - ), - TestMessages.update( - contextId, - id2, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") - ) - ), TestMessages.update( contextId, - id3, + idMainFoo, ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") - ) + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) ), + TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), context.executionComplete(contextId) ) + } - // push call1 + it should "send updates when function body is changed" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + // foo definition + metadata.addItem(25, 22) + // foo name + metadata.addItem(25, 3) + val fooX = metadata.addItem(45, 1, "aa") + val fooRes = metadata.addItem(51, 1, "ab") + val mainFoo = metadata.addItem(69, 3, "ac") + val mainRes = metadata.addItem(77, 12, "ad") + + val code = + """from Standard.Base import all + | + |foo = + | x = 4 + | x + | + |main = + | y = foo + | 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)) + ) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main context.send( Api.Request( requestId, Api.PushContextRequest( contextId, - Api.StackItem.LocalCall(id1) + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) ) ) ) - context.receiveN(2) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - context.executionComplete(contextId) - ) - - // pop call1 - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)), - TestMessages.update( - contextId, - id1, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") - ), - fromCache = true, - typeChanged = true - ), - TestMessages.update( - contextId, - id2, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") - ), - fromCache = false, - typeChanged = false - ), - TestMessages.update( - contextId, - id3, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + TestMessages + .update( + contextId, + mainFoo, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) ), - fromCache = false, - typeChanged = false - ), TestMessages.update( contextId, - idMain, + mainRes, ConstantsGen.NOTHING, - typeChanged = false + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ) ), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("4") - // push call2 + // push foo call context.send( Api.Request( requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.LocalCall(id2) - ) + Api.PushContextRequest(contextId, Api.StackItem.LocalCall(mainFoo)) ) ) - context.receiveN(2) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, fooX, ConstantsGen.INTEGER), + TestMessages.update(contextId, fooRes, ConstantsGen.INTEGER), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("4") - // pop call2 - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)), - TestMessages.update( - contextId, - id2, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") - ), - fromCache = false, - typeChanged = false - ), - TestMessages.update( - contextId, - id3, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") - ), - fromCache = false, - typeChanged = false - ), - TestMessages.update( - contextId, - idMain, - ConstantsGen.NOTHING, - typeChanged = false - ), - TestMessages.update( - contextId, - id1, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") - ), - fromCache = true, - typeChanged = true - ), - context.executionComplete(contextId) - ) - - // push call3 + // Modify the foo method context.send( Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.LocalCall(id3) + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(3, 8), model.Position(3, 9)), + "5" + ) + ), + execute = true, + idMap = None ) ) ) - context.receiveN(2) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), + context.receiveN(4) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, fooX, fooRes, mainFoo, mainRes), + TestMessages + .update(contextId, fooX, ConstantsGen.INTEGER, typeChanged = false), + TestMessages + .update(contextId, fooRes, ConstantsGen.INTEGER, typeChanged = false), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("5") - // pop call3 + // pop the foo call context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)), - TestMessages.update( - contextId, - id2, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") + context.receiveN(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)), + TestMessages + .update( + contextId, + mainRes, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ), + typeChanged = false ), - fromCache = false, - typeChanged = false - ), TestMessages.update( contextId, - id3, + mainFoo, ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") - ), - fromCache = false, - typeChanged = false - ), - TestMessages.update( - contextId, - idMain, - ConstantsGen.NOTHING, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")), fromCache = false, typeChanged = false ), - TestMessages.update( - contextId, - id1, - ConstantsGen.INTEGER, - Api.MethodCall( - Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") - ), - fromCache = true, - typeChanged = true - ), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("5") } - it should "send updates for a lambda" in { + it should "obey the execute parameter of edit command" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" val metadata = new Metadata - val xId = metadata.addItem(46, 10) - val mainRes = metadata.addItem(61, 12) + // foo definition + metadata.addItem(31, 22) + // foo name + metadata.addItem(31, 3) + val mainFoo = metadata.addItem(69, 3) + val mainRes = metadata.addItem(77, 12) val code = """from Standard.Base import all + | + |foo = + | x = 4 + | x | |main = - | x = a -> a + 1 - | IO.println x + | y = foo + | IO.println y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -4403,7 +3165,12 @@ class RuntimeServerTest ) context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, xId, ConstantsGen.FUNCTION), + TestMessages.update( + contextId, + mainFoo, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")) + ), TestMessages.update( contextId, mainRes, @@ -4420,25 +3187,79 @@ class RuntimeServerTest ), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("4") + + // Modify the foo method + context.send( + Api.Request( + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(3, 8), model.Position(3, 9)), + "5" + ) + ), + execute = false, + idMap = None + ) + ) + ) + context.receiveNone shouldEqual None + + // Modify the foo method + context.send( + Api.Request( + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(3, 8), model.Position(3, 9)), + "6" + ) + ), + execute = true, + idMap = None + ) + ) + ) + context.receiveN(4) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, mainFoo, mainRes), + TestMessages.update( + contextId, + mainFoo, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "foo")), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + mainRes, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ), + typeChanged = false + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("6") } - it should "send updates for a constructor type" in { + it should "send updates when the type is not changed" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - - val idMain = metadata.addItem(39, 27) - - val code = - """type My_Type - | My_Constructor - | - |main = - | My_Type.My_Constructor - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) - val mainFile = context.writeMain(contents) + val idMain = context.Main.metadata.addItem(54, 46, "aaaaa") + val contents = context.Main.code + val mainFile = context.writeMain(contents) // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) @@ -4446,7 +3267,7 @@ class RuntimeServerTest Api.Response(requestId, Api.CreateContextResponse(contextId)) ) - // Set sources for the module + // open file context.send( Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) ) @@ -4468,61 +3289,113 @@ class RuntimeServerTest ) ) ) - context.receiveN(3) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, idMain, s"$moduleName.My_Type"), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), context.executionComplete(contextId) ) + + // push foo call + val item2 = Api.StackItem.LocalCall(context.Main.idMainY) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item2)) + ) + context.receiveN(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.fooY(contextId), + context.Main.Update.fooZ(contextId), + context.executionComplete(contextId) + ) + + // pop foo call + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)), + context.Main.Update.mainY(contextId, fromCache = true), + context.Main.Update.mainZ(contextId, fromCache = true), + TestMessages + .update(contextId, idMain, ConstantsGen.INTEGER, typeChanged = false), + context.executionComplete(contextId) + ) + + // pop main + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveN(1) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)) + ) } - it should "support file modification operations without attached ids" in { + it should "send updates when the type is changed" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) - + val metadata = new Metadata + val idResult = metadata.addItem(51, 4, "aae") + val idPrintln = metadata.addItem(60, 17, "aaf") + val idMain = metadata.addItem(37, 40, "aaa") val code = - """import Standard.Base.IO + """from Standard.Base import all | - |main = IO.println "I'm a file!" + |main = + | result = 1337 + | IO.println result |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) - // Create a new file - val mainFile = context.writeMain(code) + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) - // Set sources for the module + // open file context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) ) context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) ) - context.consumeOut shouldEqual List() - // Push new item on the stack to trigger the re-execution + // push main context.send( Api.Request( requestId, Api.PushContextRequest( contextId, - Api.StackItem - .ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) ) ) ) - context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idResult, ConstantsGen.INTEGER), + TestMessages.update( + contextId, + idPrintln, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ) + ), + TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("I'm a file!") + context.consumeOut shouldEqual List("1337") // Modify the file context.send( @@ -4530,9 +3403,9 @@ class RuntimeServerTest Api.EditFileNotification( mainFile, Seq( - TextEdit( - model.Range(model.Position(2, 25), model.Position(2, 29)), - "modified" + TextEdit( + model.Range(model.Position(3, 13), model.Position(3, 17)), + "\"Hi\"" ) ), execute = true, @@ -4540,95 +3413,134 @@ class RuntimeServerTest ) ) ) - context.receiveN(1) shouldEqual Seq( + context.receiveN(5) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idResult, idPrintln, idMain), + TestMessages.update(contextId, idResult, ConstantsGen.TEXT), + TestMessages.update( + contextId, + idPrintln, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ), + typeChanged = false + ), + TestMessages + .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("I'm a modified!") - - // Close the file - context.send(Api.Request(Api.CloseFileNotification(mainFile))) - context.consumeOut shouldEqual List() + context.consumeOut shouldEqual List("Hi") } - it should "support file modifications after reopening the file" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) + it should "send updates when the method pointer is changed" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val numberTypeName = "Standard.Base.Data.Numbers.Number" - val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + val idMain = metadata.addItem(37, 34, "aaaa") + val idMainA = metadata.addItem(46, 8, "aabb") + val idMainP = metadata.addItem(59, 12, "aacc") + // pie id + metadata.addItem(89, 1, "eee") + // uwu id + metadata.addItem(87, 1, "bbb") + // hie id + metadata.addItem(95, 6, "fff") + // Number.x id + metadata.addItem(115, 1, "999") val code = - """import Standard.Base.IO + """from Standard.Base import all | - |main = IO.println "I'm a file!" + |main = + | a = 123 + 21 + | IO.println a + | + |pie = 3 + |uwu = 7 + |hie = "hie!" + |Number.x self y = y |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) - // Create a new file - val mainFile = context.writeMain(code) + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) - // Set sources for the module + // open file context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) ) context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) ) - context.consumeOut shouldEqual List() - // Push new item on the stack to trigger the re-execution + // push main context.send( Api.Request( requestId, Api.PushContextRequest( contextId, - Api.StackItem - .ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) ) ) ) - context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idMainA, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Data.Numbers", + ConstantsGen.INTEGER, + "+" + ) + ) + ), + TestMessages.update( + contextId, + idMainP, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ) + ), + TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("I'm a file!") - - // Close the file - context.send(Api.Request(Api.CloseFileNotification(mainFile))) - context.consumeOut shouldEqual List() - - val contextId2 = UUID.randomUUID() - val requestId2 = UUID.randomUUID() - - context.send(Api.Request(requestId2, Api.CreateContextRequest(contextId2))) - context.receive shouldEqual Some( - Api.Response(requestId2, Api.CreateContextResponse(contextId2)) - ) - - // Re-open the the file and apply the same operation - context.send( - Api.Request(requestId2, Api.OpenFileRequest(mainFile, code)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId2), Api.OpenFileResponse) - ) - context.consumeOut shouldEqual List() + context.consumeOut shouldEqual List("144") - // Modify the file + // Edit s/123 + 21/1234.x 4/ context.send( Api.Request( Api.EditFileNotification( mainFile, Seq( TextEdit( - model.Range(model.Position(2, 25), model.Position(2, 29)), - "modified" + model.Range(model.Position(3, 8), model.Position(3, 16)), + "1234.x 4" ) ), execute = true, @@ -4636,67 +3548,91 @@ class RuntimeServerTest ) ) ) - context.receiveN(1) shouldEqual Seq( + context.receiveN(5) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idMain, idMainA, idMainP), + TestMessages.update( + contextId, + idMainA, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "x")) + ), + TestMessages.update( + contextId, + idMainP, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ), + typeChanged = false + ), + TestMessages + .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("I'm a modified!") - - } - - it should "support file modification operations with attached ids" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - val metadata = new Metadata - val idMain = metadata.addItem(7, 2) - val code = metadata.appendToCode("main = 84") - - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) - - // Create a new file - val mainFile = context.writeMain(code) - - // Set sources for the module - context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) + context.consumeOut shouldEqual List("4") - // Push new item on the stack to trigger the re-execution + // Edit s/1234.x 4/1000.x 5/ context.send( Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem - .ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(3, 8), model.Position(3, 16)), + "1000.x 5" ) + ), + execute = true, + idMap = None ) ) ) - context.receiveN(3) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, idMain, ConstantsGen.INTEGER_BUILTIN), + context.receiveN(5) shouldEqual Seq( + TestMessages.pending(contextId, idMain, idMainA, idMainP), + TestMessages.update( + contextId, + idMainA, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, numberTypeName, "x")), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + idMainP, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ), + typeChanged = false + ), + TestMessages + .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("5") - // Modify the file + // Edit s/1000.x 5/Main.pie/ context.send( Api.Request( Api.EditFileNotification( mainFile, Seq( TextEdit( - model.Range(model.Position(0, 0), model.Position(0, 9)), - "main = 42" + model.Range(model.Position(3, 8), model.Position(3, 16)), + "Main.pie" ) ), execute = true, @@ -4704,170 +3640,46 @@ class RuntimeServerTest ) ) ) - context.receiveN(3) shouldEqual Seq( - TestMessages.pending(contextId, idMain), + context.receiveN(5) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idMain, idMainA, idMainP), TestMessages.update( contextId, - idMain, - ConstantsGen.INTEGER_BUILTIN, + idMainA, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "pie")), + fromCache = false, + typeChanged = true + ), + TestMessages.update( + contextId, + idMainP, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ), typeChanged = false ), - context.executionComplete(contextId) - ) - } - - it should "send suggestion notifications when file is executed" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - val idMain = context.Main.metadata.addItem(54, 46, "aaaa") - - val mainFile = context.writeMain(context.Main.code) - - // create context - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) - - // Set sources for the module - context.send( - Api.Request( - requestId, - Api.OpenFileRequest(mainFile, context.Main.code) - ) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - - // push main - val item1 = Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item1)) - ) - context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.mainX(contextId), - context.Main.Update.mainY(contextId), - context.Main.Update.mainZ(contextId), - TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), - context.executionComplete(contextId) - ) - - // push foo call - val item2 = Api.StackItem.LocalCall(context.Main.idMainY) - context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item2)) - ) - context.receiveN(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.fooY(contextId), - context.Main.Update.fooZ(contextId), - context.executionComplete(contextId) - ) - - // pop foo call - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)), - context.Main.Update.mainY(contextId, fromCache = true), - context.Main.Update.mainZ(contextId, fromCache = true), TestMessages - .update(contextId, idMain, ConstantsGen.INTEGER, typeChanged = false), - context.executionComplete(contextId) - ) - - // pop main - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receiveN(1) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PopContextResponse(contextId)) - ) - - // pop empty stack - context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.EmptyStackError(contextId)) - ) - } - - it should "send suggestion notifications when file is modified" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - val newline = System.lineSeparator() - - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) - - val code = - """from Standard.Base.Data.Numbers import Number - |import Standard.Base.IO - | - |main = IO.println "I'm a file!" - |""".stripMargin.linesIterator.mkString("\n") - - // Create a new file - val mainFile = context.writeMain(code) - - // Set sources for the module - context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) - context.consumeOut shouldEqual List() - - // Push new item on the stack to trigger the re-execution - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem - .ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - ) - ) - ) - context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), + .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("I'm a file!") + context.consumeOut shouldEqual List("3") - /* - Modify the file: - """from Standard.Base.Data.Numbers import Number - |import Standard.Base.IO - | - |Number.lucky = 42 - | - |main = IO.println "I'm a modified!" - |""".stripMargin.linesIterator.mkString("\n") - */ + // Edit s/Main.pie/Main.uwu/ context.send( Api.Request( Api.EditFileNotification( mainFile, Seq( TextEdit( - model.Range(model.Position(3, 25), model.Position(3, 29)), - "modified" - ), - TextEdit( - model.Range(model.Position(3, 0), model.Position(3, 0)), - s"Number.lucky = 42$newline$newline" + model.Range(model.Position(3, 8), model.Position(3, 16)), + "Main.uwu" ) ), execute = true, @@ -4875,77 +3687,64 @@ class RuntimeServerTest ) ) ) - context.receiveN(1) should contain theSameElementsAs Seq( - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("I'm a modified!") - - // Close the file - context.send(Api.Request(Api.CloseFileNotification(mainFile))) - context.receiveNone shouldEqual None - context.consumeOut shouldEqual List() - } - - it should "send expression updates when file is restored" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" - - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) - - val metadata = new Metadata - val idText = metadata.addItem(49, 12, "aa") - val idRes = metadata.addItem(66, 15, "ab") - - def template(text: String) = - metadata.appendToCode( - s"""from Standard.Base import all - | - |main = - | text = "$text" - | IO.println text - |""".stripMargin.linesIterator.mkString("\n") - ) - - val prompt1 = "I'm a one!" - val code = template(prompt1) - - // Create a new file - val mainFile = context.writeMain(code) - - // Set sources for the module - context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) + context.receiveN(5) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idMain, idMainA, idMainP), + TestMessages.update( + contextId, + idMainA, + ConstantsGen.INTEGER, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "uwu")), + fromCache = false, + typeChanged = true + ), + TestMessages.update( + contextId, + idMainP, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ), + typeChanged = false + ), + TestMessages + .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), + context.executionComplete(contextId) ) - context.consumeOut shouldEqual List() + context.consumeOut shouldEqual List("7") - // Push new item on the stack to trigger the re-execution + // Edit s/Main.uwu/Main.hie/ context.send( Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem - .ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(3, 8), model.Position(3, 16)), + "Main.hie" ) + ), + execute = true, + idMap = None ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update(contextId, idText, ConstantsGen.TEXT), + context.receiveN(5) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idMain, idMainA, idMainP), TestMessages.update( contextId, - idRes, + idMainA, + ConstantsGen.TEXT, + Api.MethodCall(Api.MethodPointer(moduleName, moduleName, "hie")) + ), + TestMessages.update( + contextId, + idMainP, ConstantsGen.NOTHING, methodCall = Some( Api.MethodCall( @@ -4955,25 +3754,24 @@ class RuntimeServerTest "println" ) ) - ) + ), + typeChanged = false ), + TestMessages + .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List(prompt1) - - // Simulate file update in FS - val prompt2 = "I'm a two!" - val code2 = template(prompt2) - context.writeMain(code2) + context.consumeOut shouldEqual List("hie!") + // Edit s/Main.hie/"Hello!"/ context.send( Api.Request( Api.EditFileNotification( mainFile, Seq( TextEdit( - model.Range(model.Position(0, 0), model.Position(9, 2)), - code2 + model.Range(model.Position(3, 8), model.Position(3, 16)), + "\"Hello!\"" ) ), execute = true, @@ -4981,14 +3779,18 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnorePendingExpressionUpdates( - 3 - ) should contain theSameElementsAs Seq( - TestMessages - .update(contextId, idText, ConstantsGen.TEXT, typeChanged = false), + context.receiveN(5) should contain theSameElementsAs Seq( + TestMessages.pending(contextId, idMain, idMainA, idMainP), TestMessages.update( contextId, - idRes, + idMainA, + ConstantsGen.TEXT, + fromCache = false, + typeChanged = true + ), + TestMessages.update( + contextId, + idMainP, ConstantsGen.NOTHING, methodCall = Some( Api.MethodCall( @@ -5001,22 +3803,43 @@ class RuntimeServerTest ), typeChanged = false ), + TestMessages + .update(contextId, idMain, ConstantsGen.NOTHING, typeChanged = false), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List(prompt2) - - // Close the file - context.send(Api.Request(Api.CloseFileNotification(mainFile))) - context.receiveNone shouldEqual None - context.consumeOut shouldEqual List() + context.consumeOut shouldEqual List("Hello!") } - it should "recompute expressions without invalidation" in { - val contents = context.Main.code - val mainFile = context.writeMain(contents) - val moduleName = "Enso_Test.Test.Main" + it should "send updates for overloaded functions" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val metadata = new Metadata + val idMain = metadata.addItem(122, 87, "aaaa") + val id1 = metadata.addItem(131, 15, "aad1") + val id2 = metadata.addItem(151, 18, "aad2") + val id3 = metadata.addItem(174, 15, "aad3") + // Note that Nothing.Nothing is on purpose. + // If not provided the full name it will resolve the expression Nothing to a Nothing module. + // Similarly Text.Text. That in turn will mismatch the expectations for method types which actually + // return proper types. + val code = + """from Standard.Base.Data.Numbers import Number + |from Standard.Base.Data.Text import all + |import Standard.Base.Nothing + | + |main = + | x = 15.overloaded 1 + | "foo".overloaded 2 + | 10.overloaded x + | Nothing.Nothing + | + |Text.overloaded self arg = arg + 1 + |Number.overloaded self arg = arg + 2 + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) @@ -5024,7 +3847,7 @@ class RuntimeServerTest Api.Response(requestId, Api.CreateContextResponse(contextId)) ) - // Set sources for the module + // open file context.send( Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) ) @@ -5033,41 +3856,243 @@ class RuntimeServerTest ) // push main - val item1 = Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idMain, ConstantsGen.NOTHING), + TestMessages.update( + contextId, + id1, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ) + ), + TestMessages.update( + contextId, + id2, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") + ) + ), + TestMessages.update( + contextId, + id3, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ) + ), + context.executionComplete(contextId) ) + + // push call1 context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.LocalCall(id1) + ) + ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveN(2) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.mainX(contextId), - context.Main.Update.mainY(contextId), - context.Main.Update.mainZ(contextId), context.executionComplete(contextId) ) - // recompute + // pop call1 + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveN(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)), + TestMessages.update( + contextId, + id1, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ), + fromCache = true, + typeChanged = true + ), + TestMessages.update( + contextId, + id2, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") + ), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + id3, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + idMain, + ConstantsGen.NOTHING, + typeChanged = false + ), + context.executionComplete(contextId) + ) + + // push call2 + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.LocalCall(id2) + ) + ) + ) + context.receiveN(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + + // pop call2 + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveN(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)), + TestMessages.update( + contextId, + id2, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") + ), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + id3, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + idMain, + ConstantsGen.NOTHING, + typeChanged = false + ), + TestMessages.update( + contextId, + id1, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ), + fromCache = true, + typeChanged = true + ), + context.executionComplete(contextId) + ) + + // push call3 context.send( Api.Request( requestId, - Api.RecomputeContextRequest(contextId, None, None, Seq()) + Api.PushContextRequest( + contextId, + Api.StackItem.LocalCall(id3) + ) ) ) context.receiveN(2) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + + // pop call3 + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveN(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)), + TestMessages.update( + contextId, + id2, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.TEXT, "overloaded") + ), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + id3, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ), + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + idMain, + ConstantsGen.NOTHING, + fromCache = false, + typeChanged = false + ), + TestMessages.update( + contextId, + id1, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer(moduleName, ConstantsGen.NUMBER, "overloaded") + ), + fromCache = true, + typeChanged = true + ), context.executionComplete(contextId) ) } - it should "recompute expressions invalidating all" in { - val contents = context.Main.code - val mainFile = context.writeMain(contents) - val moduleName = "Enso_Test.Test.Main" + it should "send updates for a lambda" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val xId = metadata.addItem(46, 10) + val mainRes = metadata.addItem(61, 12) + + val code = + """from Standard.Base import all + | + |main = + | x = a -> a + 1 + | IO.println 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))) @@ -5084,57 +4109,57 @@ class RuntimeServerTest ) // push main - val item1 = Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item1)) - ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.mainX(contextId), - context.Main.Update.mainY(contextId), - context.Main.Update.mainZ(contextId), - context.executionComplete(contextId) - ) - - // recompute context.send( Api.Request( requestId, - Api.RecomputeContextRequest( + Api.PushContextRequest( contextId, - Some(Api.InvalidatedExpressions.All()), - None, - Seq() + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) ) ) ) - context.receiveN(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.pending( + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, xId, ConstantsGen.FUNCTION), + TestMessages.update( contextId, - context.Main.idMainX, - context.Main.idMainY, - context.Main.idMainZ, - context.Main.idFooY, - context.Main.idFooZ + mainRes, + ConstantsGen.NOTHING, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) + ) + ) ), - context.Main.Update.mainX(contextId, typeChanged = false), - context.Main.Update.mainY(contextId, typeChanged = false), - context.Main.Update.mainZ(contextId, typeChanged = false), context.executionComplete(contextId) ) } - it should "recompute expressions invalidating some" in { - val contents = context.Main.code - val mainFile = context.writeMain(contents) - val moduleName = "Enso_Test.Test.Main" + it should "send updates for a constructor type" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idMain = metadata.addItem(39, 27) + + val code = + """type My_Type + | My_Constructor + | + |main = + | My_Type.My_Constructor + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) @@ -5149,151 +4174,125 @@ class RuntimeServerTest context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) ) - context.receiveNone shouldEqual None - // push main - val item1 = Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) - context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item1)) - ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.mainX(contextId), - context.Main.Update.mainY(contextId), - context.Main.Update.mainZ(contextId), - context.executionComplete(contextId) - ) - // recompute + // push main context.send( Api.Request( requestId, - Api.RecomputeContextRequest( + Api.PushContextRequest( contextId, - Some( - Api.InvalidatedExpressions.Expressions(Vector(context.Main.idMainZ)) - ), - None, - Seq() + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) ) ) ) - context.receiveN(4) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.pending(contextId, context.Main.idMainZ), - context.Main.Update.mainZ(contextId, typeChanged = false), + context.receiveN(3) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idMain, s"$moduleName.My_Type"), context.executionComplete(contextId) ) } - it should "recompute expressions changing an execution environment" in { - val contents = context.Main.code - val mainFile = context.writeMain(contents) - val moduleName = "Enso_Test.Test.Main" + it should "support file modification operations without attached ids" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" - // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( Api.Response(requestId, Api.CreateContextResponse(contextId)) ) + val code = + """import Standard.Base.IO + | + |main = IO.println "I'm a file!" + |""".stripMargin.linesIterator.mkString("\n") + + // Create a new file + val mainFile = context.writeMain(code) + // Set sources for the module context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) ) context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) ) + context.consumeOut shouldEqual List() - // push main - val item1 = Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() - ) + // Push new item on the stack to trigger the re-execution context.send( - Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) ) - context.receiveNIgnoreStdLib(5) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - context.Main.Update.mainX(contextId), - context.Main.Update.mainY(contextId), - context.Main.Update.mainZ(contextId), context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("I'm a file!") - // recompute - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment - .Design() - .name + // Modify the file context.send( Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - Some(Api.InvalidatedExpressions.All()), - Some(Api.ExecutionEnvironment.Live()), - Seq() + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(2, 25), model.Position(2, 29)), + "modified" + ) + ), + execute = true, + idMap = None ) ) ) - context.receiveN(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.pending( - contextId, - context.Main.idMainX, - context.Main.idMainY, - context.Main.idMainZ, - context.Main.idFooY, - context.Main.idFooZ - ), - context.Main.Update.mainX(contextId, typeChanged = false), - context.Main.Update.mainY(contextId, typeChanged = false), - context.Main.Update.mainZ(contextId, typeChanged = false), + context.receiveN(1) shouldEqual Seq( context.executionComplete(contextId) ) - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment - .Design() - .name + context.consumeOut shouldEqual List("I'm a modified!") + + // Close the file + context.send(Api.Request(Api.CloseFileNotification(mainFile))) + context.consumeOut shouldEqual List() } - it should "recompute expression with expression configs" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" + it should "support file modifications after reopening the file" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( Api.Response(requestId, Api.CreateContextResponse(contextId)) ) - val metadata = new Metadata - val idOut = metadata.addItem(104, 17, "aa") - val idIn = metadata.addItem(131, 16, "ab") - + val moduleName = "Enso_Test.Test.Main" val code = - """from Standard.Base import all - |from Standard.Base.Runtime.Context import Input, Output + """import Standard.Base.IO | - |main = - | out = Output.is_enabled - | in = Input.is_enabled - | IO.println out - | IO.println in + |main = IO.println "I'm a file!" |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) // Create a new file - val mainFile = context.writeMain(contents) + val mainFile = context.writeMain(code) // Set sources for the module context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) ) context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) @@ -5315,164 +4314,79 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - ConstantsGen.BOOLEAN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("False", "False") + context.consumeOut shouldEqual List("I'm a file!") - // recompute + // Close the file + context.send(Api.Request(Api.CloseFileNotification(mainFile))) + context.consumeOut shouldEqual List() + + val contextId2 = UUID.randomUUID() + val requestId2 = UUID.randomUUID() + + context.send(Api.Request(requestId2, Api.CreateContextRequest(contextId2))) + context.receive shouldEqual Some( + Api.Response(requestId2, Api.CreateContextResponse(contextId2)) + ) + + // Re-open the the file and apply the same operation context.send( - Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - None, - None, - Seq( - Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) - ) - ) - ) + Api.Request(requestId2, Api.OpenFileRequest(mainFile, code)) ) - context.receiveNIgnorePendingExpressionUpdates( - 3 - ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), - context.executionComplete(contextId) + context.receive shouldEqual Some( + Api.Response(Some(requestId2), Api.OpenFileResponse) ) - context.consumeOut shouldEqual List("True", "False") + context.consumeOut shouldEqual List() - // recompute + // Modify the file context.send( Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - None, - None, + Api.EditFileNotification( + mainFile, Seq( - Api.ExpressionConfig(idIn, Some(Api.ExecutionEnvironment.Live())) - ) + TextEdit( + model.Range(model.Position(2, 25), model.Position(2, 29)), + "modified" + ) + ), + execute = true, + idMap = None ) ) ) - context.receiveNIgnorePendingExpressionUpdates( - 3 - ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), + context.receiveN(1) shouldEqual Seq( context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("True", "True") + context.consumeOut shouldEqual List("I'm a modified!") + } - it should "recompute recursive method call with expression configs" in { + it should "support file modification operations with attached ids" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + val idMain = metadata.addItem(7, 2) + val code = metadata.appendToCode("main = 84") context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( Api.Response(requestId, Api.CreateContextResponse(contextId)) ) - val metadata = new Metadata - val idOut = metadata.addItem(104, 5, "aa") - val idIn = metadata.addItem(119, 16, "ab") - - val code = - """from Standard.Base import all - |from Standard.Base.Runtime.Context import Input, Output - | - |main = - | out = fac 3 - | in = Input.is_enabled - | IO.println out - | IO.println in - | - |fac n=1 acc=1 = - | if Output.is_enabled.not then Nothing else - | if n <= 0 then acc else - | IO.println n - | @Tail_Call fac n-1 acc*n - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) - // Create a new file - val mainFile = context.writeMain(contents) + val mainFile = context.writeMain(code) // Set sources for the module context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) ) context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) ) - context.consumeOut shouldEqual List() // Push new item on the stack to trigger the re-execution context.send( @@ -5489,117 +4403,143 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveN(3) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - ConstantsGen.NOTHING, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - moduleName, - moduleName, - "fac" - ), - Vector(1) - ) + TestMessages.update(contextId, idMain, ConstantsGen.INTEGER_BUILTIN), + context.executionComplete(contextId) + ) + + // Modify the file + context.send( + Api.Request( + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(0, 0), model.Position(0, 9)), + "main = 42" + ) + ), + execute = true, + idMap = None ) - ), + ) + ) + context.receiveN(3) shouldEqual Seq( + TestMessages.pending(contextId, idMain), TestMessages.update( contextId, - idIn, - ConstantsGen.BOOLEAN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) + idMain, + ConstantsGen.INTEGER_BUILTIN, + typeChanged = false ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("Nothing", "False") + } + + it should "send suggestion notifications when file is executed" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val idMain = context.Main.metadata.addItem(54, 46, "aaaa") + + val mainFile = context.writeMain(context.Main.code) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Set sources for the module + context.send( + Api.Request( + requestId, + Api.OpenFileRequest(mainFile, context.Main.code) + ) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.mainX(contextId), + context.Main.Update.mainY(contextId), + context.Main.Update.mainZ(contextId), + TestMessages.update(contextId, idMain, ConstantsGen.INTEGER), + context.executionComplete(contextId) + ) + + // push foo call + val item2 = Api.StackItem.LocalCall(context.Main.idMainY) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item2)) + ) + context.receiveN(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.Main.Update.fooY(contextId), + context.Main.Update.fooZ(contextId), + context.executionComplete(contextId) + ) - // recompute - context.send( - Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - None, - None, - Seq( - Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) - ) - ) - ) - ) - context.receiveNIgnorePendingExpressionUpdates( - 3 - ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - ConstantsGen.INTEGER, - fromCache = false, - typeChanged = true, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - moduleName, - moduleName, - "fac" - ), - Vector(1) - ) - ) - ), + // pop foo call + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveN(5) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)), + context.Main.Update.mainY(contextId, fromCache = true), + context.Main.Update.mainZ(contextId, fromCache = true), + TestMessages + .update(contextId, idMain, ConstantsGen.INTEGER, typeChanged = false), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("3", "2", "1", "6", "False") + + // pop main + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receiveN(1) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PopContextResponse(contextId)) + ) + + // pop empty stack + context.send(Api.Request(requestId, Api.PopContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.EmptyStackError(contextId)) + ) } - it should "recompute method call returning Panic with expression configs" in { + it should "send suggestion notifications when file is modified" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" + val newline = System.lineSeparator() context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( Api.Response(requestId, Api.CreateContextResponse(contextId)) ) - val metadata = new Metadata - val idOut = metadata.addItem(104, 34, "aa") - val idIn = metadata.addItem(148, 16, "ab") - val code = - """from Standard.Base import all - |from Standard.Base.Runtime.Context import Input, Output - | - |main = - | out = Panic.catch Any (foo 42) _.payload - | in = Input.is_enabled - | IO.println out - | IO.println in + """from Standard.Base.Data.Numbers import Number + |import Standard.Base.IO | - |foo n=Nothing = - | Output.if_enabled (Panic.throw n) + |main = IO.println "I'm a file!" |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) // Create a new file - val mainFile = context.writeMain(contents) + val mainFile = context.writeMain(code) // Set sources for the module context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) ) context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) @@ -5621,82 +4561,53 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - "Standard.Base.Errors.Common.Forbidden_Operation", - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Panic", - "Standard.Base.Panic.Panic", - "catch" - ) - ) - ) - ), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List( - "(Forbidden_Operation.Error 'The Output context is disabled.')", - "False" - ) + context.consumeOut shouldEqual List("I'm a file!") - // recompute + /* + Modify the file: + """from Standard.Base.Data.Numbers import Number + |import Standard.Base.IO + | + |Number.lucky = 42 + | + |main = IO.println "I'm a modified!" + |""".stripMargin.linesIterator.mkString("\n") + */ context.send( Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - None, - None, + Api.EditFileNotification( + mainFile, Seq( - Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) - ) + TextEdit( + model.Range(model.Position(3, 25), model.Position(3, 29)), + "modified" + ), + TextEdit( + model.Range(model.Position(3, 0), model.Position(3, 0)), + s"Number.lucky = 42$newline$newline" + ) + ), + execute = true, + idMap = None ) ) ) - context.receiveNIgnorePendingExpressionUpdates( - 3 - ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - ConstantsGen.INTEGER, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Panic", - "Standard.Base.Panic.Panic", - "catch" - ) - ) - ) - ), + context.receiveN(1) should contain theSameElementsAs Seq( context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("42", "False") + context.consumeOut shouldEqual List("I'm a modified!") + + // Close the file + context.send(Api.Request(Api.CloseFileNotification(mainFile))) + context.receiveNone shouldEqual None + context.consumeOut shouldEqual List() } - it should "recompute expression dropping the cache by providing empty expression configs" in { + it should "send expression updates when file is restored" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" @@ -5707,31 +4618,28 @@ class RuntimeServerTest ) val metadata = new Metadata - val idOut = metadata.addItem(104, 17, "aa") - val idIn = metadata.addItem(131, 16, "ab") - val idOutTxt = metadata.addItem(162, 11, "ac") - val idInTxt = metadata.addItem(187, 10, "ad") + val idText = metadata.addItem(49, 12, "aa") + val idRes = metadata.addItem(66, 15, "ab") - val code = - """from Standard.Base import all - |from Standard.Base.Runtime.Context import Input, Output - | - |main = - | out = Output.is_enabled - | in = Input.is_enabled - | out_txt = out.to_text - | in_txt = in.to_text - | IO.println out_txt - | IO.println in_txt - |""".stripMargin.linesIterator.mkString("\n") - val contents = metadata.appendToCode(code) + def template(text: String) = + metadata.appendToCode( + s"""from Standard.Base import all + | + |main = + | text = "$text" + | IO.println text + |""".stripMargin.linesIterator.mkString("\n") + ) + + val prompt1 = "I'm a one!" + val code = template(prompt1) // Create a new file - val mainFile = context.writeMain(contents) + val mainFile = context.writeMain(code) // Set sources for the module context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) ) context.receive shouldEqual Some( Api.Response(Some(requestId), Api.OpenFileResponse) @@ -5753,210 +4661,75 @@ class RuntimeServerTest ) ) ) - context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - TestMessages.update( - contextId, - idOut, - ConstantsGen.BOOLEAN, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ), - TestMessages.update( - contextId, - idOutTxt, - ConstantsGen.TEXT, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Any", - "Standard.Base.Any.Any", - "to_text" - ), - Vector() - ) - ), - TestMessages.update( - contextId, - idInTxt, - ConstantsGen.TEXT, - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Any", - "Standard.Base.Any.Any", - "to_text" - ), - Vector() - ) - ), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("False", "False") - - // recompute - context.send( - Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - None, - None, - Seq( - Api.ExpressionConfig(idIn, Some(Api.ExecutionEnvironment.Live())), - Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) - ) - ) - ) - ) - context.receiveNIgnorePendingExpressionUpdates( - 6 - ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), - TestMessages.update( - contextId, - idOut, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), - TestMessages.update( - contextId, - idInTxt, - ConstantsGen.TEXT, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Any", - "Standard.Base.Any.Any", - "to_text" - ), - Vector() - ) - ) - ), + context.receiveNIgnoreStdLib(4) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idText, ConstantsGen.TEXT), TestMessages.update( contextId, - idOutTxt, - ConstantsGen.TEXT, - fromCache = false, - typeChanged = false, + idRes, + ConstantsGen.NOTHING, methodCall = Some( Api.MethodCall( Api.MethodPointer( - "Standard.Base.Any", - "Standard.Base.Any.Any", - "to_text" - ), - Vector() + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) ) ) ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("True", "True") + context.consumeOut shouldEqual List(prompt1) + + // Simulate file update in FS + val prompt2 = "I'm a two!" + val code2 = template(prompt2) + context.writeMain(code2) - // recompute dropping the caches context.send( Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - None, - None, + Api.EditFileNotification( + mainFile, Seq( - Api.ExpressionConfig(idIn, None) - ) + TextEdit( + model.Range(model.Position(0, 0), model.Position(9, 2)), + code2 + ) + ), + execute = true, + idMap = None ) ) ) context.receiveNIgnorePendingExpressionUpdates( - 4 + 3 ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), - TestMessages.update( - contextId, - idIn, - ConstantsGen.BOOLEAN, - fromCache = false, - typeChanged = false, - methodCall = Some( - Api.MethodCall( - Api.MethodPointer( - "Standard.Base.Runtime", - "Standard.Base.Runtime.Context", - "is_enabled" - ), - Vector(1) - ) - ) - ), + TestMessages + .update(contextId, idText, ConstantsGen.TEXT, typeChanged = false), TestMessages.update( contextId, - idInTxt, - ConstantsGen.TEXT, - fromCache = false, - typeChanged = false, + idRes, + ConstantsGen.NOTHING, methodCall = Some( Api.MethodCall( Api.MethodPointer( - "Standard.Base.Any", - "Standard.Base.Any.Any", - "to_text" - ), - Vector() + "Standard.Base.IO", + "Standard.Base.IO", + "println" + ) ) - ) + ), + typeChanged = false ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("True", "False") + context.consumeOut shouldEqual List(prompt2) + + // Close the file + context.send(Api.Request(Api.CloseFileNotification(mainFile))) + context.receiveNone shouldEqual None + context.consumeOut shouldEqual List() } it should "return error when module not found" in { @@ -8338,99 +7111,380 @@ class RuntimeServerTest Api.Response(Some(requestId), Api.OpenFileResponse) ) - // push main - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() + // push main + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + } + + it should "support file edit notification with IdMap" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + + val xId = new UUID(0, 1) + + val code = + """from Standard.Base import all + | + |main = + | x = 0 + | IO.println x + |""".stripMargin.linesIterator.mkString("\n") + + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Create a new file + val mainFile = context.writeMain(code) + + // Set sources for the module + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // Push new item on the stack to trigger the re-execution + context.send( + Api.Request( + requestId, + Api.PushContextRequest( + contextId, + Api.StackItem + .ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("0") + + // Modify the file + context.send( + Api.Request( + Api.EditFileNotification( + mainFile, + Seq( + TextEdit( + model.Range(model.Position(3, 8), model.Position(3, 9)), + "\"Hello World!\"" + ) + ), + execute = true, + idMap = Some(model.IdMap(Vector(model.Span(46, 60) -> xId))) + ) + ) + ) + context.receiveN(2) shouldEqual Seq( + TestMessages.update(contextId, xId, ConstantsGen.TEXT), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("Hello World!") + } + +} +object RuntimeServerTest { + + trait TestMain { + object Main { + + val metadata = new Metadata + + val idMainX = metadata.addItem(63, 1, "aa1") + val idMainY = metadata.addItem(73, 7, "aa2") + val idMainZ = metadata.addItem(89, 5, "aa3") + val idFooY = metadata.addItem(133, 8, "ff2") + val idFooZ = metadata.addItem(150, 5, "ff3") + + def code = + metadata.appendToCode( + """ + |from Standard.Base.Data.Numbers import all + | + |main = + | x = 6 + | y = x.foo 5 + | z = y + 5 + | z + | + |Number.foo self = x -> + | y = self + 3 + | z = y * x + | z + |""".stripMargin.linesIterator.mkString("\n") + ) + + object Update { + + def mainX( + contextId: UUID, + fromCache: Boolean = false, + typeChanged: Boolean = true + ): Api.Response = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + Main.idMainX, + Some(ConstantsGen.INTEGER), + None, + Vector(Api.ProfilingInfo.ExecutionTime(0)), + fromCache, + typeChanged, + Api.ExpressionUpdate.Payload.Value() + ) + ) + ) + ) + + def pendingZ(): Api.ExpressionUpdate = + Api.ExpressionUpdate( + Main.idFooZ, + None, + None, + Vector(), + true, + false, + Api.ExpressionUpdate.Payload.Pending(None, None) + ) + + def pendingY(): Api.ExpressionUpdate = + Api.ExpressionUpdate( + Main.idFooY, + None, + None, + Vector(), + true, + false, + Api.ExpressionUpdate.Payload.Pending(None, None) + ) + + def mainY( + contextId: UUID, + fromCache: Boolean = false, + typeChanged: Boolean = true + ): Api.Response = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + Main.idMainY, + Some(ConstantsGen.INTEGER), + Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + ConstantsGen.NUMBER, + "foo" + ) + ) + ), + Vector(Api.ProfilingInfo.ExecutionTime(0)), + fromCache, + typeChanged, + Api.ExpressionUpdate.Payload.Value() + ) + ) + ) + ) + + def mainZ( + contextId: UUID, + fromCache: Boolean = false, + typeChanged: Boolean = true + ): Api.Response = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + Main.idMainZ, + Some(ConstantsGen.INTEGER), + Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Data.Numbers", + ConstantsGen.INTEGER, + "+" + ) + ) + ), + Vector(Api.ProfilingInfo.ExecutionTime(0)), + fromCache, + typeChanged, + Api.ExpressionUpdate.Payload.Value() + ) + ) + ) ) - ) - ) - ) - context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.executionComplete(contextId) - ) - } - it should "support file edit notification with IdMap" in { - val contextId = UUID.randomUUID() - val requestId = UUID.randomUUID() - val moduleName = "Enso_Test.Test.Main" + def fooY( + contextId: UUID, + fromCache: Boolean = false, + typeChanged: Boolean = true + ): Api.Response = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + Main.idFooY, + Some(ConstantsGen.INTEGER), + Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Data.Numbers", + ConstantsGen.INTEGER, + "+" + ) + ) + ), + Vector(Api.ProfilingInfo.ExecutionTime(0)), + fromCache, + typeChanged, + Api.ExpressionUpdate.Payload.Value() + ) + ) + ) + ) - val xId = new UUID(0, 1) + def fooZ( + contextId: UUID, + fromCache: Boolean = false, + typeChanged: Boolean = true + ): Api.Response = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + Main.idFooZ, + Some(ConstantsGen.INTEGER), + Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Data.Numbers", + ConstantsGen.INTEGER, + "*" + ) + ) + ), + Vector(Api.ProfilingInfo.ExecutionTime(0)), + fromCache, + typeChanged, + Api.ExpressionUpdate.Payload.Value() + ) + ) + ) + ) + } + } - val code = - """from Standard.Base import all - | - |main = - | x = 0 - | IO.println x - |""".stripMargin.linesIterator.mkString("\n") + object Main2 { - context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) - context.receive shouldEqual Some( - Api.Response(requestId, Api.CreateContextResponse(contextId)) - ) + val metadata = new Metadata + val idMainY = metadata.addItem(178, 5) + val idMainZ = metadata.addItem(192, 5) - // Create a new file - val mainFile = context.writeMain(code) + val code = metadata.appendToCode( + """from Standard.Base import all + | + |foo = arg -> + | IO.println "I'm expensive!" + | arg + 5 + | + |bar = arg -> + | IO.println "I'm more expensive!" + | arg * 5 + | + |main = + | x = 10 + | y = foo x + | z = bar y + | z + |""".stripMargin.linesIterator.mkString("\n") + ) - // Set sources for the module - context.send( - Api.Request(requestId, Api.OpenFileRequest(mainFile, code)) - ) - context.receive shouldEqual Some( - Api.Response(Some(requestId), Api.OpenFileResponse) - ) + object Update { - // Push new item on the stack to trigger the re-execution - context.send( - Api.Request( - requestId, - Api.PushContextRequest( - contextId, - Api.StackItem - .ExplicitCall( - Api.MethodPointer(moduleName, moduleName, "main"), - None, - Vector() + def mainY(contextId: UUID) = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + idMainY, + Some(ConstantsGen.INTEGER), + Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main", + "foo" + ) + ) + ), + Vector(Api.ProfilingInfo.ExecutionTime(0)), + false, + true, + Api.ExpressionUpdate.Payload.Value() + ) + ) ) - ) - ) - ) - context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("0") + ) - // Modify the file - context.send( - Api.Request( - Api.EditFileNotification( - mainFile, - Seq( - TextEdit( - model.Range(model.Position(3, 8), model.Position(3, 9)), - "\"Hello World!\"" + def mainZ(contextId: UUID) = + Api.Response( + Api.ExpressionUpdates( + contextId, + Set( + Api.ExpressionUpdate( + idMainZ, + Some(ConstantsGen.INTEGER), + Some( + Api.MethodCall( + Api.MethodPointer( + "Enso_Test.Test.Main", + "Enso_Test.Test.Main", + "bar" + ) + ) + ), + Vector(Api.ProfilingInfo.ExecutionTime(0)), + false, + true, + Api.ExpressionUpdate.Payload.Value() + ) + ) ) - ), - execute = true, - idMap = Some(model.IdMap(Vector(model.Span(46, 60) -> xId))) - ) - ) - ) - context.receiveN(2) shouldEqual Seq( - TestMessages.update(contextId, xId, ConstantsGen.TEXT), - context.executionComplete(contextId) - ) - context.consumeOut shouldEqual List("Hello World!") + ) + } + } } - } From a55f90677670289162e70df8653e34a36d4ec247 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 25 Sep 2024 17:36:49 +0100 Subject: [PATCH 05/24] update: language server api --- .../executioncontext/RecomputeHandler.scala | 3 +- .../runtime/ContextRegistry.scala | 6 +- .../runtime/ContextRegistryProtocol.scala | 4 +- .../languageserver/runtime/ExecutionApi.scala | 3 +- .../runtime/ExpressionConfig.scala | 23 +++++ .../runtime/InvalidatedExpressions.scala | 3 +- .../websocket/json/ContextRegistryTest.scala | 90 ++++++++++++++++++- 7 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExpressionConfig.scala diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/executioncontext/RecomputeHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/executioncontext/RecomputeHandler.scala index 8191dd4febde..c55d4d7ed686 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/executioncontext/RecomputeHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/executioncontext/RecomputeHandler.scala @@ -48,7 +48,8 @@ class RecomputeHandler( session, msg.params.contextId, msg.params.invalidatedExpressions, - msg.params.executionEnvironment + msg.params.executionEnvironment, + msg.params.expressionConfigs.getOrElse(Seq()) ) override protected def positiveResponse( diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala index b740444483ce..a9f4d43481f4 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala @@ -193,7 +193,8 @@ final class ContextRegistry( client, contextId, expressions, - environment + environment, + expressionConfigs ) => if (store.hasContext(client.clientId, contextId)) { val handler = @@ -210,7 +211,8 @@ final class ContextRegistry( Api.RecomputeContextRequest( contextId, invalidatedExpressions, - environment.map(ExecutionEnvironments.toApi) + environment.map(ExecutionEnvironments.toApi), + expressionConfigs.map(_.toApi) ) ) } else { 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 81a0ddc4c06b..23eed01dbf8d 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 @@ -98,12 +98,14 @@ object ContextRegistryProtocol { * @param contextId execution context identifier * @param invalidatedExpressions the expressions that should be invalidated * @param executionEnvironment the environment that should be used for execution + * @param expressionConfigs the execution configurations for particular expressions */ case class RecomputeContextRequest( rpcSession: JsonSession, contextId: ContextId, invalidatedExpressions: Option[InvalidatedExpressions], - executionEnvironment: Option[ExecutionEnvironments.ExecutionEnvironment] + executionEnvironment: Option[ExecutionEnvironments.ExecutionEnvironment], + expressionConfigs: Seq[ExpressionConfig] ) /** A response about recomputing the context. diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala index 986719fea191..f1d562bea407 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExecutionApi.scala @@ -108,7 +108,8 @@ object ExecutionApi { case class Params( contextId: ContextId, invalidatedExpressions: Option[InvalidatedExpressions], - executionEnvironment: Option[ExecutionEnvironments.ExecutionEnvironment] + executionEnvironment: Option[ExecutionEnvironments.ExecutionEnvironment], + expressionConfigs: Option[Seq[ExpressionConfig]] ) implicit diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExpressionConfig.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExpressionConfig.scala new file mode 100644 index 000000000000..3a4ffc28fcca --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ExpressionConfig.scala @@ -0,0 +1,23 @@ +package org.enso.languageserver.runtime + +import org.enso.polyglot.runtime.Runtime.Api + +import java.util.UUID + +/** The expression configuration used in the recompute request. + * + * @param expressionId the expression identifier + * @param executionEnvironment the execution environment used to run this expression + */ +case class ExpressionConfig( + expressionId: UUID, + executionEnvironment: Option[ExecutionEnvironments.ExecutionEnvironment] +) { + + /** Convert this expression config to the runtime API. */ + def toApi: Api.ExpressionConfig = + Api.ExpressionConfig( + expressionId, + executionEnvironment.map(ExecutionEnvironments.toApi) + ) +} diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/InvalidatedExpressions.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/InvalidatedExpressions.scala index 44877176994e..de9898ddb190 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/InvalidatedExpressions.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/InvalidatedExpressions.scala @@ -3,8 +3,7 @@ package org.enso.languageserver.runtime import io.circe.syntax._ import io.circe.{Decoder, DecodingFailure, Encoder} -/** A request to invalidate expressions. - */ +/** A request to invalidate expressions. */ sealed trait InvalidatedExpressions object InvalidatedExpressions { diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala index f4c44567feac..6a83480033a1 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala @@ -449,7 +449,8 @@ class ContextRegistryTest extends BaseServerTest with ReportLogsOnFailure { Api.RecomputeContextRequest( `contextId`, Some(Api.InvalidatedExpressions.All()), - None + None, + Seq() ) ) => requestId @@ -527,7 +528,92 @@ class ContextRegistryTest extends BaseServerTest with ReportLogsOnFailure { Vector(`expressionId`) ) ), - None + None, + Seq() + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId3, + Api.RecomputeContextResponse(contextId) + ) + client.expectJson(json.ok(3)) + } + + "recompute with expression configs" in { + val client = getInitialisedWsClient() + + // create context + client.send(json.executionContextCreateRequest(1)) + val (requestId, contextId) = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request(requestId, Api.CreateContextRequest(contextId)) => + (requestId, contextId) + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId, + Api.CreateContextResponse(contextId) + ) + client.expectJson(json.executionContextCreateResponse(1, contextId)) + + // push stack item + val expressionId = UUID.randomUUID() + client.send(json.executionContextPushRequest(2, contextId, expressionId)) + val requestId2 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.PushContextRequest( + `contextId`, + Api.StackItem.LocalCall(`expressionId`) + ) + ) => + requestId + case msg => + fail(s"Unexpected message: $msg") + } + runtimeConnectorProbe.lastSender ! Api.Response( + requestId2, + Api.PushContextResponse(contextId) + ) + client.expectJson(json.ok(2)) + + // recompute + client.send( + json""" + { "jsonrpc": "2.0", + "method": "executionContext/recompute", + "id": 3, + "params": { + "contextId": $contextId, + "expressionConfigs": [ + { "expressionId": $expressionId, + "executionEnvironment": "Live" + } + ] + } + } + """ + ) + val requestId3 = + runtimeConnectorProbe.receiveN(1).head match { + case Api.Request( + requestId, + Api.RecomputeContextRequest( + `contextId`, + None, + None, + Seq( + Api.ExpressionConfig( + `expressionId`, + Some(Api.ExecutionEnvironment.Live()) + ) + ) ) ) => requestId From 169e38650743f7a6bab200df85549e1d8b0ef811 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 26 Sep 2024 12:21:34 +0100 Subject: [PATCH 06/24] doc: update language server api --- .../protocol-language-server.md | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 685b7769d5dd..e711f3d30558 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -25,6 +25,8 @@ transport formats, please look [here](./protocol-architecture). - [`MethodCall`](#methodcall) - [`MethodPointer`](#methodpointer) - [`ProfilingInfo`](#profilinginfo) + - [`ExecutionEnvironment`](#executionenvironment) + - [`ExpressionConfig`](#expressionConfig) - [`ExpressionUpdate`](#expressionupdate) - [`ExpressionUpdatePayload`](#expressionupdatepayload) - [`VisualizationConfiguration`](#visualizationconfiguration) @@ -343,6 +345,19 @@ The execution environment of Enso runtime. type ExecutionEnvironment = Design | Live; ``` +### `ExpressionConfig` + +The expression configuration used in the recompute request. + +```typescript +interface ExpressionConfig { + /** The expression identifier. */ + expressionId: ExpressionId; + /** The execution environment that should be used to run this expression. */ + executionEnvironment?: ExecutionEnvironment; +} +``` + ### `ExpressionUpdate` An update about the computed expression. @@ -3633,10 +3648,21 @@ May include a list of expressions for which caches should be invalidated. interface ExecutionContextRecomputeParameters { /** The execution context identifier. */ contextId: ContextId; - /** The expressions that will be invalidated before the execution. */ + + /** The expressions that will be invalidated before the execution. + * + * Only the provided expression ids are invalidated excluding the dependencies. + */ invalidatedExpressions?: "all" | ExpressionId[]; + /** The execution environment that will be used in the execution. */ executionEnvironment?: ExecutionEnvironment; + + /** The execution configurations for particular expressions. + * + * The provided expressions will be invalidated from the cache with the dependencies. + */ + expressionConfigs?: ExpressionConfig[]; } ``` From 95f2c13bff1dc04e4d45f7a972cf6e5073ff175a Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 26 Sep 2024 13:53:34 +0100 Subject: [PATCH 07/24] update: truffle boundaries --- docs/language-server/protocol-language-server.md | 4 +++- .../org/enso/interpreter/service/ExecutionCallbacks.java | 1 + .../java/org/enso/interpreter/runtime/EnsoContext.java | 8 +++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index e711f3d30558..5d7b662b2718 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -3660,7 +3660,9 @@ interface ExecutionContextRecomputeParameters { /** The execution configurations for particular expressions. * - * The provided expressions will be invalidated from the cache with the dependencies. + * The provided expressions will be invalidated from the cache with the + * dependencies. The result of the execution will stay in the cache until the + * cache is invalidated by editing the node or other means. */ expressionConfigs?: ExpressionConfig[]; } diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index a4c048a7715f..1c3d8f18dee2 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -151,6 +151,7 @@ public Object onFunctionReturn(IdExecutionService.Info info) { return null; } + @CompilerDirectives.TruffleBoundary @Override public void setExecutionEnvironment(IdExecutionService.Info info) { ExecutionEnvironment nodeEnvironment = expressionConfigs.get(info.getId()); 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 f09d88291cfe..26233cc78f28 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 @@ -145,7 +145,7 @@ public EnsoContext( this.isPrivateCheckDisabled = getOption(RuntimeOptions.DISABLE_PRIVATE_CHECK_KEY); this.isStaticTypeAnalysisEnabled = getOption(RuntimeOptions.ENABLE_STATIC_ANALYSIS_KEY); this.contextExecutionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); - this.threadExecutionEnvironment = ThreadLocal.withInitial(() -> contextExecutionEnvironment); + this.threadExecutionEnvironment = initializeThreadExecutionEnvironment(); this.assertionsEnabled = shouldAssertionsBeEnabled(); this.shouldWaitForPendingSerializationJobs = getOption(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS_KEY); @@ -218,6 +218,11 @@ public void initialize() { } } + @TruffleBoundary + private ThreadLocal initializeThreadExecutionEnvironment() { + return ThreadLocal.withInitial(this::getExecutionEnvironment); + } + /** Checks if the working directory is as expected and reports a warning if not. */ private void checkWorkingDirectory(Optional maybeProjectRoot) { if (maybeProjectRoot.isPresent()) { @@ -873,6 +878,7 @@ public ExecutionEnvironment getExecutionEnvironment() { return contextExecutionEnvironment; } + @TruffleBoundary public ExecutionEnvironment getThreadExecutionEnvironment() { return threadExecutionEnvironment.get(); } From a25b2e5928b38904662acf1dcbece3e03388db91 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 26 Sep 2024 15:04:29 +0100 Subject: [PATCH 08/24] misc: javafmt --- .../main/java/org/enso/polyglot/debugger/IdExecutionService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java index bf9dff12ce72..303eddde7123 100644 --- a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java @@ -5,7 +5,6 @@ import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.nodes.RootNode; - import java.util.UUID; public interface IdExecutionService { From b5ed0c05efafbcbfa7c7f25dccc78f4d9d8f1803 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 2 Oct 2024 17:01:23 +0100 Subject: [PATCH 09/24] feat: ExpressionExecutionState --- .../instrument/ExpressionExecutionState.java | 32 +++++++++++++++++++ .../service/ExecutionCallbacks.java | 20 ++++++++---- .../interpreter/service/ExecutionService.java | 18 +++++------ .../command/RecomputeContextCmd.scala | 13 +++++--- .../instrument/execution/ExecutionState.scala | 4 +++ .../instrument/job/ExecuteJob.scala | 29 +++++++++-------- .../job/ProgramExecutionSupport.scala | 15 +++------ 7 files changed, 88 insertions(+), 43 deletions(-) create mode 100644 engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java new file mode 100644 index 000000000000..5d5fa93520b1 --- /dev/null +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java @@ -0,0 +1,32 @@ +package org.enso.interpreter.instrument; + +import org.enso.interpreter.runtime.state.ExecutionEnvironment; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class ExpressionExecutionState { + + private final Map expressionConfigs; + + public ExpressionExecutionState() { + this.expressionConfigs = new HashMap<>(); + } + + public ExpressionExecutionState(Map expressionConfigs) { + this.expressionConfigs = expressionConfigs; + } + + public void setExpressionConfigs(Map expressionConfigs) { + this.expressionConfigs.putAll(expressionConfigs); + } + + public void setExpressionExecuted(UUID expressionId) { + expressionConfigs.remove(expressionId); + } + + public ExecutionEnvironment getExpressionExecutionEnvironment(UUID expressionId) { + return expressionConfigs.get(expressionId); + } +} diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index 1c3d8f18dee2..19274b28e2c0 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -5,6 +5,8 @@ import java.util.Map; import java.util.UUID; import java.util.function.Consumer; + +import org.enso.interpreter.instrument.ExpressionExecutionState; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.OneshotExpression; import org.enso.interpreter.instrument.RuntimeCache; @@ -33,7 +35,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { private final MethodCallsCache methodCallsCache; private final UpdatesSynchronizationState syncState; private final Map calls = new HashMap<>(); - private final Map expressionConfigs; + private final ExpressionExecutionState expressionExecutionState; private final Consumer onCachedCallback; private final Consumer onComputedCallback; private final Consumer functionCallCallback; @@ -49,7 +51,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { * @param cache the precomputed expression values. * @param methodCallsCache the storage tracking the executed updateCachedResult calls. * @param syncState the synchronization state of runtime updates. - * @param expressionConfigs the execution config for each expression. + * @param expressionExecutionState the execution state for each expression. * @param onComputedCallback the consumer of the computed value events. * @param onCachedCallback the consumer of the cached value events. * @param functionCallCallback the consumer of function call events. @@ -61,7 +63,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { RuntimeCache cache, MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, - Map expressionConfigs, + ExpressionExecutionState expressionExecutionState, Consumer onCachedCallback, Consumer onComputedCallback, Consumer functionCallCallback, @@ -71,7 +73,7 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { this.cache = cache; this.methodCallsCache = methodCallsCache; this.syncState = syncState; - this.expressionConfigs = expressionConfigs; + this.expressionExecutionState = expressionExecutionState; this.onCachedCallback = onCachedCallback; this.onComputedCallback = onComputedCallback; this.functionCallCallback = functionCallCallback; @@ -151,10 +153,10 @@ public Object onFunctionReturn(IdExecutionService.Info info) { return null; } - @CompilerDirectives.TruffleBoundary @Override public void setExecutionEnvironment(IdExecutionService.Info info) { - ExecutionEnvironment nodeEnvironment = expressionConfigs.get(info.getId()); + ExecutionEnvironment nodeEnvironment = + expressionExecutionState.getExpressionExecutionEnvironment(info.getId()); if (nodeEnvironment != null && originalExecutionEnvironment == null) { EnsoContext context = EnsoContext.get(info.getRootNode()); originalExecutionEnvironment = context.getExecutionEnvironment(); @@ -165,11 +167,17 @@ public void setExecutionEnvironment(IdExecutionService.Info info) { @Override public void resetExecutionEnvironment(IdExecutionService.Info info) { if (originalExecutionEnvironment != null) { + expressionExecutionState.setExpressionExecuted(info.getId()); EnsoContext.get(info.getRootNode()).setExecutionEnvironment(originalExecutionEnvironment); originalExecutionEnvironment = null; } } + @CompilerDirectives.TruffleBoundary + private ExecutionEnvironment getExecutionEnvironment(UUID expressionId) { + return expressionExecutionState.getExpressionExecutionEnvironment(expressionId); + } + @CompilerDirectives.TruffleBoundary private void callOnComputedCallback(ExpressionValue expressionValue) { onComputedCallback.accept(expressionValue); diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java index 6b32a0d65087..56270d13e814 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java @@ -20,8 +20,6 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; -import java.util.Collections; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; @@ -31,6 +29,7 @@ import org.enso.common.MethodNames; import org.enso.compiler.suggestions.SimpleUpdate; import org.enso.interpreter.instrument.Endpoint; +import org.enso.interpreter.instrument.ExpressionExecutionState; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.RuntimeCache; import org.enso.interpreter.instrument.UpdatesSynchronizationState; @@ -160,7 +159,7 @@ public void initializeLanguageServerConnection(Endpoint endpoint) { * @param methodCallsCache the storage tracking the executed method calls. * @param syncState the synchronization state of runtime updates. * @param nextExecutionItem the next item scheduled for execution. - * @param expressionConfigs the execution config for each expression. + * @param expressionExecutionState the execution state for each expression. * @param funCallCallback the consumer for function call events. * @param onComputedCallback the consumer of the computed value events. * @param onCachedCallback the consumer of the cached value events. @@ -174,7 +173,7 @@ public void execute( MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, - Map expressionConfigs, + ExpressionExecutionState expressionExecutionState, Consumer funCallCallback, Consumer onComputedCallback, Consumer onCachedCallback, @@ -194,7 +193,7 @@ public void execute( cache, methodCallsCache, syncState, - expressionConfigs, + expressionExecutionState, onCachedCallback, onComputedCallback, funCallCallback, @@ -227,7 +226,7 @@ public void execute( * @param methodCallsCache the storage tracking the executed method calls. * @param syncState the synchronization state of runtime updates. * @param nextExecutionItem the next item scheduled for execution. - * @param expressionConfigs the execution config for each expression. + * @param expressionExecutionState the execution state for each expression. * @param funCallCallback the consumer for function call events. * @param onComputedCallback the consumer of the computed value events. * @param onCachedCallback the consumer of the cached value events. @@ -242,7 +241,7 @@ public void execute( MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, - Map expressionConfigs, + ExpressionExecutionState expressionExecutionState, Consumer funCallCallback, Consumer onComputedCallback, Consumer onCachedCallback, @@ -265,7 +264,7 @@ public void execute( methodCallsCache, syncState, nextExecutionItem, - expressionConfigs, + expressionExecutionState, funCallCallback, onComputedCallback, onCachedCallback, @@ -371,6 +370,7 @@ public Object callFunctionWithInstrument( Consumer onCachedCallback = (value) -> context.getLogger().finest("_ON_CACHED_VALUE " + value.getExpressionId()); Consumer onExecutedVisualizationCallback = (value) -> {}; + ExpressionExecutionState expressionExecutionState = new ExpressionExecutionState(); var callbacks = new ExecutionCallbacks( @@ -379,7 +379,7 @@ public Object callFunctionWithInstrument( cache, methodCallsCache, syncState, - Collections.emptyMap(), + expressionExecutionState, onCachedCallback, onComputedCallback, funCallCallback, diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala index 6d6bb3809bba..1ee2cd2cb4bd 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/RecomputeContextCmd.scala @@ -79,17 +79,20 @@ class RecomputeContextCmd( ): Future[Unit] = { if (isStackNonEmpty) { val stack = ctx.contextManager.getStack(request.contextId) - val executionConfig = ExecutionConfig.create( - request.executionEnvironment, - request.expressionConfigs - ) + val executionConfig = + ExecutionConfig.create( + request.executionEnvironment, + request.expressionConfigs + ) + ctx.state.expressionExecutionState + .setExpressionConfigs(executionConfig.expressionConfigs) for { _ <- ctx.jobProcessor.run(EnsureCompiledJob(stack)) _ <- ctx.jobProcessor.run( new ExecuteJob( request.contextId, stack.toList, - executionConfig + request.executionEnvironment ) ) } yield () diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ExecutionState.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ExecutionState.scala index d20aae19453e..9cbe5cc2165f 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ExecutionState.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/execution/ExecutionState.scala @@ -1,5 +1,7 @@ package org.enso.interpreter.instrument.execution +import org.enso.interpreter.instrument.ExpressionExecutionState + /** The state of the runtime */ final class ExecutionState { @@ -8,5 +10,7 @@ final class ExecutionState { val executionHooks: ExecutionHooks = new RuntimeExecutionHooks + val expressionExecutionState = new ExpressionExecutionState() + val suggestions: ModuleIndexing = ModuleIndexing.createInstance() } diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala index d4a07f99b225..88db0df63c85 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala @@ -1,8 +1,9 @@ package org.enso.interpreter.instrument.job import java.util.UUID -import org.enso.interpreter.instrument.{ExecutionConfig, InstrumentFrame} +import org.enso.interpreter.instrument.InstrumentFrame import org.enso.interpreter.instrument.execution.{Executable, RuntimeContext} +import org.enso.interpreter.runtime.state.ExecutionEnvironment import org.enso.polyglot.runtime.Runtime.Api import java.util.logging.Level @@ -11,12 +12,12 @@ import java.util.logging.Level * * @param contextId an identifier of a context to execute * @param stack a call stack to execute - * @param executionConfig the program execution configuration + * @param executionEnvironment the execution environment to use */ class ExecuteJob( contextId: UUID, stack: List[InstrumentFrame], - val executionConfig: ExecutionConfig, + val executionEnvironment: Option[Api.ExecutionEnvironment], val visualizationTriggered: Boolean = false ) extends Job[Unit]( List(contextId), @@ -67,18 +68,20 @@ class ExecuteJob( ctx.locking.withReadCompilationLock( this.getClass, () => { - val context = ctx.executionService.getContext - val originalExecutionEnvironment = context.getExecutionEnvironment - if (executionConfig.executionEnvironment ne null) { + val context = ctx.executionService.getContext + val originalExecutionEnvironment = + executionEnvironment.map(_ => context.getExecutionEnvironment) + executionEnvironment.foreach(env => context.setExecutionEnvironment( - executionConfig.executionEnvironment + ExecutionEnvironment.forName(env.name) ) - } + ) val outcome = - try ProgramExecutionSupport - .runProgram(contextId, stack, executionConfig) + try ProgramExecutionSupport.runProgram(contextId, stack) finally { - context.setExecutionEnvironment(originalExecutionEnvironment) + originalExecutionEnvironment.foreach( + context.setExecutionEnvironment + ) } outcome match { case Some(diagnostic: Api.ExecutionResult.Diagnostic) => @@ -131,7 +134,7 @@ object ExecuteJob { new ExecuteJob( executable.contextId, executable.stack.toList, - ExecutionConfig.empty(), + None, visualizationTriggered ) @@ -142,5 +145,5 @@ object ExecuteJob { * @return new execute job */ def apply(contextId: UUID, stack: List[InstrumentFrame]): ExecuteJob = - new ExecuteJob(contextId, stack, ExecutionConfig.empty()) + new ExecuteJob(contextId, stack, None) } diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 4a13c49f31f7..348a11afef91 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -2,7 +2,6 @@ package org.enso.interpreter.instrument.job import com.oracle.truffle.api.exception.AbstractTruffleException import org.enso.interpreter.instrument.{ - ExecutionConfig, InstrumentFrame, MethodCallsCache, RuntimeCache, @@ -62,14 +61,12 @@ object ProgramExecutionSupport { /** Runs the program. * * @param contextId an identifier of an execution context - * @param executionConfig the program execution config * @param executionFrame an execution frame * @param callStack a call stack */ @scala.annotation.tailrec final private def executeProgram( contextId: Api.ContextId, - executionConfig: ExecutionConfig, executionFrame: ExecutionFrame, callStack: List[LocalCallFrame] )(implicit ctx: RuntimeContext): Unit = { @@ -146,7 +143,7 @@ object ProgramExecutionSupport { methodCallsCache, syncState, callStack.headOption.map(_.expressionId).orNull, - executionConfig.expressionConfigs, + ctx.state.expressionExecutionState, callablesCallback, onComputedValueCallback, onCachedValueCallback, @@ -188,7 +185,7 @@ object ProgramExecutionSupport { methodCallsCache, syncState, callStack.headOption.map(_.expressionId).orNull, - executionConfig.expressionConfigs, + ctx.state.expressionExecutionState, callablesCallback, onComputedValueCallback, onCachedValueCallback, @@ -225,7 +222,7 @@ object ProgramExecutionSupport { item.cache, item.syncState ) - executeProgram(contextId, executionConfig, executionFrame, tail) + executeProgram(contextId, executionFrame, tail) case None => () } @@ -236,14 +233,12 @@ object ProgramExecutionSupport { * * @param contextId an identifier of an execution context * @param stack a call stack - * @param executionConfig the program execution config * @param ctx a runtime context * @return an execution result */ final def runProgram( contextId: Api.ContextId, - stack: List[InstrumentFrame], - executionConfig: ExecutionConfig + stack: List[InstrumentFrame] )(implicit ctx: RuntimeContext): Option[Api.ExecutionResult] = { val logger = ctx.executionService.getLogger logger.log(Level.FINEST, s"Run program $contextId") @@ -280,7 +275,7 @@ object ProgramExecutionSupport { ) _ <- Try( - executeProgram(contextId, executionConfig, stackItem, localCalls) + executeProgram(contextId, stackItem, localCalls) ).toEither.left .map(onExecutionError(stackItem.item, _)) } yield () From 1b8b9f8cd9010667d5d47ca0dc5e0c6db645060e Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 2 Oct 2024 17:12:14 +0100 Subject: [PATCH 10/24] DRAFT: interrupt test --- .../instrument/RuntimeAsyncCommandsTest.scala | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala index c5a295183073..9f2f5d15e2e2 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala @@ -3,6 +3,7 @@ package org.enso.interpreter.test.instrument import org.enso.interpreter.test.Metadata import org.enso.common.LanguageInfo import org.enso.common.RuntimeOptions +import org.enso.interpreter.runtime.`type`.ConstantsGen import org.enso.polyglot.RuntimeServerInfo import org.enso.polyglot.runtime.Runtime.Api import org.enso.text.{ContentVersion, Sha3_224VersionCalculator} @@ -242,4 +243,114 @@ class RuntimeAsyncCommandsTest diagnostic.message shouldEqual Some("sleep interrupted") diagnostic.stack should not be empty } + + it should "recompute expression in context after interruption111" in { + val moduleName = "Enso_Test.Test.Main" + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + + val metadata = new Metadata + val idOut = metadata.addItem(180, 17, "aa") + + val code = + """from Standard.Base import all + |from Standard.Base.Runtime.Context import Input, Output + |polyglot java import java.lang.Thread + | + |loop n s=0 = + | if (s > n) then s else + | Thread.sleep 100 + | loop n s+1 + | + |main = + | IO.println "started" + | loop 200 + | out = Output.is_enabled + | IO.println out + |""".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(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // 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.receiveNIgnoreExpressionUpdates( + 1 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)) + ) + + // wait for program to start + var isProgramStarted = false + var iteration = 0 + while (!isProgramStarted && iteration < 100) { + val out = context.consumeOut + Thread.sleep(200) + isProgramStarted = out == List("started") + iteration += 1 + } + if (!isProgramStarted) { + fail("Program start timed out") + } + context.consumeOut shouldEqual List() + + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 3 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + fromCache = false, + typeChanged = false, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual List("True") + } } From 1be1988f3b832e07ea9901ec146641151bddf77d Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 2 Oct 2024 18:30:31 +0100 Subject: [PATCH 11/24] test: interruption --- .../instrument/RuntimeAsyncCommandsTest.scala | 95 +++++++++++++------ 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala index 7ebf2c9f8871..b6d16b32d59f 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala @@ -8,6 +8,7 @@ import org.enso.polyglot.RuntimeServerInfo import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api.{MethodCall, MethodPointer} import org.enso.text.{ContentVersion, Sha3_224VersionCalculator} +import org.enso.text.editing.model import org.graalvm.polyglot.Context import org.scalatest.BeforeAndAfterEach import org.scalatest.flatspec.AnyFlatSpec @@ -245,29 +246,29 @@ class RuntimeAsyncCommandsTest diagnostic.stack should not be empty } - it should "recompute expression in context after interruption111" in { + it should "recompute expression in context after interruption" in { val moduleName = "Enso_Test.Test.Main" val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() val metadata = new Metadata - val idOut = metadata.addItem(180, 17, "aa") + val idOut = metadata.addItem(179, 17, "aa") val code = """from Standard.Base import all |from Standard.Base.Runtime.Context import Input, Output |polyglot java import java.lang.Thread | - |loop n s=0 = - | if (s > n) then s else - | Thread.sleep 100 - | loop n s+1 - | |main = | IO.println "started" - | loop 200 + | loop 50 | out = Output.is_enabled | IO.println out + | + |loop n=0 s=0 = + | if (s > n) then s else + | Thread.sleep 100 + | loop n s+1 |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -288,20 +289,57 @@ class RuntimeAsyncCommandsTest // push main val item1 = Api.StackItem.ExplicitCall( - Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"), + Api.MethodPointer(moduleName, moduleName, "main"), None, Vector() ) context.send( Api.Request(requestId, Api.PushContextRequest(contextId, item1)) ) - context.receiveNIgnoreExpressionUpdates( - 1 + context.receiveNIgnoreStdLib( + 3 ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.PushContextResponse(contextId)) + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update( + contextId, + idOut, + ConstantsGen.BOOLEAN, + methodCall = Some( + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Runtime", + "Standard.Base.Runtime.Context", + "is_enabled" + ), + Vector(1) + ) + ) + ), + context.executionComplete(contextId) ) + context.consumeOut shouldEqual List("started", "False") - // wait for program to start + // recompute + context.send( + Api.Request( + requestId, + Api.RecomputeContextRequest( + contextId, + None, + None, + Seq( + Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) + ) + ) + ) + ) + + val responses = context.receiveNIgnorePendingExpressionUpdates(1) + responses should contain( + Api.Response(requestId, Api.RecomputeContextResponse(contextId)) + ) + + // wait for program to start and interrupt var isProgramStarted = false var iteration = 0 while (!isProgramStarted && iteration < 100) { @@ -315,24 +353,24 @@ class RuntimeAsyncCommandsTest } context.consumeOut shouldEqual List() - // recompute + // trigger re-computation context.send( Api.Request( - requestId, - Api.RecomputeContextRequest( - contextId, - None, - None, + Api.EditFileNotification( + mainFile, Seq( - Api.ExpressionConfig(idOut, Some(Api.ExecutionEnvironment.Live())) - ) + model.TextEdit( + model.Range(model.Position(10, 7), model.Position(10, 8)), + "0" + ) + ), + execute = true, + idMap = None ) ) ) - context.receiveNIgnorePendingExpressionUpdates( - 3 - ) should contain theSameElementsAs Seq( - Api.Response(requestId, Api.RecomputeContextResponse(contextId)), + val responses1 = context.receiveNIgnorePendingExpressionUpdates(3) + responses1 should contain allOf ( TestMessages.update( contextId, idOut, @@ -352,7 +390,7 @@ class RuntimeAsyncCommandsTest ), context.executionComplete(contextId) ) - context.consumeOut shouldEqual List("True") + context.consumeOut should contain("True") } it should "interrupt running execution context without raising Panic" in { @@ -423,7 +461,10 @@ class RuntimeAsyncCommandsTest // recompute/interrupt context.send( - Api.Request(requestId, Api.RecomputeContextRequest(contextId, None, None)) + Api.Request( + requestId, + Api.RecomputeContextRequest(contextId, None, None, Seq()) + ) ) val responses = context.receiveNIgnoreStdLib( 3 From cd365aa5e968f9a543cf5eb368ab75d33d3de32f Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 2 Oct 2024 20:41:09 +0100 Subject: [PATCH 12/24] fix: native image --- .../enso/interpreter/service/ExecutionCallbacks.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index 19274b28e2c0..a9fdb535ec35 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -155,8 +155,7 @@ public Object onFunctionReturn(IdExecutionService.Info info) { @Override public void setExecutionEnvironment(IdExecutionService.Info info) { - ExecutionEnvironment nodeEnvironment = - expressionExecutionState.getExpressionExecutionEnvironment(info.getId()); + ExecutionEnvironment nodeEnvironment = getExecutionEnvironment(info.getId()); if (nodeEnvironment != null && originalExecutionEnvironment == null) { EnsoContext context = EnsoContext.get(info.getRootNode()); originalExecutionEnvironment = context.getExecutionEnvironment(); @@ -167,12 +166,17 @@ public void setExecutionEnvironment(IdExecutionService.Info info) { @Override public void resetExecutionEnvironment(IdExecutionService.Info info) { if (originalExecutionEnvironment != null) { - expressionExecutionState.setExpressionExecuted(info.getId()); + setExpressionExecuted(info.getId()); EnsoContext.get(info.getRootNode()).setExecutionEnvironment(originalExecutionEnvironment); originalExecutionEnvironment = null; } } + @CompilerDirectives.TruffleBoundary + private void setExpressionExecuted(UUID expressionId) { + expressionExecutionState.setExpressionExecuted(expressionId); + } + @CompilerDirectives.TruffleBoundary private ExecutionEnvironment getExecutionEnvironment(UUID expressionId) { return expressionExecutionState.getExpressionExecutionEnvironment(expressionId); From ff97389ada284762a812c3c478e68174f8778f85 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 2 Oct 2024 20:46:15 +0100 Subject: [PATCH 13/24] misc: javafmt --- .../enso/interpreter/instrument/ExpressionExecutionState.java | 3 +-- .../java/org/enso/interpreter/service/ExecutionCallbacks.java | 1 - .../java/org/enso/interpreter/service/ExecutionService.java | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java index 5d5fa93520b1..836f66225992 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java @@ -1,10 +1,9 @@ package org.enso.interpreter.instrument; -import org.enso.interpreter.runtime.state.ExecutionEnvironment; - import java.util.HashMap; import java.util.Map; import java.util.UUID; +import org.enso.interpreter.runtime.state.ExecutionEnvironment; public final class ExpressionExecutionState { diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index a9fdb535ec35..cddaaf9a90eb 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -5,7 +5,6 @@ import java.util.Map; import java.util.UUID; import java.util.function.Consumer; - import org.enso.interpreter.instrument.ExpressionExecutionState; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.OneshotExpression; diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java index 56270d13e814..05306d88b0d7 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java @@ -49,7 +49,6 @@ import org.enso.interpreter.runtime.instrument.NotificationHandler; import org.enso.interpreter.runtime.instrument.Timer; import org.enso.interpreter.runtime.scope.ModuleScope; -import org.enso.interpreter.runtime.state.ExecutionEnvironment; import org.enso.interpreter.runtime.state.State; import org.enso.interpreter.service.error.FailedToApplyEditsException; import org.enso.interpreter.service.error.MethodNotFoundException; From 4662720d93c88a3d694c13fce9ab605ab668fd53 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 2 Oct 2024 11:24:47 +0100 Subject: [PATCH 14/24] DRAFT: ContextThreadLocal --- .../org/enso/interpreter/EnsoLanguage.java | 31 +++++++++++++++++++ .../enso/interpreter/runtime/EnsoContext.java | 12 ++----- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java index 696e46dbee13..bc8a97051830 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java @@ -1,6 +1,7 @@ package org.enso.interpreter; import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.ContextThreadLocal; import com.oracle.truffle.api.Option; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLogger; @@ -87,10 +88,31 @@ public final class EnsoLanguage extends TruffleLanguage { private static final LanguageReference REFERENCE = LanguageReference.create(EnsoLanguage.class); + private final ContextThreadLocal threadExecutionEnvironment = + locals.createContextThreadLocal( + (ctx, thread) -> new ExecutionEnvironmentReference(ctx.getExecutionEnvironment())); + public static EnsoLanguage get(Node node) { return REFERENCE.get(node); } + private static final class ExecutionEnvironmentReference { + + private ExecutionEnvironment executionEnvironment; + + ExecutionEnvironmentReference(ExecutionEnvironment executionEnvironment) { + this.executionEnvironment = executionEnvironment; + } + + ExecutionEnvironment getExecutionEnvironment() { + return executionEnvironment; + } + + void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { + this.executionEnvironment = executionEnvironment; + } + } + /** * Creates a new Enso context. * @@ -362,6 +384,7 @@ protected Object getScope(EnsoContext context) { } /** Conversions of primitive values */ + @Override protected Object getLanguageView(EnsoContext context, Object value) { if (value instanceof Boolean b) { var bool = context.getBuiltins().bool(); @@ -370,4 +393,12 @@ protected Object getLanguageView(EnsoContext context, Object value) { } return null; } + + public ExecutionEnvironment getThreadExecutionEnvironment() { + return threadExecutionEnvironment.get().getExecutionEnvironment(); + } + + public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { + threadExecutionEnvironment.get().setExecutionEnvironment(executionEnvironment); + } } 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 4f9f6058055e..7890d2a4a78f 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 @@ -109,7 +109,6 @@ public final class EnsoContext { private final Shape rootStateShape = Shape.newBuilder().layout(State.Container.class).build(); private ExecutionEnvironment contextExecutionEnvironment; - private final ThreadLocal threadExecutionEnvironment; private final int warningsLimit; @@ -146,7 +145,6 @@ public EnsoContext( this.isPrivateCheckDisabled = getOption(RuntimeOptions.DISABLE_PRIVATE_CHECK_KEY); this.isStaticTypeAnalysisEnabled = getOption(RuntimeOptions.ENABLE_STATIC_ANALYSIS_KEY); this.contextExecutionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); - this.threadExecutionEnvironment = initializeThreadExecutionEnvironment(); this.assertionsEnabled = shouldAssertionsBeEnabled(); this.shouldWaitForPendingSerializationJobs = getOption(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS_KEY); @@ -219,11 +217,6 @@ public void initialize() { } } - @TruffleBoundary - private ThreadLocal initializeThreadExecutionEnvironment() { - return ThreadLocal.withInitial(this::getExecutionEnvironment); - } - /** Checks if the working directory is as expected and reports a warning if not. */ private void checkWorkingDirectory(Optional maybeProjectRoot) { if (maybeProjectRoot.isPresent()) { @@ -880,15 +873,14 @@ public ExecutionEnvironment getExecutionEnvironment() { return contextExecutionEnvironment; } - @TruffleBoundary public ExecutionEnvironment getThreadExecutionEnvironment() { - return threadExecutionEnvironment.get(); + return language.getThreadExecutionEnvironment(); } /** Set the runtime execution environment of this context. */ public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { this.contextExecutionEnvironment = executionEnvironment; - this.threadExecutionEnvironment.remove(); + language.setExecutionEnvironment(executionEnvironment); } /** From 4250a3b8a679554e200941595dc72fd3cb91febd Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Mon, 7 Oct 2024 15:30:10 +0100 Subject: [PATCH 15/24] update: callbacks interface --- .../polyglot/debugger/IdExecutionService.java | 4 +-- .../service/ExecutionCallbacks.java | 29 ++----------------- .../job/ProgramExecutionSupport.scala | 1 + .../id/execution/IdExecutionInstrument.java | 25 ++++++++++++++-- .../instrument/RuntimeAsyncCommandsTest.scala | 3 +- .../expression/builtin/meta/Instrumentor.java | 7 ++--- 6 files changed, 31 insertions(+), 38 deletions(-) diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java index 303eddde7123..722ce038c928 100644 --- a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java @@ -76,9 +76,7 @@ public interface Callbacks { */ Object onFunctionReturn(Info info); - void setExecutionEnvironment(Info info); - - void resetExecutionEnvironment(Info info); + Object getExecutionEnvironment(Info info); } /** diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index cddaaf9a90eb..348497599878 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -40,8 +40,6 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { private final Consumer functionCallCallback; private final Consumer onExecutedVisualizationCallback; - private ExecutionEnvironment originalExecutionEnvironment = null; - /** * Creates callbacks instance. * @@ -153,32 +151,9 @@ public Object onFunctionReturn(IdExecutionService.Info info) { } @Override - public void setExecutionEnvironment(IdExecutionService.Info info) { - ExecutionEnvironment nodeEnvironment = getExecutionEnvironment(info.getId()); - if (nodeEnvironment != null && originalExecutionEnvironment == null) { - EnsoContext context = EnsoContext.get(info.getRootNode()); - originalExecutionEnvironment = context.getExecutionEnvironment(); - context.setExecutionEnvironment(nodeEnvironment); - } - } - - @Override - public void resetExecutionEnvironment(IdExecutionService.Info info) { - if (originalExecutionEnvironment != null) { - setExpressionExecuted(info.getId()); - EnsoContext.get(info.getRootNode()).setExecutionEnvironment(originalExecutionEnvironment); - originalExecutionEnvironment = null; - } - } - - @CompilerDirectives.TruffleBoundary - private void setExpressionExecuted(UUID expressionId) { - expressionExecutionState.setExpressionExecuted(expressionId); - } - @CompilerDirectives.TruffleBoundary - private ExecutionEnvironment getExecutionEnvironment(UUID expressionId) { - return expressionExecutionState.getExpressionExecutionEnvironment(expressionId); + public Object getExecutionEnvironment(IdExecutionService.Info info) { + return expressionExecutionState.getExpressionExecutionEnvironment(info.getId()); } @CompilerDirectives.TruffleBoundary diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 78bf59ed957c..c522022d49d4 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -519,6 +519,7 @@ object ProgramExecutionSupport { } syncState.setExpressionSync(expressionId) + ctx.state.expressionExecutionState.setExpressionExecuted(expressionId) if (methodCall.isDefined) { syncState.setMethodPointerSync(expressionId) } diff --git a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java index 3812f60ba5b6..04bef9e92dba 100644 --- a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java @@ -38,6 +38,7 @@ import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.error.PanicSentinel; import org.enso.interpreter.runtime.instrument.Timer; +import org.enso.interpreter.runtime.state.ExecutionEnvironment; import org.enso.interpreter.runtime.state.State; import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.IdentifiedTag; @@ -187,6 +188,7 @@ private class IdExecutionEventNode extends ExecutionEventNode { private final EventContext context; private long nanoTimeElapsed = 0; + private ExecutionEnvironment originalExecutionEnvironment = null; /** * Creates a new event node. @@ -214,7 +216,7 @@ public void onEnter(VirtualFrame frame) { if (result != null) { throw context.createUnwind(result); } - callbacks.setExecutionEnvironment(info); + setExecutionEnvironment(info); nanoTimeElapsed = timer.getTime(); } @@ -257,7 +259,7 @@ public void onReturnValue(VirtualFrame frame, Object result) { frame == null ? null : frame.materialize(), node); callbacks.updateCachedResult(info); - callbacks.resetExecutionEnvironment(info); + resetExecutionEnvironment(info); if (info.isPanic()) { throw context.createUnwind(result); @@ -270,7 +272,7 @@ public void onReturnValue(VirtualFrame frame, Object result) { nanoTimeElapsed, frame == null ? null : frame.materialize(), node); - callbacks.resetExecutionEnvironment(info); + resetExecutionEnvironment(info); } } @@ -329,6 +331,23 @@ public Object visitFrame(FrameInstance frameInstance) { }); return result == null; } + + private void setExecutionEnvironment(IdExecutionService.Info info) { + ExecutionEnvironment nodeEnvironment = + (ExecutionEnvironment) callbacks.getExecutionEnvironment(info); + if (nodeEnvironment != null && originalExecutionEnvironment == null) { + EnsoContext context = EnsoContext.get(info.getRootNode()); + originalExecutionEnvironment = context.getExecutionEnvironment(); + context.setExecutionEnvironment(nodeEnvironment); + } + } + + private void resetExecutionEnvironment(IdExecutionService.Info info) { + if (originalExecutionEnvironment != null) { + EnsoContext.get(info.getRootNode()).setExecutionEnvironment(originalExecutionEnvironment); + originalExecutionEnvironment = null; + } + } } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala index a3b40ee79c24..a36ce16a7eee 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeAsyncCommandsTest.scala @@ -637,7 +637,8 @@ class RuntimeAsyncCommandsTest Api.RecomputeContextRequest( contextId, Some(InvalidatedExpressions.Expressions(Vector(idOp1, idOp2))), - None + None, + Seq() ) ) ) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java index 33a1fa677cf5..c9a5c44f482f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java @@ -122,8 +122,7 @@ public Object onFunctionReturn(IdExecutionService.Info info) { } @Override - public void setExecutionEnvironment(IdExecutionService.Info info) {} - - @Override - public void resetExecutionEnvironment(IdExecutionService.Info info) {} + public Object getExecutionEnvironment(IdExecutionService.Info info) { + return null; + } } From 0ccd2cdaadab7c448ecde5db629d31ac7b969931 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Mon, 7 Oct 2024 17:29:12 +0100 Subject: [PATCH 16/24] misc: javafmt --- .../java/org/enso/interpreter/service/ExecutionCallbacks.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index 348497599878..53f09fe3ce1e 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -14,11 +14,9 @@ import org.enso.interpreter.instrument.profiling.ExecutionTime; import org.enso.interpreter.instrument.profiling.ProfilingInfo; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; -import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; -import org.enso.interpreter.runtime.state.ExecutionEnvironment; import org.enso.interpreter.runtime.type.Constants; import org.enso.interpreter.service.ExecutionService.ExpressionCall; import org.enso.interpreter.service.ExecutionService.ExpressionValue; From c05283d41a747901f09586d1de318fefdcbe8731 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 14:38:22 +0100 Subject: [PATCH 17/24] remove: getRootNode --- .../org/enso/polyglot/debugger/IdExecutionService.java | 6 ------ .../interpreter/instrument/ExpressionExecutionState.java | 2 +- .../org/enso/interpreter/service/ExecutionCallbacks.java | 2 +- .../instrument/id/execution/IdExecutionInstrument.java | 9 ++------- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java index 722ce038c928..040bab372dd4 100644 --- a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java @@ -4,7 +4,6 @@ import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; import com.oracle.truffle.api.interop.TruffleObject; -import com.oracle.truffle.api.nodes.RootNode; import java.util.UUID; public interface IdExecutionService { @@ -22,11 +21,6 @@ public abstract class Info { */ public abstract Object getResult(); - /** - * @return the root of this node. - */ - public abstract RootNode getRootNode(); - /** * @return {@code true} when the result is panic, {@code false} otherwise. */ diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java index 836f66225992..16ae485233d6 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExpressionExecutionState.java @@ -25,7 +25,7 @@ public void setExpressionExecuted(UUID expressionId) { expressionConfigs.remove(expressionId); } - public ExecutionEnvironment getExpressionExecutionEnvironment(UUID expressionId) { + public ExecutionEnvironment getExecutionEnvironment(UUID expressionId) { return expressionConfigs.get(expressionId); } } diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java index 53f09fe3ce1e..83cda99cbb45 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -151,7 +151,7 @@ public Object onFunctionReturn(IdExecutionService.Info info) { @Override @CompilerDirectives.TruffleBoundary public Object getExecutionEnvironment(IdExecutionService.Info info) { - return expressionExecutionState.getExpressionExecutionEnvironment(info.getId()); + return expressionExecutionState.getExecutionEnvironment(info.getId()); } @CompilerDirectives.TruffleBoundary diff --git a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java index 04bef9e92dba..8e09275a3820 100644 --- a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java @@ -149,11 +149,6 @@ public Object getResult() { return result; } - @Override - public EnsoRootNode getRootNode() { - return ensoRootNode; - } - @Override public boolean isPanic() { return result instanceof AbstractTruffleException && !(result instanceof DataflowError); @@ -336,7 +331,7 @@ private void setExecutionEnvironment(IdExecutionService.Info info) { ExecutionEnvironment nodeEnvironment = (ExecutionEnvironment) callbacks.getExecutionEnvironment(info); if (nodeEnvironment != null && originalExecutionEnvironment == null) { - EnsoContext context = EnsoContext.get(info.getRootNode()); + EnsoContext context = EnsoContext.get(this); originalExecutionEnvironment = context.getExecutionEnvironment(); context.setExecutionEnvironment(nodeEnvironment); } @@ -344,7 +339,7 @@ private void setExecutionEnvironment(IdExecutionService.Info info) { private void resetExecutionEnvironment(IdExecutionService.Info info) { if (originalExecutionEnvironment != null) { - EnsoContext.get(info.getRootNode()).setExecutionEnvironment(originalExecutionEnvironment); + EnsoContext.get(this).setExecutionEnvironment(originalExecutionEnvironment); originalExecutionEnvironment = null; } } From 51b3e6ce4fdc10b074df79f2f03ac28e7e3f1f11 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 14:57:19 +0100 Subject: [PATCH 18/24] rename: execution environment getters --- .../command/SetExecutionEnvironmentCommand.java | 2 +- .../enso/interpreter/instrument/job/ExecuteJob.scala | 2 +- .../instrument/id/execution/IdExecutionInstrument.java | 2 +- .../instrument/RuntimeExecutionEnvironmentTest.scala | 10 +++++----- .../test/instrument/RuntimeRecomputeTest.scala | 4 ++-- .../test/instrument/RuntimeServerTest.scala | 4 ++-- .../main/java/org/enso/interpreter/EnsoLanguage.java | 2 +- .../builtin/runtime/ContextIsEnabledNode.java | 2 +- .../RuntimeCurrentExecutionEnvironmentNode.java | 2 +- .../java/org/enso/interpreter/runtime/EnsoContext.java | 4 ++-- .../enso/interpreter/runtime/error/DataflowError.java | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java index 827db6141b63..7bf93656c239 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/SetExecutionEnvironmentCommand.java @@ -48,7 +48,7 @@ private void setExecutionEnvironment( this.getClass(), () -> { var oldEnvironmentName = - ctx.executionService().getContext().getExecutionEnvironment().getName(); + ctx.executionService().getContext().getGlobalExecutionEnvironment().getName(); if (!oldEnvironmentName.equals(executionEnvironment.name())) { ctx.jobControlPlane() .abortJobs( diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala index 88db0df63c85..e377387a4c51 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala @@ -70,7 +70,7 @@ class ExecuteJob( () => { val context = ctx.executionService.getContext val originalExecutionEnvironment = - executionEnvironment.map(_ => context.getExecutionEnvironment) + executionEnvironment.map(_ => context.getGlobalExecutionEnvironment) executionEnvironment.foreach(env => context.setExecutionEnvironment( ExecutionEnvironment.forName(env.name) diff --git a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java index 8e09275a3820..de3fd7f069b7 100644 --- a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java @@ -332,7 +332,7 @@ private void setExecutionEnvironment(IdExecutionService.Info info) { (ExecutionEnvironment) callbacks.getExecutionEnvironment(info); if (nodeEnvironment != null && originalExecutionEnvironment == null) { EnsoContext context = EnsoContext.get(this); - originalExecutionEnvironment = context.getExecutionEnvironment(); + originalExecutionEnvironment = context.getGlobalExecutionEnvironment(); context.setExecutionEnvironment(nodeEnvironment); } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeExecutionEnvironmentTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeExecutionEnvironmentTest.scala index 38e122949286..b15ed8ac8513 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeExecutionEnvironmentTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeExecutionEnvironmentTest.scala @@ -196,7 +196,7 @@ class RuntimeExecutionEnvironmentTest context.executionComplete(contextId) ) context.consumeOut shouldEqual List() - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Design() .name @@ -232,7 +232,7 @@ class RuntimeExecutionEnvironmentTest context.executionComplete(contextId) ) context.consumeOut shouldEqual List("Hello World!") - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Live() .name } @@ -300,7 +300,7 @@ class RuntimeExecutionEnvironmentTest context.executionComplete(contextId) ) context.consumeOut shouldEqual List() - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Design() .name @@ -318,7 +318,7 @@ class RuntimeExecutionEnvironmentTest context.receiveNIgnoreStdLib(1) should contain theSameElementsAs Seq( Api.Response(requestId, Api.SetExecutionEnvironmentResponse(contextId)) ) - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Design() .name @@ -353,7 +353,7 @@ class RuntimeExecutionEnvironmentTest ), context.executionComplete(contextId) ) - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Live() .name } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala index 47816084bb8e..304c85cdc79a 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeRecomputeTest.scala @@ -323,7 +323,7 @@ class RuntimeRecomputeTest ) // recompute - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Design() .name context.send( @@ -352,7 +352,7 @@ class RuntimeRecomputeTest context.Main.Update.mainZ(contextId, typeChanged = false), context.executionComplete(contextId) ) - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Design() .name } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index 5893ce32c658..5a9317cad3cc 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -6951,7 +6951,7 @@ class RuntimeServerTest context.executionComplete(contextId) ) context.consumeOut shouldEqual List("Hello World!") - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Design() .name @@ -6972,7 +6972,7 @@ class RuntimeServerTest context.executionComplete(contextId) ) context.consumeOut shouldEqual List("Hello World!") - context.languageContext.getExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment + context.languageContext.getGlobalExecutionEnvironment.getName shouldEqual Api.ExecutionEnvironment .Live() .name } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java index bc8a97051830..ac052b737ba5 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java @@ -90,7 +90,7 @@ public final class EnsoLanguage extends TruffleLanguage { private final ContextThreadLocal threadExecutionEnvironment = locals.createContextThreadLocal( - (ctx, thread) -> new ExecutionEnvironmentReference(ctx.getExecutionEnvironment())); + (ctx, thread) -> new ExecutionEnvironmentReference(ctx.getGlobalExecutionEnvironment())); public static EnsoLanguage get(Node node) { return REFERENCE.get(node); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java index b8f19174fecc..b2e74dbf2d72 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/ContextIsEnabledNode.java @@ -17,7 +17,7 @@ public class ContextIsEnabledNode extends Node { Object execute(Atom self, Object environmentName) { String envName = expectStringNode.execute(environmentName); - ExecutionEnvironment currentEnv = EnsoContext.get(this).getThreadExecutionEnvironment(); + ExecutionEnvironment currentEnv = EnsoContext.get(this).getExecutionEnvironment(); if (!currentEnv.getName().equals(envName)) { Atom error = EnsoContext.get(this) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java index e8d63bd8b76d..7fd13a913790 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/runtime/RuntimeCurrentExecutionEnvironmentNode.java @@ -12,6 +12,6 @@ autoRegister = false) public class RuntimeCurrentExecutionEnvironmentNode extends Node { Object execute() { - return Text.create(EnsoContext.get(this).getThreadExecutionEnvironment().getName()); + return Text.create(EnsoContext.get(this).getExecutionEnvironment().getName()); } } 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 7890d2a4a78f..de11c00733d8 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 @@ -869,11 +869,11 @@ public long nextSequenceId() { return clock.getAndIncrement(); } - public ExecutionEnvironment getExecutionEnvironment() { + public ExecutionEnvironment getGlobalExecutionEnvironment() { return contextExecutionEnvironment; } - public ExecutionEnvironment getThreadExecutionEnvironment() { + public ExecutionEnvironment getExecutionEnvironment() { return language.getThreadExecutionEnvironment(); } 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 a57ddff19896..1d65eac65161 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 @@ -56,7 +56,7 @@ public static DataflowError withDefaultTrace(State state, Object payload, Node l boolean attachFullStackTrace = state == null || EnsoContext.get(location) - .getThreadExecutionEnvironment() + .getExecutionEnvironment() .hasContextEnabled("Dataflow_Stack_Trace"); if (attachFullStackTrace) { var result = new DataflowError(payload, UNLIMITED_STACK_TRACE, location); From 220605ec1c930fa6324873fc82e320d4921bce98 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 15:00:13 +0100 Subject: [PATCH 19/24] refactor: resetExecutionEnvironment --- .../id/execution/IdExecutionInstrument.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java index de3fd7f069b7..bb4a5ae65d67 100644 --- a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/id/execution/IdExecutionInstrument.java @@ -254,20 +254,13 @@ public void onReturnValue(VirtualFrame frame, Object result) { frame == null ? null : frame.materialize(), node); callbacks.updateCachedResult(info); - resetExecutionEnvironment(info); + resetExecutionEnvironment(); if (info.isPanic()) { throw context.createUnwind(result); } - } else if (node instanceof ExpressionNode expressionNode) { - Info info = - new NodeInfo( - expressionNode.getId(), - result, - nanoTimeElapsed, - frame == null ? null : frame.materialize(), - node); - resetExecutionEnvironment(info); + } else if (node instanceof ExpressionNode) { + resetExecutionEnvironment(); } } @@ -337,7 +330,7 @@ private void setExecutionEnvironment(IdExecutionService.Info info) { } } - private void resetExecutionEnvironment(IdExecutionService.Info info) { + private void resetExecutionEnvironment() { if (originalExecutionEnvironment != null) { EnsoContext.get(this).setExecutionEnvironment(originalExecutionEnvironment); originalExecutionEnvironment = null; From 21f8675b1d640ae10f3e7ade67839ce55150c9ca Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 15:15:35 +0100 Subject: [PATCH 20/24] misc: cleanup ExecutionConfig --- .../org/enso/interpreter/instrument/ExecutionConfig.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java index 0719b04181d1..15efce5f0ef1 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java @@ -25,13 +25,9 @@ public static ExecutionConfig empty() { @SuppressWarnings("unchecked") public static ExecutionConfig create( - Object executionEnvironmentOption1, Object expressionConfigs1) { + Option executionEnvironmentOption, + Seq expressionConfigs) { Map expressionConfigsBuilder = new HashMap<>(); - Option executionEnvironmentOption = - (Option) executionEnvironmentOption1; - Seq expressionConfigs = - (Seq) expressionConfigs1; - expressionConfigs.foreach( expressionConfig -> { expressionConfig From 4d29bfac676924eae6e73dbb02a4d8fb74f3393a Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 15:35:44 +0100 Subject: [PATCH 21/24] remove: ExecutionEnvironmentReference --- .../org/enso/interpreter/EnsoLanguage.java | 25 +++---------------- .../enso/interpreter/runtime/EnsoContext.java | 12 ++++----- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java index ac052b737ba5..b2d288d648d9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java @@ -88,31 +88,14 @@ public final class EnsoLanguage extends TruffleLanguage { private static final LanguageReference REFERENCE = LanguageReference.create(EnsoLanguage.class); - private final ContextThreadLocal threadExecutionEnvironment = + private final ContextThreadLocal threadExecutionEnvironment = locals.createContextThreadLocal( - (ctx, thread) -> new ExecutionEnvironmentReference(ctx.getGlobalExecutionEnvironment())); + (ctx, thread) -> new ExecutionEnvironment[] { ctx.getGlobalExecutionEnvironment() }); public static EnsoLanguage get(Node node) { return REFERENCE.get(node); } - private static final class ExecutionEnvironmentReference { - - private ExecutionEnvironment executionEnvironment; - - ExecutionEnvironmentReference(ExecutionEnvironment executionEnvironment) { - this.executionEnvironment = executionEnvironment; - } - - ExecutionEnvironment getExecutionEnvironment() { - return executionEnvironment; - } - - void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { - this.executionEnvironment = executionEnvironment; - } - } - /** * Creates a new Enso context. * @@ -395,10 +378,10 @@ protected Object getLanguageView(EnsoContext context, Object value) { } public ExecutionEnvironment getThreadExecutionEnvironment() { - return threadExecutionEnvironment.get().getExecutionEnvironment(); + return threadExecutionEnvironment.get()[0]; } public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { - threadExecutionEnvironment.get().setExecutionEnvironment(executionEnvironment); + threadExecutionEnvironment.get()[0] = executionEnvironment; } } 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 de11c00733d8..ac98fb3ea91f 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 @@ -108,7 +108,7 @@ public final class EnsoContext { private final AtomicLong clock = new AtomicLong(); private final Shape rootStateShape = Shape.newBuilder().layout(State.Container.class).build(); - private ExecutionEnvironment contextExecutionEnvironment; + private ExecutionEnvironment globalExecutionEnvironment; private final int warningsLimit; @@ -144,7 +144,7 @@ public EnsoContext( getOption(RuntimeOptions.DISABLE_IR_CACHES_KEY) || isParallelismEnabled; this.isPrivateCheckDisabled = getOption(RuntimeOptions.DISABLE_PRIVATE_CHECK_KEY); this.isStaticTypeAnalysisEnabled = getOption(RuntimeOptions.ENABLE_STATIC_ANALYSIS_KEY); - this.contextExecutionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); + this.globalExecutionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); this.assertionsEnabled = shouldAssertionsBeEnabled(); this.shouldWaitForPendingSerializationJobs = getOption(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS_KEY); @@ -870,7 +870,7 @@ public long nextSequenceId() { } public ExecutionEnvironment getGlobalExecutionEnvironment() { - return contextExecutionEnvironment; + return globalExecutionEnvironment; } public ExecutionEnvironment getExecutionEnvironment() { @@ -879,7 +879,7 @@ public ExecutionEnvironment getExecutionEnvironment() { /** Set the runtime execution environment of this context. */ public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { - this.contextExecutionEnvironment = executionEnvironment; + this.globalExecutionEnvironment = executionEnvironment; language.setExecutionEnvironment(executionEnvironment); } @@ -891,7 +891,7 @@ public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { * @return the execution environment version before modification */ public ExecutionEnvironment enableExecutionEnvironment(Atom context, String environmentName) { - ExecutionEnvironment original = contextExecutionEnvironment; + ExecutionEnvironment original = globalExecutionEnvironment; if (original.getName().equals(environmentName)) { setExecutionEnvironment(original.withContextEnabled(context)); } @@ -906,7 +906,7 @@ public ExecutionEnvironment enableExecutionEnvironment(Atom context, String envi * @return the execution environment version before modification */ public ExecutionEnvironment disableExecutionEnvironment(Atom context, String environmentName) { - ExecutionEnvironment original = contextExecutionEnvironment; + ExecutionEnvironment original = globalExecutionEnvironment; if (original.getName().equals(environmentName)) { setExecutionEnvironment(original.withContextDisabled(context)); } From c5b3c0c2a7727b13babb36e0b52170decc31a600 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 16:25:18 +0100 Subject: [PATCH 22/24] refactor: ContextThreadLocal execution environment --- .../main/java/org/enso/interpreter/EnsoLanguage.java | 11 +++++------ .../org/enso/interpreter/runtime/EnsoContext.java | 3 ++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java index b2d288d648d9..998276aa4551 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java @@ -88,9 +88,8 @@ public final class EnsoLanguage extends TruffleLanguage { private static final LanguageReference REFERENCE = LanguageReference.create(EnsoLanguage.class); - private final ContextThreadLocal threadExecutionEnvironment = - locals.createContextThreadLocal( - (ctx, thread) -> new ExecutionEnvironment[] { ctx.getGlobalExecutionEnvironment() }); + private final ContextThreadLocal executionEnvironment = + locals.createContextThreadLocal((ctx, thread) -> new ExecutionEnvironment[1]); public static EnsoLanguage get(Node node) { return REFERENCE.get(node); @@ -377,11 +376,11 @@ protected Object getLanguageView(EnsoContext context, Object value) { return null; } - public ExecutionEnvironment getThreadExecutionEnvironment() { - return threadExecutionEnvironment.get()[0]; + public ExecutionEnvironment getExecutionEnvironment() { + return executionEnvironment.get()[0]; } public void setExecutionEnvironment(ExecutionEnvironment executionEnvironment) { - threadExecutionEnvironment.get()[0] = executionEnvironment; + this.executionEnvironment.get()[0] = executionEnvironment; } } 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 ac98fb3ea91f..1f44b23295d7 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 @@ -874,7 +874,8 @@ public ExecutionEnvironment getGlobalExecutionEnvironment() { } public ExecutionEnvironment getExecutionEnvironment() { - return language.getThreadExecutionEnvironment(); + ExecutionEnvironment env = language.getExecutionEnvironment(); + return env == null ? getGlobalExecutionEnvironment() : env; } /** Set the runtime execution environment of this context. */ From 6d642534b8a70f96614f8553576356bd39131e6e Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 17:14:24 +0100 Subject: [PATCH 23/24] misc: fmt --- .../org/enso/interpreter/instrument/job/ExecuteJob.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala index e377387a4c51..47136f71fa27 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ExecuteJob.scala @@ -70,7 +70,9 @@ class ExecuteJob( () => { val context = ctx.executionService.getContext val originalExecutionEnvironment = - executionEnvironment.map(_ => context.getGlobalExecutionEnvironment) + executionEnvironment.map(_ => + context.getGlobalExecutionEnvironment + ) executionEnvironment.foreach(env => context.setExecutionEnvironment( ExecutionEnvironment.forName(env.name) From 9eeb917d29a14f8061ec4ba6a1cc3f2d30fce8b0 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 8 Oct 2024 17:20:21 +0100 Subject: [PATCH 24/24] fix: ExecutionConfig compilation --- .../org/enso/interpreter/instrument/ExecutionConfig.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java index 15efce5f0ef1..68ccccc6600e 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/ExecutionConfig.java @@ -25,9 +25,12 @@ public static ExecutionConfig empty() { @SuppressWarnings("unchecked") public static ExecutionConfig create( - Option executionEnvironmentOption, - Seq expressionConfigs) { + Object executionEnvironmentOption1, Object expressionConfigs1) { Map expressionConfigsBuilder = new HashMap<>(); + Option executionEnvironmentOption = + (Option) executionEnvironmentOption1; + Seq expressionConfigs = + (Seq) expressionConfigs1; expressionConfigs.foreach( expressionConfig -> { expressionConfig