Fix argument priority handling in ObjInstance resolution logic and add corresponding tests

This commit is contained in:
Sergey Chernov 2026-01-10 20:25:06 +01:00
parent 7ab439d949
commit 827df9c8cd
3 changed files with 58 additions and 3 deletions

View File

@ -347,7 +347,16 @@ open class Scope(
// 1. Prefer direct locals/bindings declared in this frame
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
// 2. Then, check members of thisObj
val p = parent
// 2. If we share thisObj with parent, delegate to parent to maintain
// "locals shadow members" priority across the this-context.
if (p != null && p.thisObj === thisObj) {
return p.get(name)
}
// 3. Otherwise, we are the "primary" scope for this thisObj (or have no parent),
// so check members of thisObj before walking up to a different this-context.
val receiver = thisObj
val effectiveClass = receiver as? ObjClass ?: receiver.objClass
for (cls in effectiveClass.mro) {
@ -365,8 +374,8 @@ open class Scope(
}
}
// 3. Finally, walk up ancestry
return parent?.get(name)
// 4. Finally, walk up ancestry to a scope with a different thisObj context
return p?.get(name)
}
// Slot fast-path API

View File

@ -76,6 +76,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
}
override suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
if (obj.type.isArgument) return super.resolveRecord(scope, obj, name, decl)
if (obj.type == ObjRecord.Type.Delegated) {
val d = decl ?: obj.declaringClass
val storageName = "${d?.className}::$name"

View File

@ -20,6 +20,7 @@ import net.sergeych.lyng.Script
import net.sergeych.lyng.eval
import net.sergeych.lyng.obj.ObjInstance
import net.sergeych.lyng.obj.ObjList
import net.sergeych.lyng.toSource
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
@ -731,4 +732,48 @@ class OOTest {
""".trimIndent())
}
@Test
fun testArgsPriority() = runTest {
eval("""
class A(id) {
var stored = null
// Arguments should have priority on
// instance fields
fun setStored(id) { stored = id }
}
val a = A(1)
assertEquals(1, a.id)
assertEquals(null, a.stored)
// Check that arguments of the call have the priority:
a.setStored(2)
assertEquals(1, a.id)
assertEquals(2, a.stored)
""".trimIndent())
}
/**
* Demonstrates that function parameters are shadowed by class methods of the same name
* when accessed within a block, but not in a single expression.
*/
@Test
fun testParameterShadowingConflict() = runTest {
val scope = Script.newScope()
val result = scope.eval("""
class Tester() {
fun id() { "method" }
// This correctly returns "success"
fun checkOk(id) = id
// This incorrectly returns the 'id' method (a Callable) instead of "success"
fun checkFail(id) {
id
}
}
val t = Tester()
if (t.checkOk("success") != "success") throw "checkOk failed"
t.checkFail("success")
""".trimIndent().toSource("repro"))
assertEquals("success", result.toString(), "Parameter 'id' should shadow method 'id' in block")
}
}