Temporary lazy builtin baseline

This commit is contained in:
Sergey Chernov 2026-03-27 18:46:53 +03:00
parent 9ddc7dbee6
commit 217787e17a
9 changed files with 145 additions and 29 deletions

View File

@ -184,6 +184,7 @@ class Compiler(
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf() private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
private val objectDeclNames: MutableSet<String> = mutableSetOf() private val objectDeclNames: MutableSet<String> = mutableSetOf()
private val externCallableNames: MutableSet<String> = mutableSetOf() private val externCallableNames: MutableSet<String> = mutableSetOf()
private val externBindingNames: MutableSet<String> = mutableSetOf()
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf() private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
private var seedingSlotPlan: Boolean = false private var seedingSlotPlan: Boolean = false
@ -192,6 +193,7 @@ class Compiler(
if (plan.slots.isEmpty()) return emptyMap() if (plan.slots.isEmpty()) return emptyMap()
val result = LinkedHashMap<String, ForcedLocalSlotInfo>(plan.slots.size) val result = LinkedHashMap<String, ForcedLocalSlotInfo>(plan.slots.size)
for ((name, entry) in plan.slots) { for ((name, entry) in plan.slots) {
if (externBindingNames.contains(name)) continue
result[name] = ForcedLocalSlotInfo( result[name] = ForcedLocalSlotInfo(
index = entry.index, index = entry.index,
isMutable = entry.isMutable, isMutable = entry.isMutable,
@ -885,6 +887,7 @@ class Compiler(
name = declaredName, name = declaredName,
isMutable = false, isMutable = false,
visibility = Visibility.Public, visibility = Visibility.Public,
actualExtern = false,
initializer = initStmt, initializer = initStmt,
isTransient = false, isTransient = false,
typeDecl = null, typeDecl = null,
@ -1751,6 +1754,7 @@ class Compiler(
enumEntriesByName = enumEntriesByName, enumEntriesByName = enumEntriesByName,
callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName, callableReturnTypeByName = callableReturnTypeByName,
externBindingNames = externBindingNames,
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
) as BytecodeStatement ) as BytecodeStatement
unwrapped to bytecodeStmt.bytecodeFunction() unwrapped to bytecodeStmt.bytecodeFunction()
@ -1957,6 +1961,9 @@ class Compiler(
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId } val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
val modulePlan = moduleSlotPlan() val modulePlan = moduleSlotPlan()
if (modulePlan != null && slotLoc.scopeId == modulePlan.id && externBindingNames.contains(name)) {
return null
}
if (useScopeSlots && modulePlan != null && slotLoc.scopeId == modulePlan.id) { if (useScopeSlots && modulePlan != null && slotLoc.scopeId == modulePlan.id) {
return null return null
} }
@ -2075,6 +2082,7 @@ class Compiler(
callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName, callableReturnTypeByName = callableReturnTypeByName,
externCallableNames = externCallableNames, externCallableNames = externCallableNames,
externBindingNames = externBindingNames,
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
) )
} }
@ -2105,6 +2113,7 @@ class Compiler(
callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName, callableReturnTypeByName = callableReturnTypeByName,
externCallableNames = externCallableNames, externCallableNames = externCallableNames,
externBindingNames = externBindingNames,
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
) )
} }
@ -2160,6 +2169,7 @@ class Compiler(
callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName, callableReturnTypeByName = callableReturnTypeByName,
externCallableNames = externCallableNames, externCallableNames = externCallableNames,
externBindingNames = externBindingNames,
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
) )
} }
@ -2343,6 +2353,7 @@ class Compiler(
stmt.name, stmt.name,
stmt.isMutable, stmt.isMutable,
stmt.visibility, stmt.visibility,
stmt.actualExtern,
init, init,
stmt.isTransient, stmt.isTransient,
stmt.typeDecl, stmt.typeDecl,
@ -8893,7 +8904,7 @@ class Compiler(
val effectiveEqToken = if (isProperty) null else eqToken val effectiveEqToken = if (isProperty) null else eqToken
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals // Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
if (!isStatic && declaringClassNameCaptured == null) declareLocalName(name, isMutable) if (!isStatic && declaringClassNameCaptured == null && !actualExtern) declareLocalName(name, isMutable)
val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) { val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) {
SymbolKind.MEMBER SymbolKind.MEMBER
} else { } else {
@ -8902,6 +8913,8 @@ class Compiler(
resolutionSink?.declareSymbol(name, declKind, isMutable, nameStartPos, isOverride = isOverride) resolutionSink?.declareSymbol(name, declKind, isMutable, nameStartPos, isOverride = isOverride)
if (declKind == SymbolKind.MEMBER && extTypeName == null) { if (declKind == SymbolKind.MEMBER && extTypeName == null) {
(codeContexts.lastOrNull() as? CodeContext.ClassBody)?.declaredMembers?.add(name) (codeContexts.lastOrNull() as? CodeContext.ClassBody)?.declaredMembers?.add(name)
} else if (actualExtern) {
externBindingNames.add(name)
} }
val isDelegate = if (isAbstract || actualExtern) { val isDelegate = if (isAbstract || actualExtern) {
@ -9059,6 +9072,7 @@ class Compiler(
name, name,
isMutable, isMutable,
visibility, visibility,
actualExtern,
initialExpression, initialExpression,
isTransient, isTransient,
declaredType, declaredType,

View File

@ -542,7 +542,11 @@ class Script(
} }
addFn("lazy") { addFn("lazy") {
val builder = requireOnlyArg<Obj>() val builder = requireOnlyArg<Obj>()
ObjLazyDelegate(builder, requireScope()) 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()

View File

@ -24,6 +24,7 @@ class VarDeclStatement(
val name: String, val name: String,
val isMutable: Boolean, val isMutable: Boolean,
val visibility: Visibility, val visibility: Visibility,
val actualExtern: Boolean,
val initializer: Statement?, val initializer: Statement?,
val isTransient: Boolean, val isTransient: Boolean,
val typeDecl: TypeDecl?, val typeDecl: TypeDecl?,

View File

@ -42,6 +42,7 @@ class BytecodeCompiler(
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(), private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
private val externCallableNames: Set<String> = emptySet(), private val externCallableNames: Set<String> = emptySet(),
private val externBindingNames: Set<String> = emptySet(),
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(), private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
) { ) {
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
@ -4508,7 +4509,7 @@ class BytecodeCompiler(
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved) return CompiledValue(slot, resolved)
} }
if (useScopeSlots && allowedScopeNames?.contains(name) == true) { if (useScopeSlots && isPreparedScopeName(name)) {
scopeSlotIndexByName[name]?.let { slot -> scopeSlotIndexByName[name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved) return CompiledValue(slot, resolved)
@ -8073,7 +8074,7 @@ class BytecodeCompiler(
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
} }
} }
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) { if (allowLocalSlots && slotIndex != null && (stmt.actualExtern || !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = false))) {
val key = ScopeSlotKey(scopeId, slotIndex) val key = ScopeSlotKey(scopeId, slotIndex)
declaredLocalKeys.add(key) declaredLocalKeys.add(key)
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
@ -8100,7 +8101,7 @@ class BytecodeCompiler(
val scopeId = stmt.spec.scopeId ?: 0 val scopeId = stmt.spec.scopeId ?: 0
if (slotIndex != null) { if (slotIndex != null) {
val key = ScopeSlotKey(scopeId, slotIndex) val key = ScopeSlotKey(scopeId, slotIndex)
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId)) { if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId, stmt.spec.name, isDelegated = false)) {
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false) localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
} }
@ -8117,7 +8118,7 @@ class BytecodeCompiler(
is DelegatedVarDeclStatement -> { is DelegatedVarDeclStatement -> {
val slotIndex = stmt.slotIndex val slotIndex = stmt.slotIndex
val scopeId = stmt.scopeId ?: 0 val scopeId = stmt.scopeId ?: 0
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) { if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = true)) {
val key = ScopeSlotKey(scopeId, slotIndex) val key = ScopeSlotKey(scopeId, slotIndex)
declaredLocalKeys.add(key) declaredLocalKeys.add(key)
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
@ -8283,15 +8284,26 @@ class BytecodeCompiler(
private fun isModuleSlot(scopeId: Int, name: String?): Boolean { private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
if (moduleScopeId != null && scopeId != moduleScopeId) return false if (moduleScopeId != null && scopeId != moduleScopeId) return false
val scopeNames = allowedScopeNames ?: scopeSlotNameSet return isPreparedScopeName(name)
if (scopeNames == null || name == null) return false
return scopeNames.contains(name)
} }
private fun shouldUseScopeSlotFor(scopeId: Int): Boolean { private fun shouldUseScopeSlotFor(scopeId: Int): Boolean {
return useScopeSlots && moduleScopeId != null && scopeId == moduleScopeId return useScopeSlots && moduleScopeId != null && scopeId == moduleScopeId
} }
private fun shouldUseScopeSlotFor(scopeId: Int, name: String, isDelegated: Boolean): Boolean {
if (moduleScopeId == null || scopeId != moduleScopeId) return false
if (isDelegated) return false
if (externBindingNames.contains(name)) return true
return useScopeSlots && isPreparedScopeName(name)
}
private fun isPreparedScopeName(name: String?): Boolean {
if (name == null) return false
if (scopeSlotNameSet?.contains(name) == true) return true
return allowedScopeNames?.contains(name) == true
}
private fun collectLoopVarNames(stmt: Statement) { private fun collectLoopVarNames(stmt: Statement) {
if (stmt is BytecodeStatement) { if (stmt is BytecodeStatement) {
collectLoopVarNames(stmt.original) collectLoopVarNames(stmt.original)
@ -8400,8 +8412,9 @@ class BytecodeCompiler(
captureSlotKeys.add(key) captureSlotKeys.add(key)
return return
} }
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, ref.name, ref.isDelegated)
val isModuleSlot = if (ref.isDelegated) false else isModuleSlot(scopeId, ref.name) val isModuleSlot = if (ref.isDelegated) false else isModuleSlot(scopeId, ref.name)
if (allowLocalSlots && !isModuleSlot) { if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated) localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
} }
@ -8448,8 +8461,9 @@ class BytecodeCompiler(
} }
captureSlotKeys.add(key) captureSlotKeys.add(key)
} else { } else {
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, target.name, target.isDelegated)
val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name) val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name)
if (allowLocalSlots && !isModuleSlot) { if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated) localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
} }

View File

@ -87,6 +87,7 @@ class BytecodeStatement private constructor(
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(), callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
externCallableNames: Set<String> = emptySet(), externCallableNames: Set<String> = emptySet(),
externBindingNames: Set<String> = emptySet(),
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(), lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(), slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
): Statement { ): Statement {
@ -122,6 +123,7 @@ class BytecodeStatement private constructor(
callableReturnTypeByScopeId = callableReturnTypeByScopeId, callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName, callableReturnTypeByName = callableReturnTypeByName,
externCallableNames = externCallableNames, externCallableNames = externCallableNames,
externBindingNames = externBindingNames,
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
) )
val compiled = compiler.compileStatement(nameHint, statement) val compiled = compiler.compileStatement(nameHint, statement)
@ -236,6 +238,7 @@ class BytecodeStatement private constructor(
stmt.name, stmt.name,
stmt.isMutable, stmt.isMutable,
stmt.visibility, stmt.visibility,
stmt.actualExtern,
stmt.initializer?.let { unwrapDeep(it) }, stmt.initializer?.let { unwrapDeep(it) },
stmt.isTransient, stmt.isTransient,
stmt.typeDecl, stmt.typeDecl,

View File

@ -2392,8 +2392,13 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
?: error("DECL_LOCAL expects LocalDecl at $constId") ?: error("DECL_LOCAL expects LocalDecl at $constId")
if (slot < frame.fn.scopeSlotCount) { if (slot < frame.fn.scopeSlotCount) {
val target = frame.scopeTarget(slot) val target = frame.scopeTarget(slot)
frame.ensureScopeSlot(target, slot) val index = frame.ensureScopeSlot(target, slot)
val value = frame.slotToObj(slot).byValueCopy() val raw = target.getSlotRecord(index).value
val value = when (raw) {
is FrameSlotRef -> raw.read()
is RecordSlotRef -> raw.read()
else -> raw
}.byValueCopy()
target.updateSlotFor( target.updateSlotFor(
decl.name, decl.name,
ObjRecord( ObjRecord(
@ -4557,7 +4562,7 @@ class CmdFrame(
return getScopeSlotValueAtAddr(addrSlot) return getScopeSlotValueAtAddr(addrSlot)
} }
fun setAddrObj(addrSlot: Int, value: Obj) { suspend fun setAddrObj(addrSlot: Int, value: Obj) {
setScopeSlotValueAtAddr(addrSlot, value) setScopeSlotValueAtAddr(addrSlot, value)
} }
@ -4565,7 +4570,7 @@ class CmdFrame(
return getScopeSlotValueAtAddr(addrSlot).toLong() return getScopeSlotValueAtAddr(addrSlot).toLong()
} }
fun setAddrInt(addrSlot: Int, value: Long) { suspend fun setAddrInt(addrSlot: Int, value: Long) {
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value)) setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
} }
@ -4573,7 +4578,7 @@ class CmdFrame(
return getScopeSlotValueAtAddr(addrSlot).toDouble() return getScopeSlotValueAtAddr(addrSlot).toDouble()
} }
fun setAddrReal(addrSlot: Int, value: Double) { suspend fun setAddrReal(addrSlot: Int, value: Double) {
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value)) setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
} }
@ -4581,7 +4586,7 @@ class CmdFrame(
return getScopeSlotValueAtAddr(addrSlot).toBool() return getScopeSlotValueAtAddr(addrSlot).toBool()
} }
fun setAddrBool(addrSlot: Int, value: Boolean) { suspend fun setAddrBool(addrSlot: Int, value: Boolean) {
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse) setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
} }
@ -4870,9 +4875,16 @@ class CmdFrame(
return resolved.value return resolved.value
} }
private fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) { private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved") val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
val index = addrIndices[addrSlot] val index = addrIndices[addrSlot]
val record = target.getSlotRecord(index)
val slotId = addrScopeSlots[addrSlot]
val name = fn.scopeSlotNames.getOrNull(slotId)
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
target.assign(record, name, value)
return
}
target.setSlotValue(index, value) target.setSlotValue(index, value)
} }

