Compare commits
No commits in common. "4269310beb5ec476d35d6325d3de4bce1108c971" and "24937c7cf5f6d2d6fe8403e6de0b4efa474f62d7" have entirely different histories.
4269310beb
...
24937c7cf5
@ -184,7 +184,6 @@ class Compiler(
|
||||
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
||||
private val externCallableNames: MutableSet<String> = mutableSetOf()
|
||||
private val externBindingNames: MutableSet<String> = mutableSetOf()
|
||||
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
|
||||
private var seedingSlotPlan: Boolean = false
|
||||
|
||||
@ -193,7 +192,6 @@ class Compiler(
|
||||
if (plan.slots.isEmpty()) return emptyMap()
|
||||
val result = LinkedHashMap<String, ForcedLocalSlotInfo>(plan.slots.size)
|
||||
for ((name, entry) in plan.slots) {
|
||||
if (externBindingNames.contains(name)) continue
|
||||
result[name] = ForcedLocalSlotInfo(
|
||||
index = entry.index,
|
||||
isMutable = entry.isMutable,
|
||||
@ -583,7 +581,7 @@ class Compiler(
|
||||
val baseNames = cls.directParents.map { it.className }
|
||||
val nextFieldId = (fieldIds.values.maxOrNull() ?: -1) + 1
|
||||
val nextMethodId = (methodIds.values.maxOrNull() ?: -1) + 1
|
||||
return CompileClassInfo(name, cls.logicalPackageName, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames)
|
||||
return CompileClassInfo(name, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames)
|
||||
}
|
||||
|
||||
private data class BaseMemberIds(
|
||||
@ -887,7 +885,6 @@ class Compiler(
|
||||
name = declaredName,
|
||||
isMutable = false,
|
||||
visibility = Visibility.Public,
|
||||
actualExtern = false,
|
||||
initializer = initStmt,
|
||||
isTransient = false,
|
||||
typeDecl = null,
|
||||
@ -1553,7 +1550,6 @@ class Compiler(
|
||||
|
||||
private data class CompileClassInfo(
|
||||
val name: String,
|
||||
val packageName: String?,
|
||||
val fieldIds: Map<String, Int>,
|
||||
val methodIds: Map<String, Int>,
|
||||
val nextFieldId: Int,
|
||||
@ -1755,7 +1751,6 @@ class Compiler(
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
) as BytecodeStatement
|
||||
unwrapped to bytecodeStmt.bytecodeFunction()
|
||||
@ -1962,9 +1957,6 @@ class Compiler(
|
||||
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
||||
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
||||
val modulePlan = moduleSlotPlan()
|
||||
if (modulePlan != null && slotLoc.scopeId == modulePlan.id && externBindingNames.contains(name)) {
|
||||
return null
|
||||
}
|
||||
if (useScopeSlots && modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
||||
return null
|
||||
}
|
||||
@ -2083,7 +2075,6 @@ class Compiler(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
@ -2114,7 +2105,6 @@ class Compiler(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
@ -2170,7 +2160,6 @@ class Compiler(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
@ -2354,7 +2343,6 @@ class Compiler(
|
||||
stmt.name,
|
||||
stmt.isMutable,
|
||||
stmt.visibility,
|
||||
stmt.actualExtern,
|
||||
init,
|
||||
stmt.isTransient,
|
||||
stmt.typeDecl,
|
||||
@ -6458,7 +6446,7 @@ class Compiler(
|
||||
WhenStatement(value, cases, elseCase, whenPos)
|
||||
} else {
|
||||
// when { cond -> ... }
|
||||
throw ScriptError(t.pos, "when without subject is not implemented")
|
||||
TODO("when without object is not yet implemented")
|
||||
}
|
||||
return wrapBytecode(stmt)
|
||||
}
|
||||
@ -6711,7 +6699,6 @@ class Compiler(
|
||||
)
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
packageName = packageName,
|
||||
fieldIds = fieldIds,
|
||||
methodIds = methodIds,
|
||||
nextFieldId = fieldIds.size,
|
||||
@ -6834,7 +6821,6 @@ class Compiler(
|
||||
classCtx?.let { ctx ->
|
||||
compileClassInfos[className] = CompileClassInfo(
|
||||
name = className,
|
||||
packageName = packageName,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
@ -6876,7 +6862,6 @@ class Compiler(
|
||||
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
||||
compileClassInfos[className] = CompileClassInfo(
|
||||
name = className,
|
||||
packageName = packageName,
|
||||
fieldIds = baseIds.fieldIds,
|
||||
methodIds = baseIds.methodIds,
|
||||
nextFieldId = baseIds.nextFieldId,
|
||||
@ -7125,7 +7110,6 @@ class Compiler(
|
||||
}
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
packageName = packageName,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
@ -7141,7 +7125,6 @@ class Compiler(
|
||||
classCtx?.let { ctx ->
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
packageName = packageName,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
@ -7228,7 +7211,6 @@ class Compiler(
|
||||
}
|
||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||
name = qualifiedName,
|
||||
packageName = packageName,
|
||||
fieldIds = ctx.memberFieldIds.toMap(),
|
||||
methodIds = ctx.memberMethodIds.toMap(),
|
||||
nextFieldId = ctx.nextFieldId,
|
||||
@ -8408,7 +8390,7 @@ class Compiler(
|
||||
is ImplicitThisMethodCallRef -> {
|
||||
when (directRef.methodName()) {
|
||||
"iterator" -> ObjIterator
|
||||
"lazy" -> resolveClassByName("lazy") ?: inferMethodCallReturnClass(directRef.methodName())
|
||||
"lazy" -> ObjLazyDelegate.type
|
||||
else -> inferMethodCallReturnClass(directRef.methodName())
|
||||
}
|
||||
}
|
||||
@ -8427,7 +8409,7 @@ class Compiler(
|
||||
when {
|
||||
target is LocalSlotRef -> {
|
||||
when (target.name) {
|
||||
"lazy" -> resolveClassByName("lazy")
|
||||
"lazy" -> ObjLazyDelegate.type
|
||||
"iterator" -> ObjIterator
|
||||
"flow" -> ObjFlow.type
|
||||
"launch" -> ObjDeferred.type
|
||||
@ -8438,7 +8420,7 @@ class Compiler(
|
||||
}
|
||||
target is LocalVarRef -> {
|
||||
when (target.name) {
|
||||
"lazy" -> resolveClassByName("lazy")
|
||||
"lazy" -> ObjLazyDelegate.type
|
||||
"iterator" -> ObjIterator
|
||||
"flow" -> ObjFlow.type
|
||||
"launch" -> ObjDeferred.type
|
||||
@ -8477,9 +8459,7 @@ class Compiler(
|
||||
?: unwrapDirectRef(initializer)?.let { inferObjClassFromRef(it) }
|
||||
?: throw ScriptError(initializer.pos, "Delegate type must be known at compile time")
|
||||
if (initClass !== delegateClass &&
|
||||
initClass.logicalName != delegateClass.logicalName &&
|
||||
!initClass.allParentsSet.contains(delegateClass) &&
|
||||
!initClass.allParentsSet.any { it.logicalName == delegateClass.logicalName } &&
|
||||
!initClass.allImplementingNames.contains(delegateClass.className)
|
||||
) {
|
||||
throw ScriptError(
|
||||
@ -8569,9 +8549,7 @@ class Compiler(
|
||||
if (closedParent != null) {
|
||||
throw ScriptError(Pos.builtIn, "can't inherit from closed class ${closedParent.className}")
|
||||
}
|
||||
ObjInstanceClass(info.name, *parents.toTypedArray()).apply {
|
||||
logicalPackageNameOverride = info.packageName
|
||||
}
|
||||
ObjInstanceClass(info.name, *parents.toTypedArray())
|
||||
}
|
||||
if (stub is ObjInstanceClass) {
|
||||
for ((fieldName, fieldId) in info.fieldIds) {
|
||||
@ -8915,7 +8893,7 @@ class Compiler(
|
||||
val effectiveEqToken = if (isProperty) null else eqToken
|
||||
|
||||
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
||||
if (!isStatic && declaringClassNameCaptured == null && !actualExtern) declareLocalName(name, isMutable)
|
||||
if (!isStatic && declaringClassNameCaptured == null) declareLocalName(name, isMutable)
|
||||
val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) {
|
||||
SymbolKind.MEMBER
|
||||
} else {
|
||||
@ -8924,8 +8902,6 @@ class Compiler(
|
||||
resolutionSink?.declareSymbol(name, declKind, isMutable, nameStartPos, isOverride = isOverride)
|
||||
if (declKind == SymbolKind.MEMBER && extTypeName == null) {
|
||||
(codeContexts.lastOrNull() as? CodeContext.ClassBody)?.declaredMembers?.add(name)
|
||||
} else if (actualExtern) {
|
||||
externBindingNames.add(name)
|
||||
}
|
||||
|
||||
val isDelegate = if (isAbstract || actualExtern) {
|
||||
@ -8991,11 +8967,7 @@ class Compiler(
|
||||
|
||||
if (isDelegate && initialExpression != null) {
|
||||
ensureDelegateType(initialExpression)
|
||||
val lazyClass = resolveClassByName("lazy")
|
||||
if (isMutable &&
|
||||
lazyClass != null &&
|
||||
resolveInitializerObjClass(initialExpression)?.logicalName == lazyClass.logicalName
|
||||
) {
|
||||
if (isMutable && resolveInitializerObjClass(initialExpression) == ObjLazyDelegate.type) {
|
||||
throw ScriptError(initialExpression.pos, "lazy delegate is read-only")
|
||||
}
|
||||
}
|
||||
@ -9087,7 +9059,6 @@ class Compiler(
|
||||
name,
|
||||
isMutable,
|
||||
visibility,
|
||||
actualExtern,
|
||||
initialExpression,
|
||||
isTransient,
|
||||
declaredType,
|
||||
|
||||
@ -540,6 +540,10 @@ class Script(
|
||||
cachedValue
|
||||
}
|
||||
}
|
||||
addFn("lazy") {
|
||||
val builder = requireOnlyArg<Obj>()
|
||||
ObjLazyDelegate(builder, requireScope())
|
||||
}
|
||||
addVoidFn("delay") {
|
||||
val a = args.firstAndOnly()
|
||||
when (a) {
|
||||
|
||||
@ -24,7 +24,6 @@ class VarDeclStatement(
|
||||
val name: String,
|
||||
val isMutable: Boolean,
|
||||
val visibility: Visibility,
|
||||
val actualExtern: Boolean,
|
||||
val initializer: Statement?,
|
||||
val isTransient: Boolean,
|
||||
val typeDecl: TypeDecl?,
|
||||
|
||||
@ -42,7 +42,6 @@ class BytecodeCompiler(
|
||||
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
private val externCallableNames: Set<String> = emptySet(),
|
||||
private val externBindingNames: Set<String> = emptySet(),
|
||||
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||
) {
|
||||
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
|
||||
@ -1089,6 +1088,7 @@ class BytecodeCompiler(
|
||||
|
||||
private fun isDelegateClass(receiverClass: ObjClass): Boolean =
|
||||
receiverClass.className == "Delegate" ||
|
||||
receiverClass.className == "LazyDelegate" ||
|
||||
receiverClass.implementingNames.contains("Delegate")
|
||||
|
||||
private fun operatorMemberName(op: BinOp): String? = when (op) {
|
||||
@ -4508,7 +4508,7 @@ class BytecodeCompiler(
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
}
|
||||
if (useScopeSlots && isPreparedScopeName(name)) {
|
||||
if (useScopeSlots && allowedScopeNames?.contains(name) == true) {
|
||||
scopeSlotIndexByName[name]?.let { slot ->
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
@ -8073,7 +8073,7 @@ class BytecodeCompiler(
|
||||
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||
}
|
||||
}
|
||||
if (allowLocalSlots && slotIndex != null && (stmt.actualExtern || !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = false))) {
|
||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
declaredLocalKeys.add(key)
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
@ -8100,7 +8100,7 @@ class BytecodeCompiler(
|
||||
val scopeId = stmt.spec.scopeId ?: 0
|
||||
if (slotIndex != null) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId, stmt.spec.name, isDelegated = false)) {
|
||||
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId)) {
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
||||
}
|
||||
@ -8117,7 +8117,7 @@ class BytecodeCompiler(
|
||||
is DelegatedVarDeclStatement -> {
|
||||
val slotIndex = stmt.slotIndex
|
||||
val scopeId = stmt.scopeId ?: 0
|
||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = true)) {
|
||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
declaredLocalKeys.add(key)
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
@ -8283,26 +8283,15 @@ class BytecodeCompiler(
|
||||
|
||||
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
||||
if (moduleScopeId != null && scopeId != moduleScopeId) return false
|
||||
return isPreparedScopeName(name)
|
||||
val scopeNames = allowedScopeNames ?: scopeSlotNameSet
|
||||
if (scopeNames == null || name == null) return false
|
||||
return scopeNames.contains(name)
|
||||
}
|
||||
|
||||
private fun shouldUseScopeSlotFor(scopeId: Int): Boolean {
|
||||
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) {
|
||||
if (stmt is BytecodeStatement) {
|
||||
collectLoopVarNames(stmt.original)
|
||||
@ -8411,9 +8400,8 @@ class BytecodeCompiler(
|
||||
captureSlotKeys.add(key)
|
||||
return
|
||||
}
|
||||
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, ref.name, ref.isDelegated)
|
||||
val isModuleSlot = if (ref.isDelegated) false else isModuleSlot(scopeId, ref.name)
|
||||
if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
|
||||
if (allowLocalSlots && !isModuleSlot) {
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
|
||||
}
|
||||
@ -8460,9 +8448,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
captureSlotKeys.add(key)
|
||||
} else {
|
||||
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, target.name, target.isDelegated)
|
||||
val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name)
|
||||
if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
|
||||
if (allowLocalSlots && !isModuleSlot) {
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
|
||||
}
|
||||
|
||||
@ -87,7 +87,6 @@ class BytecodeStatement private constructor(
|
||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
externCallableNames: Set<String> = emptySet(),
|
||||
externBindingNames: Set<String> = emptySet(),
|
||||
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
||||
): Statement {
|
||||
@ -123,7 +122,6 @@ class BytecodeStatement private constructor(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
val compiled = compiler.compileStatement(nameHint, statement)
|
||||
@ -238,7 +236,6 @@ class BytecodeStatement private constructor(
|
||||
stmt.name,
|
||||
stmt.isMutable,
|
||||
stmt.visibility,
|
||||
stmt.actualExtern,
|
||||
stmt.initializer?.let { unwrapDeep(it) },
|
||||
stmt.isTransient,
|
||||
stmt.typeDecl,
|
||||
|
||||
@ -2392,13 +2392,8 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||
?: error("DECL_LOCAL expects LocalDecl at $constId")
|
||||
if (slot < frame.fn.scopeSlotCount) {
|
||||
val target = frame.scopeTarget(slot)
|
||||
val index = frame.ensureScopeSlot(target, slot)
|
||||
val raw = target.getSlotRecord(index).value
|
||||
val value = when (raw) {
|
||||
is FrameSlotRef -> raw.read()
|
||||
is RecordSlotRef -> raw.read()
|
||||
else -> raw
|
||||
}.byValueCopy()
|
||||
frame.ensureScopeSlot(target, slot)
|
||||
val value = frame.slotToObj(slot).byValueCopy()
|
||||
target.updateSlotFor(
|
||||
decl.name,
|
||||
ObjRecord(
|
||||
@ -3935,7 +3930,7 @@ class CmdFrame(
|
||||
} else {
|
||||
val raw = frame.getRawObj(localIndex)
|
||||
if (raw == null && name != null) {
|
||||
val record = findNamedExistingRecord(scope, name)
|
||||
val record = scope.get(name)
|
||||
if (record != null) {
|
||||
val value = record.value
|
||||
return@mapIndexed when (value) {
|
||||
@ -3944,12 +3939,6 @@ class CmdFrame(
|
||||
else -> ObjRecord(value, isMutable)
|
||||
}
|
||||
}
|
||||
if (hasNamedScopeBinding(scope, name)) {
|
||||
throw ScriptError(
|
||||
ensureScope().pos,
|
||||
"captured binding '$name' is not available in the execution scope; prepare the script imports/module bindings explicitly"
|
||||
)
|
||||
}
|
||||
}
|
||||
when (raw) {
|
||||
is FrameSlotRef -> ObjRecord(raw, isMutable)
|
||||
@ -3963,27 +3952,22 @@ class CmdFrame(
|
||||
val target = moduleScope
|
||||
val name = captureNames?.getOrNull(index)
|
||||
if (name != null) {
|
||||
findNamedExistingRecord(target, name)?.let { return@mapIndexed it }
|
||||
target.tryGetLocalRecord(target, name, target.currentClassCtx)?.let { return@mapIndexed it }
|
||||
target.getSlotIndexOf(name)?.let { return@mapIndexed target.getSlotRecord(it) }
|
||||
target.get(name)?.let { return@mapIndexed it }
|
||||
// Fallback to current scope in case the module scope isn't in the parent chain
|
||||
// or doesn't carry the imported symbol yet.
|
||||
findNamedExistingRecord(scope, name)?.let { return@mapIndexed it }
|
||||
scope.tryGetLocalRecord(scope, name, scope.currentClassCtx)?.let { return@mapIndexed it }
|
||||
scope.get(name)?.let { return@mapIndexed it }
|
||||
}
|
||||
if (slotId < target.slotCount) {
|
||||
val existing = target.getSlotRecord(slotId)
|
||||
if (name == null || existing.value !== ObjUnset || hasResolvedNamedScopeBinding(target, name)) {
|
||||
return@mapIndexed existing
|
||||
}
|
||||
return@mapIndexed target.getSlotRecord(slotId)
|
||||
}
|
||||
if (name != null) {
|
||||
throw ScriptError(
|
||||
ensureScope().pos,
|
||||
"module capture '$name' is not available in the execution scope; prepare the script imports/module bindings explicitly"
|
||||
)
|
||||
target.applySlotPlan(mapOf(name to slotId))
|
||||
return@mapIndexed target.getSlotRecord(slotId)
|
||||
}
|
||||
throw ScriptError(
|
||||
ensureScope().pos,
|
||||
"missing module capture slot $slotId"
|
||||
)
|
||||
error("Missing module capture slot $slotId")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4562,7 +4546,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot)
|
||||
}
|
||||
|
||||
suspend fun setAddrObj(addrSlot: Int, value: Obj) {
|
||||
fun setAddrObj(addrSlot: Int, value: Obj) {
|
||||
setScopeSlotValueAtAddr(addrSlot, value)
|
||||
}
|
||||
|
||||
@ -4570,7 +4554,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot).toLong()
|
||||
}
|
||||
|
||||
suspend fun setAddrInt(addrSlot: Int, value: Long) {
|
||||
fun setAddrInt(addrSlot: Int, value: Long) {
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
|
||||
}
|
||||
|
||||
@ -4578,7 +4562,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot).toDouble()
|
||||
}
|
||||
|
||||
suspend fun setAddrReal(addrSlot: Int, value: Double) {
|
||||
fun setAddrReal(addrSlot: Int, value: Double) {
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
|
||||
}
|
||||
|
||||
@ -4586,7 +4570,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot).toBool()
|
||||
}
|
||||
|
||||
suspend fun setAddrBool(addrSlot: Int, value: Boolean) {
|
||||
fun setAddrBool(addrSlot: Int, value: Boolean) {
|
||||
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
|
||||
}
|
||||
|
||||
@ -4792,13 +4776,12 @@ class CmdFrame(
|
||||
|
||||
private suspend fun getScopeSlotValue(slot: Int): Obj {
|
||||
val target = scopeTarget(slot)
|
||||
val name = fn.scopeSlotNames[slot]
|
||||
val hadNamedBinding = name != null && hasResolvedNamedScopeBinding(target, name)
|
||||
val index = ensureScopeSlot(target, slot)
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
if (direct is RecordSlotRef) return direct.read()
|
||||
val name = fn.scopeSlotNames[slot]
|
||||
if (name != null && record.memberName != null && record.memberName != name) {
|
||||
val resolved = target.get(name)
|
||||
if (resolved != null) {
|
||||
@ -4819,15 +4802,9 @@ class CmdFrame(
|
||||
return direct
|
||||
}
|
||||
if (name == null) return record.value
|
||||
val resolved = target.get(name)
|
||||
if (resolved == null) {
|
||||
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, record)
|
||||
return record.value
|
||||
}
|
||||
val resolved = target.get(name) ?: return record.value
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
} else {
|
||||
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, resolved)
|
||||
}
|
||||
return resolved.value
|
||||
}
|
||||
@ -4835,13 +4812,12 @@ class CmdFrame(
|
||||
private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
|
||||
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
||||
val index = addrIndices[addrSlot]
|
||||
val slotId = addrScopeSlots[addrSlot]
|
||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||
val hadNamedBinding = name != null && hasResolvedNamedScopeBinding(target, name)
|
||||
val record = target.getSlotRecord(index)
|
||||
val direct = record.value
|
||||
if (direct is FrameSlotRef) return direct.read()
|
||||
if (direct is RecordSlotRef) return direct.read()
|
||||
val slotId = addrScopeSlots[addrSlot]
|
||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||
if (name != null && record.memberName != null && record.memberName != name) {
|
||||
val resolved = target.get(name)
|
||||
if (resolved != null) {
|
||||
@ -4862,29 +4838,16 @@ class CmdFrame(
|
||||
return direct
|
||||
}
|
||||
if (name == null) return record.value
|
||||
val resolved = target.get(name)
|
||||
if (resolved == null) {
|
||||
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, record)
|
||||
return record.value
|
||||
}
|
||||
val resolved = target.get(name) ?: return record.value
|
||||
if (resolved.value !== ObjUnset) {
|
||||
target.updateSlotFor(name, resolved)
|
||||
} else {
|
||||
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, resolved)
|
||||
}
|
||||
return resolved.value
|
||||
}
|
||||
|
||||
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||
private fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
||||
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)
|
||||
}
|
||||
|
||||
@ -4912,41 +4875,6 @@ class CmdFrame(
|
||||
return index
|
||||
}
|
||||
|
||||
private fun hasNamedScopeBinding(target: Scope, name: String): Boolean {
|
||||
if (target.tryGetLocalRecord(target, name, target.currentClassCtx) != null) return true
|
||||
if (target.getSlotIndexOf(name) != null) return true
|
||||
if (target.get(name) != null) return true
|
||||
return false
|
||||
}
|
||||
|
||||
private fun hasResolvedNamedScopeBinding(target: Scope, name: String): Boolean =
|
||||
findNamedExistingRecord(target, name) != null
|
||||
|
||||
private fun findNamedExistingRecord(target: Scope, name: String): ObjRecord? {
|
||||
target.tryGetLocalRecord(target, name, target.currentClassCtx)?.let { return it }
|
||||
target.get(name)?.let { record ->
|
||||
if (record.value !== ObjUnset || record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||
return record
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun failMissingPreparedModuleBinding(
|
||||
slot: Int,
|
||||
name: String,
|
||||
hadNamedBinding: Boolean,
|
||||
record: ObjRecord
|
||||
) {
|
||||
if (hadNamedBinding) return
|
||||
if (record.value !== ObjUnset) return
|
||||
if (fn.scopeSlotIsModule.getOrNull(slot) != true) return
|
||||
throw ScriptError(
|
||||
ensureScope().pos,
|
||||
"module binding '$name' is not available in the execution scope; prepare the script imports/module bindings explicitly"
|
||||
)
|
||||
}
|
||||
|
||||
private fun ensureLocalMutable(localIndex: Int) {
|
||||
val name = fn.localSlotNames.getOrNull(localIndex) ?: return
|
||||
val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: true
|
||||
|
||||
@ -106,20 +106,10 @@ open class ObjClass(
|
||||
vararg parents: ObjClass,
|
||||
) : Obj() {
|
||||
|
||||
private fun declaringModulePackageName(scope: Scope?): String? {
|
||||
var current = scope
|
||||
while (current != null) {
|
||||
if (current is ModuleScope) return current.packageName
|
||||
current = current.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var isAnonymous: Boolean = false
|
||||
|
||||
var isAbstract: Boolean = false
|
||||
var isClosed: Boolean = false
|
||||
var logicalPackageNameOverride: String? = null
|
||||
|
||||
// Stable identity and simple structural version for PICs
|
||||
val classId: Long = ClassIdGen.nextId()
|
||||
@ -179,16 +169,6 @@ open class ObjClass(
|
||||
*/
|
||||
var classScope: Scope? = null
|
||||
|
||||
/**
|
||||
* Stable logical identity for class matching across separately instantiated modules.
|
||||
* Uses declaring module package plus class name when available.
|
||||
*/
|
||||
val logicalPackageName: String?
|
||||
get() = logicalPackageNameOverride ?: declaringModulePackageName(classScope)
|
||||
|
||||
val logicalName: String
|
||||
get() = logicalPackageName?.let { "$it::$className" } ?: className
|
||||
|
||||
/** Direct parents in declaration order (kept deterministic). */
|
||||
val directParents: List<ObjClass> = parents.toList()
|
||||
|
||||
|
||||
@ -129,7 +129,6 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
|
||||
}
|
||||
|
||||
val type = object : ObjClass("Delegate") {}.apply {
|
||||
logicalPackageNameOverride = "lyng.stdlib"
|
||||
addFn("getValue") { raiseError("Delegate.getValue is not implemented") }
|
||||
addFn("setValue") { raiseError("Delegate.setValue is not implemented") }
|
||||
addFn("invoke") { raiseError("Delegate.invoke is not implemented") }
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.Pos
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
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) {
|
||||
"getValue" -> {
|
||||
if (!calculated) {
|
||||
val callScope = capturedScope.createChildScope(capturedScope.pos, args = Arguments.EMPTY)
|
||||
cachedValue = if (builder is Statement) {
|
||||
executeBytecodeWithSeed(callScope, builder, "lazy delegate")
|
||||
} else {
|
||||
builder.callOn(callScope)
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.Compiler
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.ScriptError
|
||||
import net.sergeych.lyng.Source
|
||||
import net.sergeych.lyng.asFacade
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
import net.sergeych.lyng.obj.toInt
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContains
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class CompilerVmReviewRegressionTest {
|
||||
|
||||
@Test
|
||||
fun missingModuleCaptureFailsFastInsteadOfBecomingUnset() = runTest {
|
||||
val manager = ImportManager()
|
||||
manager.addTextPackages(
|
||||
"""
|
||||
package foo
|
||||
|
||||
val answer = 42
|
||||
""".trimIndent()
|
||||
)
|
||||
val script = Compiler.compile(
|
||||
Source(
|
||||
"<missing-module-capture>",
|
||||
"""
|
||||
import foo
|
||||
fun make() = { answer }
|
||||
make()
|
||||
""".trimIndent()
|
||||
),
|
||||
manager
|
||||
)
|
||||
|
||||
val prepared = manager.newModule()
|
||||
script.importInto(prepared)
|
||||
val preparedLambda = script.execute(prepared)
|
||||
assertEquals(42, prepared.asFacade().call(preparedLambda).toInt())
|
||||
|
||||
val rawModule = manager.newModule()
|
||||
val hostScope = Scope(parent = rawModule, thisObj = ObjString("receiver"))
|
||||
|
||||
val lambda = script.execute(hostScope)
|
||||
val ex = assertFailsWith<ScriptError> {
|
||||
hostScope.asFacade().call(lambda)
|
||||
}
|
||||
assertContains(ex.errorMessage, "module binding 'answer'")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun subjectlessWhenReportsScriptError() = runTest {
|
||||
val ex = assertFailsWith<ScriptError> {
|
||||
Compiler.compile(
|
||||
Source(
|
||||
"<when-without-subject>",
|
||||
"""
|
||||
when {
|
||||
true -> 1
|
||||
}
|
||||
""".trimIndent()
|
||||
),
|
||||
Script.defaultImportManager
|
||||
)
|
||||
}
|
||||
assertContains(ex.errorMessage, "when without subject")
|
||||
}
|
||||
}
|
||||
@ -1,52 +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
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -212,96 +212,6 @@ class DelegationTest {
|
||||
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
|
||||
fun testLazyIsDelegate() = runTest {
|
||||
eval("""
|
||||
|
||||
@ -421,6 +421,7 @@ fun with<T,R>(self: T, block: T.()->R): R {
|
||||
Can only be used with 'val' properties.
|
||||
*/
|
||||
class lazy<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 {
|
||||
@ -430,7 +431,7 @@ class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,T
|
||||
|
||||
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||
if (value == Unset)
|
||||
value = with(thisRef, creatorParam)
|
||||
value = with(thisRef,creator)
|
||||
value as T
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,20 +30,18 @@
|
||||
- Coverage added: `ScriptImportPreparationTest`.
|
||||
|
||||
### 3. Medium: missing module captures are silently converted into fresh `Unset` slots
|
||||
- Status: fixed in worktree on 2026-03-27; covered by `CompilerVmReviewRegressionTest`.
|
||||
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt:3950-3970`
|
||||
- In `CmdFrame.buildCaptureRecords()`, the module-capture path first tries several lookups. If the requested `slotId` is missing but the capture has a `name`, it calls `target.applySlotPlan(mapOf(name to slotId))` and immediately returns `target.getSlotRecord(slotId)`.
|
||||
- That record is a newly created placeholder from `Scope.applySlotPlan()` (`Scope.kt:460-471`) and defaults to `ObjUnset`.
|
||||
- Impact: a compiler/runtime disagreement in capture resolution is masked as a normal capture of `Unset`, so the failure moves far away from closure creation and becomes data corruption or an unrelated later exception. This will be difficult to debug when it happens.
|
||||
- Resolution taken: the VM now refuses to treat synthetic placeholder slots as real module/captured bindings. Reads and capture construction fail with a source-positioned `ScriptError` when the execution scope was not prepared with the script's required module/import bindings.
|
||||
- Suggested fix: if the named module capture cannot be resolved to an existing record, fail immediately with a Lyng error instead of manufacturing a placeholder slot. Add a regression test around missing imported/module captures.
|
||||
|
||||
### 4. Medium: subject-less `when { ... }` still crashes through a raw Kotlin `TODO`
|
||||
- Status: fixed in worktree on 2026-03-27; covered by `CompilerVmReviewRegressionTest`.
|
||||
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:6447-6449`
|
||||
- The unsupported branch uses `TODO("when without object is not yet implemented")`.
|
||||
- Current docs explicitly say subject-less `when` is not implemented, so the language limitation itself is documented. The problem is the failure mode: the compiler throws a raw Kotlin `NotImplementedError` instead of a normal `ScriptError` or a feature diagnostic.
|
||||
- Impact: IDE/embedding callers get an implementation exception rather than a source-positioned language error, which is especially bad across non-JVM targets and for editor tooling.
|
||||
- Resolution taken: the parser now throws a normal `ScriptError` at the `when` position with an explicit "when without subject is not implemented" message.
|
||||
- Suggested fix: replace the `TODO(...)` with a `ScriptError` at the current source position, or gate it earlier in parsing with a normal diagnostic.
|
||||
|
||||
## Risks Worth Checking Next
|
||||
|
||||
@ -58,12 +56,11 @@
|
||||
## Test Status
|
||||
- `./gradlew :lynglib:jvmTest` passed during this review.
|
||||
- `./gradlew :lynglib:jvmTest --tests ScriptImportPreparationTest --tests SeedLocalsRegressionTest` passed after the fixes/API additions.
|
||||
- `./gradlew :lynglib:jvmTest --tests CompilerVmReviewRegressionTest --tests ScriptImportPreparationTest --tests SeedLocalsRegressionTest` passed after fixing findings 3 and 4.
|
||||
- Finding 1 is covered directly; finding 2 is covered by explicit preparation API tests.
|
||||
|
||||
## Suggested Fix Order
|
||||
1. Fix finding 1 first: it is a concrete slot-index bug with likely recursive failure modes. Done.
|
||||
2. Keep `Script.execute(scope)` semantics stable and use explicit preparation APIs where script-owned import/module setup is needed. Done.
|
||||
3. Tighten finding 3 next: fail fast on capture mismatches. Done.
|
||||
4. Replace the raw `TODO` in finding 4 so unsupported syntax produces normal diagnostics. Done.
|
||||
3. Tighten finding 3 next: fail fast on capture mismatches.
|
||||
4. Replace the raw `TODO` in finding 4 so unsupported syntax produces normal diagnostics.
|
||||
5. Decide whether finding 5 matters for current module-reload workflows; add a regression before changing behavior.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user