anotther compiler inference bug andlyng.io.console improvements
This commit is contained in:
parent
caad7d8ab9
commit
fd6d05d568
@ -80,6 +80,30 @@ class GameState(
|
||||
var paused = false
|
||||
}
|
||||
class LoopFrame(val resized: Bool, val originRow: Int, val originCol: Int) {}
|
||||
class InputBuffer {
|
||||
private val mutex: Mutex = Mutex()
|
||||
private val items: List<String> = []
|
||||
|
||||
fun push(value: String): Void {
|
||||
mutex.withLock {
|
||||
if (items.size >= MAX_PENDING_INPUTS) {
|
||||
items.removeAt(0)
|
||||
}
|
||||
items.add(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun drain(): List<String> {
|
||||
val out: List<String> = []
|
||||
mutex.withLock {
|
||||
while (items.size > 0) {
|
||||
out.add(items[0])
|
||||
items.removeAt(0)
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
fun clearAndHome() {
|
||||
Console.clear()
|
||||
@ -540,9 +564,8 @@ if (!Console.isSupported()) {
|
||||
)
|
||||
var prevFrameLines: List<String> = []
|
||||
|
||||
val gameMutex: Mutex = Mutex()
|
||||
var forceRedraw = false
|
||||
val pendingInputs: List<String> = []
|
||||
val inputBuffer: InputBuffer = InputBuffer()
|
||||
|
||||
val rawModeEnabled = Console.setRawMode(true)
|
||||
if (!rawModeEnabled) {
|
||||
@ -642,13 +665,7 @@ if (!Console.isSupported()) {
|
||||
continue
|
||||
}
|
||||
val mapped = if (ctrl && (key == "c" || key == "C")) "__CTRL_C__" else key
|
||||
val mm: Mutex = gameMutex
|
||||
mm.withLock {
|
||||
if (pendingInputs.size >= MAX_PENDING_INPUTS) {
|
||||
pendingInputs.removeAt(0)
|
||||
}
|
||||
pendingInputs.add(mapped)
|
||||
}
|
||||
inputBuffer.push(mapped)
|
||||
}
|
||||
}
|
||||
} catch (eventErr: Object) {
|
||||
@ -745,19 +762,11 @@ if (!Console.isSupported()) {
|
||||
continue
|
||||
}
|
||||
|
||||
val mm: Mutex = gameMutex
|
||||
mm.withLock {
|
||||
if (pendingInputs.size > 0) {
|
||||
val toApply: List<String> = []
|
||||
while (pendingInputs.size > 0) {
|
||||
val k = pendingInputs[0]
|
||||
pendingInputs.removeAt(0)
|
||||
toApply.add(k)
|
||||
}
|
||||
for (k in toApply) {
|
||||
applyKeyInput(state, k)
|
||||
if (!state.running || state.gameOver) break
|
||||
}
|
||||
val toApply = inputBuffer.drain()
|
||||
if (toApply.size > 0) {
|
||||
for (k in toApply) {
|
||||
applyKeyInput(state, k)
|
||||
if (!state.running || state.gameOver) break
|
||||
}
|
||||
}
|
||||
if (!state.running || state.gameOver) {
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package net.sergeych.lyng.io.console
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeFacade
|
||||
@ -58,18 +59,31 @@ fun createConsoleModule(policy: ConsoleAccessPolicy, manager: ImportManager): Bo
|
||||
if (manager.packageNames.contains(CONSOLE_MODULE_NAME)) return false
|
||||
|
||||
manager.addPackage(CONSOLE_MODULE_NAME) { module ->
|
||||
buildConsoleModule(module, policy)
|
||||
buildConsoleModule(module, policy, getSystemConsole())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun createConsole(policy: ConsoleAccessPolicy, manager: ImportManager): Boolean = createConsoleModule(policy, manager)
|
||||
|
||||
private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAccessPolicy) {
|
||||
internal fun createConsoleModule(
|
||||
policy: ConsoleAccessPolicy,
|
||||
manager: ImportManager,
|
||||
console: LyngConsole,
|
||||
): Boolean {
|
||||
if (manager.packageNames.contains(CONSOLE_MODULE_NAME)) return false
|
||||
|
||||
manager.addPackage(CONSOLE_MODULE_NAME) { module ->
|
||||
buildConsoleModule(module, policy, console)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAccessPolicy, baseConsole: LyngConsole) {
|
||||
// Load Lyng declarations for console enums/types first (module-local source of truth).
|
||||
module.eval(Source(CONSOLE_MODULE_NAME, consoleLyng))
|
||||
ConsoleEnums.initialize(module)
|
||||
val console: LyngConsole = LyngConsoleSecured(getSystemConsole(), policy)
|
||||
val console: LyngConsole = LyngConsoleSecured(baseConsole, policy)
|
||||
|
||||
val consoleType = object : net.sergeych.lyng.obj.ObjClass("Console") {}
|
||||
|
||||
@ -179,7 +193,7 @@ private suspend fun buildConsoleModule(module: ModuleScope, policy: ConsoleAcces
|
||||
|
||||
addClassFn("events") {
|
||||
consoleGuard {
|
||||
console.events().toConsoleEventStream()
|
||||
ObjConsoleEventStream { console.events() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,12 +224,8 @@ private suspend inline fun ScopeFacade.consoleGuard(crossinline block: suspend (
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConsoleEventSource.toConsoleEventStream(): ObjConsoleEventStream {
|
||||
return ObjConsoleEventStream(this)
|
||||
}
|
||||
|
||||
private class ObjConsoleEventStream(
|
||||
private val source: ConsoleEventSource,
|
||||
private val sourceFactory: () -> ConsoleEventSource,
|
||||
) : Obj() {
|
||||
override val objClass: net.sergeych.lyng.obj.ObjClass
|
||||
get() = type
|
||||
@ -224,35 +234,61 @@ private class ObjConsoleEventStream(
|
||||
val type = net.sergeych.lyng.obj.ObjClass("ConsoleEventStream", ObjIterable).apply {
|
||||
addFn("iterator") {
|
||||
val stream = thisAs<ObjConsoleEventStream>()
|
||||
ObjConsoleEventIterator(stream.source)
|
||||
ObjConsoleEventIterator(stream.sourceFactory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ObjConsoleEventIterator(
|
||||
private val source: ConsoleEventSource,
|
||||
private val sourceFactory: () -> ConsoleEventSource,
|
||||
) : Obj() {
|
||||
private var cached: Obj? = null
|
||||
private var closed = false
|
||||
private var source: ConsoleEventSource? = null
|
||||
|
||||
override val objClass: net.sergeych.lyng.obj.ObjClass
|
||||
get() = type
|
||||
|
||||
private fun ensureSource(): ConsoleEventSource {
|
||||
val current = source
|
||||
if (current != null) return current
|
||||
return sourceFactory().also { source = it }
|
||||
}
|
||||
|
||||
private suspend fun recycleSource(reason: String, error: Throwable? = null) {
|
||||
if (error != null) {
|
||||
consoleFlowDebug(reason, error)
|
||||
} else {
|
||||
consoleFlowDebug(reason)
|
||||
}
|
||||
val current = source
|
||||
source = null
|
||||
runCatching { current?.close() }
|
||||
.onFailure { consoleFlowDebug("console-bridge: failed to close recycled source", it) }
|
||||
if (!closed) delay(25)
|
||||
}
|
||||
|
||||
private suspend fun ensureCached(): Boolean {
|
||||
if (closed) return false
|
||||
if (cached != null) return true
|
||||
while (!closed && cached == null) {
|
||||
val event = try {
|
||||
source.nextEvent()
|
||||
val currentSource = try {
|
||||
ensureSource()
|
||||
} catch (e: Throwable) {
|
||||
// Consumer loops must survive source/read failures: report and keep polling.
|
||||
consoleFlowDebug("console-bridge: nextEvent failed; dropping failure and continuing", e)
|
||||
recycleSource("console-bridge: source creation failed; retrying", e)
|
||||
continue
|
||||
}
|
||||
val event = try {
|
||||
currentSource.nextEvent()
|
||||
} catch (e: Throwable) {
|
||||
// Consumer loops must survive source/read failures: rebuild the source and keep polling.
|
||||
recycleSource("console-bridge: nextEvent failed; recycling source", e)
|
||||
continue
|
||||
}
|
||||
if (event == null) {
|
||||
closeSource()
|
||||
return false
|
||||
recycleSource("console-bridge: source ended; recreating")
|
||||
continue
|
||||
}
|
||||
cached = try {
|
||||
event.toObjEvent()
|
||||
@ -268,10 +304,10 @@ private class ObjConsoleEventIterator(
|
||||
private suspend fun closeSource() {
|
||||
if (closed) return
|
||||
closed = true
|
||||
// Do not close the underlying console source from VM iterator cancellation.
|
||||
// CmdFrame.cancelIterators() may call cancelIteration() while user code is still
|
||||
// expected to keep processing input (e.g. recover from app-level exceptions).
|
||||
// The source lifecycle is managed by the console runtime.
|
||||
val current = source
|
||||
source = null
|
||||
runCatching { current?.close() }
|
||||
.onFailure { consoleFlowDebug("console-bridge: failed to close iterator source", it) }
|
||||
}
|
||||
|
||||
suspend fun hasNext(): Boolean = ensureCached()
|
||||
|
||||
@ -36,6 +36,97 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.TimeSource
|
||||
|
||||
internal object JvmConsoleKeyDecoder {
|
||||
fun decode(firstCode: Int, nextCode: (Long) -> Int?): ConsoleEvent.KeyDown {
|
||||
if (firstCode == 27) {
|
||||
val next = nextCode(25L)
|
||||
if (next == null || next < 0) {
|
||||
return key("Escape")
|
||||
}
|
||||
if (next == '['.code || next == 'O'.code) {
|
||||
val sb = StringBuilder()
|
||||
sb.append(next.toChar())
|
||||
var i = 0
|
||||
while (i < 6) {
|
||||
val c = nextCode(25L) ?: break
|
||||
if (c < 0) break
|
||||
sb.append(c.toChar())
|
||||
if (c.toChar().isLetter() || c == '~'.code) break
|
||||
i += 1
|
||||
}
|
||||
return keyFromAnsiSequence(sb.toString()) ?: key("Escape")
|
||||
}
|
||||
val base = decodePlainKey(next)
|
||||
return ConsoleEvent.KeyDown(
|
||||
key = base.key,
|
||||
code = base.code,
|
||||
ctrl = base.ctrl,
|
||||
alt = true,
|
||||
shift = base.shift,
|
||||
meta = false,
|
||||
)
|
||||
}
|
||||
return decodePlainKey(firstCode)
|
||||
}
|
||||
|
||||
private fun decodePlainKey(code: Int): ConsoleEvent.KeyDown = when (code) {
|
||||
3 -> key("c", ctrl = true)
|
||||
9 -> key("Tab")
|
||||
10, 13 -> key("Enter")
|
||||
127, 8 -> key("Backspace")
|
||||
32 -> key(" ")
|
||||
else -> {
|
||||
if (code in 1..26) {
|
||||
val ch = ('a'.code + code - 1).toChar().toString()
|
||||
key(ch, ctrl = true)
|
||||
} else {
|
||||
val ch = code.toChar().toString()
|
||||
key(ch, shift = ch.length == 1 && ch[0].isLetter() && ch[0].isUpperCase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyFromAnsiSequence(seq: String): ConsoleEvent.KeyDown? {
|
||||
val shift = seq.contains(";2")
|
||||
val alt = seq.contains(";3")
|
||||
val ctrl = seq.contains(";5")
|
||||
val key = when {
|
||||
seq.endsWith("A") -> "ArrowUp"
|
||||
seq.endsWith("B") -> "ArrowDown"
|
||||
seq.endsWith("C") -> "ArrowRight"
|
||||
seq.endsWith("D") -> "ArrowLeft"
|
||||
seq.endsWith("H") -> "Home"
|
||||
seq.endsWith("F") -> "End"
|
||||
seq.endsWith("~") -> when (seq.substringAfter('[').substringBefore(';').substringBefore('~')) {
|
||||
"2" -> "Insert"
|
||||
"3" -> "Delete"
|
||||
"5" -> "PageUp"
|
||||
"6" -> "PageDown"
|
||||
else -> null
|
||||
}
|
||||
else -> null
|
||||
} ?: return null
|
||||
return key(key, ctrl = ctrl, alt = alt, shift = shift)
|
||||
}
|
||||
|
||||
private fun key(
|
||||
value: String,
|
||||
ctrl: Boolean = false,
|
||||
alt: Boolean = false,
|
||||
shift: Boolean = false,
|
||||
): ConsoleEvent.KeyDown {
|
||||
require(value.isNotEmpty()) { "ConsoleEvent.KeyDown.key must never be empty" }
|
||||
return ConsoleEvent.KeyDown(
|
||||
key = value,
|
||||
code = null,
|
||||
ctrl = ctrl,
|
||||
alt = alt,
|
||||
shift = shift,
|
||||
meta = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JVM console implementation:
|
||||
* - output/capabilities/input use a single JLine terminal instance
|
||||
@ -508,40 +599,7 @@ object JvmLyngConsole : LyngConsole {
|
||||
val code = reader.read(120L)
|
||||
if (code == NonBlockingReader.READ_EXPIRED) return null
|
||||
if (code < 0) throw EOFException("non-blocking reader returned EOF")
|
||||
return decodeKey(code) { timeout -> readNextCode(reader, timeout) }
|
||||
}
|
||||
|
||||
private fun decodeKey(code: Int, nextCode: (Long) -> Int?): ConsoleEvent.KeyDown {
|
||||
if (code == 27) {
|
||||
val next = nextCode(25L)
|
||||
if (next == null || next < 0) {
|
||||
return key("Escape")
|
||||
}
|
||||
if (next == '['.code || next == 'O'.code) {
|
||||
val sb = StringBuilder()
|
||||
sb.append(next.toChar())
|
||||
var i = 0
|
||||
while (i < 6) {
|
||||
val c = nextCode(25L) ?: break
|
||||
if (c < 0) break
|
||||
sb.append(c.toChar())
|
||||
if (c.toChar().isLetter() || c == '~'.code) break
|
||||
i += 1
|
||||
}
|
||||
return keyFromAnsiSequence(sb.toString()) ?: key("Escape")
|
||||
}
|
||||
// Alt+key
|
||||
val base = decodePlainKey(next)
|
||||
return ConsoleEvent.KeyDown(
|
||||
key = base.key,
|
||||
code = base.code,
|
||||
ctrl = base.ctrl,
|
||||
alt = true,
|
||||
shift = base.shift,
|
||||
meta = false
|
||||
)
|
||||
}
|
||||
return decodePlainKey(code)
|
||||
return JvmConsoleKeyDecoder.decode(code) { timeout -> readNextCode(reader, timeout) }
|
||||
}
|
||||
|
||||
private fun readNextCode(reader: NonBlockingReader, timeoutMs: Long): Int? {
|
||||
@ -550,53 +608,4 @@ object JvmLyngConsole : LyngConsole {
|
||||
if (c < 0) throw EOFException("non-blocking reader returned EOF while decoding key sequence")
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
private fun decodePlainKey(code: Int): ConsoleEvent.KeyDown = when (code) {
|
||||
3 -> key("c", ctrl = true)
|
||||
9 -> key("Tab")
|
||||
10, 13 -> key("Enter")
|
||||
127, 8 -> key("Backspace")
|
||||
32 -> key(" ")
|
||||
else -> {
|
||||
if (code in 1..26) {
|
||||
val ch = ('a'.code + code - 1).toChar().toString()
|
||||
key(ch, ctrl = true)
|
||||
} else {
|
||||
val ch = code.toChar().toString()
|
||||
key(ch, shift = ch.length == 1 && ch[0].isLetter() && ch[0].isUpperCase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyFromAnsiSequence(seq: String): ConsoleEvent.KeyDown? = when (seq) {
|
||||
"[A", "OA" -> key("ArrowUp")
|
||||
"[B", "OB" -> key("ArrowDown")
|
||||
"[C", "OC" -> key("ArrowRight")
|
||||
"[D", "OD" -> key("ArrowLeft")
|
||||
"[H", "OH" -> key("Home")
|
||||
"[F", "OF" -> key("End")
|
||||
"[2~" -> key("Insert")
|
||||
"[3~" -> key("Delete")
|
||||
"[5~" -> key("PageUp")
|
||||
"[6~" -> key("PageDown")
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun key(
|
||||
value: String,
|
||||
ctrl: Boolean = false,
|
||||
alt: Boolean = false,
|
||||
shift: Boolean = false,
|
||||
): ConsoleEvent.KeyDown {
|
||||
require(value.isNotEmpty()) { "ConsoleEvent.KeyDown.key must never be empty" }
|
||||
return ConsoleEvent.KeyDown(
|
||||
key = value,
|
||||
code = null,
|
||||
ctrl = ctrl,
|
||||
alt = alt,
|
||||
shift = shift,
|
||||
meta = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,16 +22,14 @@ import net.sergeych.lyng.ExecutionError
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.ObjBool
|
||||
import net.sergeych.lyng.obj.ObjIllegalOperationException
|
||||
import net.sergeych.lyngio.console.*
|
||||
import net.sergeych.lyngio.console.security.ConsoleAccessOp
|
||||
import net.sergeych.lyngio.console.security.ConsoleAccessPolicy
|
||||
import net.sergeych.lyngio.console.security.PermitAllConsoleAccessPolicy
|
||||
import net.sergeych.lyngio.fs.security.AccessContext
|
||||
import net.sergeych.lyngio.fs.security.AccessDecision
|
||||
import net.sergeych.lyngio.fs.security.Decision
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.*
|
||||
|
||||
class LyngConsoleModuleTest {
|
||||
|
||||
@ -112,4 +110,63 @@ class LyngConsoleModuleTest {
|
||||
assertIs<ObjIllegalOperationException>(error.errorObject)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun eventsIteratorRecoversAfterSourceFailure() = runBlocking {
|
||||
val scope = newScope()
|
||||
var eventsCalls = 0
|
||||
val console = object : LyngConsole {
|
||||
override val isSupported: Boolean = true
|
||||
|
||||
override suspend fun isTty(): Boolean = true
|
||||
|
||||
override suspend fun geometry(): ConsoleGeometry? = null
|
||||
|
||||
override suspend fun ansiLevel(): ConsoleAnsiLevel = ConsoleAnsiLevel.NONE
|
||||
|
||||
override suspend fun write(text: String) {}
|
||||
|
||||
override suspend fun flush() {}
|
||||
|
||||
override fun events(): ConsoleEventSource {
|
||||
eventsCalls += 1
|
||||
val callNo = eventsCalls
|
||||
return object : ConsoleEventSource {
|
||||
private var emitted = false
|
||||
|
||||
override suspend fun nextEvent(timeoutMs: Long): ConsoleEvent? {
|
||||
if (emitted) return null
|
||||
emitted = true
|
||||
return when (callNo) {
|
||||
1 -> throw IllegalStateException("synthetic source failure")
|
||||
else -> ConsoleEvent.KeyDown(key = "x")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun close() {}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setRawMode(enabled: Boolean): Boolean = enabled
|
||||
}
|
||||
|
||||
assertTrue(createConsoleModule(PermitAllConsoleAccessPolicy, scope.importManager, console))
|
||||
|
||||
val result = scope.eval(
|
||||
"""
|
||||
import lyng.io.console
|
||||
|
||||
val it = Console.events().iterator()
|
||||
assert(it.hasNext())
|
||||
val ev = it.next()
|
||||
assert(ev is ConsoleKeyEvent)
|
||||
assert((ev as ConsoleKeyEvent).key == "x")
|
||||
true
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
assertIs<ObjBool>(result)
|
||||
assertTrue(result.value)
|
||||
assertEquals(2, eventsCalls)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyngio.console
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class JvmConsoleKeyDecoderTest {
|
||||
|
||||
private fun decode(vararg bytes: Int): ConsoleEvent.KeyDown {
|
||||
var index = 1
|
||||
return JvmConsoleKeyDecoder.decode(bytes[0]) { _ ->
|
||||
if (index >= bytes.size) null else bytes[index++]
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decodesArrowLeft() {
|
||||
val ev = decode(27, '['.code, 'D'.code)
|
||||
assertEquals("ArrowLeft", ev.key)
|
||||
assertFalse(ev.ctrl)
|
||||
assertFalse(ev.alt)
|
||||
assertFalse(ev.shift)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decodesCtrlArrowRightModifier() {
|
||||
val ev = decode(27, '['.code, '1'.code, ';'.code, '5'.code, 'C'.code)
|
||||
assertEquals("ArrowRight", ev.key)
|
||||
assertTrue(ev.ctrl)
|
||||
assertFalse(ev.alt)
|
||||
assertFalse(ev.shift)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun decodesAltCharacter() {
|
||||
val ev = decode(27, 'x'.code)
|
||||
assertEquals("x", ev.key)
|
||||
assertTrue(ev.alt)
|
||||
assertFalse(ev.ctrl)
|
||||
}
|
||||
}
|
||||
@ -4674,6 +4674,7 @@ class Compiler(
|
||||
?: nameObjClass[ref.name]
|
||||
?: resolveClassByName(ref.name)
|
||||
}
|
||||
is ImplicitThisMemberRef -> resolveReceiverClassForMember(ref)
|
||||
is ClassScopeMemberRef -> {
|
||||
val targetClass = resolveClassByName(ref.ownerClassName()) ?: return null
|
||||
inferFieldReturnClass(targetClass, ref.name)
|
||||
@ -4728,6 +4729,15 @@ class Compiler(
|
||||
is FastLocalVarRef -> nameTypeDecl[ref.name]
|
||||
?: lookupLocalTypeDeclByName(ref.name)
|
||||
?: seedTypeDeclByName(ref.name)
|
||||
is ImplicitThisMemberRef -> {
|
||||
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
|
||||
val targetClass = typeName?.let { resolveClassByName(it) } ?: return null
|
||||
targetClass.getInstanceMemberOrNull(ref.name, includeAbstract = true)?.typeDecl?.let { return it }
|
||||
classFieldTypesByName[targetClass.className]?.get(ref.name)
|
||||
?.let { return TypeDecl.Simple(it.className, false) }
|
||||
classMethodReturnTypeDeclByName[targetClass.className]?.get(ref.name)?.let { return it }
|
||||
null
|
||||
}
|
||||
is FieldRef -> {
|
||||
val targetDecl = resolveReceiverTypeDecl(ref.target) ?: return null
|
||||
val targetClass = resolveTypeDeclObjClass(targetDecl) ?: resolveReceiverClassForMember(ref.target)
|
||||
@ -7388,6 +7398,15 @@ class Compiler(
|
||||
val mutable = param.accessType?.isMutable ?: false
|
||||
declareSlotNameIn(classSlotPlan, param.name, mutable, isDelegated = false)
|
||||
}
|
||||
constructorArgsDeclaration?.let { ctorDecl ->
|
||||
val classParamTypeMap = slotTypeByScopeId.getOrPut(classSlotPlan.id) { mutableMapOf() }
|
||||
val classParamTypeDeclMap = slotTypeDeclByScopeId.getOrPut(classSlotPlan.id) { mutableMapOf() }
|
||||
for (param in ctorDecl.params) {
|
||||
val slot = classSlotPlan.slots[param.name]?.index ?: continue
|
||||
classParamTypeDeclMap[slot] = param.type
|
||||
resolveTypeDeclObjClass(param.type)?.let { classParamTypeMap[slot] = it }
|
||||
}
|
||||
}
|
||||
val ctorForcedLocalSlots = LinkedHashMap<String, Int>()
|
||||
if (constructorArgsDeclaration != null) {
|
||||
val ctorDecl = constructorArgsDeclaration
|
||||
@ -8873,6 +8892,7 @@ class Compiler(
|
||||
val targetClass = resolveReceiverClassForMember(directRef.target)
|
||||
inferFieldReturnClass(targetClass, directRef.name)
|
||||
}
|
||||
is ImplicitThisMemberRef -> resolveReceiverClassForMember(directRef)
|
||||
is CallRef -> {
|
||||
val target = directRef.target
|
||||
when {
|
||||
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.eval
|
||||
import kotlin.test.Test
|
||||
|
||||
class InferredCtorFieldIntRegressionTest {
|
||||
|
||||
@Test
|
||||
fun inferredIntFieldsFromCtorParamsStayIntInsideTypedCalls() = runTest {
|
||||
eval(
|
||||
"""
|
||||
class GameState(
|
||||
pieceId0: Int,
|
||||
nextId0: Int,
|
||||
next2Id0: Int,
|
||||
px0: Int,
|
||||
py0: Int,
|
||||
) {
|
||||
var pieceId = pieceId0
|
||||
var nextId = nextId0
|
||||
var next2Id = next2Id0
|
||||
var rot = 0
|
||||
var px = px0
|
||||
var py = py0
|
||||
var score = 0
|
||||
var totalLines = 0
|
||||
var level = 1
|
||||
var running = true
|
||||
var gameOver = false
|
||||
var paused = false
|
||||
}
|
||||
|
||||
fun canPlace(board: List<List<Int>>, boardW: Int, boardH: Int, pieceId: Int, rot: Int, px: Int, py: Int): Bool {
|
||||
true
|
||||
}
|
||||
|
||||
val board: List<List<Int>> = []
|
||||
val boardW = 10
|
||||
val boardH = 20
|
||||
|
||||
fun applyKeyInput(s: GameState, key: String) {
|
||||
if (key == "ArrowLeft") {
|
||||
if (canPlace(board, boardW, boardH, s.pieceId, s.rot, s.px - 1, s.py) == true) s.px--
|
||||
}
|
||||
}
|
||||
|
||||
val s = GameState(1, 2, 3, 4, 5)
|
||||
applyKeyInput(s, "ArrowLeft")
|
||||
assertEquals(3, s.px)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user