Skip to content

ReplVariableRebindingSmokeTest.kt

src/jvmMain/kotlin/afield/runtime/kotlin/ReplVariableRebindingSmokeTest.kt · 111 lines

package afield.runtime.kotlin

import kotlin.script.experimental.api.ResultValue

FIELD_TEST id: kotlin.repl-variable-rebinding.smoke runner: jvm-main entrypoint: afield.runtime.kotlin.ReplVariableRebindingSmokeTest tags: [smoke, kotlin, k2, repl, regression] timeoutSec: 240 expect: exitCode: 0 stdoutContains: - "REPL variable rebinding smoke passed"

object ReplVariableRebindingSmokeTest

Real K2 closure checks for declaration-time resets and the snapshots they must preserve.

object ReplVariableRebindingSmokeTest {
    private fun KotlinInterface2.run(code: String): Any? {
        var failure: String? = null
        val result = execAndWait(
            code,
            { failure = "compile: $it" },
            { failure = "evaluate: $it" },
            { failure = "exception: $it" },
            emptyList(),
        )
        check(failure == null) { "$code: $failure" }
        return (result as? ResultValue.Value)?.value
    }

fun main(args: Array<String>)

body — 79 lines, from line 32
    @JvmStatic
    fun main(args: Array<String>) {
        val ki = KotlinInterface2()
        try {
            ki.run("var x = 10; val tick = { ++x }; val readFirstX = { x }")
            check(ki.run("tick()") == 11)
            ki.run("val readThrough = { readFirstX() }")
            check(ki.run("readThrough()") == 11)
            check(ki.run("tick()") == 12) { "Cached readers must not fork mutable backing classes" }
            ki.run("var x = 20; val readSecondX = { x }")
            check(ki.run("tick()") == 21)
            check(ki.run("readFirstX()") == 21)
            check(ki.run("x") == 20) { "Declaration propagation is not continuous shared storage" }
            ki.run("var x = 30")
            check(ki.run("readFirstX()") == 30)
            check(ki.run("readSecondX()") == 30) { "Every older compatible slot must reset" }
            check(ki.run("tick()") == 31)
            ki.run("var x = 30")
            check(ki.run("tick()") == 31) { "Repeated declarations must reset older closures again" }
            ki.run("private var URL = 1; val readURL = { URL }")
            ki.run("readURL()")
            ki.run("private var URL = 2")
            check(ki.run("readURL()") == 2)
            ki.run("private var URL = 2")
            check(ki.run("readURL()") == 2)
            println("mutable closures: repeated declarations reset every older compatible slot")

            ki.run("var maybe: String? = \"before\"; val readMaybe = { maybe }")
            ki.run("var maybe: String? = null")
            check(ki.run("readMaybe()") == null)
            ki.run("var typed = 7; val readInt = { typed }")
            ki.run("var typed = \"seven\"; val readString = { typed }")
            ki.run("var typed = 9")
            check(ki.run("readInt()") == 9)
            check(ki.run("readString()") == "seven")
            println("types: nullable resets work; incompatible older bindings retain their values")

            ki.run("val gain = 2.0; val readGain = { gain }")
            ki.run("val gain = 3.0")
            check(ki.run("readGain()") == 2.0)
            ki.run("var resource = mutableListOf(1); val ownedResource = resource; val cleanup = { ownedResource.clear() }")
            ki.run("var resource = mutableListOf(2)")
            ki.run("cleanup()")
            check(ki.run("resource.single()") == 2) { "Old cleanup must not target the replacement" }
            check(ki.run("ownedResource.isEmpty()") == true)
            println("snapshots: val captures and exact-instance cleanup stay fixed")

            ki.run("var delegated by kotlin.properties.Delegates.observable(1) { _, _, _ -> }; val readDelegate = { delegated }")
            ki.run("var delegated by kotlin.properties.Delegates.observable(2) { _, _, _ -> }")
            check(ki.run("readDelegate()") == 1)
            println("delegates: declaration reset does not invoke arbitrary delegate setters")

            val separate = KotlinInterface2()
            try {
                separate.run("var x = 100")
                check(ki.run("readFirstX()") == 31)
            } finally {
                separate.dispose()
            }

            ki.run("var stable = 4; val readStable = { stable }")
            val readStable = ki.context.vars.getValue("readStable").value as Function0<*>
            var failed = false
            ki.execAndWait(
                "var stable: Int = error(\"intentional failed initializer\")",
                { error("Expected evaluation failure, got compile failure: $it") },
                { failed = true },
                { failed = true },
                emptyList(),
            )
            check(failed)
            check(readStable.invoke() == 4) { "Failed initialization changed a running closure" }
            println("failure: unsuccessful declarations preserve the old value")
            println("REPL variable rebinding smoke passed")
        } finally {
            ki.dispose()
        }
    }
}