From 8f60a84e3bf9cdbc8ed35a52c8369b718bfa0ae5 Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 30 Jan 2026 23:46:25 +0300 Subject: [PATCH] Fix loop scoping in bytecode and unignore ScriptTests --- .../kotlin/net/sergeych/lyng/Compiler.kt | 16 +++++++--- .../lyng/bytecode/BytecodeCompiler.kt | 30 +++++++++++++++++++ lynglib/src/commonTest/kotlin/ScriptTest.kt | 4 --- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 07596ed..5c0d5db 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -101,11 +101,16 @@ class Compiler( private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull() - private fun seedSlotPlanFromScope(scope: Scope) { + private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) { val plan = moduleSlotPlan() ?: return - for ((name, record) in scope.objects) { - if (!record.visibility.isPublic) continue - declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated) + var current: Scope? = scope + while (current != null) { + for ((name, record) in current.objects) { + if (!record.visibility.isPublic) continue + declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated) + } + if (!includeParents) return + current = current.parent } } @@ -399,6 +404,9 @@ class Compiler( return LocalVarRef(name, pos) } resolutionSink?.reference(name, pos) + seedScope?.chainLookupIgnoreClosure(name)?.let { + return LocalVarRef(name, pos) + } if (allowUnresolvedRefs || (name.isNotEmpty() && name[0].isUpperCase())) { return LocalVarRef(name, pos) } 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 3cd87fc..f8d4e1f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -2575,6 +2575,12 @@ class BytecodeCompiler( rangeRef = extractRangeFromLocal(stmt.source) } val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null + val useLoopScope = stmt.loopSlotPlan.isNotEmpty() + val planId = if (useLoopScope) { + builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan, emptyList())) + } else { + -1 + } val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] var usedOverride = false val loopSlotId = when { @@ -2631,6 +2637,10 @@ class BytecodeCompiler( val loopLabel = builder.label() val continueLabel = builder.label() val endLabel = builder.label() + if (useLoopScope) { + builder.emit(Opcode.PUSH_SCOPE, planId) + resetAddrCache() + } builder.mark(loopLabel) val hasNextSlot = allocSlot() @@ -2694,6 +2704,10 @@ class BytecodeCompiler( } builder.mark(afterElse) } + if (useLoopScope) { + builder.emit(Opcode.POP_SCOPE) + resetAddrCache() + } return resultSlot } @@ -2739,6 +2753,10 @@ class BytecodeCompiler( val continueLabel = builder.label() val endLabel = builder.label() val doneLabel = builder.label() + if (useLoopScope) { + builder.emit(Opcode.PUSH_SCOPE, planId) + resetAddrCache() + } builder.mark(loopLabel) val cmpSlot = allocSlot() builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot) @@ -2786,6 +2804,10 @@ class BytecodeCompiler( } builder.mark(afterElse) } + if (useLoopScope) { + builder.emit(Opcode.POP_SCOPE) + resetAddrCache() + } builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel))) builder.mark(badRangeLabel) val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range")) @@ -2808,6 +2830,10 @@ class BytecodeCompiler( val loopLabel = builder.label() val continueLabel = builder.label() val endLabel = builder.label() + if (useLoopScope) { + builder.emit(Opcode.PUSH_SCOPE, planId) + resetAddrCache() + } builder.mark(loopLabel) val cmpSlot = allocSlot() builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot) @@ -2855,6 +2881,10 @@ class BytecodeCompiler( } builder.mark(afterElse) } + if (useLoopScope) { + builder.emit(Opcode.POP_SCOPE) + resetAddrCache() + } return resultSlot } finally { if (usedOverride) { diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index f93911c..1f75482 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -905,7 +905,6 @@ class ScriptTest { eval(code) } - @Ignore("bytecode fallback in labeled break") @Test fun whileNonLocalBreakTest() = runTest { assertEquals( @@ -3752,7 +3751,6 @@ class ScriptTest { ) } - @Ignore("incremental enable: closure capture in acc?.let { acc + f(x) } mis-evaluates") @Test fun testThisInClosure() = runTest { eval( @@ -4428,7 +4426,6 @@ class ScriptTest { println(r) } - @Ignore("incremental enable: parent scope capture for child eval not wired") @Test fun testScopeShortCircuit() = runTest() { val baseScope = Script.newScope() @@ -4862,7 +4859,6 @@ class ScriptTest { assertContains(x1.message!!, "tc2") } - @Ignore("incremental enable: filtered stack trace missing source frame") @Test fun testFilterStackTrace() = runTest { var x = try {