Fix lazy delegate type resolution across modules
This commit is contained in:
parent
217787e17a
commit
7c059b4741
@ -8401,7 +8401,7 @@ class Compiler(
|
|||||||
is ImplicitThisMethodCallRef -> {
|
is ImplicitThisMethodCallRef -> {
|
||||||
when (directRef.methodName()) {
|
when (directRef.methodName()) {
|
||||||
"iterator" -> ObjIterator
|
"iterator" -> ObjIterator
|
||||||
"lazy" -> ObjLazyDelegate.type
|
"lazy" -> resolveClassByName("lazy") ?: inferMethodCallReturnClass(directRef.methodName())
|
||||||
else -> inferMethodCallReturnClass(directRef.methodName())
|
else -> inferMethodCallReturnClass(directRef.methodName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8420,7 +8420,7 @@ class Compiler(
|
|||||||
when {
|
when {
|
||||||
target is LocalSlotRef -> {
|
target is LocalSlotRef -> {
|
||||||
when (target.name) {
|
when (target.name) {
|
||||||
"lazy" -> ObjLazyDelegate.type
|
"lazy" -> resolveClassByName("lazy")
|
||||||
"iterator" -> ObjIterator
|
"iterator" -> ObjIterator
|
||||||
"flow" -> ObjFlow.type
|
"flow" -> ObjFlow.type
|
||||||
"launch" -> ObjDeferred.type
|
"launch" -> ObjDeferred.type
|
||||||
@ -8431,7 +8431,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
target is LocalVarRef -> {
|
target is LocalVarRef -> {
|
||||||
when (target.name) {
|
when (target.name) {
|
||||||
"lazy" -> ObjLazyDelegate.type
|
"lazy" -> resolveClassByName("lazy")
|
||||||
"iterator" -> ObjIterator
|
"iterator" -> ObjIterator
|
||||||
"flow" -> ObjFlow.type
|
"flow" -> ObjFlow.type
|
||||||
"launch" -> ObjDeferred.type
|
"launch" -> ObjDeferred.type
|
||||||
@ -8470,7 +8470,9 @@ class Compiler(
|
|||||||
?: unwrapDirectRef(initializer)?.let { inferObjClassFromRef(it) }
|
?: unwrapDirectRef(initializer)?.let { inferObjClassFromRef(it) }
|
||||||
?: throw ScriptError(initializer.pos, "Delegate type must be known at compile time")
|
?: throw ScriptError(initializer.pos, "Delegate type must be known at compile time")
|
||||||
if (initClass !== delegateClass &&
|
if (initClass !== delegateClass &&
|
||||||
|
initClass.className != delegateClass.className &&
|
||||||
!initClass.allParentsSet.contains(delegateClass) &&
|
!initClass.allParentsSet.contains(delegateClass) &&
|
||||||
|
!initClass.allParentsSet.any { it.className == delegateClass.className } &&
|
||||||
!initClass.allImplementingNames.contains(delegateClass.className)
|
!initClass.allImplementingNames.contains(delegateClass.className)
|
||||||
) {
|
) {
|
||||||
throw ScriptError(
|
throw ScriptError(
|
||||||
@ -8980,7 +8982,7 @@ class Compiler(
|
|||||||
|
|
||||||
if (isDelegate && initialExpression != null) {
|
if (isDelegate && initialExpression != null) {
|
||||||
ensureDelegateType(initialExpression)
|
ensureDelegateType(initialExpression)
|
||||||
if (isMutable && resolveInitializerObjClass(initialExpression) == ObjLazyDelegate.type) {
|
if (isMutable && resolveInitializerObjClass(initialExpression)?.className == "lazy") {
|
||||||
throw ScriptError(initialExpression.pos, "lazy delegate is read-only")
|
throw ScriptError(initialExpression.pos, "lazy delegate is read-only")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -540,14 +540,6 @@ class Script(
|
|||||||
cachedValue
|
cachedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addFn("lazy") {
|
|
||||||
val builder = requireOnlyArg<Obj>()
|
|
||||||
ObjLazyDelegate(builder, requireScope().snapshotForClosure())
|
|
||||||
}
|
|
||||||
addFn("__builtinLazy") {
|
|
||||||
val builder = requireOnlyArg<Obj>()
|
|
||||||
ObjLazyDelegate(builder, requireScope().snapshotForClosure())
|
|
||||||
}
|
|
||||||
addVoidFn("delay") {
|
addVoidFn("delay") {
|
||||||
val a = args.firstAndOnly()
|
val a = args.firstAndOnly()
|
||||||
when (a) {
|
when (a) {
|
||||||
|
|||||||
@ -1089,7 +1089,6 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun isDelegateClass(receiverClass: ObjClass): Boolean =
|
private fun isDelegateClass(receiverClass: ObjClass): Boolean =
|
||||||
receiverClass.className == "Delegate" ||
|
receiverClass.className == "Delegate" ||
|
||||||
receiverClass.className == "LazyDelegate" ||
|
|
||||||
receiverClass.implementingNames.contains("Delegate")
|
receiverClass.implementingNames.contains("Delegate")
|
||||||
|
|
||||||
private fun operatorMemberName(op: BinOp): String? = when (op) {
|
private fun operatorMemberName(op: BinOp): String? = when (op) {
|
||||||
|
|||||||
@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.lyng.obj
|
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
|
||||||
import net.sergeych.lyng.BytecodeBodyProvider
|
|
||||||
import net.sergeych.lyng.Pos
|
|
||||||
import net.sergeych.lyng.Scope
|
|
||||||
import net.sergeych.lyng.Statement
|
|
||||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
|
||||||
import net.sergeych.lyng.Visibility
|
|
||||||
import net.sergeych.lyng.executeBytecodeWithSeed
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazy delegate used by `val x by lazy { ... }`.
|
|
||||||
*/
|
|
||||||
class ObjLazyDelegate(
|
|
||||||
private val builder: Obj,
|
|
||||||
private val capturedScope: Scope,
|
|
||||||
) : Obj() {
|
|
||||||
override val objClass: ObjClass = type
|
|
||||||
|
|
||||||
private var calculated = false
|
|
||||||
private var cachedValue: Obj = ObjVoid
|
|
||||||
|
|
||||||
override suspend fun invokeInstanceMethod(
|
|
||||||
scope: Scope,
|
|
||||||
name: String,
|
|
||||||
args: Arguments,
|
|
||||||
onNotFoundResult: (suspend () -> Obj?)?,
|
|
||||||
): Obj {
|
|
||||||
return when (name) {
|
|
||||||
"bind" -> {
|
|
||||||
val access = args.getOrNull(1)?.toString() ?: ""
|
|
||||||
if (!access.endsWith("Val")) {
|
|
||||||
scope.raiseIllegalArgument("lazy delegate can only be used with 'val'")
|
|
||||||
}
|
|
||||||
this
|
|
||||||
}
|
|
||||||
"getValue" -> {
|
|
||||||
if (!calculated) {
|
|
||||||
val receiver = args.getOrNull(0) ?: ObjNull
|
|
||||||
val callScope = scope.createChildScope(
|
|
||||||
scope.pos,
|
|
||||||
args = Arguments.EMPTY,
|
|
||||||
newThisObj = receiver
|
|
||||||
)
|
|
||||||
cachedValue = if (builder is BytecodeStatement || builder is BytecodeBodyProvider) {
|
|
||||||
executeBytecodeWithSeed(callScope, builder as Statement, "lazy delegate")
|
|
||||||
} else {
|
|
||||||
builder.invoke(callScope, receiver, Arguments.EMPTY)
|
|
||||||
}
|
|
||||||
calculated = true
|
|
||||||
}
|
|
||||||
cachedValue
|
|
||||||
}
|
|
||||||
"setValue" -> scope.raiseIllegalAssignment("lazy delegate is read-only")
|
|
||||||
else -> super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val type = ObjClass("LazyDelegate").apply {
|
|
||||||
implementingNames.add("Delegate")
|
|
||||||
createField(
|
|
||||||
"getValue",
|
|
||||||
ObjNull,
|
|
||||||
isMutable = false,
|
|
||||||
visibility = Visibility.Public,
|
|
||||||
pos = Pos.builtIn,
|
|
||||||
declaringClass = this,
|
|
||||||
type = ObjRecord.Type.Fun
|
|
||||||
)
|
|
||||||
createField(
|
|
||||||
"setValue",
|
|
||||||
ObjNull,
|
|
||||||
isMutable = false,
|
|
||||||
visibility = Visibility.Public,
|
|
||||||
pos = Pos.builtIn,
|
|
||||||
declaringClass = this,
|
|
||||||
type = ObjRecord.Type.Fun
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -212,6 +212,96 @@ class DelegationTest {
|
|||||||
assertTrue(badThrown)
|
assertTrue(badThrown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPureLyngLazyPreservesReceiverAndClosure() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
val GLOBAL_NUMBERS = [1,2,3]
|
||||||
|
|
||||||
|
class PureLazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
||||||
|
private val creator: ThisRefType.()->T = creatorParam
|
||||||
|
private var value = Unset
|
||||||
|
|
||||||
|
override fun bind(name: String, access, thisRef: ThisRefType): Object = this
|
||||||
|
|
||||||
|
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||||
|
if (value == Unset)
|
||||||
|
value = creator(thisRef)
|
||||||
|
value as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pureLazy<T,ThisRefType=Object>(creator: ThisRefType.()->T): Delegate<T,ThisRefType> = PureLazy(creator)
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val numbers = [1,2,3]
|
||||||
|
val fromThis: List by pureLazy { this.numbers }
|
||||||
|
val fromScope: List by pureLazy { GLOBAL_NUMBERS }
|
||||||
|
}
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val a: A by pureLazy { A() }
|
||||||
|
val test: List by pureLazy { (a as A).fromThis + [4] }
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals([1,2,3], A().fromThis)
|
||||||
|
assertEquals([1,2,3], A().fromScope)
|
||||||
|
assertEquals([1,2,3,4], B().test)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImportedPureLyngLazyPreservesReceiverAndClosure() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.importManager.addTextPackages(
|
||||||
|
"""
|
||||||
|
package repro.lazy
|
||||||
|
|
||||||
|
import lyng.stdlib
|
||||||
|
|
||||||
|
class PureLazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
||||||
|
private val creator: ThisRefType.()->T = creatorParam
|
||||||
|
private var value = Unset
|
||||||
|
|
||||||
|
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
||||||
|
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'"
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||||
|
if (value == Unset)
|
||||||
|
value = with(thisRef, creator)
|
||||||
|
value as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import repro.lazy
|
||||||
|
|
||||||
|
val GLOBAL_NUMBERS = [1,2,3]
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val numbers = [1,2,3]
|
||||||
|
val fromThis: List by PureLazy { this.numbers }
|
||||||
|
val fromScope: List by PureLazy { GLOBAL_NUMBERS }
|
||||||
|
}
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val a: A by PureLazy { A() }
|
||||||
|
val test: List by PureLazy { (a as A).fromThis + [4] }
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals([1,2,3], A().fromThis)
|
||||||
|
assertEquals([1,2,3], A().fromScope)
|
||||||
|
assertEquals([1,2,3,4], B().test)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLazyIsDelegate() = runTest {
|
fun testLazyIsDelegate() = runTest {
|
||||||
eval("""
|
eval("""
|
||||||
|
|||||||
@ -415,26 +415,24 @@ fun with<T,R>(self: T, block: T.()->R): R {
|
|||||||
block(self)
|
block(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fun __builtinLazy(creator: Object): Object
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Standard implementation of a lazy-initialized property delegate.
|
Standard implementation of a lazy-initialized property delegate.
|
||||||
The provided creator lambda is called once on the first access to compute the value.
|
The provided creator lambda is called once on the first access to compute the value.
|
||||||
Can only be used with 'val' properties.
|
Can only be used with 'val' properties.
|
||||||
*/
|
*/
|
||||||
class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
||||||
private val delegate: Delegate<T,ThisRefType> = __builtinLazy(creatorParam) as Delegate<T,ThisRefType>
|
private val creator: ThisRefType.()->T = creatorParam
|
||||||
|
private var value = Unset
|
||||||
|
|
||||||
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
||||||
delegate.bind(name, access, thisRef)
|
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'"
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getValue(thisRef: ThisRefType, name: String): T {
|
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||||
delegate.getValue(thisRef, name) as T
|
if (value == Unset)
|
||||||
}
|
value = with(thisRef, creator)
|
||||||
|
value as T
|
||||||
override fun setValue(thisRef: ThisRefType, name: String, newValue: T): void {
|
|
||||||
delegate.setValue(thisRef, name, newValue)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user