Fix escaping immutable closure captures

This commit is contained in:
Sergey Chernov 2026-04-22 18:50:49 +03:00
parent 0f5343fa17
commit 14214e91e1
2 changed files with 58 additions and 10 deletions

View File

@ -2204,8 +2204,8 @@ class CmdAssignOpObj(
} }
if (result == null) { if (result == null) {
val name = (frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal)?.value val name = (frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal)?.value
if (name != null) frame.ensureScope().raiseIllegalAssignment("symbol is readonly: $name") if (name != null) frame.ensureScope().raiseIllegalAssignment("can't reassign val $name")
frame.ensureScope().raiseIllegalAssignment("symbol is readonly") frame.ensureScope().raiseIllegalAssignment("can't reassign val")
} }
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
@ -4552,7 +4552,7 @@ class CmdFrame(
type = inherited.type type = inherited.type
) )
copied.delegate = inherited.delegate copied.delegate = inherited.delegate
return@mapIndexed copied return@mapIndexed freezeImmutableCaptureRecord(copied)
} }
} }
val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: false val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: false
@ -4576,11 +4576,13 @@ class CmdFrame(
val record = findNamedExistingRecord(scope, name) val record = findNamedExistingRecord(scope, name)
if (record != null) { if (record != null) {
val value = record.value val value = record.value
return@mapIndexed when (value) { return@mapIndexed freezeImmutableCaptureRecord(
when (value) {
is FrameSlotRef -> ObjRecord(value, isMutable) is FrameSlotRef -> ObjRecord(value, isMutable)
is RecordSlotRef -> ObjRecord(value, isMutable) is RecordSlotRef -> ObjRecord(value, isMutable)
else -> ObjRecord(value, isMutable) else -> ObjRecord(value, isMutable)
} }
)
} }
if (hasNamedScopeBinding(scope, name)) { if (hasNamedScopeBinding(scope, name)) {
throw ScriptError( throw ScriptError(
@ -4589,11 +4591,13 @@ class CmdFrame(
) )
} }
} }
freezeImmutableCaptureRecord(
when (raw) { when (raw) {
is FrameSlotRef -> ObjRecord(raw, isMutable) is FrameSlotRef -> ObjRecord(raw, isMutable)
is RecordSlotRef -> ObjRecord(raw, isMutable) is RecordSlotRef -> ObjRecord(raw, isMutable)
else -> ObjRecord(FrameSlotRef(frame, localIndex), isMutable) else -> ObjRecord(FrameSlotRef(frame, localIndex), isMutable)
} }
)
} }
} }

View File

@ -90,4 +90,48 @@ class OptTest {
} }
assertContains(ex.errorMessage, "can't reassign val a") assertContains(ex.errorMessage, "can't reassign val a")
} }
@Test
fun testAssignOpErrorMessageFromExample() = runTest {
val source = Source(
"examples/error1.lyng",
"""
val a = 1
a += 2
""".trimIndent()
)
val ex = assertFailsWith<ScriptError> {
Script.newScope().eval(source)
}
assertContains(ex.errorMessage, "can't reassign val a")
}
@Test
fun testClosuresInLaunchPool() = runTest {
eval($$"""
val result = Set()
val mu = Mutex()
fn doSomething(value) {
delay(100)
println(value)
mu.withLock {
result += value
}
}
val lp = LaunchPool(4, 1000)
for (i in 1 .. 10) {
val ii: Int = i
lp.launch {
doSomething( ii )
}
}
println("all tasks were placed into lauchpool")
lp.closeAndJoin()
println("ALL DONE: $result")
assertEquals((1..10).toSet(), result)
""".trimIndent())
}
} }