View File

@ -18,9 +18,11 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.BytecodeBodyProvider
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.bytecode.BytecodeStatement
import net.sergeych.lyng.Visibility import net.sergeych.lyng.Visibility
import net.sergeych.lyng.executeBytecodeWithSeed import net.sergeych.lyng.executeBytecodeWithSeed
@ -43,13 +45,25 @@ class ObjLazyDelegate(
onNotFoundResult: (suspend () -> Obj?)?, onNotFoundResult: (suspend () -> Obj?)?,
): Obj { ): Obj {
return when (name) { 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" -> { "getValue" -> {
if (!calculated) { if (!calculated) {
val callScope = capturedScope.createChildScope(capturedScope.pos, args = Arguments.EMPTY) val receiver = args.getOrNull(0) ?: ObjNull
cachedValue = if (builder is Statement) { val callScope = scope.createChildScope(
executeBytecodeWithSeed(callScope, builder, "lazy delegate") scope.pos,
args = Arguments.EMPTY,
newThisObj = receiver
)
cachedValue = if (builder is BytecodeStatement || builder is BytecodeBodyProvider) {
executeBytecodeWithSeed(callScope, builder as Statement, "lazy delegate")
} else { } else {
builder.callOn(callScope) builder.invoke(callScope, receiver, Arguments.EMPTY)
} }
calculated = true calculated = true
} }

View File

@ -0,0 +1,52 @@
/*
* 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
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.bridge.bindGlobalVar
import net.sergeych.lyng.bridge.globalBinder
import kotlin.test.Test
import kotlin.test.assertEquals
class GlobalPropertyCaptureRegressionTest {
@Test
fun externGlobalVarAssignmentInsideFunctionShouldCallBoundSetter() = runTest {
val scope = Script.newScope()
var x = 1.0
scope.eval(
"""
extern var X: Real
fun main() {
X = X + 1.0
}
""".trimIndent()
)
scope.globalBinder().bindGlobalVar(
name = "X",
get = { x },
set = { x = it }
)
scope.eval("main()")
assertEquals(2.0, x, "bound extern var should stay live inside function bodies")
}
}

View File

@ -415,24 +415,26 @@ 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 creator: ThisRefType.()->T = creatorParam private val delegate: Delegate<T,ThisRefType> = __builtinLazy(creatorParam) as Delegate<T,ThisRefType>
private var value = Unset
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object { override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'" delegate.bind(name, access, thisRef)
this
} }
override fun getValue(thisRef: ThisRefType, name: String): T { override fun getValue(thisRef: ThisRefType, name: String): T {
if (value == Unset) delegate.getValue(thisRef, name) as T
value = with(thisRef,creator) }
value as T
override fun setValue(thisRef: ThisRefType, name: String, newValue: T): void {
delegate.setValue(thisRef, name, newValue)
} }
} }