diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 279d0b8..5ef1c0e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -108,11 +108,7 @@ class Script( seedImportBindings(scope, seedScope) } if (moduleSlotPlan.isNotEmpty()) { - scope.applySlotPlan(moduleSlotPlan) - for (name in moduleSlotPlan.keys) { - val record = scope.objects[name] ?: scope.localBindings[name] ?: continue - scope.updateSlotFor(name, record) - } + installModuleSlotPlan(scope) } } @@ -120,12 +116,26 @@ class Script( if (importBindings.isEmpty() && importedModules.isEmpty()) return seedImportBindings(scope, seedScope) if (moduleSlotPlan.isNotEmpty()) { - scope.applySlotPlan(moduleSlotPlan) - for (name in moduleSlotPlan.keys) { - val record = scope.objects[name] ?: scope.localBindings[name] ?: continue - scope.updateSlotFor(name, record) + installModuleSlotPlan(scope) + } + } + + private fun installModuleSlotPlan(scope: Scope) { + for ((name, index) in moduleSlotPlan) { + if (scope.getSlotIndexOf(name) != null) continue + if (scope.hasSlotPlanConflict(mapOf(name to index))) { + val record = scope.objects[name] + ?: scope.localBindings[name] + ?: ObjRecord(ObjUnset, isMutable = true) + scope.allocateSlotFor(name, record) + } else { + scope.applySlotPlan(mapOf(name to index)) } } + for (name in moduleSlotPlan.keys) { + val record = scope.objects[name] ?: scope.localBindings[name] ?: continue + scope.updateSlotFor(name, record) + } } private suspend fun seedModuleLocals( 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 a871bed..3749ab9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -5654,6 +5654,14 @@ class CmdFrame( if (index < target.slotCount) return index return index } + if (target.hasSlotPlanConflict(mapOf(name to index))) { + val record = target.getLocalRecordDirect(name) + ?: target.localBindings[name] + ?: target.parent?.get(name) + ?: target.get(name) + ?: ObjRecord(ObjUnset, isMutable = true) + return target.allocateSlotFor(name, record) + } target.applySlotPlan(mapOf(name to index)) val existing = target.getLocalRecordDirect(name) ?: target.localBindings[name] if (existing != null) { diff --git a/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt b/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt index e21ab60..65dad3e 100644 --- a/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt +++ b/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt @@ -150,6 +150,37 @@ class CompilerVmReviewRegressionTest { assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt()) } + @Test + fun preparedLambdaKeepsImmutableModuleCaptureAcrossOtherScriptsInSameScope() = runTest { + val unaryLambda = Compiler.compile( + Source( + "", + """ + val delta = 2 + { x -> x + delta } + """.trimIndent() + ), + Script.defaultImportManager + ) + val unrelatedScript = Compiler.compile( + Source( + "", + """ + val base = 7 + base + """.trimIndent() + ), + Script.defaultImportManager + ) + + val scope = Script.newScope() + val callable = unaryLambda.execute(scope) as Statement + unrelatedScript.execute(scope) + + assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt()) + assertEquals(42, scope.asFacade().call(callable, Arguments(ObjInt.of(40))).toInt()) + } + @Test fun subjectlessWhenReportsScriptError() = runTest { val ex = assertFailsWith {