From ecf64dcbc3867248cb6cbd4d238864d3bf10cf7f Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 30 Jan 2026 15:39:03 +0300 Subject: [PATCH] Fix block capture sync for bytecode locals --- .../lyng/bytecode/BytecodeCompiler.kt | 1 + .../net/sergeych/lyng/bytecode/CmdRuntime.kt | 19 ++++++++++++++++--- lynglib/src/commonTest/kotlin/ScriptTest.kt | 12 ------------ 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 1583f73..f58eca7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -2116,6 +2116,7 @@ class BytecodeCompiler( compileRefWithFallback(ref, null, target.pos) } } + is VarDeclStatement -> emitVarDecl(target) is IfStatement -> compileIfStatement(target) is net.sergeych.lyng.ForInStatement -> { val resultSlot = emitForIn(target, false) ?: return null diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 0ab7eb8..370df81 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -1522,6 +1522,19 @@ class CmdFrame( } } + private fun shouldSyncLocalCaptures(captures: List): Boolean { + if (captures.isEmpty()) return false + val localNames = fn.localSlotNames + if (localNames.isEmpty()) return false + for (capture in captures) { + for (local in localNames) { + if (local == null) continue + if (local == capture) return true + } + } + return false + } + private fun resolveModuleScope(scope: Scope): Scope { var current: Scope? = scope var last: Scope = scope @@ -1545,7 +1558,7 @@ class CmdFrame( } else { emptyList() } - if (captures.isNotEmpty() && fn.localSlotNames.isNotEmpty()) { + if (shouldSyncLocalCaptures(captures)) { syncFrameToScope() } if (scope.skipScopeCreation) { @@ -1584,8 +1597,8 @@ class CmdFrame( ?: error("Scope stack underflow in POP_SCOPE") val captures = captureStack.removeLastOrNull() ?: emptyList() scopeDepth -= 1 - if (captures.isNotEmpty() && fn.localSlotNames.isNotEmpty()) { - syncScopeToFrame() + if (shouldSyncLocalCaptures(captures)) { + syncFrameToScope() } } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index c106952..bc617f9 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -131,7 +131,6 @@ class ScriptTest { } // --- Helpers to test iterator cancellation semantics --- -@Ignore class ObjTestIterable : Obj() { var cancelCount: Int = 0 @@ -148,7 +147,6 @@ class ScriptTest { } } -@Ignore class ObjTestIterator(private val owner: ObjTestIterable) : Obj() { override val objClass: ObjClass = type private var i = 0 @@ -171,7 +169,6 @@ class ScriptTest { } } - @Ignore("Scope.eval should seed compile-time symbols from current scope") @Test fun testForLoopDoesNotCancelOnNaturalCompletion() = runTest { val scope = Script.newScope() @@ -189,7 +186,6 @@ class ScriptTest { assertEquals(0, ti.cancelCount) } - @Ignore("incremental enable") @Test fun testForLoopCancelsOnBreak() = runTest { val scope = Script.newScope() @@ -205,7 +201,6 @@ class ScriptTest { assertEquals(1, ti.cancelCount) } - @Ignore("incremental enable") @Test fun testForLoopCancelsOnException() = runTest { val scope = Script.newScope() @@ -384,7 +379,6 @@ class ScriptTest { assertTrue(eval("sin(π)").toDouble() - 1 < 0.000001) } - @Ignore("Scope.eval should seed compile-time symbols from current scope") @Test fun varsAndConstsTest() = runTest { val scope = Scope(pos = Pos.builtIn) @@ -406,7 +400,6 @@ class ScriptTest { assertEquals(5, scope.eval("b").toInt()) } - @Ignore("incremental enable") @Test fun functionTest() = runTest { val scope = Scope(pos = Pos.builtIn) @@ -433,7 +426,6 @@ class ScriptTest { assertEquals(14, scope.eval("bar(3)").toInt()) } - @Ignore("incremental enable") @Test fun simpleClosureTest() = runTest { val scope = Scope(pos = Pos.builtIn) @@ -559,7 +551,6 @@ class ScriptTest { assertFalse { eval("4 <= 3").toBool() } } - @Ignore("incremental enable") @Test fun ifTest() = runTest { // if - single line @@ -759,7 +750,6 @@ class ScriptTest { assertEquals(ObjInt(5), c["c"]?.value) } - @Ignore("Scope.eval should seed compile-time symbols from current scope") @Test fun testAssignArgumentsEndEllipsis() = runTest { // equal args, @@ -787,7 +777,6 @@ class ScriptTest { c.eval("assert( b == [] )") } - @Ignore("incremental enable") @Test fun testAssignArgumentsStartEllipsis() = runTest { val ttEnd = Token.Type.RBRACE @@ -822,7 +811,6 @@ class ScriptTest { } } - @Ignore("incremental enable") @Test fun testAssignArgumentsMiddleEllipsis() = runTest { val ttEnd = Token.Type.RBRACE