Handle autoscoped constructor args with no UUID (#11354)

* Handle autoscoped constructor args with no UUID

An application involving >1 autoscoped atom constructor arguments
with no ID would lead to a silent type error in GUI. It was silent
because once IdMap gets updated, the original type error disappears and
users are left with a No_Such_Method on a Panic.

The type error may occur because the compiler was inferring the same
UUID for autoscoped constructors. Args with UUID are cached therefore a
type confict might occur on the second (or later) argument.

Added a unit test case demonstrating the problem (previously it would
fail). The search path is now a bit more careful when inferring
arguments.

* One more test
This commit is contained in:
Hubert Plociniczak 2024-10-18 20:43:18 +02:00 committed by GitHub
parent 4d4a2990a0
commit c6ec5c5399
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 439 additions and 2 deletions

View File

@ -7472,6 +7472,199 @@ class RuntimeServerTest
context.consumeOut shouldEqual List("16")
}
it should "resolve multiple autoscoped atomconstructor with no IDs initially" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
val moduleName = "Enso_Test.Test.Main"
val moduleNameLib = "Enso_Test.Test.Lib"
val moduleNameTypes = "Enso_Test.Test.Types"
val metadata = new Metadata
val idS = UUID.randomUUID()
val idX = UUID.randomUUID()
val idAArg = UUID.randomUUID()
val idBArg = UUID.randomUUID()
val idRes = UUID.randomUUID()
val typesMetadata = new Metadata
val codeTypes = typesMetadata.appendToCode(
"""type Foo
| A
|
|type Bar
| B
|""".stripMargin.linesIterator.mkString("\n")
)
val typesFile = context.writeInSrcDir("Types", codeTypes)
val libMetadata = new Metadata
val codeLib = libMetadata.appendToCode(
"""from project.Types import Foo, Bar
|from Standard.Base import all
|
|type Singleton
| S value
|
| test : Foo -> Bar -> Number
| test self (x : Foo = ..A) (y : Bar = ..B) =
| Singleton.from_test x y
|
| from_test : Foo -> Bar -> Number
| from_test (x : Foo = ..A) (y : Bar = ..B) =
| _ = x
| _ = y
| 42
|""".stripMargin.linesIterator.mkString("\n")
)
val libFile = context.writeInSrcDir("Lib", codeLib)
val code =
"""from project.Lib import Singleton
|
|main =
| s = Singleton.S 1
| x = s.test ..A ..B
| 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 files
context.send(
Api.Request(requestId, Api.OpenFileRequest(typesFile, codeTypes))
)
context.receive shouldEqual Some(
Api.Response(Some(requestId), Api.OpenFileResponse)
)
context.send(
Api.Request(requestId, Api.OpenFileRequest(libFile, codeLib))
)
context.receive shouldEqual Some(
Api.Response(Some(requestId), Api.OpenFileResponse)
)
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.receiveNIgnorePendingExpressionUpdates(
2
) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
context.executionComplete(contextId)
)
context.send(
Api.Request(
Api.EditFileNotification(
mainFile,
Seq(),
execute = true,
idMap = Some(
model.IdMap(
Vector(
model.Span(50, 63) -> idS,
model.Span(72, 86) -> idX,
model.Span(79, 82) -> idAArg,
model.Span(83, 86) -> idBArg,
model.Span(91, 92) -> idRes
)
)
)
)
)
)
val afterIdMapUpdate = context.receiveN(6)
afterIdMapUpdate shouldEqual Seq(
TestMessages.update(
contextId,
idS,
s"$moduleNameLib.Singleton",
methodCall = Some(
Api.MethodCall(
Api
.MethodPointer(moduleNameLib, s"$moduleNameLib.Singleton", "S")
)
),
payload = Api.ExpressionUpdate.Payload.Value(None)
),
TestMessages.update(
contextId,
idAArg,
s"$moduleNameTypes.Foo",
methodCall = Some(
Api.MethodCall(
Api
.MethodPointer(
moduleNameTypes,
s"$moduleNameTypes.Foo",
"A"
)
)
),
payload = Api.ExpressionUpdate.Payload.Value(None)
),
TestMessages.update(
contextId,
idBArg,
s"$moduleNameTypes.Bar",
methodCall = Some(
Api.MethodCall(
Api
.MethodPointer(
moduleNameTypes,
s"$moduleNameTypes.Bar",
"B"
)
)
),
payload = Api.ExpressionUpdate.Payload.Value(None)
),
TestMessages.update(
contextId,
idX,
s"Standard.Base.Data.Numbers.Integer",
methodCall = Some(
Api.MethodCall(
Api
.MethodPointer(
moduleNameLib,
s"$moduleNameLib.Singleton",
"test"
)
)
),
payload = Api.ExpressionUpdate.Payload.Value(None)
),
TestMessages.update(
contextId,
idRes,
s"Standard.Base.Data.Numbers.Integer",
payload = Api.ExpressionUpdate.Payload.Value(None)
),
context.executionComplete(contextId)
)
}
}
object RuntimeServerTest {

View File

@ -2,6 +2,7 @@ package org.enso.interpreter.test.instrument
import org.enso.interpreter.runtime.`type`.ConstantsGen
import org.enso.interpreter.test.Metadata
import org.enso.pkg.QualifiedName
import org.enso.common.RuntimeOptions
import org.enso.polyglot._
@ -4074,6 +4075,237 @@ class RuntimeVisualizationsTest extends AnyFlatSpec with Matchers {
new String(data, StandardCharsets.UTF_8) shouldEqual "(Mk_Newtype 42)"
}
it should "resolve multiple autoscoped atomconstructor" in withContext() {
context =>
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
val visualizationId = UUID.randomUUID()
val moduleName = "Enso_Test.Test.Main"
val moduleNameLib = "Enso_Test.Test.Lib"
val metadata = new Metadata
val idS = metadata.addItem(50, 13, "eeee")
val idX = metadata.addItem(72, 14, "aaaa")
val idAArg = UUID.randomUUID()
val idBArg = UUID.randomUUID()
val idRes = metadata.addItem(91, 1, "dddd")
val typesMetadata = new Metadata
val codeTypes = typesMetadata.appendToCode(
"""type Foo
| A
|
|type Bar
| B
|""".stripMargin.linesIterator.mkString("\n")
)
val typesFile = context.writeInSrcDir("Types", codeTypes)
val libMetadata = new Metadata
val codeLib = libMetadata.appendToCode(
"""from project.Types import Foo, Bar
|from Standard.Base import all
|
|type Singleton
| S value
|
| test : Foo -> Bar -> Number
| test self (x : Foo = ..A) (y : Bar = ..B) =
| Singleton.from_test x y
|
| from_test : Foo -> Bar -> Number
| from_test (x : Foo = ..A) (y : Bar = ..B) =
| _ = x
| _ = y
| 42
|""".stripMargin.linesIterator.mkString("\n")
)
val libFile = context.writeInSrcDir("Lib", codeLib)
val code =
"""from project.Lib import Singleton
|
|main =
| s = Singleton.S 1
| x = s.test ..A ..B
| x
|""".stripMargin.linesIterator.mkString("\n")
val contents = metadata.appendToCode(code)
val mainFile = context.writeMain(contents)
metadata.assertInCode(idS, code, "Singleton.S 1")
metadata.assertInCode(idX, code, "s.test ..A ..B")
metadata.assertInCode(idRes, code, "x")
// create context
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
context.receive shouldEqual Some(
Api.Response(requestId, Api.CreateContextResponse(contextId))
)
// Open files
context.send(
Api.Request(requestId, Api.OpenFileRequest(typesFile, codeTypes))
)
context.receive shouldEqual Some(
Api.Response(Some(requestId), Api.OpenFileResponse)
)
context.send(
Api.Request(requestId, Api.OpenFileRequest(libFile, codeLib))
)
context.receive shouldEqual Some(
Api.Response(Some(requestId), Api.OpenFileResponse)
)
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.receiveNIgnorePendingExpressionUpdates(
5
) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
TestMessages.update(
contextId,
idS,
s"$moduleNameLib.Singleton",
methodCall = Some(
Api.MethodCall(
Api
.MethodPointer(moduleNameLib, s"$moduleNameLib.Singleton", "S")
)
),
payload = Api.ExpressionUpdate.Payload.Value(None)
),
TestMessages.update(
contextId,
idX,
s"Standard.Base.Data.Numbers.Integer",
methodCall = Some(
Api.MethodCall(
Api
.MethodPointer(
moduleNameLib,
s"$moduleNameLib.Singleton",
"test"
)
)
),
payload = Api.ExpressionUpdate.Payload.Value(None)
),
TestMessages.update(
contextId,
idRes,
s"Standard.Base.Data.Numbers.Integer",
payload = Api.ExpressionUpdate.Payload.Value(None)
),
context.executionComplete(contextId)
)
// attach visualization
context.send(
Api.Request(
requestId,
Api.AttachVisualization(
visualizationId,
idRes,
Api.VisualizationConfiguration(
contextId,
Api.VisualizationExpression.Text(
"Enso_Test.Test.Main",
"x -> x.to_text",
Vector()
),
"Enso_Test.Test.Main"
)
)
)
)
val attachVisualizationResponses =
context.receiveNIgnoreExpressionUpdates(3)
attachVisualizationResponses should contain allOf (
Api.Response(requestId, Api.VisualizationAttached()),
context.executionComplete(contextId)
)
val Some(data) = attachVisualizationResponses.collectFirst {
case Api.Response(
None,
Api.VisualizationUpdate(
Api.VisualizationContext(
`visualizationId`,
`contextId`,
`idRes`
),
data
)
) =>
data
}
new String(data, StandardCharsets.UTF_8) shouldEqual "42"
context.send(
Api.Request(
Api.EditFileNotification(
mainFile,
Seq(),
execute = true,
idMap = Some(
model.IdMap(
Vector(
model.Span(79, 82) -> idAArg,
model.Span(83, 86) -> idBArg
)
)
)
)
)
)
val afterIdMapUpdate = context.receiveN(3)
// Can't do comparison directly because of Arrays https://github.com/scalatest/scalatest/issues/491
afterIdMapUpdate should contain allOf (
TestMessages.update(
contextId,
idRes,
s"Standard.Base.Data.Numbers.Integer",
typeChanged = false,
payload = Api.ExpressionUpdate.Payload.Value(None)
),
context.executionComplete(contextId)
)
val Some(data2) = afterIdMapUpdate.collectFirst {
case Api.Response(
None,
Api.VisualizationUpdate(
Api.VisualizationContext(
`visualizationId`,
`contextId`,
`idRes`
),
data
)
) =>
data
}
new String(data2, StandardCharsets.UTF_8) shouldEqual "42"
}
it should "emit visualization update for the target of a method call (subexpression)" in withContext() {
context =>
val contextId = UUID.randomUUID()

View File

@ -429,4 +429,9 @@ public abstract class InvokeCallableNode extends BaseNode {
childDispatch.setId(id);
}
}
/** Returns expression ID of this node. */
public UUID getId() {
return invokeFunctionNode.getId();
}
}

View File

@ -21,6 +21,7 @@ import org.enso.interpreter.node.ClosureRootNode;
import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.node.callable.ApplicationNode;
import org.enso.interpreter.node.callable.InvokeCallableNode;
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
import org.enso.interpreter.node.callable.argument.ReadArgumentNode;
import org.enso.interpreter.node.callable.function.BlockNode;
@ -177,10 +178,14 @@ public final class UnresolvedConstructor implements EnsoObject {
prototype.where.getRootNode() instanceof EnsoRootNode root ? root.getModuleScope() : null;
for (var where = prototype.where; where != null; where = where.getParent()) {
if (where instanceof ExpressionNode withId && withId.getId() != null) {
if (!(where instanceof ApplicationNode)) {
id = withId.getId();
}
section = withId.getSourceSection();
scope = withId.getRootNode() instanceof EnsoRootNode root ? root.getModuleScope() : null;
break;
} else if (where instanceof InvokeCallableNode callable && callable.getId() != null) {
id = callable.getId();
}
}
var fn = ReadArgumentNode.build(0, null, null);
@ -191,7 +196,9 @@ public final class UnresolvedConstructor implements EnsoObject {
prototype.descs[i].getName(), ReadArgumentNode.build(1 + i, null, null));
}
var expr = ApplicationNode.build(fn, args, DefaultsExecutionMode.EXECUTE);
if (id != null) {
expr.setId(id);
}
if (section != null) {
expr.setSourceLocation(section.getCharIndex(), section.getCharLength());
}