v1.5.5 release

This commit is contained in:
sergeych committed 2026-04-23 15:18:47 +03:00
1 parent 14214e91e1
commit 9735774efd
13 files changed
+269 -34

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych"
version = "1.5.5-SNAPSHOT"
version = "1.5.5"
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
@@ -2881,14 +2881,18 @@ class BytecodeCompiler(
val slot = resolveCapturedOwnerScopeSlot(localTarget) ?: resolveSlot(localTarget) ?: return null
val targetType = slotTypes[slot] ?: SlotType.OBJ
if (!localTarget.isMutable) {
if (targetType != SlotType.OBJ && targetType != SlotType.UNKNOWN) return compileEvalRef(ref)
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
if (!shouldUseAssignOpForImmutableLocal(slot, targetType, ref.op)) {
emitImmutableLocalReassignError(localTarget.name, localTarget.pos())
return rhs
}
val rhsObj = ensureObjSlot(rhs)
val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name))
if (nameId > 0xFFFF) return compileEvalRef(ref)
val dst = allocSlot()
builder.emit(Opcode.ASSIGN_OP_OBJ, ref.op.ordinal, slot, rhsObj.slot, dst, nameId)
updateSlotType(dst, SlotType.OBJ)
slotObjClass[slot]?.let { slotObjClass[dst] = it }
return CompiledValue(dst, SlotType.OBJ)
}
var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
@@ -8446,6 +8450,38 @@ class BytecodeCompiler(
builder.emit(Opcode.THROW, posId, msgSlot)
return msgSlot
}
private fun assignOpMethodName(op: BinOp): String? = when (op) {
BinOp.PLUS -> "plusAssign"
BinOp.MINUS -> "minusAssign"
BinOp.STAR -> "mulAssign"
BinOp.SLASH -> "divAssign"
BinOp.PERCENT -> "modAssign"
else -> null
}
private fun knownBuiltinAssignOpSupport(cls: ObjClass, op: BinOp): Boolean? = when (cls) {
ObjList.type -> op == BinOp.PLUS || op == BinOp.MINUS
ObjObservableList.type -> op == BinOp.PLUS || op == BinOp.MINUS
ObjSet.type -> op == BinOp.PLUS
ObjMap.type -> op == BinOp.PLUS
ObjRingBuffer.type -> op == BinOp.PLUS
else -> null
}
private fun shouldUseAssignOpForImmutableLocal(slot: Int, targetType: SlotType, op: BinOp): Boolean {
return when (targetType) {
SlotType.INT, SlotType.REAL, SlotType.BOOL -> false
SlotType.OBJ, SlotType.UNKNOWN -> {
val cls = slotObjClass[slot] ?: return true
val methodName = assignOpMethodName(op)
if (methodName != null && cls.getInstanceMemberOrNull(methodName, includeStatic = false) != null) {
return true
}
knownBuiltinAssignOpSupport(cls, op) ?: true
}
}
}
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
@@ -104,14 +104,14 @@ class WebsiteSamplesTest {
val name = "Lyng"
val base = { id:, name: } // Shorthand for id: id, name: name
val full = { ...base, version: "1.5.4", status: "stable" }
val full = { ...base, version: "1.5.5", status: "stable" }
full
""".trimIndent())
assertTrue(result is ObjMap)
val m = result.map
assertEquals(101L, (m[ObjString("id")] as ObjInt).value)
assertEquals("Lyng", (m[ObjString("name")] as ObjString).value)
assertEquals("1.5.4", (m[ObjString("version")] as ObjString).value)
assertEquals("1.5.5", (m[ObjString("version")] as ObjString).value)
assertEquals("stable", (m[ObjString("status")] as ObjString).value)
}
@@ -19,6 +19,8 @@ package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertFailsWith
class OperatorOverloadingTest {
@Test
@@ -63,6 +65,24 @@ class OperatorOverloadingTest {
""".trimIndent())
}
@Test
fun testBuiltinListPlusAssignOnVal() = runTest {
eval("""
val list = [1, 2]
list += 3
assertEquals([1, 2, 3], list)
""".trimIndent())
}
@Test
fun testBuiltinListMinusAssignOnVal() = runTest {
eval("""
val list = [1, 2, 3]
list -= 2
assertEquals([1, 3], list)
""".trimIndent())
}
@Test
fun testPlusAssignFallback() = runTest {
eval("""
@@ -76,6 +96,130 @@ class OperatorOverloadingTest {
""".trimIndent())
}
@Test
fun testPlusAssignFallbackOnValReportsReadonlyError() = runTest {
val ex = assertFailsWith<ScriptError> {
eval("""
class Vector(var x: Int, var y: Int) {
fun plus(other: Vector) = Vector(this.x + other.x, this.y + other.y)
fun equals(other: Vector) = this.x == other.x && this.y == other.y
}
val v = Vector(1, 2)
v += Vector(3, 4)
""".trimIndent())
}
assertContains(ex.errorMessage, "can't reassign val v")
}
@Test
fun testMinusAssignOverloadingOnVal() = runTest {
eval("""
class Counter(var n: Int) {
fun minusAssign(x: Int) { this.n = this.n - x }
}
val c = Counter(10)
c -= 3
assertEquals(7, c.n)
""".trimIndent())
}
@Test
fun testMinusAssignFallbackOnValReportsReadonlyError() = runTest {
val ex = assertFailsWith<ScriptError> {
eval("""
class Counter(var n: Int) {
fun minus(x: Int) = Counter(this.n - x)
}
val c = Counter(10)
c -= 3
""".trimIndent())
}
assertContains(ex.errorMessage, "can't reassign val c")
}
@Test
fun testMulAssignOverloadingOnVal() = runTest {
eval("""
class Counter(var n: Int) {
fun mulAssign(x: Int) { this.n = this.n * x }
}
val c = Counter(10)
c *= 3
assertEquals(30, c.n)
""".trimIndent())
}
@Test
fun testMulAssignFallbackOnValReportsReadonlyError() = runTest {
val ex = assertFailsWith<ScriptError> {
eval("""
class Counter(var n: Int) {
fun times(x: Int) = Counter(this.n * x)
}
val c = Counter(10)
c *= 3
""".trimIndent())
}
assertContains(ex.errorMessage, "can't reassign val c")
}
@Test
fun testDivAssignOverloadingOnVal() = runTest {
eval("""
class Counter(var n: Int) {
fun divAssign(x: Int) { this.n = this.n / x }
}
val c = Counter(21)
c /= 3
assertEquals(7, c.n)
""".trimIndent())
}
@Test
fun testDivAssignFallbackOnValReportsReadonlyError() = runTest {
val ex = assertFailsWith<ScriptError> {
eval("""
class Counter(var n: Int) {
fun div(x: Int) = Counter(this.n / x)
}
val c = Counter(21)
c /= 3
""".trimIndent())
}
assertContains(ex.errorMessage, "can't reassign val c")
}
@Test
fun testModAssignOverloadingOnVal() = runTest {
eval("""
class Counter(var n: Int) {
fun modAssign(x: Int) { this.n = this.n % x }
}
val c = Counter(23)
c %= 5
assertEquals(3, c.n)
""".trimIndent())
}
@Test
fun testModAssignFallbackOnValReportsReadonlyError() = runTest {
val ex = assertFailsWith<ScriptError> {
eval("""
class Counter(var n: Int) {
fun mod(x: Int) = Counter(this.n % x)
}
val c = Counter(23)
c %= 5
""".trimIndent())
}
assertContains(ex.errorMessage, "can't reassign val c")
}
@Test
fun testCompareOverloading() = runTest {
eval("""