Fix captured bound global writes in nested bytecode blocks
This commit is contained in:
parent
b0fb65a036
commit
418b1ae2b6
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,3 +27,4 @@ debug.log
|
||||
/compile_jvm_output.txt
|
||||
/compile_metadata_output.txt
|
||||
test_output*.txt
|
||||
/site/src/version-template/lyng-version.js
|
||||
|
||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "1.5.2"
|
||||
version = "1.5.3-SNAPSHOT"
|
||||
|
||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||
|
||||
|
||||
@ -100,6 +100,9 @@ class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
class CmdMoveInt(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val value = frame.getInt(src)
|
||||
if (frame.writeThroughPropertyLikeSlot(dst, ObjInt.of(value))) {
|
||||
return
|
||||
}
|
||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||
frame.setIntUnchecked(dst, value)
|
||||
} else {
|
||||
@ -120,6 +123,9 @@ class CmdMoveIntLocal(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
class CmdMoveReal(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val value = frame.getReal(src)
|
||||
if (frame.writeThroughPropertyLikeSlot(dst, ObjReal.of(value))) {
|
||||
return
|
||||
}
|
||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||
frame.setRealUnchecked(dst, value)
|
||||
} else {
|
||||
@ -140,6 +146,9 @@ class CmdMoveRealLocal(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
class CmdMoveBool(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val value = frame.getBool(src)
|
||||
if (frame.writeThroughPropertyLikeSlot(dst, if (value) ObjTrue else ObjFalse)) {
|
||||
return
|
||||
}
|
||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||
frame.setBoolUnchecked(dst, value)
|
||||
} else {
|
||||
@ -4344,12 +4353,13 @@ class CmdFrame(
|
||||
}
|
||||
val localIndex = slot - fn.scopeSlotCount
|
||||
val name = fn.localSlotNames.getOrNull(localIndex) ?: return false
|
||||
val isCapture = fn.localSlotCaptures.getOrNull(localIndex) == true
|
||||
val raw = frame.getRawObj(localIndex)
|
||||
if (raw is RecordSlotRef) {
|
||||
if (raw.write(scope, name, value)) return true
|
||||
return false
|
||||
}
|
||||
if (raw !== ObjUnset && raw !is ObjProperty) return false
|
||||
if (!isCapture && raw !== ObjUnset && raw !is ObjProperty) return false
|
||||
val record = scope.parent?.get(name) ?: scope.get(name) ?: return false
|
||||
if (record.type != ObjRecord.Type.Delegated && record.type != ObjRecord.Type.Property && record.value !is ObjProperty) {
|
||||
return false
|
||||
|
||||
@ -18,9 +18,14 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.bridge.bind
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
import net.sergeych.lyng.bridge.data
|
||||
import net.sergeych.lyng.bridge.bindGlobalVar
|
||||
import net.sergeych.lyng.bridge.globalBinder
|
||||
import net.sergeych.lyng.obj.ObjFalse
|
||||
import net.sergeych.lyng.obj.ObjInstance
|
||||
import net.sergeych.lyng.obj.ObjTrue
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ -81,4 +86,66 @@ class GlobalPropertyCaptureRegressionTest {
|
||||
|
||||
assertEquals(2.0, x, "bound extern var should stay live in child-scope execution")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun externGlobalVarShouldStayLiveAfterExternClassPropertyBranchInChildScope() = runTest {
|
||||
val base = Script.newScope() as ModuleScope
|
||||
var x = 3.0
|
||||
|
||||
base.eval(
|
||||
"""
|
||||
extern var X: Real
|
||||
|
||||
class ChoiceInputResult {
|
||||
extern val isSkip: Bool
|
||||
}
|
||||
|
||||
extern fun requestChoice(): ChoiceInputResult
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
base.bind("ChoiceInputResult") {
|
||||
addVal("isSkip") {
|
||||
if (thisObjData<ChoicePayload>().isSkip) ObjTrue else ObjFalse
|
||||
}
|
||||
}
|
||||
|
||||
base.globalBinder().bindGlobalVar(
|
||||
name = "X",
|
||||
get = { x },
|
||||
set = { x = it }
|
||||
)
|
||||
|
||||
base.globalBinder().bindGlobalFunRaw("requestChoice") { _, _ ->
|
||||
val instance = base.requireClass("ChoiceInputResult").callOn(base.createChildScope()) as ObjInstance
|
||||
instance.data = ChoicePayload(isSkip = true)
|
||||
instance
|
||||
}
|
||||
|
||||
val child = base.createChildScope()
|
||||
child.eval(
|
||||
"""
|
||||
fun main() {
|
||||
val c: ChoiceInputResult = requestChoice()
|
||||
if (c.isSkip) {
|
||||
X = 77.0
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
child.eval("main()")
|
||||
|
||||
assertEquals(77.0, x, "bound extern var should stay live after extern class property branch in child scope")
|
||||
}
|
||||
}
|
||||
|
||||
private data class ChoicePayload(
|
||||
val isSkip: Boolean,
|
||||
)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T> ScopeFacade.thisObjData(): T {
|
||||
val instance = thisObj as? ObjInstance ?: raiseClassCastError("Expected result object instance")
|
||||
return instance.data as? T ?: raiseIllegalState("Bridge payload is not initialized")
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user