From 90e46a3fff3320406ef3c38a73f6b42419f24937 Mon Sep 17 00:00:00 2001 From: sergeych Date: Sat, 27 Jun 2026 04:35:51 +0300 Subject: [PATCH] fix another type inference problem with external structures --- lynglib/build.gradle.kts | 2 +- .../kotlin/net/sergeych/lyng/Compiler.kt | 14 ++++- .../GlobalPropertyCaptureRegressionTest.kt | 60 +++++++++++++++++-- 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 253e44c..b8435ff 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "1.5.6" +version = "1.5.7-SNAPSHOT" // Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index dae90d8..97acdb3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -6702,7 +6702,19 @@ class Compiler( private fun inferCallReturnTypeDecl(ref: CallRef): TypeDecl? { callReturnTypeDeclByRef[ref]?.let { return it } val targetDecl = (resolveReceiverTypeDecl(ref.target) ?: seedTypeDeclFromRef(ref.target)) as? TypeDecl.Function - ?: return null + ?: run { + val targetName = when (val target = ref.target) { + is LocalVarRef -> target.name + is FastLocalVarRef -> target.name + is LocalSlotRef -> target.name + else -> null + } ?: return null + val declaredReturn = callableReturnTypeDeclByName[targetName] + ?: (seedTypeDeclByName(targetName) as? TypeDecl.Function)?.returnType + ?: return null + callReturnTypeDeclByRef[ref] = declaredReturn + return declaredReturn + } val bindings = mutableMapOf() val paramList = mutableListOf() targetDecl.receiver?.let { paramList += it } diff --git a/lynglib/src/commonTest/kotlin/GlobalPropertyCaptureRegressionTest.kt b/lynglib/src/commonTest/kotlin/GlobalPropertyCaptureRegressionTest.kt index 5a851b5..508dd2a 100644 --- a/lynglib/src/commonTest/kotlin/GlobalPropertyCaptureRegressionTest.kt +++ b/lynglib/src/commonTest/kotlin/GlobalPropertyCaptureRegressionTest.kt @@ -19,13 +19,10 @@ package net.sergeych.lyng import kotlinx.coroutines.test.runTest import net.sergeych.lyng.bridge.bind -import net.sergeych.lyng.obj.ObjRecord -import net.sergeych.lyng.bridge.data import net.sergeych.lyng.bridge.bindGlobalVar +import net.sergeych.lyng.bridge.data import net.sergeych.lyng.bridge.globalBinder -import net.sergeych.lyng.obj.ObjFalse -import net.sergeych.lyng.obj.ObjInstance -import net.sergeych.lyng.obj.ObjTrue +import net.sergeych.lyng.obj.* import kotlin.test.Test import kotlin.test.assertEquals @@ -138,12 +135,65 @@ class GlobalPropertyCaptureRegressionTest { assertEquals(77.0, x, "bound extern var should stay live after extern class property branch in child scope") } + + @Test + fun externPropertyOnExternFunctionResultCanAssignExternGlobalVar() = runTest { + val base = Script.newScope() as ModuleScope + var x = 0L + + base.eval( + """ + extern var X: Int + + class InputResult { + extern val value: Int? + } + """.trimIndent() + ) + + base.bind("InputResult") { + addVal("value") { + ObjInt.of(thisObjData().value) + } + } + + base.globalBinder().bindGlobalVar( + name = "X", + get = { x }, + set = { x = it } + ) + + base.globalBinder().bindGlobalFunRaw("input") { _, _ -> + val instance = base.requireClass("InputResult").callOn(base.createChildScope()) as ObjInstance + instance.data = InputPayload(101L) + instance + } + + val child = base.createChildScope() + child.eval( + """ + extern fun input(): InputResult + + fun main() { + X = input().value + } + """.trimIndent() + ) + + child.eval("main()") + + assertEquals(101L, x, "extern property read directly from extern function result should assign live extern var") + } } private data class ChoicePayload( val isSkip: Boolean, ) +private data class InputPayload( + val value: Long, +) + @Suppress("UNCHECKED_CAST") private fun ScopeFacade.thisObjData(): T { val instance = thisObj as? ObjInstance ?: raiseClassCastError("Expected result object instance")