Fix argument priority handling in ObjInstance resolution logic and add corresponding tests
This commit is contained in:
parent
7ab439d949
commit
827df9c8cd
@ -347,7 +347,16 @@ open class Scope(
|
|||||||
// 1. Prefer direct locals/bindings declared in this frame
|
// 1. Prefer direct locals/bindings declared in this frame
|
||||||
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }
|
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 receiver = thisObj
|
||||||
val effectiveClass = receiver as? ObjClass ?: receiver.objClass
|
val effectiveClass = receiver as? ObjClass ?: receiver.objClass
|
||||||
for (cls in effectiveClass.mro) {
|
for (cls in effectiveClass.mro) {
|
||||||
@ -365,8 +374,8 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Finally, walk up ancestry
|
// 4. Finally, walk up ancestry to a scope with a different thisObj context
|
||||||
return parent?.get(name)
|
return p?.get(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slot fast-path API
|
// Slot fast-path API
|
||||||
|
|||||||
@ -76,6 +76,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
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) {
|
if (obj.type == ObjRecord.Type.Delegated) {
|
||||||
val d = decl ?: obj.declaringClass
|
val d = decl ?: obj.declaringClass
|
||||||
val storageName = "${d?.className}::$name"
|
val storageName = "${d?.className}::$name"
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import net.sergeych.lyng.Script
|
|||||||
import net.sergeych.lyng.eval
|
import net.sergeych.lyng.eval
|
||||||
import net.sergeych.lyng.obj.ObjInstance
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
import net.sergeych.lyng.obj.ObjList
|
import net.sergeych.lyng.obj.ObjList
|
||||||
|
import net.sergeych.lyng.toSource
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFails
|
import kotlin.test.assertFails
|
||||||
@ -731,4 +732,48 @@ class OOTest {
|
|||||||
""".trimIndent())
|
""".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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user