fixed another closure bug
This commit is contained in:
parent
2acb60697d
commit
1931384116
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -66,6 +66,11 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1b) Captured locals from the entire closure ancestry. This ensures that parameters
|
||||||
|
// and local variables shadow members of captured receivers, matching standard
|
||||||
|
// lexical scoping rules.
|
||||||
|
closureScope.chainLookupIgnoreClosure(name, followClosure = true)?.let { return it }
|
||||||
|
|
||||||
// 2) Members on the captured receiver instance
|
// 2) Members on the captured receiver instance
|
||||||
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
||||||
// Check direct locals in instance scope (unmangled)
|
// Check direct locals in instance scope (unmangled)
|
||||||
@ -94,7 +99,7 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
// 3) Closure scope chain (locals/parents + members), ignore ClosureScope overrides to prevent recursion
|
||||||
closureScope.chainLookupWithMembers(name, currentClassCtx)?.let { return it }
|
closureScope.chainLookupWithMembers(name, currentClassCtx, followClosure = true)?.let { return it }
|
||||||
|
|
||||||
// 4) Caller `this` members
|
// 4) Caller `this` members
|
||||||
(callScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
(callScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)?.let { inst ->
|
||||||
|
|||||||
@ -120,14 +120,14 @@ open class Scope(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun chainLookupIgnoreClosure(name: String): ObjRecord? {
|
internal fun chainLookupIgnoreClosure(name: String, followClosure: Boolean = false): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
// use frameId to detect unexpected structural cycles in the parent chain
|
// use frameId to detect unexpected structural cycles in the parent chain
|
||||||
val visited = HashSet<Long>(4)
|
val visited = HashSet<Long>(4)
|
||||||
while (s != null) {
|
while (s != null) {
|
||||||
if (!visited.add(s.frameId)) return null
|
if (!visited.add(s.frameId)) return null
|
||||||
tryGetLocalRecord(s, name, currentClassCtx)?.let { return it }
|
tryGetLocalRecord(s, name, currentClassCtx)?.let { return it }
|
||||||
s = s.parent
|
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ open class Scope(
|
|||||||
* This completely avoids invoking overridden `get` implementations, preventing
|
* This completely avoids invoking overridden `get` implementations, preventing
|
||||||
* ping-pong recursion between `ClosureScope` frames.
|
* ping-pong recursion between `ClosureScope` frames.
|
||||||
*/
|
*/
|
||||||
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx): ObjRecord? {
|
internal fun chainLookupWithMembers(name: String, caller: net.sergeych.lyng.obj.ObjClass? = currentClassCtx, followClosure: Boolean = false): ObjRecord? {
|
||||||
var s: Scope? = this
|
var s: Scope? = this
|
||||||
val visited = HashSet<Long>(4)
|
val visited = HashSet<Long>(4)
|
||||||
while (s != null) {
|
while (s != null) {
|
||||||
@ -185,7 +185,7 @@ open class Scope(
|
|||||||
} else return rec
|
} else return rec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s = s.parent
|
s = if (followClosure && s is ClosureScope) s.closureScope else s.parent
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4683,5 +4683,42 @@ class ScriptTest {
|
|||||||
x.getLyngExceptionMessageWithStackTrace()
|
x.getLyngExceptionMessageWithStackTrace()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMapIteralAmbiguity() = runTest {
|
||||||
|
eval("""
|
||||||
|
val m = { a: 1, b: { foo: "bar" } }
|
||||||
|
assertEquals(1, m["a"])
|
||||||
|
assertEquals("bar", m["b"]["foo"])
|
||||||
|
val bar = "foobar"
|
||||||
|
val m2 = { a: 1, b: { bar: } }
|
||||||
|
assert( m2["b"] is Map )
|
||||||
|
assertEquals("foobar", m2["b"]["bar"])
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun realWorldCaptureProblem() = runTest {
|
||||||
|
eval("""
|
||||||
|
// 61755f07-630c-4181-8d50-1b044d96e1f4
|
||||||
|
class T {
|
||||||
|
static var f1 = null
|
||||||
|
static fun t(name=null) {
|
||||||
|
run {
|
||||||
|
// I expect it will catch the 'name' from
|
||||||
|
// param?
|
||||||
|
f1 = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(T.f1 == null)
|
||||||
|
println("-- "+T.f1::class)
|
||||||
|
println("-- "+T.f1)
|
||||||
|
T.t("foo")
|
||||||
|
println("2- "+T.f1::class)
|
||||||
|
println("2- "+T.f1)
|
||||||
|
assert(T.f1 == "foo")
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user