Fix transitive import re-exports and stabilize captures

This commit is contained in:
Sergey Chernov 2026-04-12 09:16:58 +03:00
parent eefa4c8f1a
commit 47eb2d7d61
8 changed files with 263 additions and 74 deletions

View File

@ -27,6 +27,8 @@ class CliLocalModuleImportRegressionJvmTest {
import lyng.io.net import lyng.io.net
class Alpha { class Alpha {
val headers = Map<String, String>()
fn startListen(port, host) { fn startListen(port, host) {
launch { launch {
println(port, host) println(port, host)

View File

@ -3436,12 +3436,12 @@ class Compiler(
val resolvedRecords = ArrayList<ObjRecord>(captureSlots.size) val resolvedRecords = ArrayList<ObjRecord>(captureSlots.size)
val resolvedNames = ArrayList<String>(captureSlots.size) val resolvedNames = ArrayList<String>(captureSlots.size)
for (capture in captureSlots) { for (capture in captureSlots) {
val rec = closureScope.chainLookupIgnoreClosure( val rec = resolveStableCaptureRecord(
closureScope,
capture.name, capture.name,
followClosure = true, context.currentClassCtx
caller = context.currentClassCtx
) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found") ) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
resolvedRecords.add(rec) resolvedRecords.add(freezeImmutableCaptureRecord(rec))
resolvedNames.add(capture.name) resolvedNames.add(capture.name)
} }
context.captureRecords = resolvedRecords context.captureRecords = resolvedRecords
@ -8766,6 +8766,46 @@ class Compiler(
} }
} }
private fun freezeImmutableCaptureRecord(record: ObjRecord): ObjRecord {
val value = record.value as Obj?
if (record.isMutable || record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty) {
return record
}
return when (value) {
is FrameSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is RecordSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is ScopeSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
null -> record
else -> record.copy()
}
}
private fun isTransientCapturePlaceholder(value: Obj?): Boolean {
return when (value) {
null, ObjVoid -> true
is FrameSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is RecordSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is ScopeSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
else -> false
}
}
private fun resolveStableCaptureRecord(scope: Scope, name: String, caller: ObjClass?): ObjRecord? {
val direct = scope.chainLookupIgnoreClosure(name, followClosure = true, caller = caller) ?: scope.get(name)
if (direct != null && !isTransientCapturePlaceholder(direct.value as Obj?)) {
return direct
}
var parent = scope.parent
while (parent != null) {
val candidate = parent.chainLookupIgnoreClosure(name, followClosure = true, caller = caller) ?: parent.get(name)
if (candidate != null && !isTransientCapturePlaceholder(candidate.value as Obj?)) {
return candidate
}
parent = parent.parent
}
return direct
}
private suspend fun parseFunctionDeclaration( private suspend fun parseFunctionDeclaration(
visibility: Visibility = Visibility.Public, visibility: Visibility = Visibility.Public,
isAbstract: Boolean = false, isAbstract: Boolean = false,
@ -9147,12 +9187,12 @@ class Compiler(
} else if (captureBase != null && captureNames.isNotEmpty()) { } else if (captureBase != null && captureNames.isNotEmpty()) {
val resolvedRecords = ArrayList<ObjRecord>(captureNames.size) val resolvedRecords = ArrayList<ObjRecord>(captureNames.size)
for (name in captureNames) { for (name in captureNames) {
val rec = captureBase.chainLookupIgnoreClosure( val rec = resolveStableCaptureRecord(
captureBase,
name, name,
followClosure = true, context.currentClassCtx
caller = context.currentClassCtx
) ?: captureBase.raiseSymbolNotFound("symbol $name not found") ) ?: captureBase.raiseSymbolNotFound("symbol $name not found")
resolvedRecords.add(rec) resolvedRecords.add(freezeImmutableCaptureRecord(rec))
} }
context.captureRecords = resolvedRecords context.captureRecords = resolvedRecords
context.captureNames = captureNames context.captureNames = captureNames
@ -9193,7 +9233,12 @@ class Compiler(
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { 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 when (val direct = record.value) {
is FrameSlotRef -> direct.read()
is RecordSlotRef -> direct.read(context, localName)
is ScopeSlotRef -> direct.read()
else -> direct
}
} }
frame.frame.setObj(i, value) frame.frame.setObj(i, value)
} }

View File

@ -79,6 +79,13 @@ class FrameSlotRef(
} }
} }
internal fun resolvedCaptureValueOrNull(): Obj? {
return when (frame.getSlotTypeCode(slot)) {
SlotType.INT.code, SlotType.REAL.code, SlotType.BOOL.code -> read()
else -> peekValue()?.let { read() }
}
}
fun write(value: Obj) { fun write(value: Obj) {
when (value) { when (value) {
is ObjInt -> frame.setInt(slot, value.value) is ObjInt -> frame.setInt(slot, value.value)
@ -132,6 +139,16 @@ class ScopeSlotRef(
} }
} }
internal fun resolvedCaptureValueOrNull(): Obj? {
val record = scope.getSlotRecord(slot)
return when (val direct = record.value as Obj?) {
is FrameSlotRef -> direct.resolvedCaptureValueOrNull()
is RecordSlotRef -> direct.resolvedCaptureValueOrNull()
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull()
else -> direct
}
}
fun write(value: Obj) { fun write(value: Obj) {
scope.setSlotValue(slot, value) scope.setSlotValue(slot, value)
} }
@ -198,6 +215,15 @@ class RecordSlotRef(
} }
} }
internal fun resolvedCaptureValueOrNull(): Obj? {
return when (val direct = record.value as Obj?) {
is FrameSlotRef -> direct.resolvedCaptureValueOrNull()
is RecordSlotRef -> direct.resolvedCaptureValueOrNull()
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull()
else -> direct
}
}
fun write(value: Obj) { fun write(value: Obj) {
val direct = record.value val direct = record.value
if (direct is ScopeSlotRef) { if (direct is ScopeSlotRef) {

View File

@ -19,8 +19,6 @@ package net.sergeych.lyng
import net.sergeych.lyng.bytecode.BytecodeFrame import net.sergeych.lyng.bytecode.BytecodeFrame
import net.sergeych.lyng.bytecode.CmdFunction import net.sergeych.lyng.bytecode.CmdFunction
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjExternCallable
import net.sergeych.lyng.obj.ObjRecord import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.pacman.ImportProvider import net.sergeych.lyng.pacman.ImportProvider
@ -34,20 +32,7 @@ class ModuleScope(
pos: Pos = Pos.builtIn, pos: Pos = Pos.builtIn,
override val packageName: String override val packageName: String
) : Scope(importProvider.rootScope, Arguments.EMPTY, pos) { ) : Scope(importProvider.rootScope, Arguments.EMPTY, pos) {
private fun ObjRecord.importedCopy(source: Scope): ObjRecord = copy(importedFrom = source)
private fun ObjRecord.isEquivalentStdlibAlias(other: ObjRecord): Boolean {
val leftOrigin = importedFrom?.packageName
val rightOrigin = other.importedFrom?.packageName
val origins = setOf(leftOrigin, rightOrigin)
val rootStdlibAlias =
"lyng.stdlib" in origins &&
origins.all { it == null || it == "<anonymous package>" || it == "lyng.stdlib" }
if (!rootStdlibAlias) return false
if (value !== other.value) return false
if (receiver !== other.receiver || delegate !== other.delegate) return false
if (memberName != other.memberName || fieldId != other.fieldId || methodId != other.methodId) return false
return value is ObjExternCallable || value is ObjClass
}
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName) constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
@ -100,7 +85,7 @@ class ModuleScope(
override suspend fun importInto(scope: Scope, symbols: Map<String, String>?) { override suspend fun importInto(scope: Scope, symbols: Map<String, String>?) {
val symbolsToImport = symbols?.keys?.toMutableSet() val symbolsToImport = symbols?.keys?.toMutableSet()
for ((symbol, record) in this.objects) { for ((symbol, record) in this.objects) {
if (record.visibility.isPublic) { if (record.visibility.isPublic && record.importedFrom == null) {
val newName = symbols?.let { ss: Map<String, String> -> val newName = symbols?.let { ss: Map<String, String> ->
ss[symbol] ss[symbol]
?.also { symbolsToImport!!.remove(symbol) } ?.also { symbolsToImport!!.remove(symbol) }
@ -110,25 +95,21 @@ class ModuleScope(
if (newName != null) { if (newName != null) {
val existing = scope.objects[newName] val existing = scope.objects[newName]
if (existing != null) { if (existing != null) {
val sameBinding = val sameBinding = existing.importedFrom == this
existing === record ||
existing.importedFrom == record.importedFrom ||
existing.isEquivalentStdlibAlias(record)
if (!sameBinding) if (!sameBinding)
scope.raiseError("symbol ${existing.importedFrom?.packageName}.$newName already exists, redefinition on import is not allowed") scope.raiseError("symbol ${existing.importedFrom?.packageName}.$newName already exists, redefinition on import is not allowed")
// already imported // already imported
} else { } else {
// when importing records, we keep track of its package (not otherwise needed) val imported = record.importedCopy(this)
if (record.importedFrom == null) record.importedFrom = this scope.objects[newName] = imported
scope.objects[newName] = record scope.updateSlotFor(newName, imported)
scope.updateSlotFor(newName, record)
} }
} }
} }
} }
for ((cls, map) in this.extensions) { for ((cls, map) in this.extensions) {
for ((symbol, record) in map) { for ((symbol, record) in map) {
if (record.visibility.isPublic) { if (record.visibility.isPublic && record.importedFrom == null) {
val newName = symbols?.let { ss: Map<String, String> -> val newName = symbols?.let { ss: Map<String, String> ->
ss[symbol] ss[symbol]
?.also { symbolsToImport!!.remove(symbol) } ?.also { symbolsToImport!!.remove(symbol) }
@ -136,7 +117,7 @@ class ModuleScope(
} ?: if (symbols == null) symbol else null } ?: if (symbols == null) symbol else null
if (newName != null) { if (newName != null) {
scope.addExtension(cls, newName, record) scope.addExtension(cls, newName, record.importedCopy(this))
} }
} }
} }

View File

@ -181,37 +181,42 @@ class Script(
} }
} }
private fun importedBindingRecord(record: ObjRecord, source: Scope): ObjRecord =
record.copy(importedFrom = source)
private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) { private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) {
val provider = scope.currentImportProvider val provider = scope.currentImportProvider
val importedModules = LinkedHashSet<ModuleScope>() val importedModules = LinkedHashSet<ModuleScope>()
for (moduleRef in this.importedModules) { for (moduleRef in this.importedModules) {
importedModules.add(provider.prepareImport(moduleRef.pos, moduleRef.name, null)) importedModules.add(provider.prepareImport(moduleRef.pos, moduleRef.name, null))
} }
if (scope is ModuleScope) {
scope.importedModules = importedModules.toList()
}
for (module in importedModules) { for (module in importedModules) {
module.importInto(scope, null) module.importInto(scope, null)
} }
for ((name, binding) in importBindings) { for ((name, binding) in importBindings) {
val record = when (val source = binding.source) { val sourceScope: Scope
val baseRecord = when (val source = binding.source) {
is ImportBindingSource.Module -> { is ImportBindingSource.Module -> {
val module = provider.prepareImport(source.pos, source.name, null) val module = provider.prepareImport(source.pos, source.name, null)
importedModules.add(module) importedModules.add(module)
sourceScope = module
module.objects[binding.symbol]?.takeIf { it.visibility.isPublic } module.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found") ?: scope.raiseSymbolNotFound("symbol ${source.name}.${binding.symbol} not found")
} }
ImportBindingSource.Root -> { ImportBindingSource.Root -> {
sourceScope = provider.rootScope
provider.rootScope.objects[binding.symbol]?.takeIf { it.visibility.isPublic } provider.rootScope.objects[binding.symbol]?.takeIf { it.visibility.isPublic }
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found") ?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
} }
ImportBindingSource.Seed -> { ImportBindingSource.Seed -> {
sourceScope = seedScope
findSeedRecord(seedScope, binding.symbol) findSeedRecord(seedScope, binding.symbol)
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found") ?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
} }
} }
val record = importedBindingRecord(baseRecord, sourceScope)
if (name == "Exception" && record.value !is ObjClass) { if (name == "Exception" && record.value !is ObjClass) {
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false)) scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false, importedFrom = sourceScope))
} else { } else {
scope.updateSlotFor(name, record) scope.updateSlotFor(name, record)
} }
@ -219,12 +224,15 @@ class Script(
for (module in importedModules) { for (module in importedModules) {
for ((cls, map) in module.extensions) { for ((cls, map) in module.extensions) {
for ((symbol, record) in map) { for ((symbol, record) in map) {
if (record.visibility.isPublic) { if (record.visibility.isPublic && record.importedFrom == null) {
scope.addExtension(cls, symbol, record) scope.addExtension(cls, symbol, importedBindingRecord(record, module))
} }
} }
} }
} }
if (scope is ModuleScope) {
scope.importedModules = importedModules.toList()
}
} }
private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? { private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? {

View File

@ -53,7 +53,12 @@ class BytecodeStatement private constructor(
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) { 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 when (val direct = record.value) {
is FrameSlotRef -> direct.read()
is RecordSlotRef -> direct.read(scope, name)
is ScopeSlotRef -> direct.read()
else -> direct
}
} }
frame.frame.setObj(i, value) frame.frame.setObj(i, value)
} }

View File

@ -2560,6 +2560,46 @@ private fun captureNamesForStatement(stmt: Statement?): List<String> {
return ordered.toList() return ordered.toList()
} }
private fun freezeImmutableCaptureRecord(record: ObjRecord): ObjRecord {
val value = record.value as Obj?
if (record.isMutable || record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty) {
return record
}
return when (value) {
is FrameSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is RecordSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is ScopeSlotRef -> value.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
null -> record
else -> record.copy()
}
}
private fun isTransientCapturePlaceholder(value: Obj?): Boolean {
return when (value) {
null, ObjVoid -> true
is FrameSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is RecordSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
is ScopeSlotRef -> value.resolvedCaptureValueOrNull().let { it == null || it === ObjVoid }
else -> false
}
}
private fun resolveStableCaptureRecord(scope: Scope, name: String): ObjRecord? {
val direct = scope.chainLookupIgnoreClosure(name, followClosure = true) ?: scope.get(name)
if (direct != null && !isTransientCapturePlaceholder(direct.value as Obj?)) {
return direct
}
var parent = scope.parent
while (parent != null) {
val candidate = parent.chainLookupIgnoreClosure(name, followClosure = true) ?: parent.get(name)
if (candidate != null && !isTransientCapturePlaceholder(candidate.value as Obj?)) {
return candidate
}
parent = parent.parent
}
return direct
}
private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<String>): List<ObjRecord>? { private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<String>): List<ObjRecord>? {
if (captureNames.isEmpty()) return null if (captureNames.isEmpty()) return null
val records = ArrayList<ObjRecord>(captureNames.size) val records = ArrayList<ObjRecord>(captureNames.size)
@ -2575,12 +2615,16 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
} }
} else { } else {
val raw = frame.frame.getRawObj(localIndex) val raw = frame.frame.getRawObj(localIndex)
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name) val captureRecord = if (isTransientCapturePlaceholder(raw)) {
if (scoped != null && scoped.value !== ObjUnset) { resolveStableCaptureRecord(frame.scope.parent ?: frame.scope, name)
records += scoped } else {
resolveStableCaptureRecord(frame.scope, name)
}
if (captureRecord != null) {
records += freezeImmutableCaptureRecord(captureRecord)
continue continue
} }
records += ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable) records += freezeImmutableCaptureRecord(ObjRecord(FrameSlotRef(frame.frame, localIndex), isMutable))
} }
continue continue
} }
@ -2588,7 +2632,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
if (scopeSlot >= 0) { if (scopeSlot >= 0) {
val target = frame.scopeTarget(scopeSlot) val target = frame.scopeTarget(scopeSlot)
val index = frame.fn.scopeSlotIndices[scopeSlot] val index = frame.fn.scopeSlotIndices[scopeSlot]
records += target.getSlotRecord(index) records += freezeImmutableCaptureRecord(target.getSlotRecord(index))
continue continue
} }
val scopeCaptures = frame.scope.captureRecords val scopeCaptures = frame.scope.captureRecords
@ -2605,7 +2649,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 += scoped records += freezeImmutableCaptureRecord(scoped)
continue continue
} }
frame.ensureScope().raiseSymbolNotFound("capture $name not found") frame.ensureScope().raiseSymbolNotFound("capture $name not found")
@ -3377,7 +3421,12 @@ class CmdGetMemberSlot(
resolved.declaringClass resolved.declaringClass
) )
} else { } else {
resolved.value when (val value = resolved.value) {
is FrameSlotRef -> value.read()
is RecordSlotRef -> value.read(frame.ensureScope(), name)
is ScopeSlotRef -> value.read()
else -> value
}
} }
} }
if (receiver is ObjQualifiedView) { if (receiver is ObjQualifiedView) {
@ -3785,13 +3834,14 @@ class BytecodeLambdaCallable(
) : Statement(), BytecodeCallable { ) : Statement(), BytecodeCallable {
private fun freezeRecord(record: ObjRecord): ObjRecord { private fun freezeRecord(record: ObjRecord): ObjRecord {
if (record.isMutable) return record if (record.isMutable) return record
val frozenValue = when (val raw = record.value) { val raw = record.value as Obj?
is net.sergeych.lyng.FrameSlotRef -> raw.read() return when (raw) {
is net.sergeych.lyng.RecordSlotRef -> raw.read() is net.sergeych.lyng.FrameSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
is net.sergeych.lyng.ScopeSlotRef -> raw.read() is net.sergeych.lyng.RecordSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
else -> raw is net.sergeych.lyng.ScopeSlotRef -> raw.resolvedCaptureValueOrNull()?.let { record.copy(value = it) } ?: record
null -> record
else -> record.copy()
} }
return record.copy(value = frozenValue)
} }
private fun resolveCaptureRecords(base: Scope): List<ObjRecord>? { private fun resolveCaptureRecords(base: Scope): List<ObjRecord>? {
@ -3924,7 +3974,12 @@ class BytecodeLambdaCallable(
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { 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 when (val direct = record.value) {
is FrameSlotRef -> direct.read()
is RecordSlotRef -> direct.read(context, name)
is ScopeSlotRef -> direct.read()
else -> direct
}
} }
frame.frame.setObj(i, value) frame.frame.setObj(i, value)
} }
@ -4091,7 +4146,31 @@ class CmdFrame(
frame.setObj(localIndex, record.delegate ?: ObjNull) frame.setObj(localIndex, record.delegate ?: ObjNull)
} else { } else {
val value = record.value val value = record.value
if (value is FrameSlotRef) { if (!record.isMutable && value is FrameSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
if (value.refersTo(frame, localIndex)) continue
frame.setObj(localIndex, value.read())
} else {
frame.setObj(localIndex, value)
}
} else if (!record.isMutable && value is RecordSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(localIndex, value.read())
} else {
frame.setObj(localIndex, value)
}
} else if (!record.isMutable && value is ScopeSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(localIndex, value.read())
} else {
frame.setObj(localIndex, value)
}
} else if (!record.isMutable) {
frame.setObj(localIndex, value)
} else if (value is FrameSlotRef) {
if (value.refersTo(frame, localIndex)) continue if (value.refersTo(frame, localIndex)) continue
frame.setObj(localIndex, value) frame.setObj(localIndex, value)
} else { } else {
@ -4106,7 +4185,30 @@ class CmdFrame(
frame.setObj(idx, record.delegate ?: ObjNull) frame.setObj(idx, record.delegate ?: ObjNull)
} else { } else {
val value = record.value val value = record.value
if (value is FrameSlotRef) { if (!record.isMutable && value is FrameSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(idx, value.read())
} else {
frame.setObj(idx, value)
}
} else if (!record.isMutable && value is RecordSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(idx, value.read())
} else {
frame.setObj(idx, value)
}
} else if (!record.isMutable && value is ScopeSlotRef) {
val resolved = value.peekValue()
if (resolved != null) {
frame.setObj(idx, value.read())
} else {
frame.setObj(idx, value)
}
} else if (!record.isMutable) {
frame.setObj(idx, value)
} else if (value is FrameSlotRef) {
frame.setObj(idx, value) frame.setObj(idx, value)
} else { } else {
frame.setObj(idx, RecordSlotRef(record)) frame.setObj(idx, RecordSlotRef(record))
@ -5085,6 +5187,7 @@ class CmdFrame(
when (obj) { when (obj) {
is FrameSlotRef -> obj.read() is FrameSlotRef -> obj.read()
is RecordSlotRef -> obj.read(scope, localName) is RecordSlotRef -> obj.read(scope, localName)
is ScopeSlotRef -> obj.read()
is ObjProperty -> resolvePropertyLikeLocal(localName, obj) is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
ObjUnset -> resolveUnsetLocal(localName) ObjUnset -> resolveUnsetLocal(localName)
else -> obj else -> obj
@ -5096,6 +5199,7 @@ class CmdFrame(
when (obj) { when (obj) {
is FrameSlotRef -> obj.read() is FrameSlotRef -> obj.read()
is RecordSlotRef -> obj.read(scope, localName) is RecordSlotRef -> obj.read(scope, localName)
is ScopeSlotRef -> obj.read()
is ObjProperty -> resolvePropertyLikeLocal(localName, obj) is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
ObjUnset -> resolveUnsetLocal(localName) ObjUnset -> resolveUnsetLocal(localName)
else -> obj else -> obj
@ -5120,7 +5224,24 @@ class CmdFrame(
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
return scope.resolve(record, localName) return scope.resolve(record, localName)
} }
return record.value return when (val value = record.value) {
is FrameSlotRef -> value.read()
is RecordSlotRef -> value.read(scope, localName)
is ScopeSlotRef -> value.read()
else -> value
}
}
private suspend fun readResolvedScopeRecord(target: Scope, name: String, record: ObjRecord): Obj {
val value = record.value
return when {
record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || value is ObjProperty ->
target.resolve(record, name)
value is FrameSlotRef -> value.read()
value is RecordSlotRef -> value.read(target, name)
value is ScopeSlotRef -> value.read()
else -> value
}
} }
private suspend fun getScopeSlotValue(slot: Int): Obj { private suspend fun getScopeSlotValue(slot: Int): Obj {
@ -5135,10 +5256,7 @@ class CmdFrame(
if (name != null && record.memberName != null && record.memberName != name) { if (name != null && record.memberName != null && record.memberName != name) {
val resolved = target.get(name) val resolved = target.get(name)
if (resolved != null) { if (resolved != null) {
val resolvedValue = resolved.value val resolvedValue = readResolvedScopeRecord(target, name, resolved)
if (resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolvedValue is ObjProperty) {
return target.resolve(resolved, name)
}
if (resolvedValue !== ObjUnset) { if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} }
@ -5157,12 +5275,13 @@ class CmdFrame(
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, record) failMissingPreparedModuleBinding(slot, name, hadNamedBinding, record)
return record.value return record.value
} }
if (resolved.value !== ObjUnset) { val resolvedValue = readResolvedScopeRecord(target, name, resolved)
if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} else { } else {
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, resolved) failMissingPreparedModuleBinding(slot, name, hadNamedBinding, resolved)
} }
return resolved.value return resolvedValue
} }
private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj { private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
@ -5178,10 +5297,7 @@ class CmdFrame(
if (name != null && record.memberName != null && record.memberName != name) { if (name != null && record.memberName != null && record.memberName != name) {
val resolved = target.get(name) val resolved = target.get(name)
if (resolved != null) { if (resolved != null) {
val resolvedValue = resolved.value val resolvedValue = readResolvedScopeRecord(target, name, resolved)
if (resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolvedValue is ObjProperty) {
return target.resolve(resolved, name)
}
if (resolvedValue !== ObjUnset) { if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} }
@ -5200,12 +5316,13 @@ class CmdFrame(
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, record) failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, record)
return record.value return record.value
} }
if (resolved.value !== ObjUnset) { val resolvedValue = readResolvedScopeRecord(target, name, resolved)
if (resolvedValue !== ObjUnset) {
target.updateSlotFor(name, resolved) target.updateSlotFor(name, resolved)
} else { } else {
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, resolved) failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, resolved)
} }
return resolved.value return resolvedValue
} }
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) { private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {

View File

@ -34,7 +34,12 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) { 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 when (val direct = record.value) {
is net.sergeych.lyng.FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
is net.sergeych.lyng.RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
is net.sergeych.lyng.ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: direct
else -> direct
}
} }
if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, i)) { if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, i)) {
continue continue