Fix bound property captures and property slot resolution
This commit is contained in:
parent
86e8b2e2bc
commit
b0fb65a036
@ -8253,7 +8253,7 @@ class Compiler(
|
|||||||
?: context.parent?.get(localName)
|
?: context.parent?.get(localName)
|
||||||
?: context.get(localName)
|
?: context.get(localName)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||||
context.resolve(record, localName)
|
context.resolve(record, localName)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
|
|||||||
@ -167,6 +167,19 @@ class RecordSlotRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun read(scope: Scope, name: String?): Obj {
|
||||||
|
val direct = record.value
|
||||||
|
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
||||||
|
return scope.resolve(record, name)
|
||||||
|
}
|
||||||
|
return when (direct) {
|
||||||
|
is FrameSlotRef -> direct.read()
|
||||||
|
is RecordSlotRef -> direct.read(scope, name)
|
||||||
|
is ScopeSlotRef -> direct.read()
|
||||||
|
else -> direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val resolved = read()
|
val resolved = read()
|
||||||
if (resolved === this) {
|
if (resolved === this) {
|
||||||
@ -193,4 +206,18 @@ class RecordSlotRef(
|
|||||||
record.value = value
|
record.value = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun write(scope: Scope, name: String?, value: Obj): Boolean {
|
||||||
|
val direct = record.value
|
||||||
|
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
||||||
|
scope.assign(record, name, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
when (direct) {
|
||||||
|
is ScopeSlotRef -> direct.write(value)
|
||||||
|
is RecordSlotRef -> if (direct.write(scope, name, value)) return true else direct.write(value)
|
||||||
|
else -> record.value = value
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2396,7 +2396,8 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.THROW, posId, msgSlot)
|
builder.emit(Opcode.THROW, posId, msgSlot)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
val slot = resolveSlot(localTarget)
|
val slot = resolveCapturedOwnerScopeSlot(localTarget)
|
||||||
|
?: resolveSlot(localTarget)
|
||||||
?: resolveAssignableSlotByName(localTarget.name)?.first
|
?: resolveAssignableSlotByName(localTarget.name)?.first
|
||||||
?: return null
|
?: return null
|
||||||
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
||||||
@ -2702,7 +2703,7 @@ class BytecodeCompiler(
|
|||||||
return CompiledValue(result, SlotType.OBJ)
|
return CompiledValue(result, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
if (localTarget.isDelegated) return compileEvalRef(ref)
|
if (localTarget.isDelegated) return compileEvalRef(ref)
|
||||||
val slot = resolveSlot(localTarget) ?: return null
|
val slot = resolveCapturedOwnerScopeSlot(localTarget) ?: resolveSlot(localTarget) ?: return null
|
||||||
val targetType = slotTypes[slot] ?: SlotType.OBJ
|
val targetType = slotTypes[slot] ?: SlotType.OBJ
|
||||||
if (!localTarget.isMutable) {
|
if (!localTarget.isMutable) {
|
||||||
if (targetType != SlotType.OBJ && targetType != SlotType.UNKNOWN) return compileEvalRef(ref)
|
if (targetType != SlotType.OBJ && targetType != SlotType.UNKNOWN) return compileEvalRef(ref)
|
||||||
@ -7630,6 +7631,13 @@ class BytecodeCompiler(
|
|||||||
return scopeSlotMap[scopeKey]
|
return scopeSlotMap[scopeKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolveCapturedOwnerScopeSlot(ref: LocalSlotRef): Int? {
|
||||||
|
val ownerScopeId = ref.captureOwnerScopeId ?: return null
|
||||||
|
val ownerSlot = ref.captureOwnerSlot ?: return null
|
||||||
|
val key = ScopeSlotKey(ownerScopeId, ownerSlot)
|
||||||
|
return scopeSlotMap[key]
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateSlotType(slot: Int, type: SlotType) {
|
private fun updateSlotType(slot: Int, type: SlotType) {
|
||||||
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
|
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
|
||||||
if (type == SlotType.UNKNOWN) {
|
if (type == SlotType.UNKNOWN) {
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class BytecodeStatement private constructor(
|
|||||||
?: scope.parent?.get(name)
|
?: scope.parent?.get(name)
|
||||||
?: scope.get(name)
|
?: scope.get(name)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
||||||
scope.resolve(record, name)
|
scope.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
|
|||||||
@ -85,6 +85,9 @@ class CmdNop : Cmd() {
|
|||||||
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
val value = frame.slotToObj(src)
|
val value = frame.slotToObj(src)
|
||||||
|
if (frame.writeThroughPropertyLikeSlot(dst, value)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||||
frame.setObjUnchecked(dst, value)
|
frame.setObjUnchecked(dst, value)
|
||||||
} else {
|
} else {
|
||||||
@ -2595,7 +2598,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
|||||||
}
|
}
|
||||||
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
||||||
if (scoped != null) {
|
if (scoped != null) {
|
||||||
records += ObjRecord(RecordSlotRef(scoped), isMutable = scoped.isMutable)
|
records += scoped
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
||||||
@ -2858,7 +2861,7 @@ class CmdDeclInstanceProperty(internal val constId: Int, internal val slot: Int)
|
|||||||
val decl = frame.fn.constants[constId] as? BytecodeConst.InstancePropertyDecl
|
val decl = frame.fn.constants[constId] as? BytecodeConst.InstancePropertyDecl
|
||||||
?: error("DECL_INSTANCE_PROPERTY expects InstancePropertyDecl at $constId")
|
?: error("DECL_INSTANCE_PROPERTY expects InstancePropertyDecl at $constId")
|
||||||
val scope = frame.ensureScope()
|
val scope = frame.ensureScope()
|
||||||
val prop = frame.slotToObj(slot)
|
val prop = frame.storedSlotObj(slot)
|
||||||
scope.addItem(
|
scope.addItem(
|
||||||
decl.name,
|
decl.name,
|
||||||
decl.isMutable,
|
decl.isMutable,
|
||||||
@ -3724,7 +3727,7 @@ class BytecodeLambdaCallable(
|
|||||||
?: context.parent?.get(name)
|
?: context.parent?.get(name)
|
||||||
?: context.get(name)
|
?: context.get(name)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||||
context.resolve(record, name)
|
context.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
@ -3817,7 +3820,29 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
||||||
internal fun readLocalObj(localIndex: Int): Obj = localSlotToObj(localIndex)
|
internal fun readLocalObj(localIndex: Int): Obj {
|
||||||
|
return when (frame.getSlotTypeCode(localIndex)) {
|
||||||
|
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
|
||||||
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
|
||||||
|
SlotType.BOOL.code -> if (frame.getBool(localIndex)) ObjTrue else ObjFalse
|
||||||
|
SlotType.OBJ.code -> {
|
||||||
|
val obj = frame.getObj(localIndex)
|
||||||
|
when (obj) {
|
||||||
|
is FrameSlotRef -> obj.read()
|
||||||
|
is RecordSlotRef -> obj.read()
|
||||||
|
else -> obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val obj = frame.getObj(localIndex)
|
||||||
|
when (obj) {
|
||||||
|
is FrameSlotRef -> obj.read()
|
||||||
|
is RecordSlotRef -> obj.read()
|
||||||
|
else -> obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
internal fun isFastLocalSlot(slot: Int): Boolean {
|
internal fun isFastLocalSlot(slot: Int): Boolean {
|
||||||
if (slot < fn.scopeSlotCount) return false
|
if (slot < fn.scopeSlotCount) return false
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
@ -4262,7 +4287,7 @@ class CmdFrame(
|
|||||||
return if (slot < fn.scopeSlotCount) {
|
return if (slot < fn.scopeSlotCount) {
|
||||||
getScopeSlotValue(slot)
|
getScopeSlotValue(slot)
|
||||||
} else {
|
} else {
|
||||||
localSlotToObj(slot - fn.scopeSlotCount)
|
readLocalSlotValue(slot - fn.scopeSlotCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4305,6 +4330,34 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun writeThroughPropertyLikeSlot(slot: Int, value: Obj): Boolean {
|
||||||
|
if (slot < fn.scopeSlotCount) {
|
||||||
|
val target = scopeTarget(slot)
|
||||||
|
val index = ensureScopeSlot(target, slot)
|
||||||
|
val name = fn.scopeSlotNames.getOrNull(slot)
|
||||||
|
val record = resolveScopeSlotRecordForWrite(target, index, name)
|
||||||
|
if (name != null && record != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
||||||
|
target.assign(record, name, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
|
val name = fn.localSlotNames.getOrNull(localIndex) ?: return false
|
||||||
|
val raw = frame.getRawObj(localIndex)
|
||||||
|
if (raw is RecordSlotRef) {
|
||||||
|
if (raw.write(scope, name, value)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (raw !== ObjUnset && raw !is ObjProperty) return false
|
||||||
|
val record = scope.parent?.get(name) ?: scope.get(name) ?: return false
|
||||||
|
if (record.type != ObjRecord.Type.Delegated && record.type != ObjRecord.Type.Property && record.value !is ObjProperty) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
scope.assign(record, name, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getInt(slot: Int): Long {
|
suspend fun getInt(slot: Int): Long {
|
||||||
return if (slot < fn.scopeSlotCount) {
|
return if (slot < fn.scopeSlotCount) {
|
||||||
getScopeSlotValue(slot).toLong()
|
getScopeSlotValue(slot).toLong()
|
||||||
@ -4314,14 +4367,7 @@ class CmdFrame(
|
|||||||
SlotType.INT.code -> frame.getInt(local)
|
SlotType.INT.code -> frame.getInt(local)
|
||||||
SlotType.REAL.code -> frame.getReal(local).toLong()
|
SlotType.REAL.code -> frame.getReal(local).toLong()
|
||||||
SlotType.BOOL.code -> if (frame.getBool(local)) 1L else 0L
|
SlotType.BOOL.code -> if (frame.getBool(local)) 1L else 0L
|
||||||
SlotType.OBJ.code -> {
|
SlotType.OBJ.code -> readLocalSlotValue(local).toLong()
|
||||||
val obj = frame.getObj(local)
|
|
||||||
when (obj) {
|
|
||||||
is FrameSlotRef -> obj.read().toLong()
|
|
||||||
is RecordSlotRef -> obj.read().toLong()
|
|
||||||
else -> obj.toLong()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> 0L
|
else -> 0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4401,14 +4447,7 @@ class CmdFrame(
|
|||||||
SlotType.REAL.code -> frame.getReal(local)
|
SlotType.REAL.code -> frame.getReal(local)
|
||||||
SlotType.INT.code -> frame.getInt(local).toDouble()
|
SlotType.INT.code -> frame.getInt(local).toDouble()
|
||||||
SlotType.BOOL.code -> if (frame.getBool(local)) 1.0 else 0.0
|
SlotType.BOOL.code -> if (frame.getBool(local)) 1.0 else 0.0
|
||||||
SlotType.OBJ.code -> {
|
SlotType.OBJ.code -> readLocalSlotValue(local).toDouble()
|
||||||
val obj = frame.getObj(local)
|
|
||||||
when (obj) {
|
|
||||||
is FrameSlotRef -> obj.read().toDouble()
|
|
||||||
is RecordSlotRef -> obj.read().toDouble()
|
|
||||||
else -> obj.toDouble()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> 0.0
|
else -> 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4477,14 +4516,7 @@ class CmdFrame(
|
|||||||
SlotType.BOOL.code -> frame.getBool(local)
|
SlotType.BOOL.code -> frame.getBool(local)
|
||||||
SlotType.INT.code -> frame.getInt(local) != 0L
|
SlotType.INT.code -> frame.getInt(local) != 0L
|
||||||
SlotType.REAL.code -> frame.getReal(local) != 0.0
|
SlotType.REAL.code -> frame.getReal(local) != 0.0
|
||||||
SlotType.OBJ.code -> {
|
SlotType.OBJ.code -> readLocalSlotValue(local).toBool()
|
||||||
val obj = frame.getObj(local)
|
|
||||||
when (obj) {
|
|
||||||
is FrameSlotRef -> obj.read().toBool()
|
|
||||||
is RecordSlotRef -> obj.read().toBool()
|
|
||||||
else -> obj.toBool()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4596,21 +4628,42 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
val local = slot - fn.scopeSlotCount
|
val local = slot - fn.scopeSlotCount
|
||||||
if (fn.localSlotCaptures.getOrNull(local) == true) {
|
if (fn.localSlotCaptures.getOrNull(local) == true) {
|
||||||
return localSlotToObj(local)
|
return readLocalSlotValue(local)
|
||||||
}
|
}
|
||||||
return when (frame.getSlotTypeCode(local)) {
|
return when (frame.getSlotTypeCode(local)) {
|
||||||
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
|
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
|
||||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
|
||||||
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
||||||
SlotType.OBJ.code -> {
|
SlotType.OBJ.code -> readLocalSlotValue(local)
|
||||||
val obj = frame.getObj(local)
|
else -> readLocalSlotValue(local)
|
||||||
when (obj) {
|
|
||||||
is FrameSlotRef -> obj.read()
|
|
||||||
is RecordSlotRef -> obj.read()
|
|
||||||
else -> obj
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> localSlotToObj(local)
|
|
||||||
|
fun storedSlotObj(slot: Int): Obj {
|
||||||
|
if (slot < fn.scopeSlotCount) {
|
||||||
|
val target = scopeTarget(slot)
|
||||||
|
val index = ensureScopeSlot(target, slot)
|
||||||
|
val record = target.getSlotRecord(index)
|
||||||
|
return when (val direct = record.value) {
|
||||||
|
is FrameSlotRef -> direct.read()
|
||||||
|
is RecordSlotRef -> direct.read()
|
||||||
|
is ScopeSlotRef -> direct.read()
|
||||||
|
else -> direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val local = slot - fn.scopeSlotCount
|
||||||
|
return when (frame.getSlotTypeCode(local)) {
|
||||||
|
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
|
||||||
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
|
||||||
|
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
||||||
|
SlotType.OBJ.code, SlotType.UNKNOWN.code -> when (val raw = frame.getRawObj(local)) {
|
||||||
|
is FrameSlotRef -> raw.read()
|
||||||
|
is RecordSlotRef -> raw.read()
|
||||||
|
is ScopeSlotRef -> raw.read()
|
||||||
|
null -> ObjNull
|
||||||
|
else -> raw
|
||||||
|
}
|
||||||
|
else -> frame.getRawObj(local) ?: ObjNull
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4766,7 +4819,8 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun localSlotToObj(localIndex: Int): Obj {
|
private suspend fun readLocalSlotValue(localIndex: Int): Obj {
|
||||||
|
val localName = fn.localSlotNames.getOrNull(localIndex)
|
||||||
return when (frame.getSlotTypeCode(localIndex)) {
|
return when (frame.getSlotTypeCode(localIndex)) {
|
||||||
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
|
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
|
||||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
|
||||||
@ -4775,7 +4829,9 @@ class CmdFrame(
|
|||||||
val obj = frame.getObj(localIndex)
|
val obj = frame.getObj(localIndex)
|
||||||
when (obj) {
|
when (obj) {
|
||||||
is FrameSlotRef -> obj.read()
|
is FrameSlotRef -> obj.read()
|
||||||
is RecordSlotRef -> obj.read()
|
is RecordSlotRef -> obj.read(scope, localName)
|
||||||
|
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
||||||
|
ObjUnset -> resolveUnsetLocal(localName)
|
||||||
else -> obj
|
else -> obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4783,13 +4839,34 @@ class CmdFrame(
|
|||||||
val obj = frame.getObj(localIndex)
|
val obj = frame.getObj(localIndex)
|
||||||
when (obj) {
|
when (obj) {
|
||||||
is FrameSlotRef -> obj.read()
|
is FrameSlotRef -> obj.read()
|
||||||
is RecordSlotRef -> obj.read()
|
is RecordSlotRef -> obj.read(scope, localName)
|
||||||
|
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
||||||
|
ObjUnset -> resolveUnsetLocal(localName)
|
||||||
else -> obj
|
else -> obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun resolvePropertyLikeLocal(localName: String?, property: ObjProperty): Obj {
|
||||||
|
if (localName != null) {
|
||||||
|
val record = scope.parent?.get(localName) ?: scope.get(localName)
|
||||||
|
if (record != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
||||||
|
return scope.resolve(record, localName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return property.callGetter(scope, scope.thisObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun resolveUnsetLocal(localName: String?): Obj {
|
||||||
|
if (localName == null) return ObjUnset
|
||||||
|
val record = scope.parent?.get(localName) ?: scope.get(localName) ?: return ObjUnset
|
||||||
|
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||||
|
return scope.resolve(record, localName)
|
||||||
|
}
|
||||||
|
return record.value
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getScopeSlotValue(slot: Int): Obj {
|
private suspend fun getScopeSlotValue(slot: Int): Obj {
|
||||||
val target = scopeTarget(slot)
|
val target = scopeTarget(slot)
|
||||||
val name = fn.scopeSlotNames[slot]
|
val name = fn.scopeSlotNames[slot]
|
||||||
@ -4878,16 +4955,33 @@ class CmdFrame(
|
|||||||
private suspend 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 slotId = addrScopeSlots[addrSlot]
|
||||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
val record = resolveScopeSlotRecordForWrite(target, index, name)
|
||||||
|
if (name != null && record != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
||||||
target.assign(record, name, value)
|
target.assign(record, name, value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
target.setSlotValue(index, value)
|
target.setSlotValue(index, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolveScopeSlotRecordForWrite(target: Scope, index: Int, name: String?): ObjRecord? {
|
||||||
|
val record = target.getSlotRecord(index)
|
||||||
|
if (name == null) return record
|
||||||
|
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
if (record.value !== ObjUnset && record.memberName == null) {
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
val resolved = target.get(name) ?: return record
|
||||||
|
if (resolved.value !== ObjUnset || resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolved.value is ObjProperty) {
|
||||||
|
target.updateSlotFor(name, resolved)
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
internal fun ensureScopeSlot(target: Scope, slot: Int): Int {
|
internal fun ensureScopeSlot(target: Scope, slot: Int): Int {
|
||||||
val name = fn.scopeSlotNames[slot]
|
val name = fn.scopeSlotNames[slot]
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
|||||||
val record = scope.getLocalRecordDirect(name)
|
val record = scope.getLocalRecordDirect(name)
|
||||||
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
||||||
scope.resolve(record, name)
|
scope.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
|
|||||||
@ -591,11 +591,14 @@ open class Obj {
|
|||||||
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
||||||
}
|
}
|
||||||
val value = obj.value
|
val value = obj.value
|
||||||
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) {
|
if (value is ObjProperty) {
|
||||||
val prop = (value as? ObjProperty)
|
val res = value.callGetter(scope, this, decl)
|
||||||
?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}")
|
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
||||||
val res = prop.callGetter(scope, this, decl)
|
}
|
||||||
return ObjRecord(res, obj.isMutable)
|
if (obj.type == ObjRecord.Type.Property) {
|
||||||
|
// Some runtime paths cache the resolved property value back into the record.
|
||||||
|
// Treat that as an already-resolved read result instead of trying to call a getter again.
|
||||||
|
return obj.copy(type = ObjRecord.Type.Other)
|
||||||
}
|
}
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
// Check visibility for non-property members here if they weren't checked before
|
// Check visibility for non-property members here if they weren't checked before
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
import net.sergeych.lyng.bridge.bindGlobalVar
|
import net.sergeych.lyng.bridge.bindGlobalVar
|
||||||
import net.sergeych.lyng.bridge.globalBinder
|
import net.sergeych.lyng.bridge.globalBinder
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -49,4 +50,35 @@ class GlobalPropertyCaptureRegressionTest {
|
|||||||
|
|
||||||
assertEquals(2.0, x, "bound extern var should stay live inside function bodies")
|
assertEquals(2.0, x, "bound extern var should stay live inside function bodies")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun externGlobalVarShouldStayLiveWhenScriptRunsInChildScope() = runTest {
|
||||||
|
val base = Script.newScope() as ModuleScope
|
||||||
|
var x = 1.0
|
||||||
|
|
||||||
|
base.eval("extern var X: Real")
|
||||||
|
base.globalBinder().bindGlobalVar(
|
||||||
|
name = "X",
|
||||||
|
get = { x },
|
||||||
|
set = { x = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
val child = base.createChildScope()
|
||||||
|
child.eval(
|
||||||
|
Source(
|
||||||
|
"child-scope-probe",
|
||||||
|
"""
|
||||||
|
fun main() {
|
||||||
|
X = X + 1.0
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val mainRecord = child["main"]
|
||||||
|
check(mainRecord?.type == ObjRecord.Type.Fun)
|
||||||
|
child.eval("main()")
|
||||||
|
|
||||||
|
assertEquals(2.0, x, "bound extern var should stay live in child-scope execution")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user