Compare commits
4 Commits
24937c7cf5
...
4269310beb
| Author | SHA1 | Date | |
|---|---|---|---|
| 4269310beb | |||
| 7c059b4741 | |||
| 217787e17a | |||
| 9ddc7dbee6 |
@ -184,6 +184,7 @@ class Compiler(
|
|||||||
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||||
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
||||||
private val externCallableNames: MutableSet<String> = mutableSetOf()
|
private val externCallableNames: MutableSet<String> = mutableSetOf()
|
||||||
|
private val externBindingNames: MutableSet<String> = mutableSetOf()
|
||||||
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
|
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
|
||||||
private var seedingSlotPlan: Boolean = false
|
private var seedingSlotPlan: Boolean = false
|
||||||
|
|
||||||
@ -192,6 +193,7 @@ class Compiler(
|
|||||||
if (plan.slots.isEmpty()) return emptyMap()
|
if (plan.slots.isEmpty()) return emptyMap()
|
||||||
val result = LinkedHashMap<String, ForcedLocalSlotInfo>(plan.slots.size)
|
val result = LinkedHashMap<String, ForcedLocalSlotInfo>(plan.slots.size)
|
||||||
for ((name, entry) in plan.slots) {
|
for ((name, entry) in plan.slots) {
|
||||||
|
if (externBindingNames.contains(name)) continue
|
||||||
result[name] = ForcedLocalSlotInfo(
|
result[name] = ForcedLocalSlotInfo(
|
||||||
index = entry.index,
|
index = entry.index,
|
||||||
isMutable = entry.isMutable,
|
isMutable = entry.isMutable,
|
||||||
@ -581,7 +583,7 @@ class Compiler(
|
|||||||
val baseNames = cls.directParents.map { it.className }
|
val baseNames = cls.directParents.map { it.className }
|
||||||
val nextFieldId = (fieldIds.values.maxOrNull() ?: -1) + 1
|
val nextFieldId = (fieldIds.values.maxOrNull() ?: -1) + 1
|
||||||
val nextMethodId = (methodIds.values.maxOrNull() ?: -1) + 1
|
val nextMethodId = (methodIds.values.maxOrNull() ?: -1) + 1
|
||||||
return CompileClassInfo(name, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames)
|
return CompileClassInfo(name, cls.logicalPackageName, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class BaseMemberIds(
|
private data class BaseMemberIds(
|
||||||
@ -885,6 +887,7 @@ class Compiler(
|
|||||||
name = declaredName,
|
name = declaredName,
|
||||||
isMutable = false,
|
isMutable = false,
|
||||||
visibility = Visibility.Public,
|
visibility = Visibility.Public,
|
||||||
|
actualExtern = false,
|
||||||
initializer = initStmt,
|
initializer = initStmt,
|
||||||
isTransient = false,
|
isTransient = false,
|
||||||
typeDecl = null,
|
typeDecl = null,
|
||||||
@ -1550,6 +1553,7 @@ class Compiler(
|
|||||||
|
|
||||||
private data class CompileClassInfo(
|
private data class CompileClassInfo(
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val packageName: String?,
|
||||||
val fieldIds: Map<String, Int>,
|
val fieldIds: Map<String, Int>,
|
||||||
val methodIds: Map<String, Int>,
|
val methodIds: Map<String, Int>,
|
||||||
val nextFieldId: Int,
|
val nextFieldId: Int,
|
||||||
@ -1751,6 +1755,7 @@ class Compiler(
|
|||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
externBindingNames = externBindingNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
) as BytecodeStatement
|
) as BytecodeStatement
|
||||||
unwrapped to bytecodeStmt.bytecodeFunction()
|
unwrapped to bytecodeStmt.bytecodeFunction()
|
||||||
@ -1957,6 +1962,9 @@ class Compiler(
|
|||||||
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
||||||
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
||||||
val modulePlan = moduleSlotPlan()
|
val modulePlan = moduleSlotPlan()
|
||||||
|
if (modulePlan != null && slotLoc.scopeId == modulePlan.id && externBindingNames.contains(name)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
if (useScopeSlots && modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
if (useScopeSlots && modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -2075,6 +2083,7 @@ class Compiler(
|
|||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
|
externBindingNames = externBindingNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2105,6 +2114,7 @@ class Compiler(
|
|||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
|
externBindingNames = externBindingNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2160,6 +2170,7 @@ class Compiler(
|
|||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
|
externBindingNames = externBindingNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2343,6 +2354,7 @@ class Compiler(
|
|||||||
stmt.name,
|
stmt.name,
|
||||||
stmt.isMutable,
|
stmt.isMutable,
|
||||||
stmt.visibility,
|
stmt.visibility,
|
||||||
|
stmt.actualExtern,
|
||||||
init,
|
init,
|
||||||
stmt.isTransient,
|
stmt.isTransient,
|
||||||
stmt.typeDecl,
|
stmt.typeDecl,
|
||||||
@ -6446,7 +6458,7 @@ class Compiler(
|
|||||||
WhenStatement(value, cases, elseCase, whenPos)
|
WhenStatement(value, cases, elseCase, whenPos)
|
||||||
} else {
|
} else {
|
||||||
// when { cond -> ... }
|
// when { cond -> ... }
|
||||||
TODO("when without object is not yet implemented")
|
throw ScriptError(t.pos, "when without subject is not implemented")
|
||||||
}
|
}
|
||||||
return wrapBytecode(stmt)
|
return wrapBytecode(stmt)
|
||||||
}
|
}
|
||||||
@ -6699,6 +6711,7 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||||
name = qualifiedName,
|
name = qualifiedName,
|
||||||
|
packageName = packageName,
|
||||||
fieldIds = fieldIds,
|
fieldIds = fieldIds,
|
||||||
methodIds = methodIds,
|
methodIds = methodIds,
|
||||||
nextFieldId = fieldIds.size,
|
nextFieldId = fieldIds.size,
|
||||||
@ -6821,6 +6834,7 @@ class Compiler(
|
|||||||
classCtx?.let { ctx ->
|
classCtx?.let { ctx ->
|
||||||
compileClassInfos[className] = CompileClassInfo(
|
compileClassInfos[className] = CompileClassInfo(
|
||||||
name = className,
|
name = className,
|
||||||
|
packageName = packageName,
|
||||||
fieldIds = ctx.memberFieldIds.toMap(),
|
fieldIds = ctx.memberFieldIds.toMap(),
|
||||||
methodIds = ctx.memberMethodIds.toMap(),
|
methodIds = ctx.memberMethodIds.toMap(),
|
||||||
nextFieldId = ctx.nextFieldId,
|
nextFieldId = ctx.nextFieldId,
|
||||||
@ -6862,6 +6876,7 @@ class Compiler(
|
|||||||
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
val baseIds = collectBaseMemberIds(baseSpecs.map { it.name })
|
||||||
compileClassInfos[className] = CompileClassInfo(
|
compileClassInfos[className] = CompileClassInfo(
|
||||||
name = className,
|
name = className,
|
||||||
|
packageName = packageName,
|
||||||
fieldIds = baseIds.fieldIds,
|
fieldIds = baseIds.fieldIds,
|
||||||
methodIds = baseIds.methodIds,
|
methodIds = baseIds.methodIds,
|
||||||
nextFieldId = baseIds.nextFieldId,
|
nextFieldId = baseIds.nextFieldId,
|
||||||
@ -7110,6 +7125,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||||
name = qualifiedName,
|
name = qualifiedName,
|
||||||
|
packageName = packageName,
|
||||||
fieldIds = ctx.memberFieldIds.toMap(),
|
fieldIds = ctx.memberFieldIds.toMap(),
|
||||||
methodIds = ctx.memberMethodIds.toMap(),
|
methodIds = ctx.memberMethodIds.toMap(),
|
||||||
nextFieldId = ctx.nextFieldId,
|
nextFieldId = ctx.nextFieldId,
|
||||||
@ -7125,6 +7141,7 @@ class Compiler(
|
|||||||
classCtx?.let { ctx ->
|
classCtx?.let { ctx ->
|
||||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||||
name = qualifiedName,
|
name = qualifiedName,
|
||||||
|
packageName = packageName,
|
||||||
fieldIds = ctx.memberFieldIds.toMap(),
|
fieldIds = ctx.memberFieldIds.toMap(),
|
||||||
methodIds = ctx.memberMethodIds.toMap(),
|
methodIds = ctx.memberMethodIds.toMap(),
|
||||||
nextFieldId = ctx.nextFieldId,
|
nextFieldId = ctx.nextFieldId,
|
||||||
@ -7211,6 +7228,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
compileClassInfos[qualifiedName] = CompileClassInfo(
|
compileClassInfos[qualifiedName] = CompileClassInfo(
|
||||||
name = qualifiedName,
|
name = qualifiedName,
|
||||||
|
packageName = packageName,
|
||||||
fieldIds = ctx.memberFieldIds.toMap(),
|
fieldIds = ctx.memberFieldIds.toMap(),
|
||||||
methodIds = ctx.memberMethodIds.toMap(),
|
methodIds = ctx.memberMethodIds.toMap(),
|
||||||
nextFieldId = ctx.nextFieldId,
|
nextFieldId = ctx.nextFieldId,
|
||||||
@ -8390,7 +8408,7 @@ class Compiler(
|
|||||||
is ImplicitThisMethodCallRef -> {
|
is ImplicitThisMethodCallRef -> {
|
||||||
when (directRef.methodName()) {
|
when (directRef.methodName()) {
|
||||||
"iterator" -> ObjIterator
|
"iterator" -> ObjIterator
|
||||||
"lazy" -> ObjLazyDelegate.type
|
"lazy" -> resolveClassByName("lazy") ?: inferMethodCallReturnClass(directRef.methodName())
|
||||||
else -> inferMethodCallReturnClass(directRef.methodName())
|
else -> inferMethodCallReturnClass(directRef.methodName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8409,7 +8427,7 @@ class Compiler(
|
|||||||
when {
|
when {
|
||||||
target is LocalSlotRef -> {
|
target is LocalSlotRef -> {
|
||||||
when (target.name) {
|
when (target.name) {
|
||||||
"lazy" -> ObjLazyDelegate.type
|
"lazy" -> resolveClassByName("lazy")
|
||||||
"iterator" -> ObjIterator
|
"iterator" -> ObjIterator
|
||||||
"flow" -> ObjFlow.type
|
"flow" -> ObjFlow.type
|
||||||
"launch" -> ObjDeferred.type
|
"launch" -> ObjDeferred.type
|
||||||
@ -8420,7 +8438,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
target is LocalVarRef -> {
|
target is LocalVarRef -> {
|
||||||
when (target.name) {
|
when (target.name) {
|
||||||
"lazy" -> ObjLazyDelegate.type
|
"lazy" -> resolveClassByName("lazy")
|
||||||
"iterator" -> ObjIterator
|
"iterator" -> ObjIterator
|
||||||
"flow" -> ObjFlow.type
|
"flow" -> ObjFlow.type
|
||||||
"launch" -> ObjDeferred.type
|
"launch" -> ObjDeferred.type
|
||||||
@ -8459,7 +8477,9 @@ class Compiler(
|
|||||||
?: unwrapDirectRef(initializer)?.let { inferObjClassFromRef(it) }
|
?: unwrapDirectRef(initializer)?.let { inferObjClassFromRef(it) }
|
||||||
?: throw ScriptError(initializer.pos, "Delegate type must be known at compile time")
|
?: throw ScriptError(initializer.pos, "Delegate type must be known at compile time")
|
||||||
if (initClass !== delegateClass &&
|
if (initClass !== delegateClass &&
|
||||||
|
initClass.logicalName != delegateClass.logicalName &&
|
||||||
!initClass.allParentsSet.contains(delegateClass) &&
|
!initClass.allParentsSet.contains(delegateClass) &&
|
||||||
|
!initClass.allParentsSet.any { it.logicalName == delegateClass.logicalName } &&
|
||||||
!initClass.allImplementingNames.contains(delegateClass.className)
|
!initClass.allImplementingNames.contains(delegateClass.className)
|
||||||
) {
|
) {
|
||||||
throw ScriptError(
|
throw ScriptError(
|
||||||
@ -8549,7 +8569,9 @@ class Compiler(
|
|||||||
if (closedParent != null) {
|
if (closedParent != null) {
|
||||||
throw ScriptError(Pos.builtIn, "can't inherit from closed class ${closedParent.className}")
|
throw ScriptError(Pos.builtIn, "can't inherit from closed class ${closedParent.className}")
|
||||||
}
|
}
|
||||||
ObjInstanceClass(info.name, *parents.toTypedArray())
|
ObjInstanceClass(info.name, *parents.toTypedArray()).apply {
|
||||||
|
logicalPackageNameOverride = info.packageName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (stub is ObjInstanceClass) {
|
if (stub is ObjInstanceClass) {
|
||||||
for ((fieldName, fieldId) in info.fieldIds) {
|
for ((fieldName, fieldId) in info.fieldIds) {
|
||||||
@ -8893,7 +8915,7 @@ class Compiler(
|
|||||||
val effectiveEqToken = if (isProperty) null else eqToken
|
val effectiveEqToken = if (isProperty) null else eqToken
|
||||||
|
|
||||||
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
||||||
if (!isStatic && declaringClassNameCaptured == null) declareLocalName(name, isMutable)
|
if (!isStatic && declaringClassNameCaptured == null && !actualExtern) declareLocalName(name, isMutable)
|
||||||
val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) {
|
val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) {
|
||||||
SymbolKind.MEMBER
|
SymbolKind.MEMBER
|
||||||
} else {
|
} else {
|
||||||
@ -8902,6 +8924,8 @@ class Compiler(
|
|||||||
resolutionSink?.declareSymbol(name, declKind, isMutable, nameStartPos, isOverride = isOverride)
|
resolutionSink?.declareSymbol(name, declKind, isMutable, nameStartPos, isOverride = isOverride)
|
||||||
if (declKind == SymbolKind.MEMBER && extTypeName == null) {
|
if (declKind == SymbolKind.MEMBER && extTypeName == null) {
|
||||||
(codeContexts.lastOrNull() as? CodeContext.ClassBody)?.declaredMembers?.add(name)
|
(codeContexts.lastOrNull() as? CodeContext.ClassBody)?.declaredMembers?.add(name)
|
||||||
|
} else if (actualExtern) {
|
||||||
|
externBindingNames.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isDelegate = if (isAbstract || actualExtern) {
|
val isDelegate = if (isAbstract || actualExtern) {
|
||||||
@ -8967,7 +8991,11 @@ class Compiler(
|
|||||||
|
|
||||||
if (isDelegate && initialExpression != null) {
|
if (isDelegate && initialExpression != null) {
|
||||||
ensureDelegateType(initialExpression)
|
ensureDelegateType(initialExpression)
|
||||||
if (isMutable && resolveInitializerObjClass(initialExpression) == ObjLazyDelegate.type) {
|
val lazyClass = resolveClassByName("lazy")
|
||||||
|
if (isMutable &&
|
||||||
|
lazyClass != null &&
|
||||||
|
resolveInitializerObjClass(initialExpression)?.logicalName == lazyClass.logicalName
|
||||||
|
) {
|
||||||
throw ScriptError(initialExpression.pos, "lazy delegate is read-only")
|
throw ScriptError(initialExpression.pos, "lazy delegate is read-only")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9059,6 +9087,7 @@ class Compiler(
|
|||||||
name,
|
name,
|
||||||
isMutable,
|
isMutable,
|
||||||
visibility,
|
visibility,
|
||||||
|
actualExtern,
|
||||||
initialExpression,
|
initialExpression,
|
||||||
isTransient,
|
isTransient,
|
||||||
declaredType,
|
declaredType,
|
||||||
|
|||||||
@ -540,10 +540,6 @@ class Script(
|
|||||||
cachedValue
|
cachedValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addFn("lazy") {
|
|
||||||
val builder = requireOnlyArg<Obj>()
|
|
||||||
ObjLazyDelegate(builder, requireScope())
|
|
||||||
}
|
|
||||||
addVoidFn("delay") {
|
addVoidFn("delay") {
|
||||||
val a = args.firstAndOnly()
|
val a = args.firstAndOnly()
|
||||||
when (a) {
|
when (a) {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class VarDeclStatement(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val isMutable: Boolean,
|
val isMutable: Boolean,
|
||||||
val visibility: Visibility,
|
val visibility: Visibility,
|
||||||
|
val actualExtern: Boolean,
|
||||||
val initializer: Statement?,
|
val initializer: Statement?,
|
||||||
val isTransient: Boolean,
|
val isTransient: Boolean,
|
||||||
val typeDecl: TypeDecl?,
|
val typeDecl: TypeDecl?,
|
||||||
|
|||||||
@ -42,6 +42,7 @@ class BytecodeCompiler(
|
|||||||
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
private val externCallableNames: Set<String> = emptySet(),
|
private val externCallableNames: Set<String> = emptySet(),
|
||||||
|
private val externBindingNames: Set<String> = emptySet(),
|
||||||
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
) {
|
) {
|
||||||
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
|
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
|
||||||
@ -1088,7 +1089,6 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun isDelegateClass(receiverClass: ObjClass): Boolean =
|
private fun isDelegateClass(receiverClass: ObjClass): Boolean =
|
||||||
receiverClass.className == "Delegate" ||
|
receiverClass.className == "Delegate" ||
|
||||||
receiverClass.className == "LazyDelegate" ||
|
|
||||||
receiverClass.implementingNames.contains("Delegate")
|
receiverClass.implementingNames.contains("Delegate")
|
||||||
|
|
||||||
private fun operatorMemberName(op: BinOp): String? = when (op) {
|
private fun operatorMemberName(op: BinOp): String? = when (op) {
|
||||||
@ -4508,7 +4508,7 @@ class BytecodeCompiler(
|
|||||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
return CompiledValue(slot, resolved)
|
return CompiledValue(slot, resolved)
|
||||||
}
|
}
|
||||||
if (useScopeSlots && allowedScopeNames?.contains(name) == true) {
|
if (useScopeSlots && isPreparedScopeName(name)) {
|
||||||
scopeSlotIndexByName[name]?.let { slot ->
|
scopeSlotIndexByName[name]?.let { slot ->
|
||||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
return CompiledValue(slot, resolved)
|
return CompiledValue(slot, resolved)
|
||||||
@ -8073,7 +8073,7 @@ class BytecodeCompiler(
|
|||||||
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
if (allowLocalSlots && slotIndex != null && (stmt.actualExtern || !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = false))) {
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
declaredLocalKeys.add(key)
|
declaredLocalKeys.add(key)
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
@ -8100,7 +8100,7 @@ class BytecodeCompiler(
|
|||||||
val scopeId = stmt.spec.scopeId ?: 0
|
val scopeId = stmt.spec.scopeId ?: 0
|
||||||
if (slotIndex != null) {
|
if (slotIndex != null) {
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId)) {
|
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId, stmt.spec.name, isDelegated = false)) {
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
||||||
}
|
}
|
||||||
@ -8117,7 +8117,7 @@ class BytecodeCompiler(
|
|||||||
is DelegatedVarDeclStatement -> {
|
is DelegatedVarDeclStatement -> {
|
||||||
val slotIndex = stmt.slotIndex
|
val slotIndex = stmt.slotIndex
|
||||||
val scopeId = stmt.scopeId ?: 0
|
val scopeId = stmt.scopeId ?: 0
|
||||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = true)) {
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
declaredLocalKeys.add(key)
|
declaredLocalKeys.add(key)
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
@ -8283,15 +8283,26 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
||||||
if (moduleScopeId != null && scopeId != moduleScopeId) return false
|
if (moduleScopeId != null && scopeId != moduleScopeId) return false
|
||||||
val scopeNames = allowedScopeNames ?: scopeSlotNameSet
|
return isPreparedScopeName(name)
|
||||||
if (scopeNames == null || name == null) return false
|
|
||||||
return scopeNames.contains(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldUseScopeSlotFor(scopeId: Int): Boolean {
|
private fun shouldUseScopeSlotFor(scopeId: Int): Boolean {
|
||||||
return useScopeSlots && moduleScopeId != null && scopeId == moduleScopeId
|
return useScopeSlots && moduleScopeId != null && scopeId == moduleScopeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shouldUseScopeSlotFor(scopeId: Int, name: String, isDelegated: Boolean): Boolean {
|
||||||
|
if (moduleScopeId == null || scopeId != moduleScopeId) return false
|
||||||
|
if (isDelegated) return false
|
||||||
|
if (externBindingNames.contains(name)) return true
|
||||||
|
return useScopeSlots && isPreparedScopeName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPreparedScopeName(name: String?): Boolean {
|
||||||
|
if (name == null) return false
|
||||||
|
if (scopeSlotNameSet?.contains(name) == true) return true
|
||||||
|
return allowedScopeNames?.contains(name) == true
|
||||||
|
}
|
||||||
|
|
||||||
private fun collectLoopVarNames(stmt: Statement) {
|
private fun collectLoopVarNames(stmt: Statement) {
|
||||||
if (stmt is BytecodeStatement) {
|
if (stmt is BytecodeStatement) {
|
||||||
collectLoopVarNames(stmt.original)
|
collectLoopVarNames(stmt.original)
|
||||||
@ -8400,8 +8411,9 @@ class BytecodeCompiler(
|
|||||||
captureSlotKeys.add(key)
|
captureSlotKeys.add(key)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, ref.name, ref.isDelegated)
|
||||||
val isModuleSlot = if (ref.isDelegated) false else isModuleSlot(scopeId, ref.name)
|
val isModuleSlot = if (ref.isDelegated) false else isModuleSlot(scopeId, ref.name)
|
||||||
if (allowLocalSlots && !isModuleSlot) {
|
if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
|
localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
|
||||||
}
|
}
|
||||||
@ -8448,8 +8460,9 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
captureSlotKeys.add(key)
|
captureSlotKeys.add(key)
|
||||||
} else {
|
} else {
|
||||||
|
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, target.name, target.isDelegated)
|
||||||
val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name)
|
val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name)
|
||||||
if (allowLocalSlots && !isModuleSlot) {
|
if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
|
localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,7 @@ class BytecodeStatement private constructor(
|
|||||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
externCallableNames: Set<String> = emptySet(),
|
externCallableNames: Set<String> = emptySet(),
|
||||||
|
externBindingNames: Set<String> = emptySet(),
|
||||||
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
||||||
): Statement {
|
): Statement {
|
||||||
@ -122,6 +123,7 @@ class BytecodeStatement private constructor(
|
|||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
|
externBindingNames = externBindingNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
@ -236,6 +238,7 @@ class BytecodeStatement private constructor(
|
|||||||
stmt.name,
|
stmt.name,
|
||||||
stmt.isMutable,
|
stmt.isMutable,
|
||||||
stmt.visibility,
|
stmt.visibility,
|
||||||
|
stmt.actualExtern,
|
||||||
stmt.initializer?.let { unwrapDeep(it) },
|
stmt.initializer?.let { unwrapDeep(it) },
|
||||||
stmt.isTransient,
|
stmt.isTransient,
|
||||||
stmt.typeDecl,
|
stmt.typeDecl,
|
||||||
|
|||||||
@ -2392,8 +2392,13 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
|||||||
?: error("DECL_LOCAL expects LocalDecl at $constId")
|
?: error("DECL_LOCAL expects LocalDecl at $constId")
|
||||||
if (slot < frame.fn.scopeSlotCount) {
|
if (slot < frame.fn.scopeSlotCount) {
|
||||||
val target = frame.scopeTarget(slot)
|
val target = frame.scopeTarget(slot)
|
||||||
frame.ensureScopeSlot(target, slot)
|
val index = frame.ensureScopeSlot(target, slot)
|
||||||
val value = frame.slotToObj(slot).byValueCopy()
|
val raw = target.getSlotRecord(index).value
|
||||||
|
val value = when (raw) {
|
||||||
|
is FrameSlotRef -> raw.read()
|
||||||
|
is RecordSlotRef -> raw.read()
|
||||||
|
else -> raw
|
||||||
|
}.byValueCopy()
|
||||||
target.updateSlotFor(
|
target.updateSlotFor(
|
||||||
decl.name,
|
decl.name,
|
||||||
ObjRecord(
|
ObjRecord(
|
||||||
@ -3930,7 +3935,7 @@ class CmdFrame(
|
|||||||
} else {
|
} else {
|
||||||
val raw = frame.getRawObj(localIndex)
|
val raw = frame.getRawObj(localIndex)
|
||||||
if (raw == null && name != null) {
|
if (raw == null && name != null) {
|
||||||
val record = scope.get(name)
|
val record = findNamedExistingRecord(scope, name)
|
||||||
if (record != null) {
|
if (record != null) {
|
||||||
val value = record.value
|
val value = record.value
|
||||||
return@mapIndexed when (value) {
|
return@mapIndexed when (value) {
|
||||||
@ -3939,6 +3944,12 @@ class CmdFrame(
|
|||||||
else -> ObjRecord(value, isMutable)
|
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) {
|
when (raw) {
|
||||||
is FrameSlotRef -> ObjRecord(raw, isMutable)
|
is FrameSlotRef -> ObjRecord(raw, isMutable)
|
||||||
@ -3952,22 +3963,27 @@ class CmdFrame(
|
|||||||
val target = moduleScope
|
val target = moduleScope
|
||||||
val name = captureNames?.getOrNull(index)
|
val name = captureNames?.getOrNull(index)
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
target.tryGetLocalRecord(target, name, target.currentClassCtx)?.let { return@mapIndexed it }
|
findNamedExistingRecord(target, name)?.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
|
// Fallback to current scope in case the module scope isn't in the parent chain
|
||||||
// or doesn't carry the imported symbol yet.
|
// or doesn't carry the imported symbol yet.
|
||||||
scope.tryGetLocalRecord(scope, name, scope.currentClassCtx)?.let { return@mapIndexed it }
|
findNamedExistingRecord(scope, name)?.let { return@mapIndexed it }
|
||||||
scope.get(name)?.let { return@mapIndexed it }
|
|
||||||
}
|
}
|
||||||
if (slotId < target.slotCount) {
|
if (slotId < target.slotCount) {
|
||||||
return@mapIndexed target.getSlotRecord(slotId)
|
val existing = target.getSlotRecord(slotId)
|
||||||
|
if (name == null || existing.value !== ObjUnset || hasResolvedNamedScopeBinding(target, name)) {
|
||||||
|
return@mapIndexed existing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
target.applySlotPlan(mapOf(name to slotId))
|
throw ScriptError(
|
||||||
return@mapIndexed target.getSlotRecord(slotId)
|
ensureScope().pos,
|
||||||
|
"module capture '$name' is not available in the execution scope; prepare the script imports/module bindings explicitly"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
error("Missing module capture slot $slotId")
|
throw ScriptError(
|
||||||
|
ensureScope().pos,
|
||||||
|
"missing module capture slot $slotId"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4546,7 +4562,7 @@ class CmdFrame(
|
|||||||
return getScopeSlotValueAtAddr(addrSlot)
|
return getScopeSlotValueAtAddr(addrSlot)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAddrObj(addrSlot: Int, value: Obj) {
|
suspend fun setAddrObj(addrSlot: Int, value: Obj) {
|
||||||
setScopeSlotValueAtAddr(addrSlot, value)
|
setScopeSlotValueAtAddr(addrSlot, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4554,7 +4570,7 @@ class CmdFrame(
|
|||||||
return getScopeSlotValueAtAddr(addrSlot).toLong()
|
return getScopeSlotValueAtAddr(addrSlot).toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAddrInt(addrSlot: Int, value: Long) {
|
suspend fun setAddrInt(addrSlot: Int, value: Long) {
|
||||||
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
|
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4562,7 +4578,7 @@ class CmdFrame(
|
|||||||
return getScopeSlotValueAtAddr(addrSlot).toDouble()
|
return getScopeSlotValueAtAddr(addrSlot).toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAddrReal(addrSlot: Int, value: Double) {
|
suspend fun setAddrReal(addrSlot: Int, value: Double) {
|
||||||
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
|
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4570,7 +4586,7 @@ class CmdFrame(
|
|||||||
return getScopeSlotValueAtAddr(addrSlot).toBool()
|
return getScopeSlotValueAtAddr(addrSlot).toBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAddrBool(addrSlot: Int, value: Boolean) {
|
suspend fun setAddrBool(addrSlot: Int, value: Boolean) {
|
||||||
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
|
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4776,12 +4792,13 @@ class CmdFrame(
|
|||||||
|
|
||||||
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 hadNamedBinding = name != null && hasResolvedNamedScopeBinding(target, name)
|
||||||
val index = ensureScopeSlot(target, slot)
|
val index = ensureScopeSlot(target, slot)
|
||||||
val record = target.getSlotRecord(index)
|
val record = target.getSlotRecord(index)
|
||||||
val direct = record.value
|
val direct = record.value
|
||||||
if (direct is FrameSlotRef) return direct.read()
|
if (direct is FrameSlotRef) return direct.read()
|
||||||
if (direct is RecordSlotRef) return direct.read()
|
if (direct is RecordSlotRef) return direct.read()
|
||||||
val name = fn.scopeSlotNames[slot]
|
|
||||||
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) {
|
||||||
@ -4802,9 +4819,15 @@ class CmdFrame(
|
|||||||
return direct
|
return direct
|
||||||
}
|
}
|
||||||
if (name == null) return record.value
|
if (name == null) return record.value
|
||||||
val resolved = target.get(name) ?: return record.value
|
val resolved = target.get(name)
|
||||||
|
if (resolved == null) {
|
||||||
|
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, record)
|
||||||
|
return record.value
|
||||||
|
}
|
||||||
if (resolved.value !== ObjUnset) {
|
if (resolved.value !== ObjUnset) {
|
||||||
target.updateSlotFor(name, resolved)
|
target.updateSlotFor(name, resolved)
|
||||||
|
} else {
|
||||||
|
failMissingPreparedModuleBinding(slot, name, hadNamedBinding, resolved)
|
||||||
}
|
}
|
||||||
return resolved.value
|
return resolved.value
|
||||||
}
|
}
|
||||||
@ -4812,12 +4835,13 @@ class CmdFrame(
|
|||||||
private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): Obj {
|
private suspend fun getScopeSlotValueAtAddr(addrSlot: Int): 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 slotId = addrScopeSlots[addrSlot]
|
||||||
|
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||||
|
val hadNamedBinding = name != null && hasResolvedNamedScopeBinding(target, name)
|
||||||
val record = target.getSlotRecord(index)
|
val record = target.getSlotRecord(index)
|
||||||
val direct = record.value
|
val direct = record.value
|
||||||
if (direct is FrameSlotRef) return direct.read()
|
if (direct is FrameSlotRef) return direct.read()
|
||||||
if (direct is RecordSlotRef) 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) {
|
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) {
|
||||||
@ -4838,16 +4862,29 @@ class CmdFrame(
|
|||||||
return direct
|
return direct
|
||||||
}
|
}
|
||||||
if (name == null) return record.value
|
if (name == null) return record.value
|
||||||
val resolved = target.get(name) ?: return record.value
|
val resolved = target.get(name)
|
||||||
|
if (resolved == null) {
|
||||||
|
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, record)
|
||||||
|
return record.value
|
||||||
|
}
|
||||||
if (resolved.value !== ObjUnset) {
|
if (resolved.value !== ObjUnset) {
|
||||||
target.updateSlotFor(name, resolved)
|
target.updateSlotFor(name, resolved)
|
||||||
|
} else {
|
||||||
|
failMissingPreparedModuleBinding(slotId, name, hadNamedBinding, resolved)
|
||||||
}
|
}
|
||||||
return resolved.value
|
return resolved.value
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||||
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
||||||
val index = addrIndices[addrSlot]
|
val index = addrIndices[addrSlot]
|
||||||
|
val record = target.getSlotRecord(index)
|
||||||
|
val slotId = addrScopeSlots[addrSlot]
|
||||||
|
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||||
|
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
||||||
|
target.assign(record, name, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
target.setSlotValue(index, value)
|
target.setSlotValue(index, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4875,6 +4912,41 @@ class CmdFrame(
|
|||||||
return index
|
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) {
|
private fun ensureLocalMutable(localIndex: Int) {
|
||||||
val name = fn.localSlotNames.getOrNull(localIndex) ?: return
|
val name = fn.localSlotNames.getOrNull(localIndex) ?: return
|
||||||
val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: true
|
val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: true
|
||||||
|
|||||||
@ -106,10 +106,20 @@ open class ObjClass(
|
|||||||
vararg parents: ObjClass,
|
vararg parents: ObjClass,
|
||||||
) : Obj() {
|
) : 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 isAnonymous: Boolean = false
|
||||||
|
|
||||||
var isAbstract: Boolean = false
|
var isAbstract: Boolean = false
|
||||||
var isClosed: Boolean = false
|
var isClosed: Boolean = false
|
||||||
|
var logicalPackageNameOverride: String? = null
|
||||||
|
|
||||||
// Stable identity and simple structural version for PICs
|
// Stable identity and simple structural version for PICs
|
||||||
val classId: Long = ClassIdGen.nextId()
|
val classId: Long = ClassIdGen.nextId()
|
||||||
@ -169,6 +179,16 @@ open class ObjClass(
|
|||||||
*/
|
*/
|
||||||
var classScope: Scope? = null
|
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). */
|
/** Direct parents in declaration order (kept deterministic). */
|
||||||
val directParents: List<ObjClass> = parents.toList()
|
val directParents: List<ObjClass> = parents.toList()
|
||||||
|
|
||||||
|
|||||||
@ -129,6 +129,7 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
|
|||||||
}
|
}
|
||||||
|
|
||||||
val type = object : ObjClass("Delegate") {}.apply {
|
val type = object : ObjClass("Delegate") {}.apply {
|
||||||
|
logicalPackageNameOverride = "lyng.stdlib"
|
||||||
addFn("getValue") { raiseError("Delegate.getValue is not implemented") }
|
addFn("getValue") { raiseError("Delegate.getValue is not implemented") }
|
||||||
addFn("setValue") { raiseError("Delegate.setValue is not implemented") }
|
addFn("setValue") { raiseError("Delegate.setValue is not implemented") }
|
||||||
addFn("invoke") { raiseError("Delegate.invoke is not implemented") }
|
addFn("invoke") { raiseError("Delegate.invoke is not implemented") }
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.obj
|
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
|
||||||
import net.sergeych.lyng.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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.bridge.bindGlobalVar
|
||||||
|
import net.sergeych.lyng.bridge.globalBinder
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class GlobalPropertyCaptureRegressionTest {
|
||||||
|
@Test
|
||||||
|
fun externGlobalVarAssignmentInsideFunctionShouldCallBoundSetter() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
var x = 1.0
|
||||||
|
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
extern var X: Real
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
X = X + 1.0
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
scope.globalBinder().bindGlobalVar(
|
||||||
|
name = "X",
|
||||||
|
get = { x },
|
||||||
|
set = { x = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
scope.eval("main()")
|
||||||
|
|
||||||
|
assertEquals(2.0, x, "bound extern var should stay live inside function bodies")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -212,6 +212,96 @@ class DelegationTest {
|
|||||||
assertTrue(badThrown)
|
assertTrue(badThrown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPureLyngLazyPreservesReceiverAndClosure() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
val GLOBAL_NUMBERS = [1,2,3]
|
||||||
|
|
||||||
|
class PureLazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
||||||
|
private val creator: ThisRefType.()->T = creatorParam
|
||||||
|
private var value = Unset
|
||||||
|
|
||||||
|
override fun bind(name: String, access, thisRef: ThisRefType): Object = this
|
||||||
|
|
||||||
|
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||||
|
if (value == Unset)
|
||||||
|
value = creator(thisRef)
|
||||||
|
value as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pureLazy<T,ThisRefType=Object>(creator: ThisRefType.()->T): Delegate<T,ThisRefType> = PureLazy(creator)
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val numbers = [1,2,3]
|
||||||
|
val fromThis: List by pureLazy { this.numbers }
|
||||||
|
val fromScope: List by pureLazy { GLOBAL_NUMBERS }
|
||||||
|
}
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val a: A by pureLazy { A() }
|
||||||
|
val test: List by pureLazy { (a as A).fromThis + [4] }
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals([1,2,3], A().fromThis)
|
||||||
|
assertEquals([1,2,3], A().fromScope)
|
||||||
|
assertEquals([1,2,3,4], B().test)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImportedPureLyngLazyPreservesReceiverAndClosure() = runTest {
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.importManager.addTextPackages(
|
||||||
|
"""
|
||||||
|
package repro.lazy
|
||||||
|
|
||||||
|
import lyng.stdlib
|
||||||
|
|
||||||
|
class PureLazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
||||||
|
private val creator: ThisRefType.()->T = creatorParam
|
||||||
|
private var value = Unset
|
||||||
|
|
||||||
|
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
||||||
|
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'"
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||||
|
if (value == Unset)
|
||||||
|
value = with(thisRef, creator)
|
||||||
|
value as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
import repro.lazy
|
||||||
|
|
||||||
|
val GLOBAL_NUMBERS = [1,2,3]
|
||||||
|
|
||||||
|
class A {
|
||||||
|
val numbers = [1,2,3]
|
||||||
|
val fromThis: List by PureLazy { this.numbers }
|
||||||
|
val fromScope: List by PureLazy { GLOBAL_NUMBERS }
|
||||||
|
}
|
||||||
|
|
||||||
|
class B {
|
||||||
|
val a: A by PureLazy { A() }
|
||||||
|
val test: List by PureLazy { (a as A).fromThis + [4] }
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals([1,2,3], A().fromThis)
|
||||||
|
assertEquals([1,2,3], A().fromScope)
|
||||||
|
assertEquals([1,2,3,4], B().test)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLazyIsDelegate() = runTest {
|
fun testLazyIsDelegate() = runTest {
|
||||||
eval("""
|
eval("""
|
||||||
|
|||||||
@ -421,7 +421,6 @@ fun with<T,R>(self: T, block: T.()->R): R {
|
|||||||
Can only be used with 'val' properties.
|
Can only be used with 'val' properties.
|
||||||
*/
|
*/
|
||||||
class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
||||||
private val creator: ThisRefType.()->T = creatorParam
|
|
||||||
private var value = Unset
|
private var value = Unset
|
||||||
|
|
||||||
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
||||||
@ -431,7 +430,7 @@ class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,T
|
|||||||
|
|
||||||
override fun getValue(thisRef: ThisRefType, name: String): T {
|
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||||
if (value == Unset)
|
if (value == Unset)
|
||||||
value = with(thisRef,creator)
|
value = with(thisRef, creatorParam)
|
||||||
value as T
|
value as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,18 +30,20 @@
|
|||||||
- Coverage added: `ScriptImportPreparationTest`.
|
- Coverage added: `ScriptImportPreparationTest`.
|
||||||
|
|
||||||
### 3. Medium: missing module captures are silently converted into fresh `Unset` slots
|
### 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`
|
- 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)`.
|
- 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`.
|
- 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.
|
- 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.
|
||||||
- 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.
|
- 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.
|
||||||
|
|
||||||
### 4. Medium: subject-less `when { ... }` still crashes through a raw Kotlin `TODO`
|
### 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`
|
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:6447-6449`
|
||||||
- The unsupported branch uses `TODO("when without object is not yet implemented")`.
|
- 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.
|
- 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.
|
- 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.
|
||||||
- Suggested fix: replace the `TODO(...)` with a `ScriptError` at the current source position, or gate it earlier in parsing with a normal diagnostic.
|
- Resolution taken: the parser now throws a normal `ScriptError` at the `when` position with an explicit "when without subject is not implemented" message.
|
||||||
|
|
||||||
## Risks Worth Checking Next
|
## Risks Worth Checking Next
|
||||||
|
|
||||||
@ -56,11 +58,12 @@
|
|||||||
## Test Status
|
## Test Status
|
||||||
- `./gradlew :lynglib:jvmTest` passed during this review.
|
- `./gradlew :lynglib:jvmTest` passed during this review.
|
||||||
- `./gradlew :lynglib:jvmTest --tests ScriptImportPreparationTest --tests SeedLocalsRegressionTest` passed after the fixes/API additions.
|
- `./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.
|
- Finding 1 is covered directly; finding 2 is covered by explicit preparation API tests.
|
||||||
|
|
||||||
## Suggested Fix Order
|
## Suggested Fix Order
|
||||||
1. Fix finding 1 first: it is a concrete slot-index bug with likely recursive failure modes. Done.
|
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.
|
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.
|
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.
|
4. Replace the raw `TODO` in finding 4 so unsupported syntax produces normal diagnostics. Done.
|
||||||
5. Decide whether finding 5 matters for current module-reload workflows; add a regression before changing behavior.
|
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