/*
 * 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.bytecode

import net.sergeych.lyng.*
import net.sergeych.lyng.obj.*

class BytecodeCompiler(
    private val allowLocalSlots: Boolean = true,
    private val returnLabels: Set<String> = emptySet(),
    private val rangeLocalNames: Set<String> = emptySet(),
    private val allowedScopeNames: Set<String>? = null,
    private val scopeSlotNameSet: Set<String>? = null,
    private val moduleScopeId: Int? = null,
    private val forcedLocalSlots: Map<String, Int> = emptyMap(),
    private val forcedLocalScopeId: Int? = null,
    private val forcedLocalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
    private val globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
    private val globalSlotScopeId: Int? = null,
    private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
    private val exactLambdaRefByScopeId: Map<Int, Map<Int, LambdaFnRef>> = emptyMap(),
    private val slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
    private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
    private val knownClassNames: Set<String> = emptySet(),
    private val knownObjectNames: Set<String> = emptySet(),
    private val classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
    private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
    private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
    private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
    private val callSignatureByName: Map<String, CallSignature> = emptyMap(),
    private val extensionContextReceiversByWrapperName: Map<String, List<String>> = emptyMap(),
    private val externCallableNames: Set<String> = emptySet(),
    private val externBindingNames: Set<String> = emptySet(),
    private val preparedModuleBindingNames: Set<String> = emptySet(),
    private val scopeRefPosByName: Map<String, Pos> = emptyMap(),
    private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
    private val implicitThisTypeName: String? = null,
) {
    private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
    private var builder = CmdBuilder()
    private var nextSlot = 0
    private var nextAddrSlot = 0
    private var scopeSlotCount = 0
    private var scopeSlotIndices = IntArray(0)
    private var scopeSlotNames = emptyArray<String?>()
    private var scopeSlotIsModule = BooleanArray(0)
    private var scopeSlotRequiresPreparedBinding = BooleanArray(0)
    private var scopeSlotRefPos = emptyArray<Pos?>()
    private var scopeSlotMutables = BooleanArray(0)
    private var scopeKeyByIndex = emptyArray<ScopeSlotKey?>()
    private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
    private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>()
    private val scopeSlotMutableMap = LinkedHashMap<ScopeSlotKey, Boolean>()
    private val scopeSlotIndexByName = LinkedHashMap<String, Int>()
    private val scopeSlotRefPosByKey = LinkedHashMap<ScopeSlotKey, Pos>()
    private val pendingScopeNameRefs = LinkedHashSet<String>()
    private val addrSlotByScopeSlot = LinkedHashMap<Int, Int>()
    private data class LocalSlotInfo(val name: String, val isMutable: Boolean, val isDelegated: Boolean)
    private val localSlotInfoMap = LinkedHashMap<ScopeSlotKey, LocalSlotInfo>()
    private val localSlotIndexByKey = LinkedHashMap<ScopeSlotKey, Int>()
    private val localSlotIndexByName = LinkedHashMap<String, Int>()
    private val captureSlotKeys = LinkedHashSet<ScopeSlotKey>()
    private val forcedObjSlots = LinkedHashSet<Int>()
    private val loopSlotOverrides = LinkedHashMap<String, Int>()
    private var localSlotNames = emptyArray<String?>()
    private var localSlotMutables = BooleanArray(0)
    private var localSlotDelegated = BooleanArray(0)
    private var localSlotCaptures = BooleanArray(0)
    private var localSlotKeyByIndex = emptyArray<ScopeSlotKey?>()
    private val declaredLocalKeys = LinkedHashSet<ScopeSlotKey>()
    private val localRangeRefs = LinkedHashMap<ScopeSlotKey, RangeRef>()
    private val slotTypes = mutableMapOf<Int, SlotType>()
    private val slotObjClass = mutableMapOf<Int, ObjClass>()
    private val stableObjSlots = mutableSetOf<Int>()
    private val nameObjClass = knownNameObjClass.toMutableMap()
    private val listElementClassBySlot = mutableMapOf<Int, ObjClass>()
    private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
    private val intLoopVarNames = LinkedHashSet<String>()
    private val valueFnRefs = LinkedHashSet<ValueFnRef>()
    private val exactLambdaRefBySlot = LinkedHashMap<Int, LambdaFnRef>()
    private val exactCallableObjBySlot = LinkedHashMap<Int, Obj>()
    private val activeInlineLambdas = LinkedHashSet<LambdaFnRef>()
    private val inlineThisBindings = ArrayDeque<InlineThisBinding>()
    private val loopVarKeys = LinkedHashSet<ScopeSlotKey>()
    private val loopVarSlots = HashSet<Int>()
    private val loopStack = ArrayDeque<LoopContext>()
    private var currentPos: Pos? = null
    private var cachedVoidSlot: Int? = null

    private data class LoopContext(
        val label: String?,
        val breakLabel: CmdBuilder.Label,
        val continueLabel: CmdBuilder.Label,
        val breakFlagSlot: Int,
        val resultSlot: Int?,
        val hasIterator: Boolean,
    )

    private data class InlineThisBinding(val slot: Int, val typeName: String?)

    fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? {
        prepareCompilation(stmt)
        setPos(stmt.pos)
        return when (stmt) {
            is ExpressionStatement -> compileExpression(name, stmt)
            is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
            is net.sergeych.lyng.ForInStatement -> compileForIn(name, stmt)
            is net.sergeych.lyng.DoWhileStatement -> compileDoWhile(name, stmt)
            is net.sergeych.lyng.WhileStatement -> compileWhile(name, stmt)
            is net.sergeych.lyng.WhenStatement -> {
                val value = compileWhen(stmt, true) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is BlockStatement -> compileBlock(name, stmt)
            is net.sergeych.lyng.InlineBlockStatement -> compileInlineBlock(name, stmt)
            is VarDeclStatement -> compileVarDecl(name, stmt)
            is net.sergeych.lyng.ClassStaticFieldInitStatement -> {
                val value = emitClassStaticFieldInit(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.ClassInstanceInitDeclStatement -> {
                val value = emitClassInstanceInitDecl(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> {
                val value = emitClassInstanceFieldDecl(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> {
                val value = emitClassInstancePropertyDecl(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> {
                val value = emitClassInstanceDelegatedDecl(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is DelegatedVarDeclStatement -> {
                val value = emitDelegatedVarDecl(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.InstanceFieldInitStatement -> {
                val value = emitInstanceFieldInit(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.InstancePropertyInitStatement -> {
                val value = emitInstancePropertyInit(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.InstanceDelegatedInitStatement -> {
                val value = emitInstanceDelegatedInit(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is DestructuringVarDeclStatement -> {
                val value = emitDestructuringVarDecl(stmt) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt)
            is net.sergeych.lyng.ExtensionPropertyDeclStatement -> compileExtensionPropertyDecl(name, stmt)
            is net.sergeych.lyng.TryStatement -> {
                val value = emitTry(stmt, true) ?: return null
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.ClassDeclStatement -> {
                val value = emitDeclClass(stmt)
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.FunctionDeclStatement -> {
                val value = emitDeclFunction(stmt)
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.EnumDeclStatement -> {
                val value = emitDeclEnum(stmt)
                builder.emit(Opcode.RET, value.slot)
                val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
                builder.build(
                    name,
                    localCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            is net.sergeych.lyng.NopStatement -> {
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                val slot = allocSlot()
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                builder.emit(Opcode.RET, slot)
                builder.build(
                    name,
                    localCount = maxOf(nextSlot, slot + 1) - scopeSlotCount,
                    addrCount = nextAddrSlot,
                    returnLabels = returnLabels,
                    scopeSlotIndices,
                    scopeSlotNames,
                    scopeSlotIsModule,
                    scopeSlotRequiresPreparedBinding,
                    scopeSlotRefPos,
                    localSlotNames,
                    localSlotMutables,
                    localSlotDelegated,
                    localSlotCaptures
                )
            }
            else -> null
        }
    }

    private fun compileThrowStatement(name: String, stmt: net.sergeych.lyng.ThrowStatement): CmdFunction? {
        prepareCompilation(stmt)
        compileThrow(stmt) ?: return null
        return builder.build(
            name,
            localCount = nextSlot - scopeSlotCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileExtensionPropertyDecl(
        name: String,
        stmt: net.sergeych.lyng.ExtensionPropertyDeclStatement,
    ): CmdFunction? {
        prepareCompilation(stmt)
        val value = emitExtensionPropertyDecl(stmt)
        builder.emit(Opcode.RET, value.slot)
        val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    fun compileExpression(name: String, stmt: ExpressionStatement): CmdFunction? {
        prepareCompilation(stmt)
        val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null
        builder.emit(Opcode.RET, value.slot)
        val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private data class CompiledValue(val slot: Int, val type: SlotType)

    private fun allocSlot(): Int = nextSlot++

    private fun encodeMemberId(receiverClass: ObjClass, id: Int?): Int? {
        if (id == null) return null
        if (receiverClass == ObjClassType) return -(id + 2)
        return id
    }

    private fun missingMemberMessage(receiverClass: ObjClass, name: String): String {
        return "no such member: $name on ${receiverClass.className}. " +
            "Considered order: ${receiverClass.renderLinearization(true)}. " +
            "Tip: try this@Base.$name(...) or (obj as Base).$name(...) if ambiguous"
    }

    private fun missingFieldMessage(receiverClass: ObjClass, name: String): String {
        return "no such field: $name on ${receiverClass.className}. " +
            "Considered order: ${receiverClass.renderLinearization(true)}"
    }

    private fun compileRef(ref: ObjRef): CompiledValue? {
        return when (ref) {
            is ConstRef -> compileConst(ref.constValue)
            is TypeDeclRef -> compileConst(ObjTypeExpr(ref.decl()))
            is IncDecRef -> compileIncDec(ref, true)
            is CastRef -> compileCast(ref)
            is LocalSlotRef -> {
                if (ref.name == "this") {
                    return compileThisRef()
                }
                loopSlotOverrides[ref.name]?.let { slot ->
                    val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                    return CompiledValue(slot, resolved)
                }
                if (!allowLocalSlots) return null
                if (ref.isDelegated) {
                    val mapped = resolveSlot(ref) ?: return null
                    if (mapped < scopeSlotCount) return null
                    val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
                    val local = allocSlot()
                    builder.emit(Opcode.DELEGATED_GET_LOCAL, mapped, nameId, local)
                    updateSlotType(local, SlotType.OBJ)
                    return CompiledValue(local, SlotType.OBJ)
                }
                if (ref.name.isEmpty()) return null
                val mapped = resolveSlot(ref) ?: return null
                var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
                if (resolved == SlotType.UNKNOWN) {
                    val key = ScopeSlotKey(
                        ref.captureOwnerScopeId ?: refScopeId(ref),
                        ref.captureOwnerSlot ?: refSlot(ref)
                    )
                    val inferred = slotTypeFromClass(slotInitClassByKey[key])
                    if (inferred != null) {
                        updateSlotType(mapped, inferred)
                        resolved = inferred
                    }
                }
                if (resolved == SlotType.UNKNOWN) {
                    val inferred = if (knownClassNames.contains(ref.name)) null else slotTypeFromClass(nameObjClass[ref.name])
                    if (inferred != null) {
                        updateSlotType(mapped, inferred)
                        resolved = inferred
                    }
                }
                if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
                    updateSlotType(mapped, SlotType.INT)
                    resolved = SlotType.INT
                }
                if (mapped < scopeSlotCount && resolved != SlotType.UNKNOWN) {
                    noteScopeSlotRef(mapped, ref.pos())
                    val addrSlot = ensureScopeAddr(mapped)
                    val local = allocSlot()
                    emitLoadFromAddr(addrSlot, local, resolved)
                    updateSlotType(local, resolved)
                    if (resolved == SlotType.OBJ) {
                        propagateObjClass(SlotType.OBJ, mapped, local)
                        seedExactCallableForNamedSlot(ref.name, local)
                    }
                    return CompiledValue(local, resolved)
                }
                if (mapped < scopeSlotCount && resolved == SlotType.UNKNOWN) {
                    noteScopeSlotRef(mapped, ref.pos())
                    val addrSlot = ensureScopeAddr(mapped)
                    val local = allocSlot()
                    emitLoadFromAddr(addrSlot, local, SlotType.OBJ)
                    updateSlotType(local, SlotType.OBJ)
                    propagateObjClass(SlotType.OBJ, mapped, local)
                    seedExactCallableForNamedSlot(ref.name, local)
                    return CompiledValue(local, SlotType.OBJ)
                }
                if (resolved == SlotType.OBJ) {
                    seedExactCallableForNamedSlot(ref.name, mapped)
                }
                CompiledValue(mapped, resolved)
            }
            is LocalVarRef -> {
                if (ref.name == "this") {
                    return compileThisRef()
                }
                loopSlotOverrides[ref.name]?.let { slot ->
                    val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                    if (resolved == SlotType.OBJ) {
                        seedExactCallableForNamedSlot(ref.name, slot)
                    }
                    return CompiledValue(slot, resolved)
                }
                if (allowLocalSlots) {
                    scopeSlotIndexByName[ref.name]?.let { slot ->
                        noteScopeSlotRef(slot, callSitePos())
                        val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                        if (resolved == SlotType.OBJ) {
                            seedExactCallableForNamedSlot(ref.name, slot)
                        }
                        return CompiledValue(slot, resolved)
                    }
                }
                null
            }
            is FastLocalVarRef -> {
                if (ref.name == "this") {
                    return compileThisRef()
                }
                loopSlotOverrides[ref.name]?.let { slot ->
                    val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                    if (resolved == SlotType.OBJ) {
                        seedExactCallableForNamedSlot(ref.name, slot)
                    }
                    return CompiledValue(slot, resolved)
                }
                if (allowLocalSlots) {
                    val localIndex = localSlotIndexByName[ref.name]
                    if (localIndex != null) {
                        val slot = scopeSlotCount + localIndex
                        val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                        if (resolved == SlotType.OBJ) {
                            seedExactCallableForNamedSlot(ref.name, slot)
                        }
                        return CompiledValue(slot, resolved)
                    }
                    scopeSlotIndexByName[ref.name]?.let { slot ->
                        noteScopeSlotRef(slot, callSitePos())
                        val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                        if (resolved == SlotType.OBJ) {
                            seedExactCallableForNamedSlot(ref.name, slot)
                        }
                        return CompiledValue(slot, resolved)
                    }
                }
                null
            }
            is BoundLocalVarRef -> {
                if (!allowLocalSlots) return null
                val slot = ref.slotIndex()
                val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                if (slot < scopeSlotCount && resolved != SlotType.UNKNOWN) {
                    noteScopeSlotRef(slot, callSitePos())
                    val addrSlot = ensureScopeAddr(slot)
                    val local = allocSlot()
                    emitLoadFromAddr(addrSlot, local, resolved)
                    updateSlotType(local, resolved)
                    if (resolved == SlotType.OBJ) {
                        propagateObjClass(SlotType.OBJ, slot, local)
                        localSlotNames.getOrNull(slot - scopeSlotCount)?.let { seedExactCallableForNamedSlot(it, local) }
                    }
                    return CompiledValue(local, resolved)
                }
                if (resolved == SlotType.OBJ) {
                    localSlotNames.getOrNull(slot - scopeSlotCount)?.let { seedExactCallableForNamedSlot(it, slot) }
                }
                CompiledValue(slot, resolved)
            }
            is ValueFnRef -> compileValueFnRef(ref)
            is ClassOperatorRef -> {
                val target = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
                val dst = allocSlot()
                builder.emit(Opcode.GET_OBJ_CLASS, target.slot, dst)
                updateSlotType(dst, SlotType.OBJ)
                slotObjClass[dst] = ObjClassType
                CompiledValue(dst, SlotType.OBJ)
            }
            is ListLiteralRef -> compileListLiteral(ref)
            is MapLiteralRef -> compileMapLiteral(ref)
            is ThisMethodSlotCallRef -> compileThisMethodSlotCall(ref)
            is StatementRef -> {
                val compiled = compileStatementValueOrFallback(ref.statement)
                compiled ?: throw BytecodeCompileException(
                    "Unsupported StatementRef(${ref.statement::class.simpleName})",
                    Pos.builtIn
                )
            }
            is BinaryOpRef -> compileBinary(ref) ?: compileEvalRef(ref)
            is UnaryOpRef -> compileUnary(ref)
            is LogicalAndRef -> compileLogicalAnd(ref)
            is LogicalOrRef -> compileLogicalOr(ref)
            is AssignRef -> compileAssign(ref) ?: compileEvalRef(ref)
            is AssignOpRef -> compileAssignOp(ref) ?: compileEvalRef(ref)
            is AssignIfNullRef -> compileAssignIfNull(ref)
            is RangeRef -> compileRangeRef(ref)
            is ConditionalRef -> compileConditional(ref)
            is ElvisRef -> compileElvis(ref)
            is CallRef -> compileCall(ref)
            is MethodCallRef -> compileMethodCall(ref)
            is FieldRef -> compileFieldRef(ref)
            is ClassScopeMemberRef -> compileClassScopeMemberRef(ref)
            is ThisFieldSlotRef -> compileThisFieldSlotRef(ref)
            is QualifiedThisFieldSlotRef -> compileQualifiedThisFieldSlotRef(ref)
            is ImplicitThisMemberRef -> {
                val receiver = ref.preferredThisTypeName()?.let { typeName ->
                    compileThisVariantRef(typeName) ?: return null
                } ?: compileThisRef()
                val ownerClass = ref.preferredThisTypeName()?.let { resolveTypeNameClass(it) }
                    ?: currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) }
                val fieldId = ref.fieldId ?: -1
                val methodId = ref.methodId ?: -1
                if (fieldId < 0 && methodId < 0) {
                    val typeName = ref.preferredThisTypeName()
                        ?: throw BytecodeCompileException("Missing member id for ${ref.name}", Pos.builtIn)
                    val wrapperName = extensionPropertyGetterName(typeName, ref.name)
                    val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
                        "Missing extension wrapper for ${typeName}.${ref.name}",
                        Pos.builtIn
                    )
                    val dst = allocSlot()
                    val calleeObj = ensureObjSlot(callee)
                    val args = compileCallArgsWithReceiver(receiver, emptyList(), false) ?: return null
                    val encodedCount = encodeCallArgCount(args) ?: return null
                    emitCallCompiled(calleeObj, args.base, encodedCount, dst)
                    updateSlotType(dst, SlotType.OBJ)
                    annotateIndexedReceiverSlot(dst, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
                    return CompiledValue(dst, SlotType.OBJ)
                }
                val slot = allocSlot()
                builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, slot)
                updateSlotType(slot, SlotType.OBJ)
                annotateIndexedReceiverSlot(slot, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
                CompiledValue(slot, SlotType.OBJ)
            }
            is ImplicitThisMethodCallRef -> compileImplicitThisMethodCall(ref)
            is QualifiedThisMethodSlotCallRef -> compileQualifiedThisMethodSlotCall(ref)
            is IndexRef -> compileIndexRef(ref)
            is QualifiedThisRef -> compileThisVariantRef(ref.typeName)
            else -> null
        }
    }

    private fun compileImplicitThisMethodCall(ref: ImplicitThisMethodCallRef): CompiledValue? {
        val callPos = ref.pos()
        val receiver = ref.preferredThisTypeName()?.let { typeName ->
            compileThisVariantRef(typeName) ?: return null
        } ?: compileThisRef()
        val methodId = ref.slotId()
        val dst = allocSlot()
        if (methodId != null) {
            if (!ref.optionalInvoke()) {
                val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
                val encodedCount = encodeCallArgCount(args) ?: return null
                setPos(callPos)
                builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
                return CompiledValue(dst, SlotType.OBJ)
            }
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, dst)
            builder.mark(endLabel)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val typeName = ref.preferredThisTypeName() ?: throw BytecodeCompileException(
            "Missing member id for ${ref.methodName()}",
            Pos.builtIn
        )
        val wrapperName = extensionCallableName(typeName, ref.methodName())
        val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
            "Missing extension wrapper for ${typeName}.${ref.methodName()}",
            Pos.builtIn
        )
        val calleeObj = ensureObjSlot(callee)
        if (!ref.optionalInvoke()) {
            val args = compileCallArgsWithReceiver(receiver, ref.arguments(), ref.hasTailBlock()) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            emitCallCompiled(calleeObj, args.base, encodedCount, dst)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
        val nullLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
        )
        val args = compileCallArgsWithReceiver(receiver, ref.arguments(), ref.hasTailBlock()) ?: return null
        val encodedCount = encodeCallArgCount(args) ?: return null
        setPos(callPos)
        emitCallCompiled(calleeObj, args.base, encodedCount, dst)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(nullLabel)
        builder.emit(Opcode.CONST_NULL, dst)
        builder.mark(endLabel)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileThisRef(): CompiledValue {
        inlineThisBindings.lastOrNull()?.let { binding ->
            return CompiledValue(binding.slot, SlotType.OBJ)
        }
        val slot = allocSlot()
        builder.emit(Opcode.LOAD_THIS, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun compileThisVariantRef(typeName: String): CompiledValue? {
        inlineThisBindings.lastOrNull { it.typeName == typeName }?.let { binding ->
            return CompiledValue(binding.slot, SlotType.OBJ)
        }
        val typeId = builder.addConst(BytecodeConst.StringVal(typeName))
        if (typeId > 0xFFFF) return null
        val slot = allocSlot()
        builder.emit(Opcode.LOAD_THIS_VARIANT, typeId, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun currentImplicitThisTypeName(): String? {
        return inlineThisBindings.lastOrNull()?.typeName ?: implicitThisTypeName
    }

    private fun compileConst(obj: Obj): CompiledValue? {
        val slot = allocSlot()
        when (obj) {
            is ObjInt -> {
                val id = builder.addConst(BytecodeConst.IntVal(obj.value))
                builder.emit(Opcode.CONST_INT, id, slot)
                return CompiledValue(slot, SlotType.INT)
            }
            is ObjReal -> {
                val id = builder.addConst(BytecodeConst.RealVal(obj.value))
                builder.emit(Opcode.CONST_REAL, id, slot)
                return CompiledValue(slot, SlotType.REAL)
            }
            is ObjBool -> {
                val id = builder.addConst(BytecodeConst.Bool(obj.value))
                builder.emit(Opcode.CONST_BOOL, id, slot)
                return CompiledValue(slot, SlotType.BOOL)
            }
            is ObjString -> {
                val id = builder.addConst(BytecodeConst.StringVal(obj.value))
                builder.emit(Opcode.CONST_OBJ, id, slot)
                slotObjClass[slot] = ObjString.type
                trackExactCallableObjAtSlot(slot, obj)
                return CompiledValue(slot, SlotType.OBJ)
            }
            ObjNull -> {
                builder.emit(Opcode.CONST_NULL, slot)
                trackExactCallableObjAtSlot(slot, null)
                return CompiledValue(slot, SlotType.OBJ)
            }
            else -> {
                val id = builder.addConst(BytecodeConst.ObjRef(obj))
                builder.emit(Opcode.CONST_OBJ, id, slot)
                trackExactCallableObjAtSlot(slot, obj)
                return CompiledValue(slot, SlotType.OBJ)
            }
        }
    }

    private fun emitCallDirect(callee: Obj, argBase: Int, encodedCount: Int, dst: Int) {
        val calleeId = builder.addConst(BytecodeConst.ObjRef(callee))
        builder.emit(Opcode.CALL_DIRECT, calleeId, argBase, encodedCount, dst)
    }

    private fun seedExactCallableForNamedSlot(name: String, slot: Int) {
        if (exactCallableObjBySlot[slot] != null) return
        if (slotObjClass[slot] != ObjClassType) return
        resolveTypeNameClass(name)?.let { trackExactCallableObjAtSlot(slot, it) }
    }

    private fun emitCallCompiled(
        callee: CompiledValue,
        argBase: Int,
        encodedCount: Int,
        dst: Int,
        isExternCall: Boolean = false,
    ) {
        if (!isExternCall) {
            exactCallableObjBySlot[callee.slot]?.let {
                emitCallDirect(it, argBase, encodedCount, dst)
                return
            }
        }
        builder.emit(
            if (isExternCall) Opcode.CALL_BRIDGE_SLOT else Opcode.CALL_SLOT,
            callee.slot,
            argBase,
            encodedCount,
            dst
        )
    }

    private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? {
        if (ref is LambdaFnRef && ref.bytecodeFn != null) {
            val captures = (lambdaCaptureEntriesByRef[ref] ?: ref.captureEntries).orEmpty()
            val captureTableId = if (captures.isEmpty()) {
                null
            } else {
                val resolved = captures.map { entry ->
                    val slotIndex = resolveCaptureSlot(entry)
                    BytecodeCaptureEntry(
                        ownerKind = entry.ownerKind,
                        ownerScopeId = entry.ownerScopeId,
                        ownerSlotId = entry.ownerSlotId,
                        slotIndex = slotIndex
                    )
                }
                builder.addConst(BytecodeConst.CaptureTable(resolved))
            }
            val captureNames = captures.map { it.ownerName }
            val id = builder.addConst(
                BytecodeConst.LambdaFn(
                    fn = ref.bytecodeFn,
                    captureTableId = captureTableId,
                    captureNames = captureNames,
                    paramSlotPlan = ref.paramSlotPlan,
                    argsDeclaration = ref.argsDeclaration,
                    supportsDirectInvokeFastPath = ref.supportsDirectInvokeFastPath,
                    preferredThisType = ref.preferredThisType,
                    wrapAsExtensionCallable = ref.wrapAsExtensionCallable,
                    returnLabels = ref.returnLabels,
                    pos = ref.pos
                )
            )
            val slot = allocSlot()
            builder.emit(Opcode.MAKE_LAMBDA_FN, id, slot)
            updateSlotType(slot, SlotType.OBJ)
            trackExactLambdaAtSlot(slot, ref)
            return CompiledValue(slot, SlotType.OBJ)
        }
        val pos = (ref as? LambdaFnRef)?.pos ?: Pos.builtIn
        val refName = ref::class.simpleName ?: "ValueFnRef"
        throw BytecodeCompileException(
            "Bytecode compile error: non-bytecode lambda $refName encountered",
            pos
        )
    }

    private fun resolveCaptureSlot(entry: LambdaCaptureEntry): Int {
        val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
        return when (entry.ownerKind) {
            CaptureOwnerFrameKind.MODULE -> {
                scopeSlotMap[key]
                    ?: throw BytecodeCompileException("Missing module capture slot for ${entry.ownerScopeId}:${entry.ownerSlotId}", Pos.builtIn)
            }
            CaptureOwnerFrameKind.LOCAL -> {
                val localIndex = localSlotIndexByKey[key]
                if (localIndex != null) {
                    return scopeSlotCount + localIndex
                }
                val captureName = entry.ownerName
                if (captureName.isNotEmpty()) {
                    for (i in localSlotCaptures.indices) {
                        if (!localSlotCaptures[i]) continue
                        if (localSlotNames.getOrNull(i) != captureName) continue
                        return scopeSlotCount + i
                    }
                }
                throw BytecodeCompileException("Missing local capture slot for ${entry.ownerScopeId}:${entry.ownerSlotId}", Pos.builtIn)
            }
        }
    }

    private fun compileEvalRef(ref: ObjRef): CompiledValue? {
        val refInfo = when (ref) {
            is BinaryOpRef -> "BinaryOpRef(${ref.op})"
            is UnaryOpRef -> "UnaryOpRef(${ref.op})"
            else -> ref::class.simpleName ?: "UnknownRef"
        }
        throw BytecodeCompileException("Unsupported expression ($refInfo)", Pos.builtIn)
    }

    private fun compileListLiteral(ref: ListLiteralRef): CompiledValue? {
        val entries = ref.entries()
        val count = entries.size
        val baseSlot = nextSlot
        val entrySlots = IntArray(count) { allocSlot() }
        var elementClass: ObjClass? = null
        var elementClassValid = true
        val spreads = ArrayList<Boolean>(count)
        for ((index, entry) in entries.withIndex()) {
            val value = when (entry) {
                is net.sergeych.lyng.ListEntry.Element ->
                    compileRefWithFallback(entry.ref, null, Pos.builtIn)
                is net.sergeych.lyng.ListEntry.Spread ->
                    compileRefWithFallback(entry.ref, null, Pos.builtIn)
            } ?: return null
            val entryClass = when (entry) {
                is net.sergeych.lyng.ListEntry.Element -> {
                    val constClass = (entry.ref as? ConstRef)?.let { elementClassFromConst(it.constValue) }
                    constClass ?: elementClassFromValue(value)
                }
                is net.sergeych.lyng.ListEntry.Spread -> listElementClassBySlot[value.slot]
            }
            if (entryClass == null) {
                elementClassValid = false
            } else if (elementClass == null) {
                elementClass = entryClass
            } else if (elementClass != entryClass) {
                elementClassValid = false
            }
            emitMove(value, entrySlots[index])
            spreads.add(entry is net.sergeych.lyng.ListEntry.Spread)
        }
        val planId = builder.addConst(BytecodeConst.ListLiteralPlan(spreads))
        val dst = allocSlot()
        builder.emit(Opcode.LIST_LITERAL, planId, baseSlot, count, dst)
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = ObjList.type
        if (elementClassValid && elementClass != null) {
            listElementClassBySlot[dst] = elementClass
        }
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileMapLiteral(ref: MapLiteralRef): CompiledValue? {
        val dst = allocSlot()
        emitCallDirect(ObjMap.type, 0, 0, dst)
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = ObjMap.type
        for (entry in ref.entries()) {
            when (entry) {
                is net.sergeych.lyng.obj.MapLiteralEntry.Named -> {
                    val keyId = builder.addConst(BytecodeConst.StringVal(entry.key))
                    val keySlot = allocSlot()
                    builder.emit(Opcode.CONST_OBJ, keyId, keySlot)
                    val value = compileRefWithFallback(entry.value, null, Pos.builtIn) ?: return null
                    builder.emit(Opcode.SET_INDEX, dst, keySlot, value.slot)
                }
                is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> {
                    if (entry.ref is ListLiteralRef) {
                        throw BytecodeCompileException(
                            "spread element in map literal must be a Map",
                            Pos.builtIn
                        )
                    }
                    val value = compileRefWithFallback(entry.ref, null, Pos.builtIn) ?: return null
                    val mapClassId = builder.addConst(BytecodeConst.ObjRef(ObjMap.type))
                    val mapClassSlot = allocSlot()
                    builder.emit(Opcode.CONST_OBJ, mapClassId, mapClassSlot)
                    val checkSlot = allocSlot()
                    builder.emit(Opcode.CHECK_IS, value.slot, mapClassSlot, checkSlot)
                    val okLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(checkSlot), CmdBuilder.Operand.LabelRef(okLabel))
                    )
                    val msgId = builder.addConst(BytecodeConst.StringVal("spread element in map literal must be a Map"))
                    val msgSlot = allocSlot()
                    builder.emit(Opcode.CONST_OBJ, msgId, msgSlot)
                    val posId = builder.addConst(BytecodeConst.PosVal(Pos.builtIn))
                    builder.emit(Opcode.THROW, posId, msgSlot)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(okLabel)
                    val mergedSlot = allocSlot()
                    builder.emit(Opcode.ADD_OBJ, dst, value.slot, mergedSlot)
                    builder.emit(Opcode.MOVE_OBJ, mergedSlot, dst)
                    builder.mark(endLabel)
                }
            }
        }
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileCast(ref: CastRef): CompiledValue? {
        val value = compileRefWithFallback(ref.castValueRef(), null, ref.castPos()) ?: return null
        val typeValue = compileRefWithFallback(ref.castTypeRef(), null, ref.castPos()) ?: return null
        val objValue = ensureObjSlot(value)
        val typeObj = ensureObjSlot(typeValue)
        val sourceSlot = allocSlot()
        builder.emit(Opcode.MOVE_OBJ, objValue.slot, sourceSlot)
        updateSlotType(sourceSlot, SlotType.OBJ)
        if (!ref.castIsNullable()) {
            builder.emit(Opcode.ASSERT_IS, sourceSlot, typeObj.slot)
            val resultSlot = allocSlot()
            builder.emit(Opcode.MAKE_QUALIFIED_VIEW, sourceSlot, typeObj.slot, resultSlot)
            updateSlotType(resultSlot, SlotType.OBJ)
            return CompiledValue(resultSlot, SlotType.OBJ)
        }
        val checkSlot = allocSlot()
        builder.emit(Opcode.CHECK_IS, sourceSlot, typeObj.slot, checkSlot)
        updateSlotType(checkSlot, SlotType.BOOL)
        val resultSlot = allocSlot()
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val okLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(checkSlot), CmdBuilder.Operand.LabelRef(okLabel))
        )
        builder.emit(Opcode.MOVE_OBJ, nullSlot, resultSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(okLabel)
        builder.emit(Opcode.MAKE_QUALIFIED_VIEW, sourceSlot, typeObj.slot, resultSlot)
        builder.mark(endLabel)
        updateSlotType(resultSlot, SlotType.OBJ)
        return CompiledValue(resultSlot, SlotType.OBJ)
    }

    private fun compileUnary(ref: UnaryOpRef): CompiledValue? {
        return when (unaryOp(ref)) {
            UnaryOp.POSITIVE -> {
                val operandRef = unaryOperand(ref)
                if (hasUnaryCallable(operandRef, "unaryPlus")) {
                    return compileMethodCall(MethodCallRef(operandRef, "unaryPlus", emptyList(), false, false))
                }
                val a = compileRef(operandRef) ?: return null
                return when (a.type) {
                    SlotType.INT, SlotType.REAL -> a
                    else -> {
                        val obj = ensureObjSlot(a)
                        val out = allocSlot()
                        builder.emit(Opcode.POS_OBJ, obj.slot, out)
                        updateSlotType(out, SlotType.OBJ)
                        slotObjClass[obj.slot]?.let { slotObjClass[out] = it }
                        CompiledValue(out, SlotType.OBJ)
                    }
                }
            }
            else -> {
                val a = compileRef(unaryOperand(ref)) ?: return null
                val out = allocSlot()
                when (unaryOp(ref)) {
                    UnaryOp.NEGATE -> when (a.type) {
                        SlotType.INT -> {
                            builder.emit(Opcode.NEG_INT, a.slot, out)
                            CompiledValue(out, SlotType.INT)
                        }
                        SlotType.REAL -> {
                            builder.emit(Opcode.NEG_REAL, a.slot, out)
                            CompiledValue(out, SlotType.REAL)
                        }
                        else -> compileObjUnaryOp(unaryOperand(ref), a, "negate", Pos.builtIn)
                    }
                    UnaryOp.NOT -> {
                        when (a.type) {
                            SlotType.BOOL -> builder.emit(Opcode.NOT_BOOL, a.slot, out)
                            SlotType.INT -> {
                                val tmp = allocSlot()
                                builder.emit(Opcode.INT_TO_BOOL, a.slot, tmp)
                                builder.emit(Opcode.NOT_BOOL, tmp, out)
                            }
                            SlotType.OBJ, SlotType.UNKNOWN -> {
                                val objSlot = ensureObjSlot(a)
                                val tmp = allocSlot()
                                builder.emit(Opcode.OBJ_TO_BOOL, objSlot.slot, tmp)
                                builder.emit(Opcode.NOT_BOOL, tmp, out)
                                updateSlotType(tmp, SlotType.BOOL)
                            }
                            else -> return null
                        }
                        CompiledValue(out, SlotType.BOOL)
                    }
                    UnaryOp.BITNOT -> {
                        if (a.type == SlotType.INT) {
                            builder.emit(Opcode.INV_INT, a.slot, out)
                            return CompiledValue(out, SlotType.INT)
                        }
                        return compileObjUnaryOp(unaryOperand(ref), a, "bitNot", Pos.builtIn)
                    }
                    UnaryOp.POSITIVE -> error("unreachable")
                }
            }
        }
    }

    private fun hasUnaryCallable(ref: ObjRef, memberName: String): Boolean {
        val receiverClass = resolveReceiverClass(ref) ?: return false
        if (receiverClass == ObjDynamic.type) return false
        if (receiverClass is ObjInstanceClass && !isThisReceiver(ref)) return true
        val resolvedMember = receiverClass.resolveInstanceMember(memberName)
        if (resolvedMember?.declaringClass?.className == "Obj") return false
        val abstractRecord = receiverClass.members[memberName] ?: receiverClass.classScope?.objects?.get(memberName)
        if (abstractRecord?.isAbstract == true) return false
        val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[memberName]
        if (methodId != null && resolvedMember?.declaringClass?.className != "Obj") return true
        val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[memberName] else null
        if (fieldId != null) return true
        return resolveExtensionCallableSlot(receiverClass, memberName) != null
    }

    private fun compileObjUnaryOp(
        ref: ObjRef,
        value: CompiledValue,
        memberName: String,
        pos: Pos,
        defaultIdentity: Boolean = false
    ): CompiledValue? {
        val receiverClass = resolveReceiverClass(ref) ?: slotObjClass[value.slot]
        val methodId = receiverClass?.instanceMethodIdMap(includeAbstract = true)?.get(memberName)
        if (methodId != null) {
            val receiverObj = ensureObjSlot(value)
            val dst = allocSlot()
            builder.emit(Opcode.CALL_MEMBER_SLOT, receiverObj.slot, methodId, 0, 0, dst)
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val extSlot = when {
            receiverClass != null -> resolveExtensionCallableSlot(receiverClass, memberName)
            else -> resolveUniqueExtensionWrapperSlot(memberName, "__ext__")
        }
        if (extSlot != null) {
            val callee = ensureObjSlot(extSlot)
            val args = compileCallArgsWithReceiver(value, emptyList(), false) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            val dst = allocSlot()
            setPos(pos)
            emitCallCompiled(callee, args.base, encodedCount, dst)
            return CompiledValue(dst, SlotType.OBJ)
        }
        if (memberName == "negate" &&
            (receiverClass == null || isDelegateClass(receiverClass) || receiverClass in setOf(ObjInt.type, ObjReal.type))
        ) {
            val zeroId = builder.addConst(BytecodeConst.IntVal(0))
            val zeroSlot = allocSlot()
            builder.emit(Opcode.CONST_INT, zeroId, zeroSlot)
            updateSlotType(zeroSlot, SlotType.INT)
            val obj = ensureObjSlot(value)
            val dst = allocSlot()
            builder.emit(Opcode.SUB_OBJ, zeroSlot, obj.slot, dst)
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        if (defaultIdentity) {
            return value
        }
        throw BytecodeCompileException(
            "Unknown member $memberName on ${receiverClass?.className ?: "unknown"}",
            pos
        )
    }

    private fun isDelegateClass(receiverClass: ObjClass): Boolean =
        receiverClass.className == "Delegate" ||
            receiverClass.implementingNames.contains("Delegate")

    private fun operatorMemberName(op: BinOp): String? = when (op) {
        BinOp.PLUS -> "plus"
        BinOp.MINUS -> "minus"
        BinOp.STAR -> "mul"
        BinOp.SLASH -> "div"
        BinOp.PERCENT -> "mod"
        BinOp.BAND -> "bitAnd"
        BinOp.BOR -> "bitOr"
        BinOp.BXOR -> "bitXor"
        BinOp.SHL -> "shl"
        BinOp.SHR -> "shr"
        else -> null
    }

    private fun allowKotlinOperatorFallback(receiverClass: ObjClass, op: BinOp): Boolean = when (op) {
        BinOp.PLUS -> receiverClass in setOf(
            ObjString.type,
            ObjInt.type,
            ObjReal.type,
            ObjList.type,
            ObjSet.type,
            ObjMap.type,
            ObjBuffer.type,
            ObjInstant.type,
            ObjDateTime.type,
            ObjDate.type
        )
        BinOp.MINUS -> receiverClass in setOf(
            ObjInt.type,
            ObjReal.type,
            ObjSet.type,
            ObjInstant.type,
            ObjDateTime.type,
            ObjDate.type
        )
        BinOp.STAR -> receiverClass in setOf(ObjInt.type, ObjReal.type, ObjString.type)
        BinOp.SLASH, BinOp.PERCENT -> receiverClass in setOf(ObjInt.type, ObjReal.type)
        else -> false
    }

    private fun compileObjBinaryOp(
        leftRef: ObjRef,
        leftValue: CompiledValue,
        rightValue: CompiledValue,
        op: BinOp,
        pos: Pos
    ): CompiledValue? {
        val memberName = operatorMemberName(op) ?: return null
        val receiverClass = resolveReceiverClass(leftRef)
        if (receiverClass == null || isDelegateClass(receiverClass)) {
            val objOpcode = when (op) {
                BinOp.PLUS -> Opcode.ADD_OBJ
                BinOp.MINUS -> Opcode.SUB_OBJ
                BinOp.STAR -> Opcode.MUL_OBJ
                BinOp.SLASH -> Opcode.DIV_OBJ
                BinOp.PERCENT -> Opcode.MOD_OBJ
                else -> null
            }
            if (objOpcode != null) {
                val receiverObj = ensureObjSlot(leftValue)
                val argObj = ensureObjSlot(rightValue)
                val dst = allocSlot()
                emitObjArithmetic(objOpcode, receiverObj.slot, argObj.slot, dst)
                updateSlotType(dst, SlotType.OBJ)
                return CompiledValue(dst, SlotType.OBJ)
            }
            throw BytecodeCompileException(
                "Operator requires compile-time receiver type: $memberName",
                pos
            )
        }
        val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[memberName]
        if (methodId != null) {
            val receiverObj = ensureObjSlot(leftValue)
            val argObj = ensureObjSlot(rightValue)
            val dst = allocSlot()
            builder.emit(Opcode.CALL_MEMBER_SLOT, receiverObj.slot, methodId, argObj.slot, 1, dst)
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val objOpcode = when (op) {
            BinOp.PLUS -> Opcode.ADD_OBJ
            BinOp.MINUS -> Opcode.SUB_OBJ
            BinOp.STAR -> Opcode.MUL_OBJ
            BinOp.SLASH -> Opcode.DIV_OBJ
            BinOp.PERCENT -> Opcode.MOD_OBJ
            else -> null
        }
        if (objOpcode != null && allowKotlinOperatorFallback(receiverClass, op)) {
            val receiverObj = ensureObjSlot(leftValue)
            val argObj = ensureObjSlot(rightValue)
            val dst = allocSlot()
            emitObjArithmetic(objOpcode, receiverObj.slot, argObj.slot, dst)
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        throw BytecodeCompileException(
            "Unknown member $memberName on ${receiverClass.className}",
            pos
        )
    }

    private fun compileBinary(ref: BinaryOpRef): CompiledValue? {
        val op = binaryOp(ref)
        if (op == BinOp.AND || op == BinOp.OR) {
            return compileLogical(op, binaryLeft(ref), binaryRight(ref), refPos(ref))
        }
        if (op == BinOp.EQARROW) {
            val leftValue = compileRefWithFallback(binaryLeft(ref), null, refPos(ref)) ?: return null
            val rightValue = compileRefWithFallback(binaryRight(ref), null, refPos(ref)) ?: return null
            val leftObj = ensureObjSlot(leftValue)
            val rightObj = ensureObjSlot(rightValue)
            val argBase = nextSlot
            val argLeft = allocSlot()
            emitMove(leftObj, argLeft)
            val argRight = allocSlot()
            emitMove(rightObj, argRight)
            val dst = allocSlot()
            emitCallDirect(ObjMapEntry.type, argBase, 2, dst)
            updateSlotType(dst, SlotType.OBJ)
            slotObjClass[dst] = ObjMapEntry.type
            return CompiledValue(dst, SlotType.OBJ)
        }
        if (op == BinOp.REF_EQ || op == BinOp.REF_NEQ) {
            val leftValue = compileRefWithFallback(binaryLeft(ref), null, refPos(ref)) ?: return null
            val rightValue = compileRefWithFallback(binaryRight(ref), null, refPos(ref)) ?: return null
            val leftObj = ensureObjSlot(leftValue)
            val rightObj = ensureObjSlot(rightValue)
            val dst = allocSlot()
            val opcode = if (op == BinOp.REF_EQ) Opcode.CMP_REF_EQ_OBJ else Opcode.CMP_REF_NEQ_OBJ
            builder.emit(opcode, leftObj.slot, rightObj.slot, dst)
            updateSlotType(dst, SlotType.BOOL)
            return CompiledValue(dst, SlotType.BOOL)
        }
        if (op == BinOp.SHUTTLE) {
            val leftValue = compileRefWithFallback(binaryLeft(ref), null, refPos(ref)) ?: return null
            val rightValue = compileRefWithFallback(binaryRight(ref), null, refPos(ref)) ?: return null
            val leftObj = ensureObjSlot(leftValue)
            val rightObj = ensureObjSlot(rightValue)
            val resultSlot = allocSlot()
            val endLabel = builder.label()

            val eqSlot = allocSlot()
            compileCompareObjSpecial(
                Opcode.CMP_EQ_OBJ,
                Opcode.CMP_EQ_STR,
                Opcode.CMP_EQ_INT_OBJ,
                Opcode.CMP_EQ_REAL_OBJ,
                leftObj,
                rightObj,
                eqSlot
            )
            val eqLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(eqSlot), CmdBuilder.Operand.LabelRef(eqLabel))
            )

            val ltSlot = allocSlot()
            compileCompareObjSpecial(
                Opcode.CMP_LT_OBJ,
                Opcode.CMP_LT_STR,
                Opcode.CMP_LT_INT_OBJ,
                Opcode.CMP_LT_REAL_OBJ,
                leftObj,
                rightObj,
                ltSlot
            )
            val ltLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(ltSlot), CmdBuilder.Operand.LabelRef(ltLabel))
            )

            val gtSlot = allocSlot()
            compileCompareObjSpecial(
                Opcode.CMP_GT_OBJ,
                Opcode.CMP_GT_STR,
                Opcode.CMP_GT_INT_OBJ,
                Opcode.CMP_GT_REAL_OBJ,
                leftObj,
                rightObj,
                gtSlot
            )
            val gtLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(gtSlot), CmdBuilder.Operand.LabelRef(gtLabel))
            )

            val minusThreeId = builder.addConst(BytecodeConst.IntVal(-3))
            builder.emit(Opcode.CONST_INT, minusThreeId, resultSlot)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))

            builder.mark(eqLabel)
            val zeroId = builder.addConst(BytecodeConst.IntVal(0))
            builder.emit(Opcode.CONST_INT, zeroId, resultSlot)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))

            builder.mark(ltLabel)
            val minusOneId = builder.addConst(BytecodeConst.IntVal(-1))
            builder.emit(Opcode.CONST_INT, minusOneId, resultSlot)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))

            builder.mark(gtLabel)
            val oneId = builder.addConst(BytecodeConst.IntVal(1))
            builder.emit(Opcode.CONST_INT, oneId, resultSlot)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))

            builder.mark(endLabel)
            updateSlotType(resultSlot, SlotType.INT)
            return CompiledValue(resultSlot, SlotType.INT)
        }
        if (op == BinOp.IN || op == BinOp.NOTIN) {
            val leftValue = compileRefWithFallback(binaryLeft(ref), null, refPos(ref)) ?: return null
            val rightValue = compileRefWithFallback(binaryRight(ref), null, refPos(ref)) ?: return null
            val leftObj = ensureObjSlot(leftValue)
            val rightObj = ensureObjSlot(rightValue)
            val boolSlot = allocSlot()
            builder.emit(Opcode.CONTAINS_OBJ, rightObj.slot, leftObj.slot, boolSlot)
            updateSlotType(boolSlot, SlotType.BOOL)
            if (op == BinOp.NOTIN) {
                val outSlot = allocSlot()
                builder.emit(Opcode.NOT_BOOL, boolSlot, outSlot)
                updateSlotType(outSlot, SlotType.BOOL)
                return CompiledValue(outSlot, SlotType.BOOL)
            }
            return CompiledValue(boolSlot, SlotType.BOOL)
        }
        if (op == BinOp.MATCH || op == BinOp.NOTMATCH) {
            val leftRef = binaryLeft(ref)
            val rightRef = binaryRight(ref)
            val receiverClass = resolveReceiverClass(leftRef) ?: throw BytecodeCompileException(
                "Match operator requires compile-time receiver type",
                refPos(ref)
            )
            val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)["operatorMatch"]
                ?: throw BytecodeCompileException(
                    "Unknown member operatorMatch on ${receiverClass.className}",
                    refPos(ref)
                )
            val receiver = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null
            val rightValue = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null
            val receiverObj = ensureObjSlot(receiver)
            val argObj = ensureObjSlot(rightValue)
            val dst = allocSlot()
            builder.emit(Opcode.CALL_MEMBER_SLOT, receiverObj.slot, methodId, argObj.slot, 1, dst)
            updateSlotType(dst, SlotType.OBJ)
            val boolSlot = allocSlot()
            builder.emit(Opcode.OBJ_TO_BOOL, dst, boolSlot)
            updateSlotType(boolSlot, SlotType.BOOL)
            if (op == BinOp.NOTMATCH) {
                val outSlot = allocSlot()
                builder.emit(Opcode.NOT_BOOL, boolSlot, outSlot)
                updateSlotType(outSlot, SlotType.BOOL)
                return CompiledValue(outSlot, SlotType.BOOL)
            }
            return CompiledValue(boolSlot, SlotType.BOOL)
        }
        if (op == BinOp.IS || op == BinOp.NOTIS) {
            val objValue = compileRefWithFallback(binaryLeft(ref), null, refPos(ref)) ?: return null
            val typeValue = compileRefWithFallback(binaryRight(ref), null, refPos(ref)) ?: return null
            val objSlot = ensureObjSlot(objValue)
            val typeSlot = ensureObjSlot(typeValue)
            val checkSlot = allocSlot()
            builder.emit(Opcode.CHECK_IS, objSlot.slot, typeSlot.slot, checkSlot)
            updateSlotType(checkSlot, SlotType.BOOL)
            if (op == BinOp.NOTIS) {
                val outSlot = allocSlot()
                builder.emit(Opcode.NOT_BOOL, checkSlot, outSlot)
                updateSlotType(outSlot, SlotType.BOOL)
                return CompiledValue(outSlot, SlotType.BOOL)
            }
            return CompiledValue(checkSlot, SlotType.BOOL)
        }
        val leftRef = binaryLeft(ref)
        val rightRef = binaryRight(ref)
        var a = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null
        var b = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null
        val intOps = setOf(
            BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
            BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
        )
        val intOnlyOps = setOf(BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR)
        if (op in intOnlyOps) {
            coerceToArithmeticInt(leftRef, a)?.let { a = it }
            coerceToArithmeticInt(rightRef, b)?.let { b = it }
            if (a.type == SlotType.OBJ) {
                val intSlot = allocSlot()
                builder.emit(Opcode.UNBOX_INT_OBJ, emitAssertObjSlotIsInt(a.slot), intSlot)
                updateSlotType(intSlot, SlotType.INT)
                a = CompiledValue(intSlot, SlotType.INT)
            }
            if (b.type == SlotType.OBJ) {
                val intSlot = allocSlot()
                builder.emit(Opcode.UNBOX_INT_OBJ, emitAssertObjSlotIsInt(b.slot), intSlot)
                updateSlotType(intSlot, SlotType.INT)
                b = CompiledValue(intSlot, SlotType.INT)
            }
        }
        val leftIsLoopVar = (leftRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
        val rightIsLoopVar = (rightRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
        if (a.type == SlotType.UNKNOWN && b.type == SlotType.INT && op in intOps && leftIsLoopVar) {
            updateSlotType(a.slot, SlotType.INT)
            a = CompiledValue(a.slot, SlotType.INT)
        }
        if (b.type == SlotType.UNKNOWN && a.type == SlotType.INT && op in intOps && rightIsLoopVar) {
            updateSlotType(b.slot, SlotType.INT)
            b = CompiledValue(b.slot, SlotType.INT)
        }
        if (a.type == SlotType.UNKNOWN && b.type == SlotType.UNKNOWN && op in intOps && leftIsLoopVar && rightIsLoopVar) {
            updateSlotType(a.slot, SlotType.INT)
            updateSlotType(b.slot, SlotType.INT)
            a = CompiledValue(a.slot, SlotType.INT)
            b = CompiledValue(b.slot, SlotType.INT)
        }
        val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
        val allowMixedNumeric = op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH)
        val isMixedNumeric = (a.type == SlotType.INT && b.type == SlotType.REAL) ||
            (a.type == SlotType.REAL && b.type == SlotType.INT)
        if (typesMismatch && op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT) &&
            !(allowMixedNumeric && isMixedNumeric)
        ) {
            return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
        }
        if ((a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) &&
            op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)
        ) {
            return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
        }
        if (typesMismatch && !allowMixedNumeric &&
            op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)
        ) {
            return null
        }
        val out = allocSlot()
        return when (op) {
            BinOp.PLUS -> when (a.type) {
                SlotType.INT -> {
                    when (b.type) {
                        SlotType.INT -> {
                            builder.emit(Opcode.ADD_INT, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.INT)
                        }
                        SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.REAL -> {
                    when (b.type) {
                        SlotType.REAL -> {
                            builder.emit(Opcode.ADD_REAL, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.REAL)
                        }
                        SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.OBJ -> {
                    if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
                    compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                }
                else -> null
            }
            BinOp.MINUS -> when (a.type) {
                SlotType.INT -> {
                    when (b.type) {
                        SlotType.INT -> {
                            builder.emit(Opcode.SUB_INT, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.INT)
                        }
                        SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.REAL -> {
                    when (b.type) {
                        SlotType.REAL -> {
                            builder.emit(Opcode.SUB_REAL, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.REAL)
                        }
                        SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.OBJ -> {
                    if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
                    compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                }
                else -> null
            }
            BinOp.STAR -> when (a.type) {
                SlotType.INT -> {
                    when (b.type) {
                        SlotType.INT -> {
                            builder.emit(Opcode.MUL_INT, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.INT)
                        }
                        SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.REAL -> {
                    when (b.type) {
                        SlotType.REAL -> {
                            builder.emit(Opcode.MUL_REAL, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.REAL)
                        }
                        SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.OBJ -> {
                    if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
                    compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                }
                else -> null
            }
            BinOp.SLASH -> when (a.type) {
                SlotType.INT -> {
                    when (b.type) {
                        SlotType.INT -> {
                            builder.emit(Opcode.DIV_INT, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.INT)
                        }
                        SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.REAL -> {
                    when (b.type) {
                        SlotType.REAL -> {
                            builder.emit(Opcode.DIV_REAL, a.slot, b.slot, out)
                            CompiledValue(out, SlotType.REAL)
                        }
                        SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
                        SlotType.OBJ -> null
                        else -> null
                    }
                }
                SlotType.OBJ -> {
                    if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
                    compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                }
                else -> null
            }
            BinOp.PERCENT -> {
                return when (a.type) {
                    SlotType.INT -> {
                        if (b.type != SlotType.INT) return null
                        builder.emit(Opcode.MOD_INT, a.slot, b.slot, out)
                        CompiledValue(out, SlotType.INT)
                    }
                    SlotType.OBJ -> {
                        if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
                        compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                    }
                    else -> null
                }
            }
            BinOp.EQ -> {
                compileCompareEq(a, b, out)
            }
            BinOp.NEQ -> {
                compileCompareNeq(a, b, out)
            }
            BinOp.LT -> {
                compileCompareLt(a, b, out)
            }
            BinOp.LTE -> {
                compileCompareLte(a, b, out)
            }
            BinOp.GT -> {
                compileCompareGt(a, b, out)
            }
            BinOp.GTE -> {
                compileCompareGte(a, b, out)
            }
            BinOp.REF_EQ -> {
                if (a.type != SlotType.OBJ || b.type != SlotType.OBJ) return null
                builder.emit(Opcode.CMP_REF_EQ_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            BinOp.REF_NEQ -> {
                if (a.type != SlotType.OBJ || b.type != SlotType.OBJ) return null
                builder.emit(Opcode.CMP_REF_NEQ_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            BinOp.AND -> {
                if (a.type != SlotType.BOOL) return null
                builder.emit(Opcode.AND_BOOL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            BinOp.OR -> {
                if (a.type != SlotType.BOOL) return null
                builder.emit(Opcode.OR_BOOL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            BinOp.BAND -> {
                when (a.type) {
                    SlotType.INT -> {
                        builder.emit(Opcode.AND_INT, a.slot, b.slot, out)
                        CompiledValue(out, SlotType.INT)
                    }
                    SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                    else -> null
                }
            }
            BinOp.BOR -> {
                when (a.type) {
                    SlotType.INT -> {
                        builder.emit(Opcode.OR_INT, a.slot, b.slot, out)
                        CompiledValue(out, SlotType.INT)
                    }
                    SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                    else -> null
                }
            }
            BinOp.BXOR -> {
                when (a.type) {
                    SlotType.INT -> {
                        builder.emit(Opcode.XOR_INT, a.slot, b.slot, out)
                        CompiledValue(out, SlotType.INT)
                    }
                    SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                    else -> null
                }
            }
            BinOp.SHL -> {
                when (a.type) {
                    SlotType.INT -> {
                        builder.emit(Opcode.SHL_INT, a.slot, b.slot, out)
                        CompiledValue(out, SlotType.INT)
                    }
                    SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                    else -> null
                }
            }
            BinOp.SHR -> {
                when (a.type) {
                    SlotType.INT -> {
                        builder.emit(Opcode.SHR_INT, a.slot, b.slot, out)
                        CompiledValue(out, SlotType.INT)
                    }
                    SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
                    else -> null
                }
            }
            //else -> null
        }
    }

    private fun compileRealArithmeticWithCoercion(
        op: Opcode,
        a: CompiledValue,
        b: CompiledValue,
        out: Int
    ): CompiledValue? {
        if (a.type == SlotType.INT && b.type == SlotType.REAL) {
            val left = allocSlot()
            builder.emit(Opcode.INT_TO_REAL, a.slot, left)
            builder.emit(op, left, b.slot, out)
            return CompiledValue(out, SlotType.REAL)
        }
        if (a.type == SlotType.REAL && b.type == SlotType.INT) {
            val right = allocSlot()
            builder.emit(Opcode.INT_TO_REAL, b.slot, right)
            builder.emit(op, a.slot, right, out)
            return CompiledValue(out, SlotType.REAL)
        }
        return null
    }

    private fun compileCompareObjSpecial(
        objOp: Opcode,
        stringOp: Opcode,
        intOp: Opcode,
        realOp: Opcode,
        a: CompiledValue,
        b: CompiledValue,
        out: Int
    ): CompiledValue {
        val left = ensureObjSlot(a)
        val right = ensureObjSlot(b)
        val opcode = when {
            isExactNonNullSlotClassOrTemp(left.slot, ObjString.type) &&
                isExactNonNullSlotClassOrTemp(right.slot, ObjString.type) -> stringOp
            isExactNonNullSlotClassOrTemp(left.slot, ObjInt.type) &&
                isExactNonNullSlotClassOrTemp(right.slot, ObjInt.type) -> intOp
            isExactNonNullSlotClassOrTemp(left.slot, ObjReal.type) &&
                isExactNonNullSlotClassOrTemp(right.slot, ObjReal.type) -> realOp
            else -> objOp
        }
        builder.emit(opcode, left.slot, right.slot, out)
        return CompiledValue(out, SlotType.BOOL)
    }

    private fun isExactNonNullSlotClass(slot: Int, expected: ObjClass): Boolean {
        if (slotObjClass[slot] != expected) return false
        if (!expected.isClosed) return false
        val decl = typeDeclForSlot(slot) ?: return false
        if (decl.isNullable) return false
        val declClass = when (decl) {
            is TypeDecl.Simple -> resolveTypeNameClass(decl.name)
            is TypeDecl.Generic -> resolveTypeNameClass(decl.name)
            else -> null
        }
        return declClass == expected
    }

    private fun isExactNonNullSlotClassOrTemp(slot: Int, expected: ObjClass): Boolean {
        if (slotObjClass[slot] != expected) return false
        if (!expected.isClosed) return false
        val decl = typeDeclForSlot(slot)
        if (decl == null) {
            if (slot < scopeSlotCount) return false
            val localIndex = slot - scopeSlotCount
            return localSlotKeyByIndex.getOrNull(localIndex) == null && stableObjSlots.contains(slot)
        }
        if (decl.isNullable) return false
        val declClass = when (decl) {
            is TypeDecl.Simple -> resolveTypeNameClass(decl.name)
            is TypeDecl.Generic -> resolveTypeNameClass(decl.name)
            else -> null
        }
        return declClass == expected
    }

    private fun isTempSlot(slot: Int): Boolean {
        if (slot < scopeSlotCount) return false
        val localIndex = slot - scopeSlotCount
        return localSlotKeyByIndex.getOrNull(localIndex) == null
    }

    private fun isStablePrimitiveSourceSlot(slot: Int): Boolean {
        if (isTempSlot(slot)) return true
        if (slot < scopeSlotCount) return false
        val localIndex = slot - scopeSlotCount
        val key = localSlotKeyByIndex.getOrNull(localIndex) ?: return true
        val info = localSlotInfoMap[key] ?: return false
        return !info.isMutable
    }

    private enum class NumericKind {
        INT,
        REAL,
        UNKNOWN
    }

    private fun numericKindFromSlot(slot: Int): NumericKind {
        return when (slotTypes[slot]) {
            SlotType.INT -> NumericKind.INT
            SlotType.REAL -> NumericKind.REAL
            else -> when (slotObjClass[slot]) {
                ObjInt.type -> NumericKind.INT
                ObjReal.type -> NumericKind.REAL
                else -> NumericKind.UNKNOWN
            }
        }
    }

    private fun numericKindFromConst(value: Obj): NumericKind {
        return when (value) {
            is ObjInt -> NumericKind.INT
            is ObjReal -> NumericKind.REAL
            else -> NumericKind.UNKNOWN
        }
    }

    private fun inferNumericKind(ref: ObjRef): NumericKind {
        return when (ref) {
            is ConstRef -> numericKindFromConst(ref.constValue)
            is LocalVarRef -> resolveDirectNameSlot(ref.name)?.let { numericKindFromSlot(it.slot) } ?: NumericKind.UNKNOWN
            is FastLocalVarRef -> resolveDirectNameSlot(ref.name)?.let { numericKindFromSlot(it.slot) } ?: NumericKind.UNKNOWN
            is LocalSlotRef -> resolveLocalSlotByRefOrName(ref)?.let { numericKindFromSlot(it) } ?: NumericKind.UNKNOWN
            is IndexRef -> {
                val receiver = when (val target = ref.targetRef) {
                    is LocalSlotRef -> resolveLocalSlotByRefOrName(target)
                    is LocalVarRef -> resolveDirectNameSlot(target.name)?.slot
                    is FastLocalVarRef -> resolveDirectNameSlot(target.name)?.slot
                    else -> null
                }
                val elementClass = receiver?.let { listElementClassBySlot[it] } ?: listElementClassFromReceiverRef(ref.targetRef)
                when (elementClass) {
                    ObjInt.type -> NumericKind.INT
                    ObjReal.type -> NumericKind.REAL
                    else -> NumericKind.UNKNOWN
                }
            }
            is UnaryOpRef -> inferNumericKind(ref.a)
            is BinaryOpRef -> {
                val op = ref.op
                if (op == BinOp.PLUS || op == BinOp.MINUS || op == BinOp.STAR || op == BinOp.SLASH || op == BinOp.PERCENT) {
                    val left = inferNumericKind(ref.left)
                    val right = inferNumericKind(ref.right)
                    when {
                        left == NumericKind.REAL || right == NumericKind.REAL -> NumericKind.REAL
                        left == NumericKind.INT && right == NumericKind.INT -> NumericKind.INT
                        else -> NumericKind.UNKNOWN
                    }
                } else {
                    NumericKind.UNKNOWN
                }
            }
            is ConditionalRef -> {
                val a = inferNumericKind(ref.ifTrue)
                val b = inferNumericKind(ref.ifFalse)
                when {
                    a == NumericKind.REAL && b == NumericKind.REAL -> NumericKind.REAL
                    a == NumericKind.INT && b == NumericKind.INT -> NumericKind.INT
                    else -> NumericKind.UNKNOWN
                }
            }
            is ElvisRef -> {
                val a = inferNumericKind(ref.left)
                val b = inferNumericKind(ref.right)
                when {
                    a == NumericKind.REAL && b == NumericKind.REAL -> NumericKind.REAL
                    a == NumericKind.INT && b == NumericKind.INT -> NumericKind.INT
                    else -> NumericKind.UNKNOWN
                }
            }
            is StatementRef -> inferNumericKindFromStatement(ref.statement)
            else -> NumericKind.UNKNOWN
        }
    }

    private fun inferNumericKindFromStatement(stmt: Statement): NumericKind {
        val target = if (stmt is BytecodeStatement) stmt.original else stmt
        return when (target) {
            is ExpressionStatement -> inferNumericKind(target.ref)
            else -> NumericKind.UNKNOWN
        }
    }

    private fun resolveAssignableSlot(ref: ObjRef): Int? {
        return when (ref) {
            is LocalSlotRef -> resolveSlot(ref)
            is LocalVarRef -> resolveAssignableSlotByName(ref.name)?.first
            is FastLocalVarRef -> resolveAssignableSlotByName(ref.name)?.first
            else -> null
        }
    }

    private fun collectLoopRealWidenSlots(stmt: Statement): Set<Int> {
        val widened = linkedSetOf<Int>()

        fun noteAssignment(target: ObjRef, value: ObjRef) {
            val slot = resolveAssignableSlot(target) ?: return
            if (numericKindFromSlot(slot) != NumericKind.INT) return
            if (inferNumericKind(value) == NumericKind.REAL) {
                widened.add(slot)
            }
        }

        lateinit var scanStatement: (Statement) -> Unit
        lateinit var scanRef: (ObjRef) -> Unit

        scanRef = { ref ->
            when (ref) {
                is AssignRef -> noteAssignment(ref.target, ref.value)
                is AssignOpRef -> noteAssignment(ref.target, ref.value)
                is IncDecRef -> {}
                is UnaryOpRef -> scanRef(ref.a)
                is BinaryOpRef -> {
                    scanRef(ref.left)
                    scanRef(ref.right)
                }
                is ConditionalRef -> {
                    scanRef(ref.condition)
                    scanRef(ref.ifTrue)
                    scanRef(ref.ifFalse)
                }
                is ElvisRef -> {
                    scanRef(ref.left)
                    scanRef(ref.right)
                }
                is LogicalAndRef -> {
                    scanRef(ref.left())
                    scanRef(ref.right())
                }
                is LogicalOrRef -> {
                    scanRef(ref.left())
                    scanRef(ref.right())
                }
                is CastRef -> scanRef(ref.castValueRef())
                is RangeRef -> {
                    ref.left?.let { scanRef(it) }
                    ref.right?.let { scanRef(it) }
                    ref.step?.let { scanRef(it) }
                }
                is CallRef -> {
                    scanRef(ref.target)
                    ref.args.forEach { arg ->
                        (arg.value as? ObjRef)?.let { scanRef(it) }
                    }
                }
                is MethodCallRef -> {
                    scanRef(ref.receiver)
                    ref.args.forEach { arg ->
                        (arg.value as? ObjRef)?.let { scanRef(it) }
                    }
                }
                is FieldRef -> scanRef(ref.target)
                is IndexRef -> {
                    scanRef(ref.targetRef)
                    scanRef(ref.indexRef)
                }
                is StatementRef -> scanStatement(ref.statement)
                is ListLiteralRef -> ref.entries().forEach { entry ->
                    when (entry) {
                        is ListEntry.Element -> scanRef(entry.ref)
                        is ListEntry.Spread -> scanRef(entry.ref)
                    }
                }
                is MapLiteralRef -> ref.entries().forEach { entry ->
                    when (entry) {
                        is MapLiteralEntry.Named -> scanRef(entry.value)
                        is MapLiteralEntry.Spread -> scanRef(entry.ref)
                    }
                }
                else -> {}
            }
        }

        scanStatement = { statement ->
            val target = if (statement is BytecodeStatement) statement.original else statement
            when (target) {
                is ExpressionStatement -> scanRef(target.ref)
                is BlockStatement -> target.statements().forEach { scanStatement(it) }
                is InlineBlockStatement -> target.statements().forEach { scanStatement(it) }
                is IfStatement -> {
                    scanStatement(target.condition)
                    scanStatement(target.ifBody)
                    target.elseBody?.let { scanStatement(it) }
                }
                is WhenStatement -> {
                    scanStatement(target.value)
                    target.cases.forEach { case ->
                        case.conditions.forEach { scanStatement(it.expr) }
                        scanStatement(case.block)
                    }
                    target.elseCase?.let { scanStatement(it) }
                }
                is WhileStatement -> {
                    scanStatement(target.condition)
                    scanStatement(target.body)
                    target.elseStatement?.let { scanStatement(it) }
                }
                is DoWhileStatement -> {
                    scanStatement(target.body)
                    scanStatement(target.condition)
                    target.elseStatement?.let { scanStatement(it) }
                }
                is ForInStatement -> {
                    scanStatement(target.source)
                    scanStatement(target.body)
                    target.elseStatement?.let { scanStatement(it) }
                }
                is TryStatement -> {
                    scanStatement(target.body)
                    target.catches.forEach { scanStatement(it.block) }
                    target.finallyClause?.let { scanStatement(it) }
                }
                is ReturnStatement -> target.resultExpr?.let { scanStatement(it) }
                is ThrowStatement -> scanStatement(target.throwExpr)
                else -> {}
            }
        }

        scanStatement(stmt)
        return widened
    }

    private fun rememberSlotTypes(slots: Set<Int>): Map<Int, SlotType?> {
        if (slots.isEmpty()) return emptyMap()
        val result = LinkedHashMap<Int, SlotType?>(slots.size)
        for (slot in slots) {
            result[slot] = slotTypes[slot]
        }
        return result
    }

    private fun applySlotTypes(slots: Set<Int>, type: SlotType) {
        for (slot in slots) {
            updateSlotType(slot, type)
        }
    }

    private fun restoreSlotTypes(previous: Map<Int, SlotType?>) {
        for ((slot, type) in previous) {
            if (type == null) {
                updateSlotType(slot, SlotType.UNKNOWN)
            } else {
                updateSlotType(slot, type)
            }
        }
    }

    private fun emitLoopRealCoercions(slots: Set<Int>) {
        for (slot in slots) {
            builder.emit(Opcode.INT_TO_REAL, slot, slot)
            updateSlotType(slot, SlotType.REAL)
        }
    }

    private fun objArithmeticOpcode(base: Opcode, forReal: Boolean): Opcode {
        return when (base) {
            Opcode.ADD_OBJ -> if (forReal) Opcode.ADD_REAL_OBJ else Opcode.ADD_INT_OBJ
            Opcode.SUB_OBJ -> if (forReal) Opcode.SUB_REAL_OBJ else Opcode.SUB_INT_OBJ
            Opcode.MUL_OBJ -> if (forReal) Opcode.MUL_REAL_OBJ else Opcode.MUL_INT_OBJ
            Opcode.DIV_OBJ -> if (forReal) Opcode.DIV_REAL_OBJ else Opcode.DIV_INT_OBJ
            Opcode.MOD_OBJ -> if (forReal) Opcode.MOD_REAL_OBJ else Opcode.MOD_INT_OBJ
            else -> base
        }
    }

    private fun coerceObjIntToObjReal(slot: Int): Int {
        val intSlot = allocSlot()
        builder.emit(Opcode.UNBOX_INT_OBJ, slot, intSlot)
        updateSlotType(intSlot, SlotType.INT)
        val realSlot = allocSlot()
        builder.emit(Opcode.INT_TO_REAL, intSlot, realSlot)
        updateSlotType(realSlot, SlotType.REAL)
        val objSlot = allocSlot()
        builder.emit(Opcode.BOX_OBJ, realSlot, objSlot)
        updateSlotType(objSlot, SlotType.OBJ)
        slotObjClass[objSlot] = ObjReal.type
        return objSlot
    }

    private fun emitObjArithmetic(base: Opcode, leftSlot: Int, rightSlot: Int, dst: Int) {
        val fastEligible = isTempSlot(leftSlot) && isTempSlot(rightSlot) && isTempSlot(dst)
        val leftInt = isExactNonNullSlotClassOrTemp(leftSlot, ObjInt.type)
        val rightInt = isExactNonNullSlotClassOrTemp(rightSlot, ObjInt.type)
        val leftReal = isExactNonNullSlotClassOrTemp(leftSlot, ObjReal.type)
        val rightReal = isExactNonNullSlotClassOrTemp(rightSlot, ObjReal.type)
        if (fastEligible && leftInt && rightInt) {
            builder.emit(objArithmeticOpcode(base, forReal = false), leftSlot, rightSlot, dst)
            slotObjClass[dst] = ObjInt.type
            stableObjSlots.add(dst)
            return
        }
        if (fastEligible && leftReal && rightReal) {
            builder.emit(objArithmeticOpcode(base, forReal = true), leftSlot, rightSlot, dst)
            slotObjClass[dst] = ObjReal.type
            stableObjSlots.add(dst)
            return
        }
        if (fastEligible && leftInt && rightReal) {
            val coerced = coerceObjIntToObjReal(leftSlot)
            builder.emit(objArithmeticOpcode(base, forReal = true), coerced, rightSlot, dst)
            slotObjClass[dst] = ObjReal.type
            stableObjSlots.add(dst)
            return
        }
        if (fastEligible && leftReal && rightInt) {
            val coerced = coerceObjIntToObjReal(rightSlot)
            builder.emit(objArithmeticOpcode(base, forReal = true), leftSlot, coerced, dst)
            slotObjClass[dst] = ObjReal.type
            stableObjSlots.add(dst)
            return
        }
        builder.emit(base, leftSlot, rightSlot, dst)
        slotObjClass.remove(dst)
        stableObjSlots.remove(dst)
    }

    private fun typeDeclForSlot(slot: Int): TypeDecl? {
        val key = if (slot < scopeSlotCount) {
            scopeKeyByIndex.getOrNull(slot)
        } else {
            localSlotKeyByIndex.getOrNull(slot - scopeSlotCount)
        } ?: return null
        return slotTypeDeclByScopeId[key.scopeId]?.get(key.slot)
    }

    private fun compileCompareEq(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
        if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) {
            val left = ensureObjSlot(a)
            val right = ensureObjSlot(b)
            builder.emit(Opcode.CMP_EQ_OBJ, left.slot, right.slot, out)
            return CompiledValue(out, SlotType.BOOL)
        }
        if (a.type == SlotType.OBJ || b.type == SlotType.OBJ) {
            return compileCompareObjSpecial(
                Opcode.CMP_EQ_OBJ,
                Opcode.CMP_EQ_STR,
                Opcode.CMP_EQ_INT_OBJ,
                Opcode.CMP_EQ_REAL_OBJ,
                a,
                b,
                out
            )
        }
        return when {
            a.type == SlotType.INT && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_EQ_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_EQ_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.BOOL && b.type == SlotType.BOOL -> {
                builder.emit(Opcode.CMP_EQ_BOOL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.INT && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_EQ_INT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_EQ_REAL_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
                builder.emit(Opcode.CMP_EQ_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            else -> null
        }
    }

    private fun compileCompareNeq(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
        if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) {
            val left = ensureObjSlot(a)
            val right = ensureObjSlot(b)
            builder.emit(Opcode.CMP_NEQ_OBJ, left.slot, right.slot, out)
            return CompiledValue(out, SlotType.BOOL)
        }
        if (a.type == SlotType.OBJ || b.type == SlotType.OBJ) {
            return compileCompareObjSpecial(
                Opcode.CMP_NEQ_OBJ,
                Opcode.CMP_NEQ_STR,
                Opcode.CMP_NEQ_INT_OBJ,
                Opcode.CMP_NEQ_REAL_OBJ,
                a,
                b,
                out
            )
        }
        return when {
            a.type == SlotType.INT && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_NEQ_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_NEQ_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.BOOL && b.type == SlotType.BOOL -> {
                builder.emit(Opcode.CMP_NEQ_BOOL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.INT && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_NEQ_INT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_NEQ_REAL_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
                builder.emit(Opcode.CMP_NEQ_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            else -> null
        }
    }

    private fun compileCompareLt(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
        if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) {
            val left = ensureObjSlot(a)
            val right = ensureObjSlot(b)
            builder.emit(Opcode.CMP_LT_OBJ, left.slot, right.slot, out)
            return CompiledValue(out, SlotType.BOOL)
        }
        if (a.type == SlotType.OBJ || b.type == SlotType.OBJ) {
            return compileCompareObjSpecial(
                Opcode.CMP_LT_OBJ,
                Opcode.CMP_LT_STR,
                Opcode.CMP_LT_INT_OBJ,
                Opcode.CMP_LT_REAL_OBJ,
                a,
                b,
                out
            )
        }
        return when {
            a.type == SlotType.INT && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_LT_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_LT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.INT && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_LT_INT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_LT_REAL_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
                builder.emit(Opcode.CMP_LT_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            else -> null
        }
    }

    private fun compileCompareLte(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
        if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) {
            val left = ensureObjSlot(a)
            val right = ensureObjSlot(b)
            builder.emit(Opcode.CMP_LTE_OBJ, left.slot, right.slot, out)
            return CompiledValue(out, SlotType.BOOL)
        }
        if (a.type == SlotType.OBJ || b.type == SlotType.OBJ) {
            return compileCompareObjSpecial(
                Opcode.CMP_LTE_OBJ,
                Opcode.CMP_LTE_STR,
                Opcode.CMP_LTE_INT_OBJ,
                Opcode.CMP_LTE_REAL_OBJ,
                a,
                b,
                out
            )
        }
        return when {
            a.type == SlotType.INT && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_LTE_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_LTE_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.INT && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_LTE_INT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_LTE_REAL_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
                builder.emit(Opcode.CMP_LTE_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            else -> null
        }
    }

    private fun compileCompareGt(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
        if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) {
            val left = ensureObjSlot(a)
            val right = ensureObjSlot(b)
            builder.emit(Opcode.CMP_GT_OBJ, left.slot, right.slot, out)
            return CompiledValue(out, SlotType.BOOL)
        }
        if (a.type == SlotType.OBJ || b.type == SlotType.OBJ) {
            return compileCompareObjSpecial(
                Opcode.CMP_GT_OBJ,
                Opcode.CMP_GT_STR,
                Opcode.CMP_GT_INT_OBJ,
                Opcode.CMP_GT_REAL_OBJ,
                a,
                b,
                out
            )
        }
        return when {
            a.type == SlotType.INT && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_GT_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_GT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.INT && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_GT_INT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_GT_REAL_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
                builder.emit(Opcode.CMP_GT_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            else -> null
        }
    }

    private fun compileCompareGte(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
        if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) {
            val left = ensureObjSlot(a)
            val right = ensureObjSlot(b)
            builder.emit(Opcode.CMP_GTE_OBJ, left.slot, right.slot, out)
            return CompiledValue(out, SlotType.BOOL)
        }
        if (a.type == SlotType.OBJ || b.type == SlotType.OBJ) {
            return compileCompareObjSpecial(
                Opcode.CMP_GTE_OBJ,
                Opcode.CMP_GTE_STR,
                Opcode.CMP_GTE_INT_OBJ,
                Opcode.CMP_GTE_REAL_OBJ,
                a,
                b,
                out
            )
        }
        return when {
            a.type == SlotType.INT && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_GTE_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_GTE_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.INT && b.type == SlotType.REAL -> {
                builder.emit(Opcode.CMP_GTE_INT_REAL, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.REAL && b.type == SlotType.INT -> {
                builder.emit(Opcode.CMP_GTE_REAL_INT, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
                builder.emit(Opcode.CMP_GTE_OBJ, a.slot, b.slot, out)
                CompiledValue(out, SlotType.BOOL)
            }
            else -> null
        }
    }

    private fun compileLogical(op: BinOp, left: ObjRef, right: ObjRef, pos: Pos): CompiledValue? {
        val leftValue = compileRefWithFallback(left, SlotType.BOOL, pos) ?: return null
        if (leftValue.type != SlotType.BOOL) return null
        val resultSlot = allocSlot()
        val shortLabel = builder.label()
        val endLabel = builder.label()
        if (op == BinOp.AND) {
            builder.emit(
                Opcode.JMP_IF_FALSE,
                listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(shortLabel))
            )
        } else {
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(shortLabel))
            )
        }
        val rightValue = compileRefWithFallback(right, SlotType.BOOL, pos) ?: return null
        emitMove(rightValue, resultSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(shortLabel)
        val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR))
        builder.emit(Opcode.CONST_BOOL, constId, resultSlot)
        builder.mark(endLabel)
        return CompiledValue(resultSlot, SlotType.BOOL)
    }

    private fun compileLogicalAnd(ref: LogicalAndRef): CompiledValue? {
        val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
        if (leftValue.type != SlotType.BOOL) return null
        val resultSlot = allocSlot()
        val falseId = builder.addConst(BytecodeConst.Bool(false))
        builder.emit(Opcode.CONST_BOOL, falseId, resultSlot)
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_FALSE,
            listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(endLabel))
        )
        val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
        if (rightValue.type != SlotType.BOOL) return null
        builder.emit(Opcode.MOVE_BOOL, rightValue.slot, resultSlot)
        builder.mark(endLabel)
        return CompiledValue(resultSlot, SlotType.BOOL)
    }

    private fun compileLogicalOr(ref: LogicalOrRef): CompiledValue? {
        val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
        if (leftValue.type != SlotType.BOOL) return null
        val resultSlot = allocSlot()
        val trueId = builder.addConst(BytecodeConst.Bool(true))
        builder.emit(Opcode.CONST_BOOL, trueId, resultSlot)
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(endLabel))
        )
        val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
        if (rightValue.type != SlotType.BOOL) return null
        builder.emit(Opcode.MOVE_BOOL, rightValue.slot, resultSlot)
        builder.mark(endLabel)
        return CompiledValue(resultSlot, SlotType.BOOL)
    }

    private fun compileAssign(ref: AssignRef): CompiledValue? {
        val localTarget = assignTarget(ref)
        if (localTarget != null) {
            if (!allowLocalSlots) return null
            if (localTarget.isDelegated) {
                val slot = resolveSlot(localTarget) ?: return null
                val value = compileRef(assignValue(ref)) ?: return null
                if (slot < scopeSlotCount) return null
                val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name))
                builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, value.slot)
                updateSlotType(slot, SlotType.OBJ)
                return value
            }
            var value = compileRef(assignValue(ref)) ?: return null
            if (isLoopVarRef(localTarget)) {
                emitLoopVarReassignError(localTarget.name, localTarget.pos())
                return value
            }
            if (!localTarget.isMutable || localTarget.isDelegated) {
                emitImmutableLocalReassignError(localTarget.name, localTarget.pos())
                return value
            }
            val slot = resolveCapturedOwnerScopeSlot(localTarget)
                ?: resolveSlot(localTarget)
                ?: resolveAssignableSlotByName(localTarget.name)?.first
                ?: return null
            if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
                val addrSlot = ensureScopeAddr(slot)
                emitStoreToAddr(value.slot, addrSlot, value.type)
            } else if (slot < scopeSlotCount) {
                val addrSlot = ensureScopeAddr(slot)
                emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
            } else {
                when (value.type) {
                    SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
                    SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot)
                    SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot)
                    else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot)
                }
            }
            updateSlotType(slot, value.type)
            propagateObjClass(value.type, value.slot, slot)
            trackExactCallableObjAtSlot(slot, null)
            trackExactLambdaAtSlot(slot, null)
            updateNameObjClassFromSlot(localTarget.name, slot)
            return value
        }
        val nameTarget = when (val targetRef = ref.target) {
            is LocalVarRef -> targetRef.name
            is FastLocalVarRef -> targetRef.name
            else -> null
        }
        if (nameTarget != null) {
            var value = compileRef(assignValue(ref)) ?: return null
            val resolved = resolveAssignableSlotByName(nameTarget) ?: return null
            val slot = resolved.first
            val isMutable = resolved.second
            if (isLoopVarSlot(slot)) {
                val pos = (ref.target as? LocalVarRef)?.pos() ?: Pos.builtIn
                emitLoopVarReassignError(nameTarget, pos)
                return value
            }
            if (!isMutable) {
                val pos = (ref.target as? LocalVarRef)?.pos() ?: Pos.builtIn
                emitImmutableLocalReassignError(nameTarget, pos)
                return value
            }
            if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
                val addrSlot = ensureScopeAddr(slot)
                emitStoreToAddr(value.slot, addrSlot, value.type)
            } else if (slot < scopeSlotCount) {
                val addrSlot = ensureScopeAddr(slot)
                emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
            } else {
                when (value.type) {
                    SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
                    SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot)
                    SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot)
                    else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot)
                }
            }
            updateSlotType(slot, value.type)
            propagateObjClass(value.type, value.slot, slot)
            trackExactCallableObjAtSlot(slot, null)
            trackExactLambdaAtSlot(slot, null)
            updateNameObjClassFromSlot(nameTarget, slot)
            return value
        }
        val listTarget = ref.target as? ListLiteralRef
        if (listTarget != null) {
            val value = compileRef(assignValue(ref)) ?: return null
            val valueObj = ensureObjSlot(value)
            val declId = builder.addConst(BytecodeConst.DestructureAssign(listTarget, callSitePos()))
            builder.emit(Opcode.ASSIGN_DESTRUCTURE, declId, valueObj.slot)
            updateSlotType(valueObj.slot, SlotType.OBJ)
            return CompiledValue(valueObj.slot, SlotType.OBJ)
        }
        val value = compileRef(assignValue(ref)) ?: return null
        val target = ref.target
        if (target is ClassScopeMemberRef) {
            val className = target.ownerClassName()
            val classSlot = compileRef(LocalVarRef(className, Pos.builtIn)) ?: run {
                val cls = resolveTypeNameClass(className) ?: return null
                val id = builder.addConst(BytecodeConst.ObjRef(cls))
                val slot = allocSlot()
                builder.emit(Opcode.CONST_OBJ, id, slot)
                updateSlotType(slot, SlotType.OBJ)
                CompiledValue(slot, SlotType.OBJ)
            }
            val classObj = ensureObjSlot(classSlot)
            val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
            builder.emit(Opcode.SET_CLASS_SCOPE, classObj.slot, nameId, value.slot)
            return value
        }
        if (target is FieldRef) {
            val receiverClass = resolveReceiverClass(target.target)
                ?: throw BytecodeCompileException(
                    "Member assignment requires compile-time receiver type: ${target.name}",
                    Pos.builtIn
                )
            val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
            if (receiverClass == ObjDynamic.type) {
                val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                if (!target.isOptional) {
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, value.slot)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
                    )
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, value.slot)
                    builder.mark(endLabel)
                }
                return value
            }
            if (shouldUseClassScopeReceiver(target.target, receiver, receiverClass)) {
                val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                if (!target.isOptional) {
                    builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, value.slot)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
                    )
                    builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, value.slot)
                    builder.mark(endLabel)
                }
                return value
            }
            val fieldId = receiverClass.instanceFieldIdMap()[target.name]
            val methodId = if (fieldId == null) {
                receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
            } else {
                null
            }
            if (fieldId == null && methodId == null) {
                val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
                    ?: throw BytecodeCompileException(
                        "Unknown member ${target.name} on ${receiverClass.className}",
                        Pos.builtIn
                    )
                val callee = ensureObjSlot(extSlot)
                val receiverObj = ensureObjSlot(receiver)
                val valueObj = ensureObjSlot(value)
                val argSlots = intArrayOf(allocSlot(), allocSlot())
                builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0])
                builder.emit(Opcode.MOVE_OBJ, valueObj.slot, argSlots[1])
                updateSlotType(argSlots[0], SlotType.OBJ)
                updateSlotType(argSlots[1], SlotType.OBJ)
                val callArgs = CallArgs(base = argSlots[0], count = argSlots.size, planId = null)
                val encodedCount = encodeCallArgCount(callArgs) ?: return null
                val callDst = allocSlot()
                if (!target.isOptional) {
                    emitCallCompiled(callee, callArgs.base, encodedCount, callDst)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
                    )
                    emitCallCompiled(callee, callArgs.base, encodedCount, callDst)
                    builder.mark(endLabel)
                }
                return value
            }
            val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: -1
            val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: -1
            if (!target.isOptional) {
                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, encodedFieldId, encodedMethodId, value.slot)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
                )
                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, encodedFieldId, encodedMethodId, value.slot)
                builder.mark(endLabel)
            }
            return value
        }
        if (target is ImplicitThisMemberRef) {
            val receiver = target.preferredThisTypeName()?.let { typeName ->
                compileThisVariantRef(typeName) ?: return null
            } ?: compileThisRef()
            val fieldId = target.fieldId ?: -1
            val methodId = target.methodId ?: -1
            if (fieldId < 0 && methodId < 0) {
                val typeName = target.preferredThisTypeName()
                    ?: throw BytecodeCompileException("Missing member id for ${target.name}", Pos.builtIn)
                val wrapperName = extensionPropertySetterName(typeName, target.name)
                val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
                    "Missing extension wrapper for ${typeName}.${target.name}",
                    Pos.builtIn
                )
                val calleeObj = ensureObjSlot(callee)
                val receiverObj = ensureObjSlot(receiver)
                val valueObj = ensureObjSlot(value)
                val argSlots = intArrayOf(allocSlot(), allocSlot())
                builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0])
                builder.emit(Opcode.MOVE_OBJ, valueObj.slot, argSlots[1])
                updateSlotType(argSlots[0], SlotType.OBJ)
                updateSlotType(argSlots[1], SlotType.OBJ)
                val callArgs = CallArgs(base = argSlots[0], count = argSlots.size, planId = null)
                val encodedCount = encodeCallArgCount(callArgs) ?: return null
                val callDst = allocSlot()
                emitCallCompiled(calleeObj, callArgs.base, encodedCount, callDst)
                return value
            }
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
            return value
        }
        if (target is ThisFieldSlotRef) {
            val receiver = compileThisRef()
            val fieldId = target.fieldId() ?: -1
            val methodId = target.methodId() ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${target.name}", Pos.builtIn)
            }
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
            return value
        }
        if (target is QualifiedThisFieldSlotRef) {
            val receiver = compileThisVariantRef(target.receiverTypeName()) ?: return null
            val fieldId = target.fieldId() ?: -1
            val methodId = target.methodId() ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${target.name}", Pos.builtIn)
            }
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
            return value
        }
        if (target is IndexRef) {
            val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
            val elementSlotType = indexElementSlotType(receiver.slot, target.targetRef)
            if (!target.optionalRef) {
                val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
                if (elementSlotType == SlotType.INT && index.type == SlotType.INT && value.type == SlotType.INT) {
                    builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, value.slot)
                } else {
                    builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot)
                }
                noteListElementClassMutation(receiver.slot, value)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
                )
                val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
                if (elementSlotType == SlotType.INT && index.type == SlotType.INT && value.type == SlotType.INT) {
                    builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, value.slot)
                } else {
                    builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot)
                }
                noteListElementClassMutation(receiver.slot, value)
                builder.mark(endLabel)
            }
            return value
        }
        return null
    }

    private fun compileAssignOp(ref: AssignOpRef): CompiledValue? {
        val localTarget = ref.target as? LocalSlotRef
        if (localTarget != null) {
            if (!allowLocalSlots) return compileEvalRef(ref)
            if (isLoopVarRef(localTarget)) {
                val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
                emitLoopVarReassignError(localTarget.name, localTarget.pos())
                return rhs
            }
            if (localTarget.isDelegated) {
                val slot = resolveSlot(localTarget) ?: return null
                if (slot < scopeSlotCount) return null
                val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name))
                val current = allocSlot()
                builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current)
                updateSlotType(current, SlotType.OBJ)
                val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
                val rhsObj = ensureObjSlot(rhs)
                val objOp = when (ref.op) {
                    BinOp.PLUS -> Opcode.ADD_OBJ
                    BinOp.MINUS -> Opcode.SUB_OBJ
                    BinOp.STAR -> Opcode.MUL_OBJ
                    BinOp.SLASH -> Opcode.DIV_OBJ
                    BinOp.PERCENT -> Opcode.MOD_OBJ
                    else -> null
                } ?: return compileEvalRef(ref)
                val result = allocSlot()
                builder.emit(objOp, current, rhsObj.slot, result)
                updateSlotType(result, SlotType.OBJ)
                builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result)
                updateSlotType(slot, SlotType.OBJ)
                return CompiledValue(result, SlotType.OBJ)
            }
            if (localTarget.isDelegated) return compileEvalRef(ref)
            val slot = resolveCapturedOwnerScopeSlot(localTarget) ?: resolveSlot(localTarget) ?: return null
            val targetType = slotTypes[slot] ?: SlotType.OBJ
            if (!localTarget.isMutable) {
                val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
                if (!shouldUseAssignOpForImmutableLocal(slot, targetType, ref.op)) {
                    emitImmutableLocalReassignError(localTarget.name, localTarget.pos())
                    return rhs
                }
                val rhsObj = ensureObjSlot(rhs)
                val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name))
                if (nameId > 0xFFFF) return compileEvalRef(ref)
                val dst = allocSlot()
                builder.emit(Opcode.ASSIGN_OP_OBJ, ref.op.ordinal, slot, rhsObj.slot, dst, nameId)
                updateSlotType(dst, SlotType.OBJ)
                slotObjClass[slot]?.let { slotObjClass[dst] = it }
                return CompiledValue(dst, SlotType.OBJ)
            }
            var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
            if (targetType == SlotType.OBJ && rhs.type != SlotType.OBJ) {
                rhs = ensureObjSlot(rhs)
            }
            if (slot < scopeSlotCount) {
                val addrSlot = ensureScopeAddr(slot)
                val current = allocSlot()
                emitLoadFromAddr(addrSlot, current, targetType)
                val result = when (ref.op) {
                    BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ)
                    BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ)
                    BinOp.STAR -> compileAssignOpBinary(targetType, rhs, current, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ)
                    BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, current, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ)
                    BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, current, Opcode.MOD_INT, null, Opcode.MOD_OBJ)
                    else -> null
                } ?: return null
                emitStoreToAddr(current, addrSlot, result.type)
                updateSlotType(slot, result.type)
                return CompiledValue(current, result.type)
            }
            val out = slot
            val result = when (ref.op) {
                BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ)
                BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ)
                BinOp.STAR -> compileAssignOpBinary(targetType, rhs, out, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ)
                BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, out, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ)
                BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, out, Opcode.MOD_INT, null, Opcode.MOD_OBJ)
                else -> null
            } ?: return null
            updateSlotType(out, result.type)
            return CompiledValue(out, result.type)
        }
        val varTarget = ref.target as? LocalVarRef
        if (varTarget != null) {
            val resolved = resolveAssignableSlotByName(varTarget.name)
            if (resolved != null && isLoopVarSlot(resolved.first)) {
                val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
                emitLoopVarReassignError(varTarget.name, varTarget.pos())
                return rhs
            }
            return compileEvalRef(ref)
        }
        val objOp = when (ref.op) {
            BinOp.PLUS -> Opcode.ADD_OBJ
            BinOp.MINUS -> Opcode.SUB_OBJ
            BinOp.STAR -> Opcode.MUL_OBJ
            BinOp.SLASH -> Opcode.DIV_OBJ
            BinOp.PERCENT -> Opcode.MOD_OBJ
            else -> null
        } ?: return compileEvalRef(ref)
        val fieldTarget = ref.target as? FieldRef
        if (fieldTarget != null) {
            val receiverClass = resolveReceiverClass(fieldTarget.target)
                ?: throw BytecodeCompileException(
                    "Member assignment requires compile-time receiver type: ${fieldTarget.name}",
                    Pos.builtIn
                )
            val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
            val current = allocSlot()
            val result = allocSlot()
            val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
            if (receiverClass == ObjDynamic.type) {
                val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
                if (!fieldTarget.isOptional) {
                    builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                    updateSlotType(result, SlotType.OBJ)
                    return CompiledValue(result, SlotType.OBJ)
                }
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                builder.emit(objOp, current, rhs.slot, result)
                builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, result)
                builder.mark(endLabel)
                updateSlotType(result, SlotType.OBJ)
                return CompiledValue(result, SlotType.OBJ)
            }
            if (receiverClass is ObjInstanceClass && !isThisReceiver(fieldTarget.target)) {
                val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
                if (!fieldTarget.isOptional) {
                    builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val nullLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                    )
                    builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(nullLabel)
                    builder.emit(Opcode.CONST_NULL, result)
                    builder.mark(endLabel)
                }
                updateSlotType(result, SlotType.OBJ)
                return CompiledValue(result, SlotType.OBJ)
            }
            val resolvedMember = receiverClass.resolveInstanceMember(fieldTarget.name)
            if (resolvedMember?.declaringClass?.className == "Obj") {
                val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
                if (!fieldTarget.isOptional) {
                    builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val nullLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                    )
                    builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(nullLabel)
                    builder.emit(Opcode.CONST_NULL, result)
                    builder.mark(endLabel)
                }
                updateSlotType(result, SlotType.OBJ)
                return CompiledValue(result, SlotType.OBJ)
            }
            val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[fieldTarget.name] else null
            val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name] else null
            if (fieldId == null && methodId == null && shouldUseClassScopeReceiver(fieldTarget.target, receiver, receiverClass)) {
                val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
                if (!fieldTarget.isOptional) {
                    builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val nullLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                    )
                    builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(nullLabel)
                    builder.emit(Opcode.CONST_NULL, result)
                    builder.mark(endLabel)
                }
                updateSlotType(result, SlotType.OBJ)
                return CompiledValue(result, SlotType.OBJ)
            }
            if (fieldId != null || methodId != null) {
                if (!fieldTarget.isOptional) {
                    builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val nullLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                    )
                    builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, current)
                    builder.emit(objOp, current, rhs.slot, result)
                    builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(nullLabel)
                    builder.emit(Opcode.CONST_NULL, result)
                    builder.mark(endLabel)
                }
                updateSlotType(result, SlotType.OBJ)
                return CompiledValue(result, SlotType.OBJ)
            }
            return compileEvalRef(ref)
        }
        val implicitTarget = ref.target as? ImplicitThisMemberRef
        if (implicitTarget != null) {
            val receiver = compileThisRef()
            val fieldId = implicitTarget.fieldId ?: -1
            val methodId = implicitTarget.methodId ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${implicitTarget.name}", Pos.builtIn)
            }
            val current = allocSlot()
            val result = allocSlot()
            val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
            builder.emit(objOp, current, rhs.slot, result)
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
            updateSlotType(result, SlotType.OBJ)
            return CompiledValue(result, SlotType.OBJ)
        }
        val thisFieldTarget = ref.target as? ThisFieldSlotRef
        if (thisFieldTarget != null) {
            val receiver = compileThisRef()
            val fieldId = thisFieldTarget.fieldId() ?: -1
            val methodId = thisFieldTarget.methodId() ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${thisFieldTarget.name}", Pos.builtIn)
            }
            val current = allocSlot()
            val result = allocSlot()
            val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
            builder.emit(objOp, current, rhs.slot, result)
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
            updateSlotType(result, SlotType.OBJ)
            return CompiledValue(result, SlotType.OBJ)
        }
        val qualifiedTarget = ref.target as? QualifiedThisFieldSlotRef
        if (qualifiedTarget != null) {
            val receiver = compileThisVariantRef(qualifiedTarget.receiverTypeName()) ?: return null
            val fieldId = qualifiedTarget.fieldId() ?: -1
            val methodId = qualifiedTarget.methodId() ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${qualifiedTarget.name}", Pos.builtIn)
            }
            val current = allocSlot()
            val result = allocSlot()
            val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
            builder.emit(objOp, current, rhs.slot, result)
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
            updateSlotType(result, SlotType.OBJ)
            return CompiledValue(result, SlotType.OBJ)
        }
        val indexTarget = ref.target as? IndexRef
        if (indexTarget != null) {
            val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null
            val current = allocSlot()
            val result = allocSlot()
            var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
            val elementClass = indexElementClass(receiver.slot, indexTarget.targetRef)
            if (!indexTarget.optionalRef) {
                val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
                if (elementClass == ObjInt.type && index.type == SlotType.INT) {
                    val currentInt = allocSlot()
                    builder.emit(Opcode.GET_INDEX_INT, receiver.slot, index.slot, currentInt)
                    updateSlotType(currentInt, SlotType.INT)
                    if (rhs.type != SlotType.INT) {
                        coerceToArithmeticInt(ref.value, rhs)?.let { rhs = it }
                    }
                    val typed = when (ref.op) {
                        BinOp.PLUS -> compileAssignOpBinary(SlotType.INT, rhs, currentInt, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ)
                        BinOp.MINUS -> compileAssignOpBinary(SlotType.INT, rhs, currentInt, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ)
                        BinOp.STAR -> compileAssignOpBinary(SlotType.INT, rhs, currentInt, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ)
                        BinOp.SLASH -> compileAssignOpBinary(SlotType.INT, rhs, currentInt, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ)
                        BinOp.PERCENT -> compileAssignOpBinary(SlotType.INT, rhs, currentInt, Opcode.MOD_INT, null, Opcode.MOD_OBJ)
                        else -> null
                    }
                    if (typed != null && typed.type == SlotType.INT) {
                        builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, typed.slot)
                        noteListElementClassMutation(receiver.slot, typed)
                        return typed
                    }
                }
                builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
                builder.emit(objOp, current, rhs.slot, result)
                builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
                updateSlotType(result, SlotType.OBJ)
                return CompiledValue(result, SlotType.OBJ)
            }
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
            builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
            builder.emit(objOp, current, rhs.slot, result)
            builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, result)
            builder.mark(endLabel)
            updateSlotType(result, SlotType.OBJ)
            return CompiledValue(result, SlotType.OBJ)
        }
        return compileEvalRef(ref)
    }

    private fun compileAssignIfNull(ref: AssignIfNullRef): CompiledValue? {
        val target = ref.target
        val currentValue = compileRefWithFallback(target, null, Pos.builtIn) ?: return null
        val currentObj = ensureObjSlot(currentValue)
        val resultSlot = allocSlot()
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, currentObj.slot, nullSlot, cmpSlot)
        val assignLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(assignLabel))
        )
        builder.emit(Opcode.MOVE_OBJ, currentObj.slot, resultSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(assignLabel)

        val newValue = compileRefWithFallback(ref.value, null, Pos.builtIn) ?: return null
        when (target) {
            is ClassScopeMemberRef -> {
                val className = target.ownerClassName()
                val classSlot = compileRef(LocalVarRef(className, Pos.builtIn)) ?: run {
                    val cls = resolveTypeNameClass(className) ?: return null
                    val id = builder.addConst(BytecodeConst.ObjRef(cls))
                    val slot = allocSlot()
                    builder.emit(Opcode.CONST_OBJ, id, slot)
                    updateSlotType(slot, SlotType.OBJ)
                    CompiledValue(slot, SlotType.OBJ)
                }
                val classObj = ensureObjSlot(classSlot)
                val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                builder.emit(Opcode.SET_CLASS_SCOPE, classObj.slot, nameId, newValue.slot)
            }
            is LocalSlotRef -> {
                if (!allowLocalSlots || !target.isMutable) return null
                if (isLoopVarRef(target)) {
                    emitLoopVarReassignError(target.name, target.pos())
                    return CompiledValue(currentObj.slot, SlotType.OBJ)
                }
                if (target.isDelegated) {
                    val slot = resolveSlot(target) ?: return null
                    if (slot < scopeSlotCount) return null
                    val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                    val current = allocSlot()
                    builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current)
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, current, nullSlot, cmpSlot)
                    val assignLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(assignLabel))
                    )
                    builder.emit(Opcode.MOVE_OBJ, current, resultSlot)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(assignLabel)
                    builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, newValue.slot)
                    builder.emit(Opcode.MOVE_OBJ, newValue.slot, resultSlot)
                    builder.mark(endLabel)
                    updateSlotType(resultSlot, SlotType.OBJ)
                    return CompiledValue(resultSlot, SlotType.OBJ)
                }
                val slot = resolveSlot(target) ?: return null
                if (slot < scopeSlotCount) {
                    val addrSlot = ensureScopeAddr(slot)
                    val storeType = if (newValue.type == SlotType.UNKNOWN) SlotType.OBJ else newValue.type
                    emitStoreToAddr(newValue.slot, addrSlot, storeType)
                } else {
                    when (newValue.type) {
                        SlotType.INT -> builder.emit(Opcode.MOVE_INT, newValue.slot, slot)
                        SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, newValue.slot, slot)
                        SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, newValue.slot, slot)
                        else -> builder.emit(Opcode.MOVE_OBJ, newValue.slot, slot)
                    }
                }
                updateSlotType(slot, newValue.type)
            }
            is FieldRef -> {
                val receiverClass = resolveReceiverClass(target.target)
                    ?: throw BytecodeCompileException(
                        "Member assignment requires compile-time receiver type: ${target.name}",
                        Pos.builtIn
                    )
                val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
                if (isKnownClassReceiver(target.target)) {
                    val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                    if (!target.isOptional) {
                        builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, newValue.slot)
                    } else {
                        val recvNull = allocSlot()
                        builder.emit(Opcode.CONST_NULL, recvNull)
                        val recvCmp = allocSlot()
                        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
                        val skipLabel = builder.label()
                        builder.emit(
                            Opcode.JMP_IF_TRUE,
                            listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
                        )
                        builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, newValue.slot)
                        builder.mark(skipLabel)
                    }
                } else if (receiverClass == ObjDynamic.type) {
                    val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                    if (!target.isOptional) {
                        builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
                    } else {
                        val recvNull = allocSlot()
                        builder.emit(Opcode.CONST_NULL, recvNull)
                        val recvCmp = allocSlot()
                        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
                        val skipLabel = builder.label()
                        builder.emit(
                            Opcode.JMP_IF_TRUE,
                            listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
                        )
                        builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
                        builder.mark(skipLabel)
                    }
                    val newObj = ensureObjSlot(newValue)
                    builder.emit(Opcode.MOVE_OBJ, newObj.slot, resultSlot)
                    builder.mark(endLabel)
                    updateSlotType(resultSlot, SlotType.OBJ)
                    return CompiledValue(resultSlot, SlotType.OBJ)
                } else if (receiverClass is ObjInstanceClass && !isThisReceiver(target.target)) {
                    val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                    if (!target.isOptional) {
                        builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
                    } else {
                        val recvNull = allocSlot()
                        builder.emit(Opcode.CONST_NULL, recvNull)
                        val recvCmp = allocSlot()
                        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
                        val skipLabel = builder.label()
                        builder.emit(
                            Opcode.JMP_IF_TRUE,
                            listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
                        )
                        builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
                        builder.mark(skipLabel)
                    }
                } else {
                    val resolvedMember = receiverClass.resolveInstanceMember(target.name)
                    if (resolvedMember?.declaringClass?.className == "Obj") {
                        val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                        if (!target.isOptional) {
                            builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
                        } else {
                            val recvNull = allocSlot()
                            builder.emit(Opcode.CONST_NULL, recvNull)
                            val recvCmp = allocSlot()
                            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
                            val skipLabel = builder.label()
                            builder.emit(
                                Opcode.JMP_IF_TRUE,
                                listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
                            )
                            builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
                            builder.mark(skipLabel)
                        }
                    } else {
                        val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[target.name] else null
                        val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] else null
                        if (fieldId != null || methodId != null) {
                            if (!target.isOptional) {
                                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
                            } else {
                                val recvNull = allocSlot()
                                builder.emit(Opcode.CONST_NULL, recvNull)
                                val recvCmp = allocSlot()
                                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
                                val skipLabel = builder.label()
                                builder.emit(
                                    Opcode.JMP_IF_TRUE,
                                    listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
                                )
                                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
                                builder.mark(skipLabel)
                            }
                        } else {
                            val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
                                ?: throw BytecodeCompileException(
                                    "Unknown member ${target.name} on ${receiverClass.className}",
                                    Pos.builtIn
                                )
                            val callee = ensureObjSlot(extSlot)
                            val receiverObj = ensureObjSlot(receiver)
                            val valueObj = ensureObjSlot(newValue)
                            val argSlots = intArrayOf(allocSlot(), allocSlot())
                            builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0])
                            builder.emit(Opcode.MOVE_OBJ, valueObj.slot, argSlots[1])
                            updateSlotType(argSlots[0], SlotType.OBJ)
                            updateSlotType(argSlots[1], SlotType.OBJ)
                            val callArgs = CallArgs(base = argSlots[0], count = argSlots.size, planId = null)
                            val encodedCount = encodeCallArgCount(callArgs) ?: return null
                            if (!target.isOptional) {
                                emitCallCompiled(callee, callArgs.base, encodedCount, resultSlot)
                            } else {
                                val recvNull = allocSlot()
                                builder.emit(Opcode.CONST_NULL, recvNull)
                                val recvCmp = allocSlot()
                                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
                                val skipLabel = builder.label()
                                builder.emit(
                                    Opcode.JMP_IF_TRUE,
                                    listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
                                )
                                emitCallCompiled(callee, callArgs.base, encodedCount, resultSlot)
                                builder.mark(skipLabel)
                            }
                        }
                    }
                }
            }
            is IndexRef -> {
                val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
                val elementSlotType = indexElementSlotType(receiver.slot, target.targetRef)
                if (!target.optionalRef) {
                    val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
                    if (elementSlotType == SlotType.INT && index.type == SlotType.INT && newValue.type == SlotType.INT) {
                        builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, newValue.slot)
                    } else {
                        builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
                    }
                } else {
                    val recvNull = allocSlot()
                    builder.emit(Opcode.CONST_NULL, recvNull)
                    val recvCmp = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
                    val skipLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
                    )
                    val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
                    if (elementSlotType == SlotType.INT && index.type == SlotType.INT && newValue.type == SlotType.INT) {
                        builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, newValue.slot)
                    } else {
                        builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
                    }
                    builder.mark(skipLabel)
                }
            }
            else -> return null
        }
        val newObj = ensureObjSlot(newValue)
        builder.emit(Opcode.MOVE_OBJ, newObj.slot, resultSlot)
        builder.mark(endLabel)
        updateSlotType(resultSlot, SlotType.OBJ)
        return CompiledValue(resultSlot, SlotType.OBJ)
    }

    private fun compileFieldRef(ref: FieldRef): CompiledValue? {
        if (ref.name.isBlank()) {
            return compileRefWithFallback(ref.target, null, Pos.builtIn)
        }
        val pos = callSitePos()
        val receiverClass = resolveReceiverClass(ref.target) ?: ObjDynamic.type
        if (receiverClass == ObjDynamic.type) {
            val receiver = compileRefWithFallback(ref.target, null, pos) ?: return null
            val dst = allocSlot()
            val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
            if (!ref.isOptional) {
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, dst)
                builder.mark(endLabel)
            }
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        if (receiverClass is ObjInstanceClass && !isThisReceiver(ref.target)) {
            val receiver = compileRefWithFallback(ref.target, null, pos) ?: return null
            val dst = allocSlot()
            val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
            if (!ref.isOptional) {
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, dst)
                builder.mark(endLabel)
            }
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val resolvedMember = receiverClass.resolveInstanceMember(ref.name)
        if (resolvedMember?.declaringClass?.className == "Obj") {
            val receiver = compileRefWithFallback(ref.target, null, pos) ?: return null
            val dst = allocSlot()
            val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
            if (!ref.isOptional) {
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, dst)
                builder.mark(endLabel)
            }
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[ref.name] else null
        val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] else null
        val encodedFieldId = encodeMemberId(receiverClass, fieldId)
        val encodedMethodId = encodeMemberId(receiverClass, methodId)
        val receiver = compileRefWithFallback(ref.target, null, pos) ?: return null
        val dst = allocSlot()
        if (fieldId == null && methodId == null && shouldUseClassScopeReceiver(ref.target, receiver, receiverClass)) {
            val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
            if (!ref.isOptional) {
                builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, dst)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, dst)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, dst)
                builder.mark(endLabel)
            }
            updateSlotType(dst, SlotType.OBJ)
            return CompiledValue(dst, SlotType.OBJ)
        }
        if (fieldId != null || methodId != null) {
            if (!ref.isOptional) {
                builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, dst)
                builder.mark(endLabel)
            }
            updateSlotType(dst, SlotType.OBJ)
            annotateIndexedReceiverSlot(dst, inferFieldReturnClass(receiverClass, ref.name))
            return CompiledValue(dst, SlotType.OBJ)
        }
        val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name)
            ?: throw BytecodeCompileException(
                missingFieldMessage(receiverClass, ref.name),
                Pos.builtIn
            )
        val callee = ensureObjSlot(extSlot)
        if (!ref.isOptional) {
            val args = compileCallArgsWithReceiver(receiver, emptyList(), false) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            emitCallCompiled(callee, args.base, encodedCount, dst)
        } else {
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            val args = compileCallArgsWithReceiver(receiver, emptyList(), false) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            emitCallCompiled(callee, args.base, encodedCount, dst)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, dst)
            builder.mark(endLabel)
        }
        updateSlotType(dst, SlotType.OBJ)
        annotateIndexedReceiverSlot(dst, inferFieldReturnClass(receiverClass, ref.name))
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileClassScopeMemberRef(ref: ClassScopeMemberRef): CompiledValue? {
        val className = ref.ownerClassName()
        val classSlot = compileRef(LocalVarRef(className, Pos.builtIn)) ?: run {
            val cls = resolveTypeNameClass(className) ?: return null
            val id = builder.addConst(BytecodeConst.ObjRef(cls))
            val slot = allocSlot()
            builder.emit(Opcode.CONST_OBJ, id, slot)
            updateSlotType(slot, SlotType.OBJ)
            CompiledValue(slot, SlotType.OBJ)
        }
        val classObj = ensureObjSlot(classSlot)
        val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
        val dst = allocSlot()
        builder.emit(Opcode.GET_CLASS_SCOPE, classObj.slot, nameId, dst)
        updateSlotType(dst, SlotType.OBJ)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileThisFieldSlotRef(ref: ThisFieldSlotRef): CompiledValue? {
        val receiver = compileThisRef()
        val ownerClass = currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) }
        val fieldId = ref.fieldId() ?: -1
        val methodId = ref.methodId() ?: -1
        if (fieldId < 0 && methodId < 0) {
            throw BytecodeCompileException("Missing member id for ${ref.name}", Pos.builtIn)
        }
        val dst = allocSlot()
        if (!ref.optional()) {
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, dst)
        } else {
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, dst)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, dst)
            builder.mark(endLabel)
        }
        updateSlotType(dst, SlotType.OBJ)
        annotateIndexedReceiverSlot(dst, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileQualifiedThisFieldSlotRef(ref: QualifiedThisFieldSlotRef): CompiledValue? {
        val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null
        val ownerClass = resolveTypeNameClass(ref.receiverTypeName())
        val fieldId = ref.fieldId() ?: -1
        val methodId = ref.methodId() ?: -1
        if (fieldId < 0 && methodId < 0) {
            throw BytecodeCompileException("Missing member id for ${ref.name}", Pos.builtIn)
        }
        val dst = allocSlot()
        if (!ref.optional()) {
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, dst)
        } else {
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, dst)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, dst)
            builder.mark(endLabel)
        }
        updateSlotType(dst, SlotType.OBJ)
        annotateIndexedReceiverSlot(dst, ownerClass?.let { inferFieldReturnClass(it, ref.name) })
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileIndexRef(ref: IndexRef): CompiledValue? {
        val indexPos = refPosOrCurrent(ref.targetRef)
        setPos(indexPos)
        val receiver = compileRefWithFallback(ref.targetRef, null, Pos.builtIn) ?: return null
        val elementSlotType = indexElementSlotType(receiver.slot, ref.targetRef)
        val dst = allocSlot()
        if (!ref.optionalRef) {
            val index = compileRefWithFallback(ref.indexRef, null, Pos.builtIn) ?: return null
            setPos(indexPos)
            if (elementSlotType == SlotType.INT && index.type == SlotType.INT) {
                builder.emit(Opcode.GET_INDEX_INT, receiver.slot, index.slot, dst)
                updateSlotType(dst, SlotType.INT)
                return CompiledValue(dst, SlotType.INT)
            }
            builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, dst)
        } else {
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            val index = compileRefWithFallback(ref.indexRef, null, Pos.builtIn) ?: return null
            builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, dst)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, dst)
            builder.mark(endLabel)
        }
        updateSlotType(dst, SlotType.OBJ)
        val elementClass = listElementClassBySlot[receiver.slot] ?: listElementClassFromReceiverRef(ref.targetRef)
        if (elementClass != null) {
            slotObjClass[dst] = elementClass
            if (elementClass.isClosed) {
                stableObjSlots.add(dst)
            } else {
                stableObjSlots.remove(dst)
            }
        }
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileRangeRef(ref: RangeRef): CompiledValue? {
        val startSlot = if (ref.left != null) {
            val start = compileRefWithFallback(ref.left, null, Pos.builtIn) ?: return null
            ensureObjSlot(start).slot
        } else {
            val slot = allocSlot()
            builder.emit(Opcode.CONST_NULL, slot)
            updateSlotType(slot, SlotType.OBJ)
            slot
        }
        val endSlot = if (ref.right != null) {
            val end = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null
            ensureObjSlot(end).slot
        } else {
            val slot = allocSlot()
            builder.emit(Opcode.CONST_NULL, slot)
            updateSlotType(slot, SlotType.OBJ)
            slot
        }
        val inclusiveSlot = allocSlot()
        val inclusiveId = builder.addConst(BytecodeConst.Bool(ref.isEndInclusive))
        builder.emit(Opcode.CONST_BOOL, inclusiveId, inclusiveSlot)
        val descendingSlot = allocSlot()
        val descendingId = builder.addConst(BytecodeConst.Bool(ref.isDescending))
        builder.emit(Opcode.CONST_BOOL, descendingId, descendingSlot)
        val stepSlot = if (ref.step != null) {
            val step = compileRefWithFallback(ref.step, null, Pos.builtIn) ?: return null
            ensureObjSlot(step).slot
        } else {
            val slot = allocSlot()
            builder.emit(Opcode.CONST_NULL, slot)
            updateSlotType(slot, SlotType.OBJ)
            slot
        }
        val dst = allocSlot()
        builder.emit(Opcode.MAKE_RANGE, startSlot, endSlot, inclusiveSlot, descendingSlot, stepSlot, dst)
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = ObjRange.type
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileAssignOpBinary(
        targetType: SlotType,
        rhs: CompiledValue,
        out: Int,
        intOp: Opcode,
        realOp: Opcode?,
        objOp: Opcode?,
    ): CompiledValue? {
        return when (targetType) {
            SlotType.INT -> {
                when (rhs.type) {
                    SlotType.INT -> {
                        builder.emit(intOp, out, rhs.slot, out)
                        CompiledValue(out, SlotType.INT)
                    }
                    SlotType.REAL -> {
                        if (realOp == null) return null
                        val left = allocSlot()
                        builder.emit(Opcode.INT_TO_REAL, out, left)
                        builder.emit(realOp, left, rhs.slot, out)
                        CompiledValue(out, SlotType.REAL)
                    }
                    SlotType.OBJ -> {
                        if (objOp == null) return null
                        if (isExactNonNullSlotClassOrTemp(rhs.slot, ObjInt.type)) {
                            val right = allocSlot()
                            builder.emit(Opcode.UNBOX_INT_OBJ, rhs.slot, right)
                            builder.emit(intOp, out, right, out)
                            return CompiledValue(out, SlotType.INT)
                        }
                        val leftObj = allocSlot()
                        builder.emit(Opcode.BOX_OBJ, out, leftObj)
                        updateSlotType(leftObj, SlotType.OBJ)
                        slotObjClass[leftObj] = ObjInt.type
                        emitObjArithmetic(objOp, leftObj, rhs.slot, out)
                        CompiledValue(out, SlotType.OBJ)
                    }
                    else -> null
                }
            }
            SlotType.REAL -> {
                if (realOp == null) return null
                when (rhs.type) {
                    SlotType.REAL -> {
                        builder.emit(realOp, out, rhs.slot, out)
                        CompiledValue(out, SlotType.REAL)
                    }
                    SlotType.INT -> {
                        val right = allocSlot()
                        builder.emit(Opcode.INT_TO_REAL, rhs.slot, right)
                        builder.emit(realOp, out, right, out)
                        CompiledValue(out, SlotType.REAL)
                    }
                    SlotType.OBJ -> {
                        if (objOp == null) return null
                        if (isExactNonNullSlotClassOrTemp(rhs.slot, ObjReal.type)) {
                            val right = allocSlot()
                            builder.emit(Opcode.UNBOX_REAL_OBJ, rhs.slot, right)
                            builder.emit(realOp, out, right, out)
                            return CompiledValue(out, SlotType.REAL)
                        }
                        val leftObj = allocSlot()
                        builder.emit(Opcode.BOX_OBJ, out, leftObj)
                        updateSlotType(leftObj, SlotType.OBJ)
                        slotObjClass[leftObj] = ObjReal.type
                        emitObjArithmetic(objOp, leftObj, rhs.slot, out)
                        CompiledValue(out, SlotType.OBJ)
                    }
                    else -> null
                }
            }
            SlotType.OBJ -> {
                if (objOp == null) return null
                if (rhs.type != SlotType.OBJ) return null
                emitObjArithmetic(objOp, out, rhs.slot, out)
                CompiledValue(out, SlotType.OBJ)
            }
            else -> null
        }
    }

    private fun compileIncDec(ref: IncDecRef, wantResult: Boolean): CompiledValue? {
        val target = ref.target as? LocalSlotRef
        if (target != null) {
            if (!allowLocalSlots) return null
            if (isLoopVarRef(target)) {
                val errorSlot = emitLoopVarReassignError(target.name, target.pos())
                return CompiledValue(errorSlot, SlotType.OBJ)
            }
            if (!target.isMutable) {
                val errorSlot = emitImmutableLocalReassignError(target.name, target.pos())
                return CompiledValue(errorSlot, SlotType.OBJ)
            }
            if (target.isDelegated) {
                val slot = resolveSlot(target) ?: return null
                if (slot < scopeSlotCount) return null
                val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
                val current = allocSlot()
                builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current)
                updateSlotType(current, SlotType.OBJ)
                val oneSlot = allocSlot()
                val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                updateSlotType(oneSlot, SlotType.OBJ)
                val result = allocSlot()
                val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                builder.emit(op, current, oneSlot, result)
                updateSlotType(result, SlotType.OBJ)
                builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result)
                updateSlotType(slot, SlotType.OBJ)
                return if (wantResult && ref.isPost) {
                    CompiledValue(current, SlotType.OBJ)
                } else {
                    CompiledValue(result, SlotType.OBJ)
                }
            }
            val slot = resolveSlot(target) ?: return null
            val slotType = slotTypes[slot] ?: SlotType.UNKNOWN
            if (slot < scopeSlotCount && slotType != SlotType.UNKNOWN) {
                val addrSlot = ensureScopeAddr(slot)
                val current = allocSlot()
                emitLoadFromAddr(addrSlot, current, slotType)
                val result = when (slotType) {
                SlotType.INT -> {
                    if (wantResult && ref.isPost) {
                        val old = allocSlot()
                        builder.emit(Opcode.MOVE_INT, current, old)
                        builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, current)
                        emitStoreToAddr(current, addrSlot, SlotType.INT)
                        CompiledValue(old, SlotType.INT)
                    } else {
                        builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, current)
                        emitStoreToAddr(current, addrSlot, SlotType.INT)
                        CompiledValue(current, SlotType.INT)
                    }
                }
                SlotType.REAL -> {
                    val oneSlot = allocSlot()
                    val oneId = builder.addConst(BytecodeConst.RealVal(1.0))
                    builder.emit(Opcode.CONST_REAL, oneId, oneSlot)
                    if (wantResult && ref.isPost) {
                        val old = allocSlot()
                        builder.emit(Opcode.MOVE_REAL, current, old)
                        val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
                        builder.emit(op, current, oneSlot, current)
                        emitStoreToAddr(current, addrSlot, SlotType.REAL)
                        CompiledValue(old, SlotType.REAL)
                    } else {
                        val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
                        builder.emit(op, current, oneSlot, current)
                        emitStoreToAddr(current, addrSlot, SlotType.REAL)
                        CompiledValue(current, SlotType.REAL)
                    }
                }
                SlotType.OBJ -> {
                    val oneSlot = allocSlot()
                    val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                    builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                    val boxed = allocSlot()
                    builder.emit(Opcode.BOX_OBJ, current, boxed)
                    if (wantResult && ref.isPost) {
                        val old = allocSlot()
                        builder.emit(Opcode.MOVE_OBJ, boxed, old)
                        val result = allocSlot()
                        val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                        builder.emit(op, boxed, oneSlot, result)
                        builder.emit(Opcode.MOVE_OBJ, result, boxed)
                        emitStoreToAddr(boxed, addrSlot, SlotType.OBJ)
                        updateSlotType(slot, SlotType.OBJ)
                        CompiledValue(old, SlotType.OBJ)
                    } else {
                        val result = allocSlot()
                        val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                        builder.emit(op, boxed, oneSlot, result)
                        builder.emit(Opcode.MOVE_OBJ, result, boxed)
                        emitStoreToAddr(boxed, addrSlot, SlotType.OBJ)
                        updateSlotType(slot, SlotType.OBJ)
                        CompiledValue(result, SlotType.OBJ)
                    }
                }
                else -> null
            }
                if (result != null) return result
            }
            return when (slotType) {
            SlotType.INT -> {
                if (wantResult && ref.isPost) {
                    val old = allocSlot()
                    builder.emit(Opcode.MOVE_INT, slot, old)
                    builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
                    CompiledValue(old, SlotType.INT)
                } else {
                    builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
                    CompiledValue(slot, SlotType.INT)
                }
            }
            SlotType.REAL -> {
                val oneSlot = allocSlot()
                val oneId = builder.addConst(BytecodeConst.RealVal(1.0))
                builder.emit(Opcode.CONST_REAL, oneId, oneSlot)
                if (wantResult && ref.isPost) {
                    val old = allocSlot()
                    builder.emit(Opcode.MOVE_REAL, slot, old)
                    val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
                    builder.emit(op, slot, oneSlot, slot)
                    CompiledValue(old, SlotType.REAL)
                } else {
                    val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
                    builder.emit(op, slot, oneSlot, slot)
                    CompiledValue(slot, SlotType.REAL)
                }
            }
            SlotType.OBJ -> {
                val oneSlot = allocSlot()
                val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                val current = allocSlot()
                builder.emit(Opcode.BOX_OBJ, slot, current)
                if (wantResult && ref.isPost) {
                    val result = allocSlot()
                    val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.MOVE_OBJ, result, slot)
                    updateSlotType(slot, SlotType.OBJ)
                    CompiledValue(current, SlotType.OBJ)
                } else {
                    val result = allocSlot()
                    val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.MOVE_OBJ, result, slot)
                    updateSlotType(slot, SlotType.OBJ)
                    CompiledValue(result, SlotType.OBJ)
                }
            }
            SlotType.UNKNOWN -> {
                val oneSlot = allocSlot()
                val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                val current = allocSlot()
                builder.emit(Opcode.BOX_OBJ, slot, current)
                if (wantResult && ref.isPost) {
                    val result = allocSlot()
                    val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.MOVE_OBJ, result, slot)
                    updateSlotType(slot, SlotType.OBJ)
                    CompiledValue(current, SlotType.OBJ)
                } else {
                    val result = allocSlot()
                    val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.MOVE_OBJ, result, slot)
                    updateSlotType(slot, SlotType.OBJ)
                    CompiledValue(result, SlotType.OBJ)
                }
            }
                else -> null
            }
        }
        val varTarget = ref.target as? LocalVarRef
        if (varTarget != null) {
            val resolved = resolveAssignableSlotByName(varTarget.name)
            if (resolved != null && isLoopVarSlot(resolved.first)) {
                val errorSlot = emitLoopVarReassignError(varTarget.name, varTarget.pos())
                return CompiledValue(errorSlot, SlotType.OBJ)
            }
            if (resolved != null && !resolved.second) {
                val errorSlot = emitImmutableLocalReassignError(varTarget.name, varTarget.pos())
                return CompiledValue(errorSlot, SlotType.OBJ)
            }
        }

        val thisFieldTarget = ref.target as? ThisFieldSlotRef
        if (thisFieldTarget != null) {
            val receiver = compileThisRef()
            val fieldId = thisFieldTarget.fieldId() ?: -1
            val methodId = thisFieldTarget.methodId() ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${thisFieldTarget.name}", Pos.builtIn)
            }
            val current = allocSlot()
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
            updateSlotType(current, SlotType.OBJ)
            val oneSlot = allocSlot()
            val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
            builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
            updateSlotType(oneSlot, SlotType.OBJ)
            val result = allocSlot()
            val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
            if (wantResult && ref.isPost) {
                val old = allocSlot()
                builder.emit(Opcode.MOVE_OBJ, current, old)
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
                return CompiledValue(old, SlotType.OBJ)
            }
            builder.emit(op, current, oneSlot, result)
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
            return CompiledValue(result, SlotType.OBJ)
        }

        val implicitTarget = ref.target as? ImplicitThisMemberRef
        if (implicitTarget != null) {
            val receiver = compileThisRef()
            val fieldId = implicitTarget.fieldId ?: -1
            val methodId = implicitTarget.methodId ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${implicitTarget.name}", Pos.builtIn)
            }
            val current = allocSlot()
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
            updateSlotType(current, SlotType.OBJ)
            val oneSlot = allocSlot()
            val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
            builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
            updateSlotType(oneSlot, SlotType.OBJ)
            val result = allocSlot()
            val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
            if (wantResult && ref.isPost) {
                val old = allocSlot()
                builder.emit(Opcode.MOVE_OBJ, current, old)
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
                return CompiledValue(old, SlotType.OBJ)
            }
            builder.emit(op, current, oneSlot, result)
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
            return CompiledValue(result, SlotType.OBJ)
        }

        val qualifiedTarget = ref.target as? QualifiedThisFieldSlotRef
        if (qualifiedTarget != null) {
            val receiver = compileThisVariantRef(qualifiedTarget.receiverTypeName()) ?: return null
            val fieldId = qualifiedTarget.fieldId() ?: -1
            val methodId = qualifiedTarget.methodId() ?: -1
            if (fieldId < 0 && methodId < 0) {
                throw BytecodeCompileException("Missing member id for ${qualifiedTarget.name}", Pos.builtIn)
            }
            val current = allocSlot()
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
            updateSlotType(current, SlotType.OBJ)
            val oneSlot = allocSlot()
            val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
            builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
            updateSlotType(oneSlot, SlotType.OBJ)
            val result = allocSlot()
            val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
            if (wantResult && ref.isPost) {
                val old = allocSlot()
                builder.emit(Opcode.MOVE_OBJ, current, old)
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
                return CompiledValue(old, SlotType.OBJ)
            }
            builder.emit(op, current, oneSlot, result)
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, result)
            return CompiledValue(result, SlotType.OBJ)
        }

        val fieldTarget = ref.target as? FieldRef
        if (fieldTarget != null) {
            val receiverClass = resolveReceiverClass(fieldTarget.target)
                ?: throw BytecodeCompileException(
                    "Member access requires compile-time receiver type: ${fieldTarget.name}",
                    Pos.builtIn
                )
            val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
            if (receiverClass == ObjDynamic.type) {
                val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
                val resultSlot = allocSlot()
                if (fieldTarget.isOptional) {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val nullLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                    )
                    val current = allocSlot()
                    builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                    updateSlotType(current, SlotType.OBJ)
                    val oneSlot = allocSlot()
                    val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                    builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                    updateSlotType(oneSlot, SlotType.OBJ)
                    val result = allocSlot()
                    val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                    if (wantResult && ref.isPost) {
                        val old = allocSlot()
                        builder.emit(Opcode.MOVE_OBJ, current, old)
                        builder.emit(op, current, oneSlot, result)
                        builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                        builder.emit(Opcode.MOVE_OBJ, old, resultSlot)
                    } else {
                        builder.emit(op, current, oneSlot, result)
                        builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                        builder.emit(Opcode.MOVE_OBJ, result, resultSlot)
                    }
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(nullLabel)
                    builder.emit(Opcode.CONST_NULL, resultSlot)
                    builder.mark(endLabel)
                    updateSlotType(resultSlot, SlotType.OBJ)
                    return CompiledValue(resultSlot, SlotType.OBJ)
                }
                val current = allocSlot()
                builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
                updateSlotType(current, SlotType.OBJ)
                val oneSlot = allocSlot()
                val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                updateSlotType(oneSlot, SlotType.OBJ)
                val result = allocSlot()
                val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                if (wantResult && ref.isPost) {
                    val old = allocSlot()
                    builder.emit(Opcode.MOVE_OBJ, current, old)
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                    builder.emit(Opcode.MOVE_OBJ, old, resultSlot)
                    return CompiledValue(resultSlot, SlotType.OBJ)
                }
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
                builder.emit(Opcode.MOVE_OBJ, result, resultSlot)
                return CompiledValue(resultSlot, SlotType.OBJ)
            }
            val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name]
            val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name]
            if (fieldId == null && methodId == null && shouldUseClassScopeReceiver(fieldTarget.target, receiver, receiverClass)) {
                val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
                val resultSlot = allocSlot()
                if (fieldTarget.isOptional) {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val nullLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                    )
                    val current = allocSlot()
                    builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, current)
                    updateSlotType(current, SlotType.OBJ)
                    val oneSlot = allocSlot()
                    val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                    builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                    updateSlotType(oneSlot, SlotType.OBJ)
                    val result = allocSlot()
                    val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                    if (wantResult && ref.isPost) {
                        val old = allocSlot()
                        builder.emit(Opcode.MOVE_OBJ, current, old)
                        builder.emit(op, current, oneSlot, result)
                        builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
                        builder.emit(Opcode.MOVE_OBJ, old, resultSlot)
                    } else {
                        builder.emit(op, current, oneSlot, result)
                        builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
                        builder.emit(Opcode.MOVE_OBJ, result, resultSlot)
                    }
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(nullLabel)
                    builder.emit(Opcode.CONST_NULL, resultSlot)
                    builder.mark(endLabel)
                    updateSlotType(resultSlot, SlotType.OBJ)
                    return CompiledValue(resultSlot, SlotType.OBJ)
                }
                val current = allocSlot()
                builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, current)
                updateSlotType(current, SlotType.OBJ)
                val oneSlot = allocSlot()
                val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                updateSlotType(oneSlot, SlotType.OBJ)
                val result = allocSlot()
                val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                if (wantResult && ref.isPost) {
                    val old = allocSlot()
                    builder.emit(Opcode.MOVE_OBJ, current, old)
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
                    builder.emit(Opcode.MOVE_OBJ, old, resultSlot)
                    return CompiledValue(resultSlot, SlotType.OBJ)
                }
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
                builder.emit(Opcode.MOVE_OBJ, result, resultSlot)
                return CompiledValue(resultSlot, SlotType.OBJ)
            }
            if (fieldId == null && methodId == null) return null
            val resultSlot = allocSlot()
            if (fieldTarget.isOptional) {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                val current = allocSlot()
                builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, current)
                updateSlotType(current, SlotType.OBJ)
                val oneSlot = allocSlot()
                val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
                builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
                updateSlotType(oneSlot, SlotType.OBJ)
                val result = allocSlot()
                val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
                if (wantResult && ref.isPost) {
                    val old = allocSlot()
                    builder.emit(Opcode.MOVE_OBJ, current, old)
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
                    builder.emit(Opcode.MOVE_OBJ, old, resultSlot)
                } else {
                    builder.emit(op, current, oneSlot, result)
                    builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
                    builder.emit(Opcode.MOVE_OBJ, result, resultSlot)
                }
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, resultSlot)
                builder.mark(endLabel)
                updateSlotType(resultSlot, SlotType.OBJ)
                return CompiledValue(resultSlot, SlotType.OBJ)
            }
            val current = allocSlot()
            builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, current)
            updateSlotType(current, SlotType.OBJ)
            val oneSlot = allocSlot()
            val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
            builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
            updateSlotType(oneSlot, SlotType.OBJ)
            val result = allocSlot()
            val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
            if (wantResult && ref.isPost) {
                val old = allocSlot()
                builder.emit(Opcode.MOVE_OBJ, current, old)
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
                builder.emit(Opcode.MOVE_OBJ, old, resultSlot)
                return CompiledValue(resultSlot, SlotType.OBJ)
            }
            builder.emit(op, current, oneSlot, result)
            builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
            builder.emit(Opcode.MOVE_OBJ, result, resultSlot)
            return CompiledValue(resultSlot, SlotType.OBJ)
        }

        val indexTarget = ref.target as? IndexRef ?: return null
        val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null
        val elementSlotType = indexElementSlotType(receiver.slot, indexTarget.targetRef)
        if (indexTarget.optionalRef) {
            val resultSlot = allocSlot()
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
            val current = allocSlot()
            builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
            updateSlotType(current, SlotType.OBJ)
            val oneSlot = allocSlot()
            val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
            builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
            val result = allocSlot()
            val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
            if (wantResult && ref.isPost) {
                val old = allocSlot()
                builder.emit(Opcode.MOVE_OBJ, current, old)
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
                builder.emit(Opcode.MOVE_OBJ, old, resultSlot)
            } else {
                builder.emit(op, current, oneSlot, result)
                builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
                builder.emit(Opcode.MOVE_OBJ, result, resultSlot)
            }
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, resultSlot)
            builder.mark(endLabel)
            return CompiledValue(resultSlot, SlotType.OBJ)
        }
        val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
        if (elementSlotType == SlotType.INT && index.type == SlotType.INT) {
            val current = allocSlot()
            builder.emit(Opcode.GET_INDEX_INT, receiver.slot, index.slot, current)
            updateSlotType(current, SlotType.INT)
            val oneSlot = allocSlot()
            val oneId = builder.addConst(BytecodeConst.IntVal(1))
            builder.emit(Opcode.CONST_INT, oneId, oneSlot)
            updateSlotType(oneSlot, SlotType.INT)
            val result = allocSlot()
            val op = if (ref.isIncrement) Opcode.ADD_INT else Opcode.SUB_INT
            builder.emit(op, current, oneSlot, result)
            updateSlotType(result, SlotType.INT)
            builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, result)
            if (wantResult && ref.isPost) {
                return CompiledValue(current, SlotType.INT)
            }
            return CompiledValue(result, SlotType.INT)
        }
        val current = allocSlot()
        builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
        updateSlotType(current, SlotType.OBJ)
        val oneSlot = allocSlot()
        val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
        builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
        val result = allocSlot()
        val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
        if (wantResult && ref.isPost) {
            val old = allocSlot()
            builder.emit(Opcode.MOVE_OBJ, current, old)
            builder.emit(op, current, oneSlot, result)
            builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
            return CompiledValue(old, SlotType.OBJ)
        }
        builder.emit(op, current, oneSlot, result)
        builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
        return CompiledValue(result, SlotType.OBJ)
    }

    private fun compileConditional(ref: ConditionalRef): CompiledValue? {
        val condition = compileRefWithFallback(ref.condition, SlotType.BOOL, Pos.builtIn) ?: return null
        if (condition.type != SlotType.BOOL) return null
        val resultSlot = allocSlot()
        val elseLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_FALSE,
            listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
        )
        val thenValue = compileRefWithFallback(ref.ifTrue, null, Pos.builtIn) ?: return null
        val thenObj = ensureObjSlot(thenValue)
        emitMove(thenObj, resultSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(elseLabel)
        val elseValue = compileRefWithFallback(ref.ifFalse, null, Pos.builtIn) ?: return null
        val elseObj = ensureObjSlot(elseValue)
        emitMove(elseObj, resultSlot)
        builder.mark(endLabel)
        updateSlotType(resultSlot, SlotType.OBJ)
        return CompiledValue(resultSlot, SlotType.OBJ)
    }

    private fun compileElvis(ref: ElvisRef): CompiledValue? {
        fun isAbruptControlRef(candidate: ObjRef): Boolean {
            var stmt = (candidate as? StatementRef)?.statement ?: return false
            while (stmt is BytecodeStatement) {
                stmt = stmt.original
            }
            return when (stmt) {
                is net.sergeych.lyng.BreakStatement,
                is net.sergeych.lyng.ContinueStatement,
                is net.sergeych.lyng.ReturnStatement,
                is net.sergeych.lyng.ThrowStatement -> true
                else -> false
            }
        }

        val leftValue = compileRefWithFallback(ref.left, null, Pos.builtIn) ?: return null
        val leftObj = ensureObjSlot(leftValue)
        val resultSlot = allocSlot()
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, leftObj.slot, nullSlot, cmpSlot)
        val rightLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(rightLabel))
        )
        emitMove(leftObj, resultSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(rightLabel)
        val rightIsAbruptControl = isAbruptControlRef(ref.right)
        val rightValue = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null
        if (!rightIsAbruptControl) {
            val rightObj = ensureObjSlot(rightValue)
            emitMove(rightObj, resultSlot)
        }
        builder.mark(endLabel)
        updateSlotType(resultSlot, SlotType.OBJ)
        return CompiledValue(resultSlot, SlotType.OBJ)
    }

    private fun compileWhen(stmt: WhenStatement, wantResult: Boolean): CompiledValue? {
        val subjectRef = extractFlowTypeSubject(stmt.value)
        val subjectValue = compileStatementValueOrFallback(stmt.value) ?: return null
        val subjectObj = ensureObjSlot(subjectValue)
        val resultSlot = allocSlot()
        if (wantResult) {
            val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
            builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
            updateSlotType(resultSlot, SlotType.OBJ)
        }
        val endLabel = builder.label()
        for (case in stmt.cases) {
            val caseLabel = builder.label()
            val nextCaseLabel = builder.label()
            for (cond in case.conditions) {
                val condValue = compileWhenCondition(cond, subjectObj) ?: return null
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(condValue.slot), CmdBuilder.Operand.LabelRef(caseLabel))
                )
            }
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(nextCaseLabel)))
            builder.mark(caseLabel)
            val caseOverride = flowTypeOverrideForWhenCase(subjectRef, case.conditions)
            val caseRestore = applyFlowTypeOverride(caseOverride)
            val bodyValue = compileStatementValueOrFallback(case.block, wantResult) ?: return null
            if (wantResult) {
                val bodyObj = ensureObjSlot(bodyValue)
                emitMove(bodyObj, resultSlot)
            }
            restoreFlowTypeOverride(caseRestore)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nextCaseLabel)
        }
        stmt.elseCase?.let {
            val elseValue = compileStatementValueOrFallback(it, wantResult) ?: return null
            if (wantResult) {
                val elseObj = ensureObjSlot(elseValue)
                emitMove(elseObj, resultSlot)
            }
        }
        builder.mark(endLabel)
        return if (wantResult) {
            updateSlotType(resultSlot, SlotType.OBJ)
            CompiledValue(resultSlot, SlotType.OBJ)
        } else {
            subjectObj
        }
    }

    private fun compileWhenCondition(cond: WhenCondition, subjectObj: CompiledValue): CompiledValue? {
        val subject = ensureObjSlot(subjectObj)
        return when (cond) {
            is WhenEqualsCondition -> {
                val expected = compileStatementValueOrFallback(cond.expr) ?: return null
                val expectedObj = ensureObjSlot(expected)
                val dst = allocSlot()
                builder.emit(Opcode.CMP_EQ_OBJ, expectedObj.slot, subject.slot, dst)
                updateSlotType(dst, SlotType.BOOL)
                CompiledValue(dst, SlotType.BOOL)
            }
            is WhenInCondition -> {
                val container = compileStatementValueOrFallback(cond.expr) ?: return null
                val containerObj = ensureObjSlot(container)
                val baseDst = allocSlot()
                builder.emit(Opcode.CONTAINS_OBJ, containerObj.slot, subject.slot, baseDst)
                updateSlotType(baseDst, SlotType.BOOL)
                if (!cond.negated) {
                    CompiledValue(baseDst, SlotType.BOOL)
                } else {
                    val neg = allocSlot()
                    builder.emit(Opcode.NOT_BOOL, baseDst, neg)
                    updateSlotType(neg, SlotType.BOOL)
                    CompiledValue(neg, SlotType.BOOL)
                }
            }
            is WhenIsCondition -> {
                val typeValue = compileStatementValueOrFallback(cond.expr) ?: return null
                val typeObj = ensureObjSlot(typeValue)
                val baseDst = allocSlot()
                builder.emit(Opcode.CHECK_IS, subject.slot, typeObj.slot, baseDst)
                updateSlotType(baseDst, SlotType.BOOL)
                if (!cond.negated) {
                    CompiledValue(baseDst, SlotType.BOOL)
                } else {
                    val neg = allocSlot()
                    builder.emit(Opcode.NOT_BOOL, baseDst, neg)
                    updateSlotType(neg, SlotType.BOOL)
                    CompiledValue(neg, SlotType.BOOL)
                }
            }
            is WhenNullableCondition -> {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                updateSlotType(nullSlot, SlotType.OBJ)
                val baseDst = allocSlot()
                builder.emit(Opcode.CHECK_IS, nullSlot, subject.slot, baseDst)
                updateSlotType(baseDst, SlotType.BOOL)
                if (!cond.negated) {
                    CompiledValue(baseDst, SlotType.BOOL)
                } else {
                    val neg = allocSlot()
                    builder.emit(Opcode.NOT_BOOL, baseDst, neg)
                    updateSlotType(neg, SlotType.BOOL)
                    CompiledValue(neg, SlotType.BOOL)
                }
            }
        }
    }

    private fun ensureObjSlot(value: CompiledValue): CompiledValue {
        if (value.type == SlotType.OBJ) return value
        val dst = allocSlot()
        builder.emit(Opcode.BOX_OBJ, value.slot, dst)
        updateSlotType(dst, SlotType.OBJ)
        when (value.type) {
            SlotType.INT -> {
                slotObjClass[dst] = ObjInt.type
                if (isStablePrimitiveSourceSlot(value.slot)) {
                    stableObjSlots.add(dst)
                } else {
                    stableObjSlots.remove(dst)
                }
            }
            SlotType.REAL -> {
                slotObjClass[dst] = ObjReal.type
                if (isStablePrimitiveSourceSlot(value.slot)) {
                    stableObjSlots.add(dst)
                } else {
                    stableObjSlots.remove(dst)
                }
            }
            SlotType.BOOL -> {
                slotObjClass[dst] = ObjBool.type
                if (isStablePrimitiveSourceSlot(value.slot)) {
                    stableObjSlots.add(dst)
                } else {
                    stableObjSlots.remove(dst)
                }
            }
            else -> stableObjSlots.remove(dst)
        }
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileCall(ref: CallRef): CompiledValue? {
        val callPos = callSitePos()
        val lambdaTarget = resolveInlineCallableLambda(ref.target)
        if (lambdaTarget != null) {
            compileInlineDirectLambdaCall(ref, lambdaTarget)?.let { return it }
        }
        val fieldTarget = ref.target as? FieldRef
        if (fieldTarget != null && isKnownClassReceiver(fieldTarget.target)) {
            val receiverClass = resolveReceiverClass(fieldTarget.target)
            val methodId = receiverClass?.instanceMethodIdMap(includeAbstract = true)?.get(fieldTarget.name)
            if (methodId != null) {
                val receiver = compileRefWithFallback(fieldTarget.target, null, refPosOrCurrent(fieldTarget.target))
                    ?: return null
                val dst = allocSlot()
                val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
                if (!ref.isOptionalInvoke) {
                    val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
                    val encodedCount = encodeCallArgCount(args) ?: return null
                    setPos(callPos)
                    builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
                } else {
                    val nullSlot = allocSlot()
                    builder.emit(Opcode.CONST_NULL, nullSlot)
                    val cmpSlot = allocSlot()
                    builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                    val nullLabel = builder.label()
                    val endLabel = builder.label()
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                    )
                    val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
                    val encodedCount = encodeCallArgCount(args) ?: return null
                    setPos(callPos)
                    builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                    builder.mark(nullLabel)
                    builder.emit(Opcode.CONST_NULL, dst)
                    builder.mark(endLabel)
                }
                updateSlotType(dst, SlotType.OBJ)
                return CompiledValue(dst, SlotType.OBJ)
            }
        }
        val localTarget = ref.target as? LocalVarRef
        val isExternCall = localTarget != null && externCallableNames.contains(localTarget.name)
        if (!isExternCall) {
            val exactCallee = resolveExactCallableObj(ref.target)
            if (exactCallee != null) {
                val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
                val encodedCount = encodeCallArgCount(args) ?: return null
                val dst = allocSlot()
                setPos(callPos)
                emitCallDirect(exactCallee, args.base, encodedCount, dst)
                updateSlotType(dst, SlotType.OBJ)
                (exactCallee as? ObjClass)?.let { slotObjClass[dst] = it }
                return CompiledValue(dst, SlotType.OBJ)
            }
        }
        if (localTarget != null) {
            val direct = resolveDirectNameSlot(localTarget.name)
            if (direct == null) {
                val thisSlot = resolveDirectNameSlot("this")
                if (thisSlot != null) {
                    throw BytecodeCompileException(
                        "Unresolved member call '${localTarget.name}': missing compile-time member id",
                        Pos.builtIn
                    )
                }
            }
        }
        val initClass = when (localTarget?.name) {
            "List" -> ObjList.type
            "ImmutableList" -> ObjImmutableList.type
            "ObservableList" -> ObjObservableList.type
            "Map" -> ObjMap.type
            "ImmutableMap" -> ObjImmutableMap.type
            "Set" -> ObjSet.type
            "ImmutableSet" -> ObjImmutableSet.type
            else -> null
        }
        val callee = compileRefWithFallback(ref.target, null, refPosOrCurrent(ref.target)) ?: return null
        val dst = allocSlot()
        if (!ref.isOptionalInvoke) {
            val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            emitCallCompiled(callee, args.base, encodedCount, dst, isExternCall = isExternCall)
            if (initClass != null) {
                slotObjClass[dst] = initClass
            }
            return CompiledValue(dst, SlotType.OBJ)
        }
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, callee.slot, nullSlot, cmpSlot)
        val nullLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
        )
        val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
        val encodedCount = encodeCallArgCount(args) ?: return null
        setPos(callPos)
        emitCallCompiled(callee, args.base, encodedCount, dst, isExternCall = isExternCall)
        if (initClass != null) {
            slotObjClass[dst] = initClass
        }
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(nullLabel)
        builder.emit(Opcode.CONST_NULL, dst)
        builder.mark(endLabel)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun resolveDirectNameSlot(name: String): CompiledValue? {
        loopSlotOverrides[name]?.let { slot ->
            val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
            return CompiledValue(slot, resolved)
        }
        if (useScopeSlots && isPreparedScopeName(name)) {
            scopeSlotIndexByName[name]?.let { slot ->
                val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
                return CompiledValue(slot, resolved)
            }
        }
        if (!allowLocalSlots) return null
        localSlotIndexByName[name]?.let { localIndex ->
            val slot = scopeSlotCount + localIndex
            val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
            return CompiledValue(slot, resolved)
        }
        scopeSlotIndexByName[name]?.let { slot ->
            val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
            return CompiledValue(slot, resolved)
        }
        return null
    }

    private fun resolveAssignableSlotByName(name: String): Pair<Int, Boolean>? {
        localSlotIndexByName[name]?.let { localIndex ->
            val slot = scopeSlotCount + localIndex
            val mutable = localSlotMutables.getOrNull(localIndex) ?: true
            return slot to mutable
        }
        scopeSlotIndexByName[name]?.let { slot ->
            val mutable = scopeSlotMutables.getOrNull(slot) ?: true
            return slot to mutable
        }
        return null
    }

    private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
        compileListFillIntCall(ref)?.let { return it }
        compileInlineHigherOrderMethodCall(ref)?.let { return it }
        val callPos = callSitePos()
        val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
        val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
        val dst = allocSlot()
        if (shouldUseClassScopeReceiver(ref.receiver, receiver, receiverClass)) {
            val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
            val memberSlot = allocSlot()
            if (!ref.isOptional) {
                builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, memberSlot)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, memberSlot)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, memberSlot)
                builder.mark(endLabel)
            }
            val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            emitCallCompiled(CompiledValue(memberSlot, SlotType.OBJ), args.base, encodedCount, dst)
            return CompiledValue(dst, SlotType.OBJ)
        }
        fun emitDynamicCall(): CompiledValue? {
            val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
            if (!ref.isOptional) {
                setPos(callPos)
                builder.emit(Opcode.CALL_DYNAMIC_MEMBER, receiver.slot, nameId, args.base, encodedCount, dst)
                return CompiledValue(dst, SlotType.OBJ)
            }
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            setPos(callPos)
            builder.emit(Opcode.CALL_DYNAMIC_MEMBER, receiver.slot, nameId, args.base, encodedCount, dst)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, dst)
            builder.mark(endLabel)
            return CompiledValue(dst, SlotType.OBJ)
        }
        if (receiverClass == ObjDynamic.type) {
            return emitDynamicCall()
        }
        if (receiverClass is ObjInstanceClass && !isThisReceiver(ref.receiver)) {
            return emitDynamicCall()
        }
        val resolvedMember = receiverClass.resolveInstanceMember(ref.name)
        if (resolvedMember?.declaringClass?.className == "Obj") {
            return emitDynamicCall()
        }
        val abstractRecord = receiverClass.members[ref.name] ?: receiverClass.classScope?.objects?.get(ref.name)
        if (abstractRecord?.isAbstract == true) {
            return emitDynamicCall()
        }
        val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
        if (methodId != null && resolvedMember == null) {
            return emitDynamicCall()
        }
        if (methodId != null && resolvedMember?.declaringClass?.className != "Obj") {
            val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
            if (!ref.isOptional) {
                val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
                val encodedCount = encodeCallArgCount(args) ?: return null
                setPos(callPos)
                builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
                if (receiverClass == ObjList.type && ref.name == "add" && ref.args.size == 1 && !ref.args.first().isSplat) {
                    noteListElementClassMutation(receiver.slot, CompiledValue(args.base, SlotType.OBJ))
                }
                return CompiledValue(dst, SlotType.OBJ)
            }
            val nullSlot = allocSlot()
            builder.emit(Opcode.CONST_NULL, nullSlot)
            val cmpSlot = allocSlot()
            builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
            val nullLabel = builder.label()
            val endLabel = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
            )
            val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
            builder.mark(nullLabel)
            builder.emit(Opcode.CONST_NULL, dst)
            builder.mark(endLabel)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[ref.name] else null
        if (fieldId != null) {
            val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: fieldId
            val calleeSlot = allocSlot()
            if (!ref.isOptional) {
                builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId, -1, calleeSlot)
            }
            if (!ref.isOptional) {
                val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
                val encodedCount = encodeCallArgCount(args) ?: return null
                setPos(callPos)
                emitCallCompiled(CompiledValue(calleeSlot, SlotType.OBJ), args.base, encodedCount, dst)
            } else {
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
                val nullLabel = builder.label()
                val endLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
                )
                builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId, -1, calleeSlot)
                val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
                val encodedCount = encodeCallArgCount(args) ?: return null
                setPos(callPos)
                emitCallCompiled(CompiledValue(calleeSlot, SlotType.OBJ), args.base, encodedCount, dst)
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                builder.mark(nullLabel)
                builder.emit(Opcode.CONST_NULL, dst)
                builder.mark(endLabel)
            }
            return CompiledValue(dst, SlotType.OBJ)
        }
        val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name)
            ?: throw BytecodeCompileException(
                missingMemberMessage(receiverClass, ref.name),
                Pos.builtIn
            )
        val callee = ensureObjSlot(extSlot)
        if (!ref.isOptional) {
            val args = compileCallArgsWithReceiver(receiver, ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            emitCallCompiled(callee, args.base, encodedCount, dst)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
        val nullLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
        )
        val args = compileCallArgsWithReceiver(receiver, ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
        val encodedCount = encodeCallArgCount(args) ?: return null
        setPos(callPos)
        emitCallCompiled(callee, args.base, encodedCount, dst)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(nullLabel)
        builder.emit(Opcode.CONST_NULL, dst)
        builder.mark(endLabel)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileInlineHigherOrderMethodCall(ref: MethodCallRef): CompiledValue? {
        val spec = inlineHigherOrderMethodSpec(ref) ?: return null
        if (ref.args.size != spec.argCount || ref.args.any { it.isSplat || it.name != null }) return null
        if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
        val lambdaRef = extractExactLambdaRef(ref.args[spec.lambdaArgIndex].value) ?: return null
        val inlineRef = lambdaRef.inlineBodyRef ?: return null
        return when (spec.kind) {
            CallSignature.Kind.UNARY_ARGUMENT -> {
                if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
                val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
                val receiverObj = ensureObjSlot(receiver)
                compileOptionalInlineMethod(ref.isOptional, receiverObj) {
                    val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(receiver)) ?: return@compileOptionalInlineMethod null
                    when (spec.result) {
                        CallSignature.ResultMode.BLOCK_RESULT ->
                            compileInlineLambdaBody(lambdaRef, inlineRef, bindings)
                        CallSignature.ResultMode.RETURN_RECEIVER -> {
                            compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return@compileOptionalInlineMethod null
                            CompiledValue(receiverObj.slot, SlotType.OBJ)
                        }
                        else -> null
                    }
                }
            }
            CallSignature.Kind.RECEIVER -> {
                val receiverInfo = receiverInlineInfo(lambdaRef) ?: return null
                if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = true, allowCaptures = true)) return null
                val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
                val receiverObj = ensureObjSlot(receiver)
                compileOptionalInlineMethod(ref.isOptional, receiverObj) {
                    compileInlineReceiverLambdaInvocation(receiverObj, lambdaRef, spec.result, receiverInfo)
                }
            }
            CallSignature.Kind.ITERABLE -> {
                if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
                val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
                val receiverObj = ensureObjSlot(receiver)
                compileOptionalInlineMethod(ref.isOptional, receiverObj) {
                    compileInlineIterableLambdaLoop(receiverObj, ref, lambdaRef, inlineRef, spec.result)
                }
            }
            CallSignature.Kind.MAP_GET_OR_PUT -> {
                val receiverClass = resolveReceiverClass(ref.receiver) ?: return null
                if (receiverClass != ObjMap.type) return null
                if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
                val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
                val receiverObj = ensureObjSlot(receiver)
                val key = compileArgValue(ref.args[0].value) ?: return null
                val keyObj = ensureObjSlot(key)
                compileOptionalInlineMethod(ref.isOptional, receiverObj) {
                    compileInlineMapGetOrPut(receiverObj, keyObj, lambdaRef, inlineRef)
                }
            }
        }
    }

    private fun compileListFillIntCall(ref: MethodCallRef): CompiledValue? {
        if (ref.name != "fill" || !isListTypeRef(ref.receiver)) return null
        if (ref.args.size != 2 && ref.args.size != 3) return null
        if (ref.args.any { it.isSplat || it.name != null }) return null
        val lambdaArgIndex = ref.args.lastIndex
        val lambdaRef = ((ref.args[lambdaArgIndex].value as? ExpressionStatement)?.ref as? LambdaFnRef) ?: return null
        if (lambdaRef.inferredReturnClass != ObjInt.type) return null
        val size = compileArgValue(ref.args[0].value) ?: return null
        if (size.type != SlotType.INT) return null
        val capacity = if (ref.args.size == 3) {
            val compiled = compileArgValue(ref.args[1].value) ?: return null
            if (compiled.type != SlotType.INT) return null
            compiled
        } else null
        lambdaRef.inlineBodyRef?.let { inlineRef ->
            return compileInlineListFillInt(size, capacity, lambdaRef, inlineRef)
        }
        run {
            val callable = ensureObjSlot(compileArgValue(ref.args[lambdaArgIndex].value) ?: return null)
            val dst = allocSlot()
            if (capacity != null) {
                builder.emit(Opcode.LIST_FILL_INT_CAP, size.slot, capacity.slot, callable.slot, dst)
            } else {
                builder.emit(Opcode.LIST_FILL_INT, size.slot, callable.slot, dst)
            }
            updateSlotType(dst, SlotType.OBJ)
            slotObjClass[dst] = ObjList.type
            listElementClassBySlot[dst] = ObjInt.type
            return CompiledValue(dst, SlotType.OBJ)
        }
    }

    private fun compileInlineDirectLambdaCall(ref: CallRef, lambdaRef: LambdaFnRef): CompiledValue? {
        if (ref.tailBlock) return null
        if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
        val inlineRef = lambdaRef.inlineBodyRef ?: return null
        if (lambdaRef.argsDeclaration == null && ref.args.size != 1 &&
            !isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = false)
        ) {
            return null
        }
        val bindings = prepareInlineLambdaBindingsFromArgs(lambdaRef, ref.args) ?: return null
        return compileInlineLambdaBody(lambdaRef, inlineRef, bindings)
    }

    private fun prepareInlineLambdaBindingsFromArgs(
        lambdaRef: LambdaFnRef,
        args: List<ParsedArgument>
    ): List<Pair<String, Int>>? {
        if (args.any { it.isSplat || it.name != null }) return null
        val compiledArgs = ArrayList<CompiledValue>(args.size)
        for (arg in args) {
            compiledArgs += compileArgValue(arg.value) ?: return null
        }
        return prepareInlineLambdaBindingsFromValues(lambdaRef, compiledArgs)
    }

    private fun prepareInlineLambdaBindingsFromValues(
        lambdaRef: LambdaFnRef,
        args: List<CompiledValue>
    ): List<Pair<String, Int>>? {
        val declaration = lambdaRef.argsDeclaration
        if (declaration == null) {
            val implicitValue = when (args.size) {
                0 -> CompiledValue(ensureVoidSlot(), SlotType.OBJ)
                1 -> args[0]
                else -> buildInlineImplicitItList(args) ?: return null
            }
            return listOf("it" to materializeInlineBinding(implicitValue))
        }
        if (declaration.params.any { it.isEllipsis || it.defaultValue != null }) return null
        if (args.size != declaration.params.size) return null
        if (args.isEmpty()) return emptyList()
        val bindings = ArrayList<Pair<String, Int>>(args.size)
        for ((index, param) in declaration.params.withIndex()) {
            bindings += param.name to materializeInlineBinding(args[index])
        }
        return bindings
    }

    private fun buildInlineImplicitItList(args: List<CompiledValue>): CompiledValue? {
        val list = createEmptyMutableList() ?: return null
        for (arg in args) {
            appendToList(list, arg) ?: return null
        }
        return list
    }

    private fun LambdaFnRef.inlineParamNames(): List<String>? {
        val declaration = argsDeclaration
        if (declaration == null) {
            return listOf("it")
        }
        if (declaration.params.any { it.isEllipsis || it.defaultValue != null }) return null
        return declaration.params.map { it.name }
    }

    private fun materializeInlineBinding(value: CompiledValue): Int {
        val slot = allocSlot()
        emitMove(value, slot)
        updateSlotType(slot, value.type)
        if (value.type == SlotType.OBJ) {
            slotObjClass[value.slot]?.let { slotObjClass[slot] = it }
        }
        return slot
    }

    private data class InlineReceiverInfo(
        val explicitBindings: List<Pair<String, Int>>,
        val thisTypeName: String?
    )

    private fun inlineHigherOrderMethodSpec(ref: MethodCallRef): CallSignature.HigherOrderInline? {
        resolveReceiverClass(ref.receiver)
            ?.resolveInstanceMember(ref.name)
            ?.record
            ?.callSignature
            ?.inlineHigherOrder
            ?.let { return it }
        val receiverClass = resolveReceiverClass(ref.receiver) ?: return null
        return extensionCallableSignature(receiverClass, ref.name)?.inlineHigherOrder
    }

    private fun compileOptionalInlineMethod(
        isOptional: Boolean,
        receiverObj: CompiledValue,
        compileNonNull: () -> CompiledValue?
    ): CompiledValue? {
        if (!isOptional) return compileNonNull()
        val dst = allocSlot()
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiverObj.slot, nullSlot, cmpSlot)
        val nullLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
        )
        val nonNullResult = compileNonNull() ?: return null
        val nonNullObj = ensureObjSlot(nonNullResult)
        builder.emit(Opcode.MOVE_OBJ, nonNullObj.slot, dst)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(nullLabel)
        builder.emit(Opcode.CONST_NULL, dst)
        builder.mark(endLabel)
        updateSlotType(dst, SlotType.OBJ)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun isMethodInlineSafe(
        lambdaRef: LambdaFnRef,
        inlineRef: ObjRef,
        allowReceiverRefs: Boolean,
        allowCaptures: Boolean
    ): Boolean {
        val allowedLocalNames = LinkedHashSet<String>()
        lambdaRef.inlineParamNames()?.let { allowedLocalNames.addAll(it) }
        if (allowCaptures) {
            val captures = (lambdaCaptureEntriesByRef[lambdaRef] ?: lambdaRef.captureEntries).orEmpty()
            allowedLocalNames.addAll(captures.map { it.ownerName })
        }

        fun isAllowedName(name: String): Boolean {
            return name == "this" || allowedLocalNames.contains(name)
        }

        fun scan(ref: ObjRef): Boolean {
            return when (ref) {
                is ConstRef,
                is TypeDeclRef,
                is ClassOperatorRef -> true
                is LambdaFnRef -> false
                is LocalSlotRef -> isAllowedName(ref.name)
                is LocalVarRef -> isAllowedName(ref.name)
                is FastLocalVarRef -> isAllowedName(ref.name)
                is BoundLocalVarRef -> false
                is FieldRef -> scan(ref.target)
                is MethodCallRef -> scan(ref.receiver) && ref.args.all { arg ->
                    val expr = arg.value as? ExpressionStatement ?: return@all false
                    scan(expr.ref)
                }
                is CallRef -> scan(ref.target) && ref.args.all { arg ->
                    val expr = arg.value as? ExpressionStatement ?: return@all false
                    scan(expr.ref)
                }
                is BinaryOpRef -> scan(ref.left) && scan(ref.right)
                is UnaryOpRef -> scan(ref.a)
                is LogicalAndRef -> scan(ref.left()) && scan(ref.right())
                is LogicalOrRef -> scan(ref.left()) && scan(ref.right())
                is ConditionalRef -> scan(ref.condition) && scan(ref.ifTrue) && scan(ref.ifFalse)
                is ElvisRef -> scan(ref.left) && scan(ref.right)
                is CastRef -> scan(ref.castValueRef()) && scan(ref.castTypeRef())
                is RangeRef -> listOfNotNull(ref.left, ref.right, ref.step).all(::scan)
                is AssignRef -> scan(ref.target) && scan(ref.value)
                is AssignOpRef -> scan(ref.target) && scan(ref.value)
                is AssignIfNullRef -> scan(ref.target) && scan(ref.value)
                is IncDecRef -> scan(ref.target)
                is IndexRef -> scan(ref.targetRef) && scan(ref.indexRef)
                is ListLiteralRef -> ref.entries().all { entry ->
                    when (entry) {
                        is ListEntry.Element -> scan(entry.ref)
                        is ListEntry.Spread -> scan(entry.ref)
                    }
                }
                is MapLiteralRef -> ref.entries().all { entry ->
                    when (entry) {
                        is MapLiteralEntry.Named -> scan(entry.value)
                        is MapLiteralEntry.Spread -> scan(entry.ref)
                    }
                }
                is StatementRef -> {
                    val expr = ref.statement as? ExpressionStatement ?: return false
                    scan(expr.ref)
                }
                is ImplicitThisMemberRef,
                is ImplicitThisMethodCallRef,
                is ThisFieldSlotRef,
                is ThisMethodSlotCallRef,
                is QualifiedThisFieldSlotRef,
                is QualifiedThisMethodSlotCallRef,
                is QualifiedThisRef -> allowReceiverRefs
                else -> false
            }
        }

        return scan(inlineRef)
    }

    private fun receiverInlineInfo(lambdaRef: LambdaFnRef): InlineReceiverInfo? {
        val declaration = lambdaRef.argsDeclaration
        return if (declaration == null) {
            InlineReceiverInfo(listOf("it" to ensureVoidSlot()), lambdaRef.preferredThisType)
        } else {
            if (declaration.params.any { it.isEllipsis || it.defaultValue != null }) return null
            if (declaration.params.isNotEmpty()) return null
            InlineReceiverInfo(emptyList(), lambdaRef.preferredThisType)
        }
    }

    private fun compileInlineReceiverLambdaInvocation(
        receiverObj: CompiledValue,
        lambdaRef: LambdaFnRef,
        behavior: CallSignature.ResultMode,
        receiverInfo: InlineReceiverInfo
    ): CompiledValue? {
        val inlineRef = lambdaRef.inlineBodyRef ?: return null
        val receiverSlot = materializeInlineBinding(receiverObj)
        val previousBinding = InlineThisBinding(receiverSlot, receiverInfo.thisTypeName)
        inlineThisBindings.addLast(previousBinding)
        return try {
            when (behavior) {
                CallSignature.ResultMode.BLOCK_RESULT ->
                    compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings)
                CallSignature.ResultMode.RETURN_RECEIVER -> {
                    compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings) ?: return null
                    CompiledValue(receiverSlot, SlotType.OBJ)
                }
                else -> null
            }
        } finally {
            inlineThisBindings.removeLast()
        }
    }

    private fun createEmptyMutableList(): CompiledValue? {
        val dst = allocSlot()
        emitCallDirect(ObjList.type, 0, 0, dst)
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = ObjList.type
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun createEmptyMutableMap(): CompiledValue? {
        val dst = allocSlot()
        emitCallDirect(ObjMap.type, 0, 0, dst)
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = ObjMap.type
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileInlineIterableLambdaLoop(
        receiverObj: CompiledValue,
        ref: MethodCallRef,
        lambdaRef: LambdaFnRef,
        inlineRef: ObjRef,
        behavior: CallSignature.ResultMode
    ): CompiledValue? {
        val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
        val iteratorMethodId = iterableMethods["iterator"]
            ?: throw BytecodeCompileException("Missing member id for Iterable.iterator", refPosOrCurrent(ref.receiver))
        val iteratorMethods = ObjIterator.instanceMethodIdMap(includeAbstract = true)
        val hasNextMethodId = iteratorMethods["hasNext"]
            ?: throw BytecodeCompileException("Missing member id for Iterator.hasNext", refPosOrCurrent(ref.receiver))
        val nextMethodId = iteratorMethods["next"]
            ?: throw BytecodeCompileException("Missing member id for Iterator.next", refPosOrCurrent(ref.receiver))

        val iterSlot = allocSlot()
        builder.emit(Opcode.CALL_MEMBER_SLOT, receiverObj.slot, iteratorMethodId, 0, 0, iterSlot)
        builder.emit(Opcode.ITER_PUSH, iterSlot)

        val result = when (behavior) {
            CallSignature.ResultMode.FOR_EACH -> CompiledValue(ensureVoidSlot(), SlotType.OBJ)
            CallSignature.ResultMode.MAP,
            CallSignature.ResultMode.FILTER,
            CallSignature.ResultMode.MAP_NOT_NULL -> createEmptyMutableList() ?: return null
            CallSignature.ResultMode.ASSOCIATE_BY -> createEmptyMutableMap() ?: return null
            else -> return null
        }
        if (behavior == CallSignature.ResultMode.FILTER) {
            listElementClassFromReceiverRef(ref.receiver)?.let { listElementClassBySlot[result.slot] = it }
        }
        if (behavior == CallSignature.ResultMode.MAP) {
            lambdaRef.inferredReturnClass?.let { listElementClassBySlot[result.slot] = it }
        }

        val loopLabel = builder.label()
        val endLabel = builder.label()
        builder.mark(loopLabel)
        val hasNextSlot = allocSlot()
        builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, hasNextMethodId, 0, 0, hasNextSlot)
        val condSlot = allocSlot()
        builder.emit(Opcode.OBJ_TO_BOOL, hasNextSlot, condSlot)
        builder.emit(
            Opcode.JMP_IF_FALSE,
            listOf(CmdBuilder.Operand.IntVal(condSlot), CmdBuilder.Operand.LabelRef(endLabel))
        )
        val nextSlot = allocSlot()
        builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, nextMethodId, 0, 0, nextSlot)
        val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN))
        val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(nextObj)) ?: return null
        when (behavior) {
            CallSignature.ResultMode.FOR_EACH -> {
                compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
            }
            CallSignature.ResultMode.MAP -> {
                val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
                appendToList(result, mapped) ?: return null
            }
            CallSignature.ResultMode.FILTER -> {
                val predicate = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
                val predicateBool = compileValueAsBool(predicate)
                val skipLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_FALSE,
                    listOf(CmdBuilder.Operand.IntVal(predicateBool.slot), CmdBuilder.Operand.LabelRef(skipLabel))
                )
                appendToList(result, nextObj) ?: return null
                builder.mark(skipLabel)
            }
            CallSignature.ResultMode.MAP_NOT_NULL -> {
                val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
                val mappedObj = ensureObjSlot(mapped)
                val nullSlot = allocSlot()
                builder.emit(Opcode.CONST_NULL, nullSlot)
                val cmpSlot = allocSlot()
                builder.emit(Opcode.CMP_REF_EQ_OBJ, mappedObj.slot, nullSlot, cmpSlot)
                val skipLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(skipLabel))
                )
                appendToList(result, mappedObj) ?: return null
                builder.mark(skipLabel)
            }
            CallSignature.ResultMode.ASSOCIATE_BY -> {
                val key = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
                appendToMap(result, key, nextObj)
            }
        }
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
        builder.mark(endLabel)
        builder.emit(Opcode.ITER_POP)
        return result
    }

    private fun appendToList(listValue: CompiledValue, itemValue: CompiledValue): CompiledValue? {
        val addMethodId = ObjList.type.instanceMethodIdMap(includeAbstract = true)["add"]
            ?: throw BytecodeCompileException("Missing member id for List.add", Pos.builtIn)
        val listObj = ensureObjSlot(listValue)
        val itemObj = ensureObjSlot(itemValue)
        val argSlot = allocSlot()
        builder.emit(Opcode.MOVE_OBJ, itemObj.slot, argSlot)
        updateSlotType(argSlot, SlotType.OBJ)
        val dst = allocSlot()
        builder.emit(Opcode.CALL_MEMBER_SLOT, listObj.slot, addMethodId, argSlot, 1, dst)
        noteListElementClassMutation(listObj.slot, itemObj)
        updateSlotType(dst, SlotType.OBJ)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun appendToMap(mapValue: CompiledValue, keyValue: CompiledValue, itemValue: CompiledValue) {
        val mapObj = ensureObjSlot(mapValue)
        val keyObj = ensureObjSlot(keyValue)
        val itemObj = ensureObjSlot(itemValue)
        builder.emit(Opcode.SET_INDEX, mapObj.slot, keyObj.slot, itemObj.slot)
    }

    private fun compileInlineMapGetOrPut(
        receiverObj: CompiledValue,
        keyObj: CompiledValue,
        lambdaRef: LambdaFnRef,
        inlineRef: ObjRef
    ): CompiledValue? {
        val dst = allocSlot()
        val hasKey = allocSlot()
        builder.emit(Opcode.CONTAINS_OBJ, receiverObj.slot, keyObj.slot, hasKey)
        val existingLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(hasKey), CmdBuilder.Operand.LabelRef(existingLabel))
        )
        val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, emptyList()) ?: return null
        val computed = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
        val computedObj = ensureObjSlot(computed)
        builder.emit(Opcode.SET_INDEX, receiverObj.slot, keyObj.slot, computedObj.slot)
        builder.emit(Opcode.MOVE_OBJ, computedObj.slot, dst)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(existingLabel)
        builder.emit(Opcode.GET_INDEX, receiverObj.slot, keyObj.slot, dst)
        builder.mark(endLabel)
        updateSlotType(dst, SlotType.OBJ)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun compileValueAsBool(value: CompiledValue): CompiledValue {
        if (value.type == SlotType.BOOL) return value
        val dst = allocSlot()
        when (value.type) {
            SlotType.INT -> builder.emit(Opcode.INT_TO_BOOL, value.slot, dst)
            SlotType.OBJ, SlotType.UNKNOWN, SlotType.REAL -> {
                val obj = ensureObjSlot(value)
                builder.emit(Opcode.OBJ_TO_BOOL, obj.slot, dst)
            }
        }
        updateSlotType(dst, SlotType.BOOL)
        return CompiledValue(dst, SlotType.BOOL)
    }

    private fun compileInlineLambdaBody(
        lambdaRef: LambdaFnRef,
        inlineRef: ObjRef,
        explicitBindings: List<Pair<String, Int>>
    ): CompiledValue? {
        if (!activeInlineLambdas.add(lambdaRef)) return null
        val previousOverrides = ArrayList<Pair<String, Int?>>(lambdaRef.captureEntries.size + explicitBindings.size)
        for (capture in lambdaRef.captureEntries) {
            val slot = resolveInlineCaptureSlot(capture) ?: continue
            previousOverrides += capture.ownerName to loopSlotOverrides.put(capture.ownerName, slot)
        }
        for ((name, slot) in explicitBindings) {
            previousOverrides += name to loopSlotOverrides.put(name, slot)
        }
        return try {
            compileRefWithFallback(inlineRef, null, refPosOrCurrent(inlineRef))
        } finally {
            for ((name, previous) in previousOverrides.asReversed()) {
                if (previous == null) loopSlotOverrides.remove(name) else loopSlotOverrides[name] = previous
            }
            activeInlineLambdas.remove(lambdaRef)
        }
    }

    private fun resolveInlineCallableLambda(target: ObjRef): LambdaFnRef? {
        val lambdaRef = resolveExactLambdaRef(target)
        return lambdaRef?.takeUnless { activeInlineLambdas.contains(it) }
    }

    private fun trackExactLambdaAtSlot(slot: Int, lambdaRef: LambdaFnRef?) {
        if (lambdaRef == null) {
            exactLambdaRefBySlot.remove(slot)
        } else {
            exactLambdaRefBySlot[slot] = lambdaRef
        }
    }

    private fun trackExactCallableObjAtSlot(slot: Int, obj: Obj?) {
        if (obj == null || obj === ObjNull || obj === ObjUnset || obj is ObjExternCallable) {
            exactCallableObjBySlot.remove(slot)
        } else {
            exactCallableObjBySlot[slot] = obj
        }
    }

    private fun preloadExactLambdaRefs() {
        if (exactLambdaRefByScopeId.isEmpty()) return
        for ((scopeId, slots) in exactLambdaRefByScopeId) {
            for ((slotIndex, lambdaRef) in slots) {
                val key = ScopeSlotKey(scopeId, slotIndex)
                val localIndex = localSlotIndexByKey[key]
                if (localIndex != null) {
                    trackExactLambdaAtSlot(scopeSlotCount + localIndex, lambdaRef)
                    continue
                }
                val scopeIndex = scopeSlotMap[key]
                if (scopeIndex != null) {
                    trackExactLambdaAtSlot(scopeIndex, lambdaRef)
                }
            }
        }
    }

    private fun preloadExactCallableNames() {
        if (scopeSlotCount > 0) {
            for (index in 0 until scopeSlotCount) {
                val name = scopeSlotNames.getOrNull(index) ?: continue
                preloadExactCallableName(index, name)
            }
        }
        if (!allowLocalSlots || localSlotNames.isEmpty()) return
        for (localIndex in localSlotNames.indices) {
            val name = localSlotNames[localIndex] ?: continue
            val key = localSlotKeyByIndex.getOrNull(localIndex)
            val isModuleLocal = key != null && moduleScopeId != null && key.scopeId == moduleScopeId
            val isCapture = key != null && captureSlotKeys.contains(key)
            if (!isModuleLocal && !isCapture) continue
            preloadExactCallableName(scopeSlotCount + localIndex, name)
        }
    }

    private fun preloadExactCallableName(slot: Int, name: String) {
        if (slotObjClass[slot] != ObjClassType && callSignatureByName[name] == null) return
        resolveTypeNameClass(name)?.let { trackExactCallableObjAtSlot(slot, it) }
    }

    private fun collectExactLambdaModuleCaptures() {
        if (exactLambdaRefByScopeId.isEmpty()) return
        val seen = LinkedHashSet<LambdaFnRef>()
        for (slots in exactLambdaRefByScopeId.values) {
            for (lambdaRef in slots.values) {
                if (!seen.add(lambdaRef)) continue
                val captures = (lambdaCaptureEntriesByRef[lambdaRef] ?: lambdaRef.captureEntries).orEmpty()
                for (entry in captures) {
                    if (entry.ownerKind != CaptureOwnerFrameKind.MODULE) continue
                    val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
                    if (useScopeSlots) {
                        if (!scopeSlotMap.containsKey(key)) {
                            scopeSlotMap[key] = scopeSlotMap.size
                        }
                        if (!scopeSlotNameMap.containsKey(key)) {
                            scopeSlotNameMap[key] = entry.ownerName
                        }
                        if (!scopeSlotMutableMap.containsKey(key)) {
                            scopeSlotMutableMap[key] = entry.ownerIsMutable
                        }
                    } else if (globalSlotInfo.isNotEmpty() && globalSlotScopeId != null) {
                        if (!localSlotInfoMap.containsKey(key)) {
                            localSlotInfoMap[key] = LocalSlotInfo(
                                entry.ownerName,
                                entry.ownerIsMutable,
                                entry.ownerIsDelegated
                            )
                        }
                        captureSlotKeys.add(key)
                    }
                }
            }
        }
    }

    private fun extractExactLambdaRef(value: Obj?): LambdaFnRef? {
        return when (value) {
            is ExpressionStatement -> resolveExactLambdaRef(value.ref)
            is IfStatement -> {
                val thenRef = extractExactLambdaRef(value.ifBody)
                val elseRef = value.elseBody?.let { extractExactLambdaRef(it) }
                if (thenRef != null && thenRef === elseRef) thenRef else null
            }
            is WhenStatement -> {
                var candidate: LambdaFnRef? = null
                for (case in value.cases) {
                    val current = extractExactLambdaRef(case.block) ?: return null
                    if (candidate == null) {
                        candidate = current
                    } else if (candidate !== current) {
                        return null
                    }
                }
                val elseRef = value.elseCase?.let { extractExactLambdaRef(it) }
                if (candidate == null) return elseRef
                if (elseRef == null || candidate !== elseRef) return null
                candidate
            }
            else -> null
        }
    }

    private fun extractExactCallableObj(value: Obj?): Obj? {
        return when (value) {
            is ExpressionStatement -> resolveExactCallableObj(value.ref)
            is IfStatement -> {
                val thenObj = extractExactCallableObj(value.ifBody)
                val elseObj = value.elseBody?.let { extractExactCallableObj(it) }
                if (thenObj != null && thenObj === elseObj) thenObj else null
            }
            is WhenStatement -> {
                var candidate: Obj? = null
                for (case in value.cases) {
                    val current = extractExactCallableObj(case.block) ?: return null
                    if (candidate == null) {
                        candidate = current
                    } else if (candidate !== current) {
                        return null
                    }
                }
                val elseObj = value.elseCase?.let { extractExactCallableObj(it) }
                if (candidate == null) return elseObj
                if (elseObj == null || candidate !== elseObj) return null
                candidate
            }
            else -> null
        }
    }

    private fun isDefinitelyNullRef(ref: ObjRef): Boolean {
        return when (ref) {
            is ConstRef -> ref.constValue === ObjNull
            is StatementRef -> {
                val statement = ref.statement
                statement is ExpressionStatement && isDefinitelyNullRef(statement.ref)
            }
            else -> false
        }
    }

    private fun resolveNamedExactCallableObj(name: String): Obj? {
        val direct = resolveDirectNameSlot(name)
        if (direct != null) {
            exactCallableObjBySlot[direct.slot]?.let { return it }
            if (slotObjClass[direct.slot] == ObjClassType) {
                return resolveTypeNameClass(name)
            }
            if (!canFallbackToNamedExactCallable(direct.slot)) {
                return null
            }
        }
        return resolveTypeNameClass(name)
    }

    private fun canFallbackToNamedExactCallable(slot: Int): Boolean {
        if (slot < scopeSlotCount) return true
        val localIndex = slot - scopeSlotCount
        if (localSlotCaptures.getOrNull(localIndex) == true) return true
        val key = localSlotKeyByIndex.getOrNull(localIndex) ?: return false
        return moduleScopeId != null && key.scopeId == moduleScopeId
    }

    private fun resolveExactCallableObj(target: ObjRef): Obj? {
        return when (target) {
            is ConstRef -> target.constValue.takeUnless { it === ObjNull || it === ObjUnset || it is ObjExternCallable }
            is CastRef -> resolveExactCallableObj(target.castValueRef())
            is ElvisRef -> {
                val left = resolveExactCallableObj(target.left)
                if (left != null) return left
                if (isDefinitelyNullRef(target.left)) resolveExactCallableObj(target.right) else null
            }
            is ConditionalRef -> {
                val thenObj = resolveExactCallableObj(target.ifTrue)
                val elseObj = resolveExactCallableObj(target.ifFalse)
                if (thenObj != null && thenObj === elseObj) thenObj else null
            }
            is StatementRef -> {
                when (val statement = target.statement) {
                    is ExpressionStatement -> resolveExactCallableObj(statement.ref)
                    is IfStatement -> {
                        val thenObj = extractExactCallableObj(statement.ifBody)
                        val elseObj = statement.elseBody?.let { extractExactCallableObj(it) }
                        if (thenObj != null && thenObj === elseObj) thenObj else null
                    }
                    is WhenStatement -> extractExactCallableObj(statement)
                    else -> null
                }
            }
            is LocalSlotRef -> {
                val resolvedSlot = resolveLocalSlotByRefOrName(target)
                if (resolvedSlot != null) {
                    exactCallableObjBySlot[resolvedSlot]?.let { return it }
                    if (slotObjClass[resolvedSlot] == ObjClassType || canFallbackToNamedExactCallable(resolvedSlot)) {
                        return resolveTypeNameClass(target.name)
                    }
                }
                null
            }
            is LocalVarRef -> resolveNamedExactCallableObj(target.name)
            is FastLocalVarRef -> resolveNamedExactCallableObj(target.name)
            is BoundLocalVarRef -> exactCallableObjBySlot[target.slotIndex()]
            else -> null
        }
    }

    private fun resolveExactLambdaRef(target: ObjRef): LambdaFnRef? {
        return when (target) {
            is LambdaFnRef -> target
            is CastRef -> resolveExactLambdaRef(target.castValueRef())
            is ElvisRef -> {
                val left = resolveExactLambdaRef(target.left)
                if (left != null) return left
                if (isDefinitelyNullRef(target.left)) resolveExactLambdaRef(target.right) else null
            }
            is ConditionalRef -> {
                val thenRef = resolveExactLambdaRef(target.ifTrue)
                val elseRef = resolveExactLambdaRef(target.ifFalse)
                if (thenRef != null && thenRef === elseRef) thenRef else null
            }
            is StatementRef -> {
                when (val statement = target.statement) {
                    is ExpressionStatement -> resolveExactLambdaRef(statement.ref)
                    is IfStatement,
                    is WhenStatement -> extractExactLambdaRef(statement)
                    else -> null
                }
            }
            is LocalSlotRef -> {
                val ownerScopeId = target.captureOwnerScopeId ?: target.scopeId
                val ownerSlot = target.captureOwnerSlot ?: target.slot
                exactLambdaRefByScopeId[ownerScopeId]?.get(ownerSlot)
                    ?: resolveLocalSlotByRefOrName(target)?.let { exactLambdaRefBySlot[it] }
            }
            is LocalVarRef -> resolveDirectNameSlot(target.name)?.slot?.let { exactLambdaRefBySlot[it] }
            is FastLocalVarRef -> resolveDirectNameSlot(target.name)?.slot?.let { exactLambdaRefBySlot[it] }
            is BoundLocalVarRef -> exactLambdaRefBySlot[target.slotIndex()]
            else -> null
        }
    }

    private fun compileInlineListFillInt(
        size: CompiledValue,
        capacity: CompiledValue?,
        lambdaRef: LambdaFnRef,
        inlineRef: ObjRef
    ): CompiledValue {
        if (capacity == null && isImplicitItIdentityRef(inlineRef)) {
            val dst = allocSlot()
            builder.emit(Opcode.LIST_IOTA_INT, size.slot, dst)
            updateSlotType(dst, SlotType.OBJ)
            slotObjClass[dst] = ObjList.type
            listElementClassBySlot[dst] = ObjInt.type
            return CompiledValue(dst, SlotType.OBJ)
        }

        val dst = allocSlot()
        if (capacity != null) {
            builder.emit(Opcode.LIST_NEW_INT_CAP, size.slot, capacity.slot, dst)
        } else {
            builder.emit(Opcode.LIST_NEW_INT, size.slot, dst)
        }
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = ObjList.type
        listElementClassBySlot[dst] = ObjInt.type

        val iSlot = allocSlot()
        val zeroId = builder.addConst(BytecodeConst.IntVal(0))
        builder.emit(Opcode.CONST_INT, zeroId, iSlot)
        updateSlotType(iSlot, SlotType.INT)

        val loopLabel = builder.label()
        val endLabel = builder.label()
        builder.mark(loopLabel)
        builder.emit(
            Opcode.JMP_IF_GTE_INT,
            listOf(
                CmdBuilder.Operand.IntVal(iSlot),
                CmdBuilder.Operand.IntVal(size.slot),
                CmdBuilder.Operand.LabelRef(endLabel)
            )
        )

        val paramName = lambdaRef.inlineParamNames()?.singleOrNull()
            ?: throw BytecodeCompileException("unsupported List.fill lambda parameters", refPosOrCurrent(inlineRef))
        val compiledValue = compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to iSlot))
            ?: throw BytecodeCompileException("failed to inline List.fill lambda", refPosOrCurrent(inlineRef))
        val intValue = coerceToLoopInt(compiledValue)
            ?: throw BytecodeCompileException("inlined List.fill lambda must produce Int", refPosOrCurrent(inlineRef))
        builder.emit(Opcode.SET_INDEX_INT, dst, iSlot, intValue.slot)
        builder.emit(Opcode.INC_INT, iSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
        builder.mark(endLabel)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun resolveInlineCaptureSlot(entry: LambdaCaptureEntry): Int? {
        val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
        return when (entry.ownerKind) {
            CaptureOwnerFrameKind.LOCAL -> {
                localSlotIndexByKey[key]?.let { return scopeSlotCount + it }
                scopeSlotMap[key]
            }
            CaptureOwnerFrameKind.MODULE -> {
                localSlotIndexByKey[key]?.let { return scopeSlotCount + it }
                scopeSlotMap[key]
            }
        }
    }

    private fun isImplicitItIdentityRef(ref: ObjRef): Boolean {
        return when (ref) {
            is LocalVarRef -> ref.name == "it"
            is FastLocalVarRef -> ref.name == "it"
            is LocalSlotRef -> ref.name == "it"
            else -> false
        }
    }

    private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
        val callPos = callSitePos()
        val receiver = compileThisRef()
        val methodId = ref.methodId() ?: throw BytecodeCompileException(
            "Missing member id for ${ref.methodName()}",
            Pos.builtIn
        )
        val dst = allocSlot()
        if (!ref.optionalInvoke()) {
            val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
        val nullLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
        )
        val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
        val encodedCount = encodeCallArgCount(args) ?: return null
        setPos(callPos)
        builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(nullLabel)
        builder.emit(Opcode.CONST_NULL, dst)
        builder.mark(endLabel)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun receiverDebugInfo(ref: ObjRef): String {
        val kind = ref::class.simpleName ?: "ObjRef"
        return when (ref) {
            is LocalVarRef -> {
                val direct = resolveDirectNameSlot(ref.name)
                val slot = direct?.slot
                val slotCls = slot?.let { slotObjClass[it]?.className }
                val nameCls = nameObjClass[ref.name]?.className
                " receiver=$kind(${ref.name}) slot=$slot slotClass=$slotCls nameClass=$nameCls"
            }
            is LocalSlotRef -> {
                val slot = resolveLocalSlotByRefOrName(ref)
                val slotCls = slot?.let { slotObjClass[it]?.className }
                val nameCls = nameObjClass[ref.name]?.className
                val scopeId = refScopeId(ref)
                val slotId = refSlot(ref)
                val initCls = slotInitClassByKey[ScopeSlotKey(scopeId, slotId)]?.className
                " receiver=$kind(${ref.name}) slot=$slot scopeId=$scopeId slotId=$slotId slotClass=$slotCls nameClass=$nameCls initClass=$initCls"
            }
            else -> " receiver=$kind"
        }
    }

    private fun compileQualifiedThisMethodSlotCall(ref: QualifiedThisMethodSlotCallRef): CompiledValue? {
        val callPos = callSitePos()
        val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null
        val methodId = ref.methodId() ?: throw BytecodeCompileException(
            "Missing member id for ${ref.methodName()}",
            Pos.builtIn
        )
        val dst = allocSlot()
        if (!ref.optionalInvoke()) {
            val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
            val encodedCount = encodeCallArgCount(args) ?: return null
            setPos(callPos)
            builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
            return CompiledValue(dst, SlotType.OBJ)
        }
        val nullSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, nullSlot)
        val cmpSlot = allocSlot()
        builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
        val nullLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
        )
        val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
        val encodedCount = encodeCallArgCount(args) ?: return null
        setPos(callPos)
        builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(nullLabel)
        builder.emit(Opcode.CONST_NULL, dst)
        builder.mark(endLabel)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private data class CallArgs(val base: Int, val count: Int, val planId: Int?)

    private fun extensionReceiverTypeNames(receiverClass: ObjClass): Set<String> {
        val names = LinkedHashSet<String>()
        for (cls in receiverClass.mro) {
            names.add(cls.className)
            for ((knownName, knownClass) in nameObjClass) {
                if (knownClass !== cls && knownClass.className != cls.className) continue
                names.add(knownName)
                names.add(knownName.substringAfterLast('.'))
            }
        }
        return names
    }

    private fun resolveExtensionWrapperNameByReceiverNames(
        receiverClass: ObjClass,
        memberName: String,
        wrapperName: (String, String) -> String
    ): String? {
        for (receiverName in extensionReceiverTypeNames(receiverClass)) {
            val candidate = wrapperName(receiverName, memberName)
            if (!extensionContextReceiversSatisfied(candidate)) continue
            if (allowedScopeNames != null &&
                !allowedScopeNames.contains(candidate) &&
                !localSlotIndexByName.containsKey(candidate)
            ) {
                continue
            }
            if (resolveDirectNameSlot(candidate) != null) return candidate
        }
        return null
    }

    private fun currentImplicitReceiverTypeNames(): List<String> {
        val result = mutableListOf<String>()
        inlineThisBindings.asReversed().forEach { binding ->
            val typeName = binding.typeName ?: return@forEach
            if (!result.contains(typeName)) result += typeName
        }
        implicitThisTypeName?.let {
            if (!result.contains(it)) result += it
        }
        return result
    }

    private fun extensionContextReceiversSatisfied(wrapperName: String): Boolean {
        val required = extensionContextReceiversByWrapperName[wrapperName].orEmpty()
        if (required.isEmpty()) return true
        val visible = currentImplicitReceiverTypeNames()
        return required.all { req ->
            visible.any { visibleName ->
                visibleName == req || resolveTypeNameClass(visibleName)?.let { cls ->
                    cls.className == req || cls.mro.any { it.className == req }
                } == true
            }
        }
    }

    private fun resolveUniqueExtensionWrapperName(
        memberName: String,
        wrapperPrefix: String
    ): String? {
        val suffix = "__$memberName"
        val candidates = LinkedHashSet<String>()
        for (name in localSlotIndexByName.keys) {
            if (name.startsWith(wrapperPrefix) && name.endsWith(suffix)) {
                if (extensionContextReceiversSatisfied(name)) candidates.add(name)
            }
        }
        for (name in scopeSlotIndexByName.keys) {
            if (name.startsWith(wrapperPrefix) && name.endsWith(suffix)) {
                if (extensionContextReceiversSatisfied(name)) candidates.add(name)
            }
        }
        return candidates.singleOrNull()
    }

    private fun resolveExtensionSlotByReceiverNames(
        receiverClass: ObjClass,
        memberName: String,
        wrapperName: (String, String) -> String
    ): CompiledValue? {
        val resolvedName = resolveExtensionWrapperNameByReceiverNames(receiverClass, memberName, wrapperName) ?: return null
        return resolveDirectNameSlot(resolvedName)
    }

    private fun resolveUniqueExtensionWrapperSlot(
        memberName: String,
        wrapperPrefix: String
    ): CompiledValue? {
        val resolvedName = resolveUniqueExtensionWrapperName(memberName, wrapperPrefix) ?: return null
        return resolveDirectNameSlot(resolvedName)
    }

    private fun extensionCallableSignature(receiverClass: ObjClass, memberName: String): CallSignature? {
        val wrapperName = resolveExtensionWrapperNameByReceiverNames(receiverClass, memberName, ::extensionCallableName)
            ?: resolveUniqueExtensionWrapperName(memberName, "__ext__")
            ?: return null
        return callSignatureByName[wrapperName]
    }

    private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
        return resolveExtensionSlotByReceiverNames(receiverClass, memberName, ::extensionCallableName)
            ?: resolveUniqueExtensionWrapperSlot(memberName, "__ext__")
    }

    private fun resolveExtensionGetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
        return resolveExtensionSlotByReceiverNames(receiverClass, memberName, ::extensionPropertyGetterName)
            ?: resolveUniqueExtensionWrapperSlot(memberName, "__ext_get__")
    }

    private fun resolveExtensionSetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
        return resolveExtensionSlotByReceiverNames(receiverClass, memberName, ::extensionPropertySetterName)
            ?: resolveUniqueExtensionWrapperSlot(memberName, "__ext_set__")
    }

    private fun compileCallArgsWithReceiver(
        receiver: CompiledValue,
        args: List<ParsedArgument>,
        tailBlock: Boolean,
        explicitTypeArgs: List<TypeDecl>? = null
    ): CallArgs? {
        val argSlots = IntArray(args.size + 1) { allocSlot() }
        val receiverObj = ensureObjSlot(receiver)
        builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0])
        updateSlotType(argSlots[0], SlotType.OBJ)
        val needPlan = tailBlock || args.any { it.isSplat || it.name != null } || !explicitTypeArgs.isNullOrEmpty()
        val specs = if (needPlan) ArrayList<BytecodeConst.CallArgSpec>(args.size + 1) else null
        specs?.add(BytecodeConst.CallArgSpec(null, false))
        for ((index, arg) in args.withIndex()) {
            val compiled = compileArgValue(arg.value) ?: return null
            val dst = argSlots[index + 1]
            if (compiled.slot != dst || compiled.type != SlotType.OBJ) {
                builder.emit(Opcode.BOX_OBJ, compiled.slot, dst)
            }
            updateSlotType(dst, SlotType.OBJ)
            specs?.add(BytecodeConst.CallArgSpec(arg.name, arg.isSplat))
        }
        val planId = if (needPlan) {
            builder.addConst(
                BytecodeConst.CallArgsPlan(
                    tailBlock = tailBlock,
                    specs = specs ?: emptyList(),
                    explicitTypeArgs = explicitTypeArgs ?: emptyList()
                )
            )
        } else {
            null
        }
        return CallArgs(base = argSlots[0], count = argSlots.size, planId = planId)
    }

    private fun compileCallArgs(
        args: List<ParsedArgument>,
        tailBlock: Boolean,
        explicitTypeArgs: List<TypeDecl>? = null
    ): CallArgs? {
        if (args.isEmpty() && explicitTypeArgs.isNullOrEmpty()) return CallArgs(base = 0, count = 0, planId = null)
        val argSlots = IntArray(args.size) { allocSlot() }
        val needPlan = tailBlock || args.any { it.isSplat || it.name != null } || !explicitTypeArgs.isNullOrEmpty()
        val specs = if (needPlan) ArrayList<BytecodeConst.CallArgSpec>(args.size) else null
        for ((index, arg) in args.withIndex()) {
            val compiled = compileArgValue(arg.value) ?: return null
            val objValue = ensureObjSlot(compiled)
            val dst = argSlots[index]
            if (objValue.slot != dst) {
                emitMove(objValue, dst)
            }
            updateSlotType(dst, SlotType.OBJ)
            specs?.add(BytecodeConst.CallArgSpec(arg.name, arg.isSplat))
        }
        val planId = if (needPlan) {
            builder.addConst(
                BytecodeConst.CallArgsPlan(
                    tailBlock = tailBlock,
                    specs = specs ?: emptyList(),
                    explicitTypeArgs = explicitTypeArgs ?: emptyList()
                )
            )
        } else {
            null
        }
        return CallArgs(base = if (argSlots.isEmpty()) 0 else argSlots[0], count = argSlots.size, planId = planId)
    }

    private fun compileArgValue(value: Obj): CompiledValue? {
        return when (value) {
            is ExpressionStatement -> compileRefWithFallback(value.ref, null, value.pos)
            is Statement -> {
                throw BytecodeCompileException(
                    "Bytecode compile error: unsupported argument expression",
                    value.pos
                )
            }
            else -> compileConst(value)
        }
    }

    private fun encodeCallArgCount(args: CallArgs): Int? {
        val planId = args.planId ?: return args.count
        if (planId > 0x7FFF) return null
        return 0x8000 or planId
    }

    private fun compileIf(name: String, stmt: IfStatement): CmdFunction? {
        val conditionTarget = if (stmt.condition is BytecodeStatement) {
            stmt.condition.original
        } else {
            stmt.condition
        }
        val resultSlot = allocSlot()
        val elseLabel = builder.label()
        val endLabel = builder.label()
        val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
        if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = false, target = elseLabel)) {
            val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
            if (condValue.type != SlotType.BOOL) return null
            builder.emit(
                Opcode.JMP_IF_FALSE,
                listOf(CmdBuilder.Operand.IntVal(condValue.slot), CmdBuilder.Operand.LabelRef(elseLabel))
            )
        }
        val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
        emitMove(thenValue, resultSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))

        builder.mark(elseLabel)
        if (stmt.elseBody != null) {
            val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return null
            emitMove(elseValue, resultSlot)
        } else {
            val id = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
            builder.emit(Opcode.CONST_OBJ, id, resultSlot)
        }

        builder.mark(endLabel)
        builder.emit(Opcode.RET, resultSlot)
        val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): CmdFunction? {
        val resultSlot = emitForIn(stmt, true) ?: return null
        builder.emit(Opcode.RET, resultSlot)
        val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileWhile(name: String, stmt: net.sergeych.lyng.WhileStatement): CmdFunction? {
        if (!allowLocalSlots) return null
        val resultSlot = emitWhile(stmt, true) ?: return null
        builder.emit(Opcode.RET, resultSlot)
        val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileDoWhile(name: String, stmt: net.sergeych.lyng.DoWhileStatement): CmdFunction? {
        if (!allowLocalSlots) return null
        val resultSlot = emitDoWhile(stmt, true) ?: return null
        builder.emit(Opcode.RET, resultSlot)
        val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? {
        val result = if (shouldInlineBlock(stmt)) {
            emitInlineStatements(stmt.statements(), true)
        } else {
            emitBlock(stmt, true)
        } ?: return null
        builder.emit(Opcode.RET, result.slot)
        val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileVarDecl(name: String, stmt: VarDeclStatement): CmdFunction? {
        val result = emitVarDecl(stmt) ?: return null
        builder.emit(Opcode.RET, result.slot)
        val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileStatementValue(stmt: Statement): CompiledValue? {
        return when (stmt) {
            is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
            is net.sergeych.lyng.NopStatement -> {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                CompiledValue(slot, SlotType.OBJ)
            }
            else -> null
        }
    }

    private fun emitFallbackStatement(stmt: Statement): CompiledValue {
        throw BytecodeCompileException(
            "Bytecode compile error: unsupported statement",
            stmt.pos
        )
    }

    private fun emitDeclEnum(stmt: net.sergeych.lyng.EnumDeclStatement): CompiledValue {
        val constId = builder.addConst(
            BytecodeConst.EnumDecl(
                declaredName = stmt.declaredName,
                qualifiedName = stmt.qualifiedName,
                entries = stmt.entries,
                lifted = stmt.lifted
            )
        )
        val dst = resolveDirectNameSlot(stmt.declaredName)?.slot ?: allocSlot()
        builder.emit(Opcode.DECL_ENUM, constId, dst)
        updateSlotType(dst, SlotType.OBJ)
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun emitDeclFunction(stmt: net.sergeych.lyng.FunctionDeclStatement): CompiledValue {
        val constId = builder.addConst(BytecodeConst.FunctionDecl(stmt.spec))
        val dst = stmt.spec.slotIndex?.let { slotIndex ->
            val scopeId = stmt.spec.scopeId ?: 0
            val key = ScopeSlotKey(scopeId, slotIndex)
            localSlotIndexByKey[key]?.let { scopeSlotCount + it }
                ?: scopeSlotMap[key]
        } ?: allocSlot()
        builder.emit(Opcode.DECL_FUNCTION, constId, dst)
        updateSlotType(dst, SlotType.OBJ)
        if (!stmt.spec.actualExtern &&
            !stmt.spec.isDelegated &&
            stmt.spec.annotation == null &&
            stmt.spec.extTypeName == null
        ) {
            trackExactCallableObjAtSlot(dst, stmt.spec.fnBody)
        } else {
            trackExactCallableObjAtSlot(dst, null)
        }
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun emitDeclClass(stmt: net.sergeych.lyng.ClassDeclStatement): CompiledValue {
        val constId = builder.addConst(BytecodeConst.ClassDecl(stmt.spec))
        val dst = stmt.spec.declaredName?.let { name ->
            resolveDirectNameSlot(name)?.slot
        } ?: allocSlot()
        builder.emit(Opcode.DECL_CLASS, constId, dst)
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = if (stmt.spec.isObject) ObjDynamic.type else ObjClassType
        stmt.spec.declaredName?.let { updateNameObjClassFromSlot(it, dst) }
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun emitClassStaticFieldInit(stmt: net.sergeych.lyng.ClassStaticFieldInitStatement): CompiledValue? {
        val initValue = if (stmt.initializer != null) {
            val compiled = compileStatementValueOrFallback(stmt.initializer) ?: return null
            ensureObjSlot(compiled)
        } else {
            val slot = allocSlot()
            builder.emit(Opcode.CONST_NULL, slot)
            updateSlotType(slot, SlotType.OBJ)
            CompiledValue(slot, SlotType.OBJ)
        }
        val constId = if (stmt.isDelegated) {
            builder.addConst(
                BytecodeConst.ClassDelegatedDecl(
                    name = stmt.name,
                    isMutable = stmt.isMutable,
                    visibility = stmt.visibility,
                    writeVisibility = stmt.writeVisibility,
                    isTransient = stmt.isTransient,
                    annotationSpecs = stmt.annotationSpecs
                )
            )
        } else {
            builder.addConst(
                BytecodeConst.ClassFieldDecl(
                    name = stmt.name,
                    isMutable = stmt.isMutable,
                    visibility = stmt.visibility,
                    writeVisibility = stmt.writeVisibility,
                    isTransient = stmt.isTransient,
                    annotationSpecs = stmt.annotationSpecs
                )
            )
        }
        val opcode = if (stmt.isDelegated) Opcode.DECL_CLASS_DELEGATED else Opcode.DECL_CLASS_FIELD
        builder.emit(opcode, constId, initValue.slot)
        updateSlotType(initValue.slot, SlotType.OBJ)
        return initValue
    }

    private fun emitClassInstanceInitDecl(stmt: net.sergeych.lyng.ClassInstanceInitDeclStatement): CompiledValue? {
        val constId = builder.addConst(BytecodeConst.ClassInstanceInitDecl(stmt.initStatement))
        val slot = allocSlot()
        builder.emit(Opcode.DECL_CLASS_INSTANCE_INIT, constId, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun emitClassInstanceFieldDecl(stmt: net.sergeych.lyng.ClassInstanceFieldDeclStatement): CompiledValue? {
        val constId = builder.addConst(
            BytecodeConst.ClassInstanceFieldDecl(
                name = stmt.name,
                isMutable = stmt.isMutable,
                visibility = stmt.visibility,
                writeVisibility = stmt.writeVisibility,
                typeDecl = stmt.typeDecl,
                isTransient = stmt.isTransient,
                annotationSpecs = stmt.annotationSpecs,
                isAbstract = stmt.isAbstract,
                isClosed = stmt.isClosed,
                isOverride = stmt.isOverride,
                fieldId = stmt.fieldId,
                initStatement = stmt.initStatement,
                pos = stmt.pos
            )
        )
        val slot = allocSlot()
        builder.emit(Opcode.DECL_CLASS_INSTANCE_FIELD, constId, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun emitClassInstancePropertyDecl(stmt: net.sergeych.lyng.ClassInstancePropertyDeclStatement): CompiledValue? {
        val constId = builder.addConst(
            BytecodeConst.ClassInstancePropertyDecl(
                name = stmt.name,
                isMutable = stmt.isMutable,
                visibility = stmt.visibility,
                writeVisibility = stmt.writeVisibility,
                isTransient = stmt.isTransient,
                annotationSpecs = stmt.annotationSpecs,
                isAbstract = stmt.isAbstract,
                isClosed = stmt.isClosed,
                isOverride = stmt.isOverride,
                prop = stmt.prop,
                methodId = stmt.methodId,
                initStatement = stmt.initStatement,
                pos = stmt.pos
            )
        )
        val slot = allocSlot()
        builder.emit(Opcode.DECL_CLASS_INSTANCE_PROPERTY, constId, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun emitClassInstanceDelegatedDecl(stmt: net.sergeych.lyng.ClassInstanceDelegatedDeclStatement): CompiledValue? {
        val constId = builder.addConst(
            BytecodeConst.ClassInstanceDelegatedDecl(
                name = stmt.name,
                isMutable = stmt.isMutable,
                visibility = stmt.visibility,
                writeVisibility = stmt.writeVisibility,
                isTransient = stmt.isTransient,
                annotationSpecs = stmt.annotationSpecs,
                isAbstract = stmt.isAbstract,
                isClosed = stmt.isClosed,
                isOverride = stmt.isOverride,
                methodId = stmt.methodId,
                initStatement = stmt.initStatement,
                pos = stmt.pos
            )
        )
        val slot = allocSlot()
        builder.emit(Opcode.DECL_CLASS_INSTANCE_DELEGATED, constId, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun emitInstanceFieldInit(stmt: net.sergeych.lyng.InstanceFieldInitStatement): CompiledValue? {
        val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
            val slot = allocSlot()
            val constId = if (stmt.isLateInitVal) {
                builder.addConst(BytecodeConst.ObjRef(ObjUnset))
            } else {
                builder.addConst(BytecodeConst.ObjRef(ObjNull))
            }
            builder.emit(Opcode.CONST_OBJ, constId, slot)
            updateSlotType(slot, SlotType.OBJ)
            CompiledValue(slot, SlotType.OBJ)
        }
        val declId = builder.addConst(
            BytecodeConst.InstanceFieldDecl(
                name = stmt.storageName,
                isMutable = stmt.isMutable,
                visibility = stmt.visibility,
                writeVisibility = stmt.writeVisibility,
                isTransient = stmt.isTransient,
                annotations = stmt.annotations,
                isAbstract = stmt.isAbstract,
                isClosed = stmt.isClosed,
                isOverride = stmt.isOverride
            )
        )
        builder.emit(Opcode.DECL_INSTANCE_FIELD, declId, value.slot)
        updateSlotType(value.slot, SlotType.OBJ)
        return value
    }

    private fun emitInstancePropertyInit(stmt: net.sergeych.lyng.InstancePropertyInitStatement): CompiledValue? {
        val slot = allocSlot()
        val constId = builder.addConst(BytecodeConst.ObjRef(stmt.prop))
        builder.emit(Opcode.CONST_OBJ, constId, slot)
        updateSlotType(slot, SlotType.OBJ)
        val declId = builder.addConst(
            BytecodeConst.InstancePropertyDecl(
                name = stmt.storageName,
                isMutable = stmt.isMutable,
                visibility = stmt.visibility,
                writeVisibility = stmt.writeVisibility,
                isTransient = stmt.isTransient,
                annotations = stmt.annotations,
                isAbstract = stmt.isAbstract,
                isClosed = stmt.isClosed,
                isOverride = stmt.isOverride
            )
        )
        builder.emit(Opcode.DECL_INSTANCE_PROPERTY, declId, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun emitInstanceDelegatedInit(stmt: net.sergeych.lyng.InstanceDelegatedInitStatement): CompiledValue? {
        val value = compileStatementValueOrFallback(stmt.initializer) ?: return null
        val declId = builder.addConst(
            BytecodeConst.InstanceDelegatedDecl(
                storageName = stmt.storageName,
                memberName = stmt.memberName,
                isMutable = stmt.isMutable,
                visibility = stmt.visibility,
                writeVisibility = stmt.writeVisibility,
                isTransient = stmt.isTransient,
                annotations = stmt.annotations,
                isAbstract = stmt.isAbstract,
                isClosed = stmt.isClosed,
                isOverride = stmt.isOverride,
                accessTypeLabel = stmt.accessTypeLabel
            )
        )
        builder.emit(Opcode.DECL_INSTANCE_DELEGATED, declId, value.slot)
        updateSlotType(value.slot, SlotType.OBJ)
        return CompiledValue(value.slot, SlotType.OBJ)
    }

    private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
        val target = if (stmt is BytecodeStatement) stmt.original else stmt
        setPos(target.pos)
        return if (needResult) {
            when (target) {
                is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos)
                is IfStatement -> compileIfExpression(target)
                is net.sergeych.lyng.ForInStatement -> {
                    val resultSlot = emitForIn(target, true) ?: return null
                    updateSlotType(resultSlot, SlotType.OBJ)
                    CompiledValue(resultSlot, SlotType.OBJ)
                }
                is net.sergeych.lyng.WhileStatement -> {
                    if (!allowLocalSlots) emitFallbackStatement(target)
                    else {
                        val resultSlot = emitWhile(target, true) ?: return null
                        updateSlotType(resultSlot, SlotType.OBJ)
                        CompiledValue(resultSlot, SlotType.OBJ)
                    }
                }
                is net.sergeych.lyng.DoWhileStatement -> {
                    if (!allowLocalSlots) emitFallbackStatement(target)
                    else {
                        val resultSlot = emitDoWhile(target, true) ?: return null
                        updateSlotType(resultSlot, SlotType.OBJ)
                        CompiledValue(resultSlot, SlotType.OBJ)
                    }
                }
                is BlockStatement -> emitBlock(target, true)
                is VarDeclStatement -> emitVarDecl(target)
                is net.sergeych.lyng.ClassStaticFieldInitStatement -> emitClassStaticFieldInit(target)
                is net.sergeych.lyng.ClassInstanceInitDeclStatement -> emitClassInstanceInitDecl(target)
                is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> emitClassInstanceFieldDecl(target)
                is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> emitClassInstancePropertyDecl(target)
                is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> emitClassInstanceDelegatedDecl(target)
                is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
                is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
                is net.sergeych.lyng.InstanceFieldInitStatement -> emitInstanceFieldInit(target)
                is net.sergeych.lyng.InstancePropertyInitStatement -> emitInstancePropertyInit(target)
                is net.sergeych.lyng.InstanceDelegatedInitStatement -> emitInstanceDelegatedInit(target)
                is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
                is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target)
                is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target)
                is net.sergeych.lyng.EnumDeclStatement -> emitDeclEnum(target)
                is net.sergeych.lyng.TryStatement -> emitTry(target, true)
                is net.sergeych.lyng.WhenStatement -> compileWhen(target, true)
                is net.sergeych.lyng.BreakStatement -> compileBreak(target)
                is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
                is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
                is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
                is net.sergeych.lyng.NopStatement -> {
                    val slot = allocSlot()
                    val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                    builder.emit(Opcode.CONST_OBJ, voidId, slot)
                    CompiledValue(slot, SlotType.OBJ)
                }
                else -> {
                    emitFallbackStatement(target)
                }
            }
        } else {
            when (target) {
                is ExpressionStatement -> {
                    val ref = target.ref
                    if (ref is IncDecRef) {
                        compileIncDec(ref, false)
                    } else {
                        compileRefWithFallback(ref, null, target.pos)
                    }
                }
                is VarDeclStatement -> emitVarDecl(target)
                is net.sergeych.lyng.ClassStaticFieldInitStatement -> emitClassStaticFieldInit(target)
                is net.sergeych.lyng.ClassInstanceInitDeclStatement -> emitClassInstanceInitDecl(target)
                is net.sergeych.lyng.ClassInstanceFieldDeclStatement -> emitClassInstanceFieldDecl(target)
                is net.sergeych.lyng.ClassInstancePropertyDeclStatement -> emitClassInstancePropertyDecl(target)
                is net.sergeych.lyng.ClassInstanceDelegatedDeclStatement -> emitClassInstanceDelegatedDecl(target)
                is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
                is net.sergeych.lyng.InstanceFieldInitStatement -> emitInstanceFieldInit(target)
                is net.sergeych.lyng.InstancePropertyInitStatement -> emitInstancePropertyInit(target)
                is net.sergeych.lyng.InstanceDelegatedInitStatement -> emitInstanceDelegatedInit(target)
                is IfStatement -> compileIfStatement(target)
                is net.sergeych.lyng.ClassDeclStatement -> emitDeclClass(target)
                is net.sergeych.lyng.FunctionDeclStatement -> emitDeclFunction(target)
                is net.sergeych.lyng.EnumDeclStatement -> emitDeclEnum(target)
                is net.sergeych.lyng.ForInStatement -> {
                    val resultSlot = emitForIn(target, false) ?: return null
                    CompiledValue(resultSlot, SlotType.OBJ)
                }
                is net.sergeych.lyng.WhileStatement -> {
                    if (!allowLocalSlots) emitFallbackStatement(target)
                    else {
                        val resultSlot = emitWhile(target, false) ?: return null
                        CompiledValue(resultSlot, SlotType.OBJ)
                    }
                }
                is net.sergeych.lyng.DoWhileStatement -> {
                    if (!allowLocalSlots) emitFallbackStatement(target)
                    else {
                        val resultSlot = emitDoWhile(target, false) ?: return null
                        CompiledValue(resultSlot, SlotType.OBJ)
                    }
                }
                is BlockStatement -> emitBlock(target, false)
                is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
                is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
                is net.sergeych.lyng.BreakStatement -> compileBreak(target)
                is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
                is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
                is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
                is net.sergeych.lyng.TryStatement -> emitTry(target, false)
                is net.sergeych.lyng.WhenStatement -> compileWhen(target, false)
                is net.sergeych.lyng.NopStatement -> {
                    val slot = allocSlot()
                    val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                    builder.emit(Opcode.CONST_OBJ, voidId, slot)
                    CompiledValue(slot, SlotType.OBJ)
                }
                else -> {
                    emitFallbackStatement(target)
                }
            }
        }
    }

    private fun emitBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? {
        if (shouldInlineBlock(stmt)) {
            return emitInlineStatements(stmt.statements(), needResult)
        }
        val statements = stmt.statements()
        var lastValue: CompiledValue? = null
        for ((index, statement) in statements.withIndex()) {
            val isLast = index == statements.lastIndex
            val wantResult = needResult && isLast
            val value = compileStatementValueOrFallback(statement, wantResult)
                ?: run {
                    val original = (statement as? BytecodeStatement)?.original
                    val name = original?.let { "${statement::class.simpleName}(${it::class.simpleName})" }
                        ?: statement::class.simpleName
                    throw BytecodeCompileException(
                        "Bytecode compile error: failed to compile block statement ($name)",
                        statement.pos
                    )
            }
            if (wantResult) {
                lastValue = value
            }
        }
        return if (needResult) {
            lastValue ?: run {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                CompiledValue(slot, SlotType.OBJ)
            }
        } else {
            lastValue ?: run {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                CompiledValue(slot, SlotType.OBJ)
            }
        }
    }

    private fun emitTry(stmt: net.sergeych.lyng.TryStatement, needResult: Boolean): CompiledValue? {
        val resultSlot = allocSlot()
        updateSlotType(resultSlot, SlotType.OBJ)
        val exceptionSlot = allocSlot()
        updateSlotType(exceptionSlot, SlotType.OBJ)
        val catchLabel = if (stmt.catches.isNotEmpty()) builder.label() else null
        val finallyLabel = if (stmt.finallyClause != null) builder.label() else null
        val endLabel = builder.label()
        val catchOperand = catchLabel?.let { CmdBuilder.Operand.LabelRef(it) }
            ?: CmdBuilder.Operand.IntVal(-1)
        val finallyOperand = finallyLabel?.let { CmdBuilder.Operand.LabelRef(it) }
            ?: CmdBuilder.Operand.IntVal(-1)
        builder.emit(
            Opcode.PUSH_TRY,
            listOf(
                CmdBuilder.Operand.IntVal(exceptionSlot),
                catchOperand,
                finallyOperand
            )
        )
        val bodyValue = compileStatementValueOrFallback(stmt.body, needResult) ?: return null
        if (needResult) {
            emitMove(bodyValue, resultSlot)
        }
        if (finallyLabel != null) {
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(finallyLabel)))
        } else {
            builder.emit(Opcode.POP_TRY)
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        }
        if (catchLabel != null) {
            builder.mark(catchLabel)
            val catchBlockLabels = stmt.catches.map { builder.label() }
            val noMatchLabel = builder.label()
            for ((index, cdata) in stmt.catches.withIndex()) {
                val handlerLabel = catchBlockLabels[index]
                for (className in cdata.classNames) {
                    val classValue = compileCatchClassSlot(className) ?: return null
                    val checkSlot = allocSlot()
                    builder.emit(Opcode.CHECK_IS, exceptionSlot, classValue.slot, checkSlot)
                    builder.emit(
                        Opcode.JMP_IF_TRUE,
                        listOf(CmdBuilder.Operand.IntVal(checkSlot), CmdBuilder.Operand.LabelRef(handlerLabel))
                    )
                }
            }
            builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(noMatchLabel)))
            for ((index, cdata) in stmt.catches.withIndex()) {
                val handlerLabel = catchBlockLabels[index]
                builder.mark(handlerLabel)
                builder.emit(Opcode.CLEAR_PENDING_THROWABLE)
                val catchValue = emitCatchBlock(cdata.block, cdata.catchVarName, exceptionSlot, needResult)
                    ?: return null
                if (needResult) {
                    emitMove(catchValue, resultSlot)
                }
                if (finallyLabel != null) {
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(finallyLabel)))
                } else {
                    builder.emit(Opcode.POP_TRY)
                    builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
                }
            }
            builder.mark(noMatchLabel)
            if (finallyLabel != null) {
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(finallyLabel)))
            } else {
                builder.emit(Opcode.POP_TRY)
                builder.emit(Opcode.RETHROW_PENDING)
            }
        }
        if (finallyLabel != null) {
            builder.mark(finallyLabel)
            builder.emit(Opcode.POP_TRY)
            stmt.finallyClause?.let { finallyClause ->
                compileStatementValueOrFallback(finallyClause, false) ?: return null
            }
            builder.emit(Opcode.RETHROW_PENDING)
        }
        builder.mark(endLabel)
        if (needResult) return CompiledValue(resultSlot, SlotType.OBJ)
        val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
        val voidSlot = allocSlot()
        builder.emit(Opcode.CONST_OBJ, voidId, voidSlot)
        return CompiledValue(voidSlot, SlotType.OBJ)
    }

    private fun emitCatchBlock(
        block: Statement,
        catchVarName: String,
        exceptionSlot: Int,
        needResult: Boolean
    ): CompiledValue? {
        val stmt = block as? BlockStatement
        if (stmt == null) {
            return compileStatementValueOrFallback(block, needResult)
        }
        val catchSlotIndex = stmt.slotPlan[catchVarName]
        if (catchSlotIndex != null) {
            val key = ScopeSlotKey(stmt.scopeId, catchSlotIndex)
            val localIndex = localSlotIndexByKey[key]
            if (localIndex != null) {
                val localSlot = scopeSlotCount + localIndex
                if (localSlot != exceptionSlot) {
                    emitMove(CompiledValue(exceptionSlot, SlotType.OBJ), localSlot)
                }
                updateSlotType(localSlot, SlotType.OBJ)
            }
        }
        val statements = stmt.statements()
        var lastValue: CompiledValue? = null
        for ((index, statement) in statements.withIndex()) {
            val isLast = index == statements.lastIndex
            val wantResult = needResult && isLast
            val value = compileStatementValueOrFallback(statement, wantResult) ?: return null
            if (wantResult) {
                lastValue = value
            }
        }
        val result = if (needResult) {
            lastValue ?: run {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                CompiledValue(slot, SlotType.OBJ)
            }
        } else {
            lastValue ?: run {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                CompiledValue(slot, SlotType.OBJ)
            }
        }
        return result
    }

    private fun compileCatchClassSlot(name: String): CompiledValue? {
        // resolveTypeNameClass always returns the correct Lyng class for built-in type names.
        // nameObjClass[name] tracks variable type inference — for class declarations it stores
        // ObjClassType (the "Class" meta-type), NOT the actual class object. Using nameObjClass
        // for catch clause matching would cause CHECK_IS to compare against the wrong class,
        // silently failing to catch exceptions. Always prefer resolveTypeNameClass first.
        val cls = resolveTypeNameClass(name) ?: nameObjClass[name]?.takeIf { it != ObjClassType }
        if (cls != null) {
            val id = builder.addConst(BytecodeConst.ObjRef(cls))
            val slot = allocSlot()
            builder.emit(Opcode.CONST_OBJ, id, slot)
            updateSlotType(slot, SlotType.OBJ)
            return CompiledValue(slot, SlotType.OBJ)
        }
        val ref = LocalVarRef(name, Pos.builtIn)
        val compiled = compileRef(ref) ?: return null
        return ensureObjSlot(compiled)
    }

    private fun emitInlineStatements(statements: List<Statement>, needResult: Boolean): CompiledValue? {
        var lastValue: CompiledValue? = null
        for ((index, statement) in statements.withIndex()) {
            val isLast = index == statements.lastIndex
            val wantResult = needResult && isLast
            val value = compileStatementValueOrFallback(statement, wantResult) ?: return null
            if (wantResult) {
                lastValue = value
            }
        }
        return if (needResult) {
            lastValue ?: run {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                CompiledValue(slot, SlotType.OBJ)
            }
        } else {
            lastValue ?: run {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                CompiledValue(slot, SlotType.OBJ)
            }
        }
    }

    private fun emitInlineBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? =
        emitInlineStatements(stmt.statements(), needResult)

    private fun ensureVoidSlot(): Int {
        val existing = cachedVoidSlot
        if (existing != null) return existing
        val slot = allocSlot()
        val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
        builder.emit(Opcode.CONST_OBJ, voidId, slot)
        updateSlotType(slot, SlotType.OBJ)
        cachedVoidSlot = slot
        return slot
    }

    private fun shouldInlineBlock(stmt: BlockStatement): Boolean {
        return allowLocalSlots
    }

    private fun compileInlineBlock(name: String, stmt: net.sergeych.lyng.InlineBlockStatement): CmdFunction? {
        val result = emitInlineStatements(stmt.statements(), true) ?: return null
        builder.emit(Opcode.RET, result.slot)
        val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
        return builder.build(
            name,
            localCount,
            addrCount = nextAddrSlot,
            returnLabels = returnLabels,
            scopeSlotIndices,
            scopeSlotNames,
            scopeSlotIsModule,
            scopeSlotRequiresPreparedBinding,
            scopeSlotRefPos,
            localSlotNames,
            localSlotMutables,
            localSlotDelegated,
            localSlotCaptures
        )
    }

    private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? {
        val target = if (stmt is BytecodeStatement) stmt.original else stmt
        if (target is BlockStatement) {
            val useInline = target.slotPlan.isEmpty() && target.captureSlots.isEmpty()
            return if (useInline) emitInlineBlock(target, needResult) else emitBlock(target, needResult)
        }
        return compileStatementValueOrFallback(target, needResult)
    }

    private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
        updateNameObjClass(stmt.name, stmt.initializer, stmt.initializerObjClass)
        val scopeId = stmt.scopeId ?: 0
        val scopeSlot = stmt.slotIndex?.let { slotIndex ->
            val key = ScopeSlotKey(scopeId, slotIndex)
            scopeSlotMap[key]
        }
        val localSlot = if (allowLocalSlots && stmt.slotIndex != null) {
            val key = ScopeSlotKey(scopeId, stmt.slotIndex)
            val localIndex = localSlotIndexByKey[key]
            localIndex?.let { scopeSlotCount + it }
        } else {
            null
        }
        if (localSlot != null) {
            val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
                val unsetId = builder.addConst(BytecodeConst.ObjRef(ObjUnset))
                builder.emit(Opcode.CONST_OBJ, unsetId, localSlot)
                updateSlotType(localSlot, SlotType.OBJ)
                CompiledValue(localSlot, SlotType.OBJ)
            }
            if (value.slot != localSlot) {
                emitMove(value, localSlot)
            }
            updateSlotType(localSlot, value.type)
            slotObjClass[value.slot]?.let { slotObjClass[localSlot] = it }
                ?: updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass)
            updateListElementClassFromDecl(localSlot, scopeId, stmt.slotIndex)
            updateListElementClassFromInitializer(localSlot, stmt.initializer)
            trackExactCallableObjAtSlot(
                localSlot,
                if (!stmt.isMutable) extractExactCallableObj(stmt.initializer) ?: exactCallableObjBySlot[localSlot] else null
            )
            trackExactLambdaAtSlot(
                localSlot,
                if (!stmt.isMutable) extractExactLambdaRef(stmt.initializer) ?: exactLambdaRefBySlot[localSlot] else null
            )
            updateNameObjClassFromSlot(stmt.name, localSlot)
            val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name)
            val isModuleScope = moduleScopeId != null && scopeId == moduleScopeId
            if (!shadowedScopeSlot || isModuleScope) {
                val declId = builder.addConst(
                    BytecodeConst.LocalDecl(
                        stmt.name,
                        stmt.isMutable,
                        stmt.visibility,
                        stmt.isTransient,
                        stmt.typeDecl
                    )
                )
                builder.emit(Opcode.DECL_LOCAL, declId, localSlot)
            }
            return CompiledValue(localSlot, value.type)
        }
        if (scopeSlot != null) {
            val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
                val unsetId = builder.addConst(BytecodeConst.ObjRef(ObjUnset))
                builder.emit(Opcode.CONST_OBJ, unsetId, scopeSlot)
                updateSlotType(scopeSlot, SlotType.OBJ)
                CompiledValue(scopeSlot, SlotType.OBJ)
            }
            if (value.slot != scopeSlot) {
                emitMove(value, scopeSlot)
            }
            updateSlotType(scopeSlot, value.type)
            updateNameObjClassFromSlot(stmt.name, scopeSlot)
            slotObjClass[value.slot]?.let { slotObjClass[scopeSlot] = it }
                ?: updateSlotObjClass(scopeSlot, stmt.initializer, stmt.initializerObjClass)
            updateListElementClassFromDecl(scopeSlot, scopeId, stmt.slotIndex)
            updateListElementClassFromInitializer(scopeSlot, stmt.initializer)
            trackExactCallableObjAtSlot(
                scopeSlot,
                if (!stmt.isMutable) extractExactCallableObj(stmt.initializer) ?: exactCallableObjBySlot[scopeSlot] else null
            )
            trackExactLambdaAtSlot(
                scopeSlot,
                if (!stmt.isMutable) extractExactLambdaRef(stmt.initializer) ?: exactLambdaRefBySlot[scopeSlot] else null
            )
            val declId = builder.addConst(
                BytecodeConst.LocalDecl(
                    stmt.name,
                    stmt.isMutable,
                    stmt.visibility,
                    stmt.isTransient,
                    stmt.typeDecl
                )
            )
            builder.emit(Opcode.DECL_LOCAL, declId, scopeSlot)
            return CompiledValue(scopeSlot, value.type)
        }
        val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
            val slot = allocSlot()
            val unsetId = builder.addConst(BytecodeConst.ObjRef(ObjUnset))
            builder.emit(Opcode.CONST_OBJ, unsetId, slot)
            updateSlotType(slot, SlotType.OBJ)
            CompiledValue(slot, SlotType.OBJ)
        }
        val declId = builder.addConst(
            BytecodeConst.LocalDecl(
                stmt.name,
                stmt.isMutable,
                stmt.visibility,
                stmt.isTransient,
                stmt.typeDecl
            )
        )
        builder.emit(Opcode.DECL_LOCAL, declId, value.slot)
        if (value.type != SlotType.UNKNOWN) {
            updateSlotTypeByName(stmt.name, value.type)
        }
        updateNameObjClassFromSlot(stmt.name, value.slot)
        if (slotObjClass[value.slot] == null) {
            updateSlotObjClass(value.slot, stmt.initializer, stmt.initializerObjClass)
        }
        updateListElementClassFromDecl(value.slot, scopeId, stmt.slotIndex)
        updateListElementClassFromInitializer(value.slot, stmt.initializer)
        return value
    }

    private fun emitDelegatedVarDecl(stmt: DelegatedVarDeclStatement): CompiledValue? {
        val value = compileStatementValueOrFallback(stmt.initializer) ?: return null
        val slotIndex = stmt.slotIndex
        val scopeId = stmt.scopeId ?: 0
        val localSlot = if (slotIndex != null) {
            val key = ScopeSlotKey(scopeId, slotIndex)
            localSlotIndexByKey[key]?.let { scopeSlotCount + it }
        } else {
            null
        }
        if (allowLocalSlots && localSlot != null) {
            if (value.slot != localSlot) {
                emitMove(value, localSlot)
            }
            val declId = builder.addConst(
                BytecodeConst.DelegatedDecl(
                    stmt.name,
                    stmt.isMutable,
                    stmt.visibility,
                    stmt.isTransient
                )
            )
            builder.emit(Opcode.DECL_DELEGATED, declId, localSlot)
            updateSlotType(localSlot, SlotType.OBJ)
            return CompiledValue(localSlot, SlotType.OBJ)
        }
        val declId = builder.addConst(
            BytecodeConst.DelegatedDecl(
                stmt.name,
                stmt.isMutable,
                stmt.visibility,
                stmt.isTransient
            )
        )
        builder.emit(Opcode.DECL_DELEGATED, declId, value.slot)
        updateSlotType(value.slot, SlotType.OBJ)
        return CompiledValue(value.slot, SlotType.OBJ)
    }

    private fun emitDestructuringVarDecl(stmt: DestructuringVarDeclStatement): CompiledValue? {
        val value = compileStatementValueOrFallback(stmt.initializer) ?: return null
        val declId = builder.addConst(
            BytecodeConst.DestructureDecl(
                stmt.pattern,
                stmt.names,
                stmt.isMutable,
                stmt.visibility,
                stmt.isTransient,
                stmt.pos
            )
        )
        builder.emit(Opcode.DECL_DESTRUCTURE, declId, value.slot)
        updateSlotType(value.slot, SlotType.OBJ)
        return CompiledValue(value.slot, SlotType.OBJ)
    }

    private fun updateNameObjClass(name: String, initializer: Statement?, initializerObjClass: ObjClass? = null) {
        val cls = initializerObjClass ?: objClassForInitializer(initializer)
        if (cls != null) {
            nameObjClass[name] = cls
        } else {
            nameObjClass.remove(name)
        }
    }

    private fun updateSlotObjClass(slot: Int, initializer: Statement?, initializerObjClass: ObjClass? = null) {
        val cls = initializerObjClass ?: objClassForInitializer(initializer)
        if (cls != null) {
            slotObjClass[slot] = cls
        }
    }

    private fun updateListElementClassFromDecl(slot: Int, scopeId: Int, slotIndex: Int?) {
        if (slotIndex == null) return
        val decl = slotTypeDeclByScopeId[scopeId]?.get(slotIndex) ?: return
        val elementClass = listElementClassFromDecl(decl) ?: return
        listElementClassBySlot[slot] = elementClass
    }

    private fun updateListElementClassFromInitializer(slot: Int, initializer: Statement?) {
        val elementClass = listElementClassFromInitializer(initializer) ?: return
        listElementClassBySlot[slot] = elementClass
    }

    private fun noteListElementClassMutation(receiverSlot: Int, value: CompiledValue) {
        val newClass = elementClassFromValue(value) ?: return
        val current = listElementClassBySlot[receiverSlot]
        if (current == null || current == newClass) {
            listElementClassBySlot[receiverSlot] = newClass
        } else {
            listElementClassBySlot.remove(receiverSlot)
        }
    }

    private fun updateNameObjClassFromSlot(name: String, slot: Int) {
        val cls = slotObjClass[slot] ?: return
        nameObjClass[name] = cls
    }

    private fun objClassForInitializer(initializer: Statement?): ObjClass? {
        var initStmt = initializer
        if (initStmt is BytecodeStatement) {
            val fn = initStmt.bytecodeFunction()
            if (fn.cmds.any { it is CmdListLiteral }) return ObjList.type
            if (fn.cmds.any { it is CmdMakeRange || it is CmdRangeIntBounds }) return ObjRange.type
        }
        while (initStmt is BytecodeStatement) {
            initStmt = initStmt.original
        }
        val initRef = (initStmt as? ExpressionStatement)?.ref
        val directRef = when (initRef) {
            is StatementRef -> (initRef.statement as? ExpressionStatement)?.ref
            else -> initRef
        }
        return when (directRef) {
            is ListLiteralRef -> ObjList.type
            is MapLiteralRef -> ObjMap.type
            is RangeRef -> ObjRange.type
            is ImplicitThisMethodCallRef -> {
                if (directRef.methodName() == "iterator") ObjIterator else null
            }
            is ThisMethodSlotCallRef -> {
                if (directRef.methodName() == "iterator") ObjIterator else null
            }
            is MethodCallRef -> {
                if (directRef.name == "iterator") ObjIterator else null
            }
            is CallRef -> {
                val target = directRef.target
                when {
                    target is LocalVarRef && target.name == "List" -> ObjList.type
                    target is LocalVarRef && target.name == "Map" -> ObjMap.type
                    target is LocalVarRef && target.name == "iterator" -> ObjIterator
                    target is ImplicitThisMemberRef && target.name == "iterator" -> ObjIterator
                    target is ThisFieldSlotRef && target.name == "iterator" -> ObjIterator
                    target is FieldRef && target.name == "iterator" -> ObjIterator
                    else -> null
                }
            }
            is ConstRef -> when (directRef.constValue) {
                is ObjList -> ObjList.type
                is ObjMap -> ObjMap.type
                is ObjRange -> ObjRange.type
                else -> null
            }
            else -> null
        }
    }

    private fun listElementClassFromInitializer(initializer: Statement?): ObjClass? {
        var initStmt = initializer
        while (initStmt is BytecodeStatement) {
            initStmt = initStmt.original
        }
        val initRef = (initStmt as? ExpressionStatement)?.ref
        val directRef = when (initRef) {
            is StatementRef -> (initRef.statement as? ExpressionStatement)?.ref
            else -> initRef
        }
        return when (directRef) {
            is ListLiteralRef -> listElementClassFromListLiteralRef(directRef)
            is MethodCallRef -> listElementClassFromMethodCallRef(directRef)
            else -> null
        }
    }

    private fun listElementClassFromMethodCallRef(ref: MethodCallRef): ObjClass? {
        if (ref.name != "fill" || !isListTypeRef(ref.receiver)) return null
        val block = ref.args.lastOrNull() ?: return null
        return supportedListElementClass(listElementClassFromFillValue(block.value))
    }

    private fun listElementClassFromFillValue(value: Obj): ObjClass? {
        val expr = value as? ExpressionStatement ?: return null
        val ref = when (val directRef = expr.ref) {
            is StatementRef -> (directRef.statement as? ExpressionStatement)?.ref
            else -> directRef
        } ?: return null
        return when (ref) {
            is ConstRef -> elementClassFromConst(ref.constValue)
            is LambdaFnRef -> ref.inferredReturnClass
            else -> null
        }
    }

    private fun isListTypeRef(ref: ObjRef): Boolean {
        return when (ref) {
            is LocalVarRef -> ref.name == "List"
            is LocalSlotRef -> ref.name == "List"
            is FastLocalVarRef -> ref.name == "List"
            else -> false
        }
    }

    private fun supportedListElementClass(cls: ObjClass?): ObjClass? {
        return when (cls) {
            ObjInt.type, ObjReal.type, ObjString.type, ObjBool.type -> cls
            else -> null
        }
    }

    private fun listElementClassFromListLiteralRef(ref: ListLiteralRef): ObjClass? {
        var elementClass: ObjClass? = null
        for (entry in ref.entries()) {
            val entryClass = when (entry) {
                is net.sergeych.lyng.ListEntry.Element -> {
                    val const = entry.ref as? ConstRef ?: return null
                    elementClassFromConst(const.constValue)
                }
                is net.sergeych.lyng.ListEntry.Spread -> return null
            } ?: return null
            if (elementClass == null) {
                elementClass = entryClass
            } else if (elementClass != entryClass) {
                return null
            }
        }
        return elementClass
    }
    private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement, wantResult: Boolean): Int? {
        val range = stmt.constRange
        var rangeRef = if (range == null) extractRangeRef(stmt.source) else null
        if (range == null && rangeRef == null) {
            rangeRef = extractRangeFromLocal(stmt.source)
        }
        val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
        val loopSlotPlan = stmt.loopSlotPlan
        val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
        val loopKey = loopSlotIndex?.let { ScopeSlotKey(stmt.loopScopeId, it) }
        val loopLocalIndex = loopKey?.let { localSlotIndexByKey[it] } ?: localSlotIndexByName[stmt.loopVarName]
        var usedOverride = false
        var loopSlotId = when {
            loopLocalIndex != null -> scopeSlotCount + loopLocalIndex
            else -> {
                val localKey = localSlotInfoMap.entries.firstOrNull { it.value.name == stmt.loopVarName }?.key
                val localIndex = localKey?.let { localSlotIndexByKey[it] }
                when {
                    localIndex != null -> scopeSlotCount + localIndex
                    else -> scopeSlotIndexByName[stmt.loopVarName]
                }
            }
        } ?: run {
            val slot = allocSlot()
            loopSlotOverrides[stmt.loopVarName] = slot
            usedOverride = true
            slot
        }
        // Loop scopes are intentionally disabled; loop vars live in frame slots only.
        if (loopSlotId < scopeSlotCount) {
            val localSlot = allocSlot()
            loopSlotOverrides[stmt.loopVarName] = localSlot
            usedOverride = true
            loopSlotId = localSlot
        }

        try {
        val needsBreakFlag = stmt.canBreak || stmt.elseStatement != null
        val realWidenSlots = collectLoopRealWidenSlots(stmt.body)
        val hasRealWiden = realWidenSlots.isNotEmpty()
        if (hasRealWiden) {
            applySlotTypes(realWidenSlots, SlotType.REAL)
        }
        val breakFlagSlot = allocSlot()
        if (range == null && rangeRef == null && typedRangeLocal == null) {
            val sourceValue = compileStatementValueOrFallback(stmt.source) ?: return null
            return emitIterableForIn(
                stmt = stmt,
                sourceValue = sourceValue,
                wantResult = wantResult,
                loopSlotId = loopSlotId,
                breakFlagSlot = breakFlagSlot,
                needsBreakFlag = needsBreakFlag,
                hasRealWiden = hasRealWiden,
                realWidenSlots = realWidenSlots,
            )
        }

        val iSlot = loopSlotId
        val endSlot = allocSlot()
        val descendingSlot = allocSlot()
        if (range != null) {
            val startId = builder.addConst(BytecodeConst.IntVal(range.start))
            val endId = builder.addConst(BytecodeConst.IntVal(range.stopBoundary))
            val descendingId = builder.addConst(BytecodeConst.Bool(range.isDescending))
            builder.emit(Opcode.CONST_INT, startId, iSlot)
            builder.emit(Opcode.CONST_INT, endId, endSlot)
            builder.emit(Opcode.CONST_BOOL, descendingId, descendingSlot)
            updateSlotType(iSlot, SlotType.INT)
            updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
        } else {
            if (rangeRef != null) {
                val left = rangeRef.left ?: return null
                val right = rangeRef.right ?: return null
                val startCompiled = compileRef(left) ?: return null
                val endCompiled = compileRef(right) ?: return null
                val startValue = coerceToLoopInt(startCompiled)
                val endValue = coerceToLoopInt(endCompiled)
                if (startValue == null || endValue == null) {
                    val rangeValue = emitRangeObject(startCompiled, endCompiled, rangeRef)
                    return emitIterableForIn(
                        stmt = stmt,
                        sourceValue = rangeValue,
                        wantResult = wantResult,
                        loopSlotId = loopSlotId,
                        breakFlagSlot = breakFlagSlot,
                        needsBreakFlag = needsBreakFlag,
                        hasRealWiden = hasRealWiden,
                        realWidenSlots = realWidenSlots,
                    )
                }
                val descendingId = builder.addConst(BytecodeConst.Bool(rangeRef.isDescending))
                emitMove(startValue, iSlot)
                emitMove(endValue, endSlot)
                builder.emit(Opcode.CONST_BOOL, descendingId, descendingSlot)
                if (rangeRef.isDescending) {
                    if (rangeRef.isEndInclusive) {
                        builder.emit(Opcode.DEC_INT, endSlot)
                    }
                } else if (rangeRef.isEndInclusive) {
                    builder.emit(Opcode.INC_INT, endSlot)
                }
                updateSlotType(iSlot, SlotType.INT)
                updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
            } else {
                val rangeLocal = typedRangeLocal ?: return null
                val rangeValue = compileRef(rangeLocal) ?: return null
                val rangeObj = ensureObjSlot(rangeValue)
                val okSlot = allocSlot()
                builder.emit(Opcode.RANGE_INT_BOUNDS, rangeObj.slot, iSlot, endSlot, descendingSlot, okSlot)
                val badRangeLabel = builder.label()
                builder.emit(
                    Opcode.JMP_IF_FALSE,
                    listOf(CmdBuilder.Operand.IntVal(okSlot), CmdBuilder.Operand.LabelRef(badRangeLabel))
                )
                if (needsBreakFlag) {
                    val falseId = builder.addConst(BytecodeConst.Bool(false))
                    builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)
                }
                val resultSlot = if (wantResult) {
                    val slot = allocSlot()
                    val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                    builder.emit(Opcode.CONST_OBJ, voidId, slot)
                    slot
                } else {
                    null
                }

                val loopLabel = builder.label()
                val continueLabel = builder.label()
                val endLabel = builder.label()
                val doneLabel = builder.label()
                builder.mark(loopLabel)
                emitIntForLoopCheck(iSlot, endSlot, descendingSlot, endLabel)
                updateSlotType(iSlot, SlotType.INT)
                updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
                loopStack.addLast(
                    LoopContext(
                        stmt.label,
                        endLabel,
                        continueLabel,
                        breakFlagSlot,
                        resultSlot,
                        hasIterator = false
                    )
                )
                val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
                if (hasRealWiden) {
                    applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
                }
                loopStack.removeLast()
                if (wantResult) {
                    val bodyObj = ensureObjSlot(bodyValue)
                    builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
                }
                builder.mark(continueLabel)
                emitIntForLoopStep(iSlot, descendingSlot)
                if (hasRealWiden) {
                    emitLoopRealCoercions(realWidenSlots)
                }
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))

                builder.mark(endLabel)
                if (stmt.elseStatement != null) {
                    val afterElse = if (needsBreakFlag) builder.label() else null
                    if (needsBreakFlag) {
                        builder.emit(
                            Opcode.JMP_IF_TRUE,
                            listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse!!))
                        )
                    }
                    val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
                    if (wantResult) {
                        val elseObj = ensureObjSlot(elseValue)
                        builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot!!)
                    }
                    if (needsBreakFlag) {
                        builder.mark(afterElse!!)
                    }
                }
                builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel)))
                builder.mark(badRangeLabel)
                val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range"))
                val errorSlot = resultSlot ?: allocSlot()
                builder.emit(Opcode.CONST_OBJ, msgId, errorSlot)
                val posId = builder.addConst(BytecodeConst.PosVal(stmt.pos))
                builder.emit(Opcode.THROW, posId, errorSlot)
                builder.mark(doneLabel)
                return resultSlot ?: breakFlagSlot
            }
        }

        if (needsBreakFlag) {
            val falseId = builder.addConst(BytecodeConst.Bool(false))
            builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)
        }
        val resultSlot = if (wantResult) {
            val slot = allocSlot()
            val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
            builder.emit(Opcode.CONST_OBJ, voidId, slot)
            slot
        } else {
            null
        }

        val loopLabel = builder.label()
        val continueLabel = builder.label()
        val endLabel = builder.label()
        builder.mark(loopLabel)
        emitIntForLoopCheck(iSlot, endSlot, descendingSlot, endLabel)
        updateSlotType(iSlot, SlotType.INT)
        updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
        loopStack.addLast(
            LoopContext(
                stmt.label,
                endLabel,
                continueLabel,
                breakFlagSlot,
                resultSlot,
                hasIterator = false
            )
        )
        val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
        if (hasRealWiden) {
            applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
        }
        loopStack.removeLast()
        if (wantResult) {
            val bodyObj = ensureObjSlot(bodyValue)
            builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
        }
        builder.mark(continueLabel)
        emitIntForLoopStep(iSlot, descendingSlot)
        if (hasRealWiden) {
            emitLoopRealCoercions(realWidenSlots)
        }
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))

        builder.mark(endLabel)
        if (stmt.elseStatement != null) {
            val afterElse = if (needsBreakFlag) builder.label() else null
            if (needsBreakFlag) {
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse!!))
                )
            }
            val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
            if (wantResult) {
                val elseObj = ensureObjSlot(elseValue)
                builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot!!)
            }
            if (needsBreakFlag) {
                builder.mark(afterElse!!)
            }
        }
        return resultSlot ?: breakFlagSlot
        } finally {
            if (usedOverride) {
                loopSlotOverrides.remove(stmt.loopVarName)
            }
        }
    }

    private fun emitWhile(stmt: net.sergeych.lyng.WhileStatement, wantResult: Boolean): Int? {
        val breakFlagSlot = allocSlot()
        val falseId = builder.addConst(BytecodeConst.Bool(false))
        builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)

        val resultSlot = allocSlot()
        val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
        builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)

        val realWidenSlots = collectLoopRealWidenSlots(stmt.body)
        val hasRealWiden = realWidenSlots.isNotEmpty()
        if (hasRealWiden) {
            applySlotTypes(realWidenSlots, SlotType.REAL)
        }

        val loopLabel = builder.label()
        val continueLabel = builder.label()
        val endLabel = builder.label()
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(continueLabel)))
        builder.mark(loopLabel)
        loopStack.addLast(
            LoopContext(
                stmt.label,
                endLabel,
                continueLabel,
                breakFlagSlot,
                if (wantResult) resultSlot else null,
                hasIterator = false
            )
        )
        val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
        if (hasRealWiden) {
            applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
        }
        loopStack.removeLast()
        if (wantResult) {
            val bodyObj = ensureObjSlot(bodyValue)
            builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
        }
        builder.mark(continueLabel)
        if (hasRealWiden) {
            emitLoopRealCoercions(realWidenSlots)
        }
        val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
        val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
        if (hasRealWiden || !emitIntCompareJump(conditionStmt.ref, jumpOnTrue = true, target = loopLabel)) {
            val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
            if (condition.type != SlotType.BOOL) return null
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
            )
        }

        builder.mark(endLabel)
        if (stmt.elseStatement != null) {
            val afterElse = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse))
            )
            val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
            if (wantResult) {
                val elseObj = ensureObjSlot(elseValue)
                builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
            }
            builder.mark(afterElse)
        }
        return resultSlot
    }

    private fun emitDoWhile(stmt: net.sergeych.lyng.DoWhileStatement, wantResult: Boolean): Int? {
        val breakFlagSlot = allocSlot()
        val falseId = builder.addConst(BytecodeConst.Bool(false))
        builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)

        val resultSlot = allocSlot()
        val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
        builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)

        val realWidenSlots = collectLoopRealWidenSlots(stmt.body)
        val hasRealWiden = realWidenSlots.isNotEmpty()
        if (hasRealWiden) {
            applySlotTypes(realWidenSlots, SlotType.REAL)
        }

        val loopLabel = builder.label()
        val continueLabel = builder.label()
        val endLabel = builder.label()
        val breakLabel = endLabel
        builder.mark(loopLabel)
        if (hasRealWiden) {
            emitLoopRealCoercions(realWidenSlots)
        }
        loopStack.addLast(
            LoopContext(
                stmt.label,
                breakLabel,
                continueLabel,
                breakFlagSlot,
                if (wantResult) resultSlot else null,
                hasIterator = false
            )
        )
        val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
        loopStack.removeLast()
        if (wantResult) {
            val bodyObj = ensureObjSlot(bodyValue)
            builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
        }
        builder.mark(continueLabel)
        val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
        val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
        if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = true, target = loopLabel)) {
            val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
            if (condition.type != SlotType.BOOL) return null
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
            )
        }

        builder.mark(endLabel)
        if (stmt.elseStatement != null) {
            val afterElse = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse))
            )
            val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
            if (wantResult) {
                val elseObj = ensureObjSlot(elseValue)
                builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
            }
            builder.mark(afterElse)
        }
        return resultSlot
    }

    private fun compileIfStatement(stmt: IfStatement): CompiledValue? {
        val elseLabel = builder.label()
        val endLabel = builder.label()
        val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
        val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
        if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = false, target = elseLabel)) {
            val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
            if (condition.type != SlotType.BOOL) return null
            builder.emit(
                Opcode.JMP_IF_FALSE,
                listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
            )
        }
        val thenRestore = applyFlowTypeOverride(flowTypeOverrideForIf(stmt.condition, applyForThen = true))
        compileStatementValueOrFallback(stmt.ifBody, false) ?: return null
        restoreFlowTypeOverride(thenRestore)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(elseLabel)
        stmt.elseBody?.let {
            val elseRestore = applyFlowTypeOverride(flowTypeOverrideForIf(stmt.condition, applyForThen = false))
            compileStatementValueOrFallback(it, false) ?: return null
            restoreFlowTypeOverride(elseRestore)
        }
        builder.mark(endLabel)
        return CompiledValue(ensureVoidSlot(), SlotType.OBJ)
    }

    private fun updateSlotTypeByName(name: String, type: SlotType) {
        val localIndex = localSlotIndexByName[name]
        if (localIndex != null) {
            updateSlotType(scopeSlotCount + localIndex, type)
            return
        }
        for ((key, index) in scopeSlotMap) {
            if (scopeSlotNameMap[key] == name) {
                updateSlotType(index, type)
            }
        }
    }

    private fun compileIfExpression(stmt: IfStatement): CompiledValue? {
        val resultSlot = allocSlot()
        val elseLabel = builder.label()
        val endLabel = builder.label()
        val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
        val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
        if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = false, target = elseLabel)) {
            val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
            if (condition.type != SlotType.BOOL) return null
            builder.emit(
                Opcode.JMP_IF_FALSE,
                listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
            )
        }
        val thenRestore = applyFlowTypeOverride(flowTypeOverrideForIf(stmt.condition, applyForThen = true))
        val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
        restoreFlowTypeOverride(thenRestore)
        val thenObj = ensureObjSlot(thenValue)
        emitMove(thenObj, resultSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
        builder.mark(elseLabel)
        if (stmt.elseBody != null) {
            val elseRestore = applyFlowTypeOverride(flowTypeOverrideForIf(stmt.condition, applyForThen = false))
            val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return null
            restoreFlowTypeOverride(elseRestore)
            val elseObj = ensureObjSlot(elseValue)
            emitMove(elseObj, resultSlot)
        } else {
            val id = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
            builder.emit(Opcode.CONST_OBJ, id, resultSlot)
        }
        builder.mark(endLabel)
        updateSlotType(resultSlot, SlotType.OBJ)
        return CompiledValue(resultSlot, SlotType.OBJ)
    }

    private fun emitIntCompareJump(ref: ObjRef, jumpOnTrue: Boolean, target: CmdBuilder.Label): Boolean {
        setPos(refPosOrCurrent(ref))
        val binary = ref as? BinaryOpRef ?: return false
        val op = binaryOp(binary)
        if (op != BinOp.EQ && op != BinOp.NEQ && op != BinOp.LT && op != BinOp.LTE && op != BinOp.GT && op != BinOp.GTE) {
            return false
        }
        val leftRef = binaryLeft(binary)
        val rightRef = binaryRight(binary)
        if (!isSimpleIntCompareRef(leftRef) || !isSimpleIntCompareRef(rightRef)) return false
        val left = compileRef(leftRef) ?: return false
        val right = compileRef(rightRef) ?: return false
        if (left.type != SlotType.INT || right.type != SlotType.INT) return false
        val opcode = if (jumpOnTrue) {
            intCompareJumpOpcode(op)
        } else {
            intCompareJumpOpcode(invertIntCompareOp(op))
        }
        builder.emit(
            opcode,
            listOf(
                CmdBuilder.Operand.IntVal(left.slot),
                CmdBuilder.Operand.IntVal(right.slot),
                CmdBuilder.Operand.LabelRef(target)
            )
        )
        return true
    }

    private fun isSimpleIntCompareRef(ref: ObjRef): Boolean {
        return when (ref) {
            is ConstRef -> ref.constValue is ObjInt
            is LocalVarRef -> ref.name != "this"
            is FastLocalVarRef -> ref.name != "this"
            is BoundLocalVarRef -> true
            is LocalSlotRef -> !ref.isDelegated && ref.name != "this"
            else -> false
        }
    }

    private fun invertIntCompareOp(op: BinOp): BinOp {
        return when (op) {
            BinOp.EQ -> BinOp.NEQ
            BinOp.NEQ -> BinOp.EQ
            BinOp.LT -> BinOp.GTE
            BinOp.LTE -> BinOp.GT
            BinOp.GT -> BinOp.LTE
            BinOp.GTE -> BinOp.LT
            else -> op
        }
    }

    private fun intCompareJumpOpcode(op: BinOp): Opcode {
        return when (op) {
            BinOp.EQ -> Opcode.JMP_IF_EQ_INT
            BinOp.NEQ -> Opcode.JMP_IF_NEQ_INT
            BinOp.LT -> Opcode.JMP_IF_LT_INT
            BinOp.LTE -> Opcode.JMP_IF_LTE_INT
            BinOp.GT -> Opcode.JMP_IF_GT_INT
            BinOp.GTE -> Opcode.JMP_IF_GTE_INT
            else -> Opcode.JMP_IF_NEQ_INT
        }
    }

    private fun compileCondition(stmt: Statement, pos: Pos): CompiledValue? {
        val target = if (stmt is BytecodeStatement) stmt.original else stmt
        return when (target) {
            is ExpressionStatement -> compileRefWithFallback(target.ref, SlotType.BOOL, target.pos)
            else -> {
                throw BytecodeCompileException(
                    "Bytecode compile error: unsupported condition",
                    pos
                )
            }
        }
    }

    private data class FlowTypeSubject(val name: String, val slot: Int?)
    private data class FlowTypeInfo(val name: String, val slot: Int?, val cls: ObjClass)
    private data class FlowTypeRestore(
        val name: String,
        val prevNameClass: ObjClass?,
        val slot: Int?,
        val prevSlotClass: ObjClass?,
    )

    private fun extractFlowTypeSubject(stmt: Statement): FlowTypeSubject? {
        val target = if (stmt is BytecodeStatement) stmt.original else stmt
        val expr = target as? ExpressionStatement ?: return null
        return flowTypeSubjectFromRef(expr.ref)
    }

    private fun flowTypeSubjectFromRef(ref: ObjRef): FlowTypeSubject? {
        return when (ref) {
            is LocalSlotRef -> FlowTypeSubject(ref.name, resolveSlot(ref))
            is LocalVarRef -> FlowTypeSubject(ref.name, resolveDirectNameSlot(ref.name)?.slot)
            else -> null
        }
    }

    private fun flowTypeOverrideForIf(condition: Statement, applyForThen: Boolean): FlowTypeInfo? {
        val target = if (condition is BytecodeStatement) condition.original else condition
        val expr = target as? ExpressionStatement ?: return null
        val ref = expr.ref as? BinaryOpRef ?: return null
        val op = binaryOp(ref)
        val apply = when (op) {
            BinOp.IS -> applyForThen
            BinOp.NOTIS -> !applyForThen
            else -> false
        }
        if (!apply) return null
        val cls = resolveTypeRefClass(binaryRight(ref)) ?: return null
        return flowTypeInfoForRef(binaryLeft(ref), cls)
    }

    private fun flowTypeOverrideForWhenCase(
        subject: FlowTypeSubject?,
        conditions: List<WhenCondition>
    ): FlowTypeInfo? {
        if (subject == null || conditions.size != 1) return null
        val cond = conditions.first() as? WhenIsCondition ?: return null
        if (cond.negated) return null
        val expr = cond.expr as? ExpressionStatement ?: return null
        val cls = resolveTypeRefClass(expr.ref) ?: return null
        return FlowTypeInfo(subject.name, subject.slot, cls)
    }

    private fun flowTypeInfoForRef(ref: ObjRef, cls: ObjClass): FlowTypeInfo? {
        return when (ref) {
            is LocalSlotRef -> FlowTypeInfo(ref.name, resolveSlot(ref), cls)
            is LocalVarRef -> FlowTypeInfo(ref.name, resolveDirectNameSlot(ref.name)?.slot, cls)
            else -> null
        }
    }

    private fun applyFlowTypeOverride(info: FlowTypeInfo?): FlowTypeRestore? {
        if (info == null) return null
        val prevNameClass = nameObjClass[info.name]
        nameObjClass[info.name] = info.cls
        val prevSlotClass = info.slot?.let { slotObjClass[it] }
        if (info.slot != null) {
            slotObjClass[info.slot] = info.cls
        }
        return FlowTypeRestore(info.name, prevNameClass, info.slot, prevSlotClass)
    }

    private fun restoreFlowTypeOverride(restore: FlowTypeRestore?) {
        if (restore == null) return
        if (restore.prevNameClass == null) {
            nameObjClass.remove(restore.name)
        } else {
            nameObjClass[restore.name] = restore.prevNameClass
        }
        if (restore.slot != null) {
            if (restore.prevSlotClass == null) {
                slotObjClass.remove(restore.slot)
            } else {
                slotObjClass[restore.slot] = restore.prevSlotClass
            }
        }
    }

    private fun findLoopContextIndex(label: String?): Int? {
        if (loopStack.isEmpty()) return null
        val stack = loopStack.toList()
        if (label == null) return stack.lastIndex
        for (i in stack.indices.reversed()) {
            if (stack[i].label == label) return i
        }
        return null
    }

    private fun emitIteratorCancel(stack: List<LoopContext>, startIndex: Int) {
        for (i in stack.lastIndex downTo startIndex) {
            if (stack[i].hasIterator) {
                builder.emit(Opcode.ITER_CANCEL)
            }
        }
    }

    private fun compileBreak(stmt: net.sergeych.lyng.BreakStatement): CompiledValue? {
        val stack = loopStack.toList()
        val targetIndex = findLoopContextIndex(stmt.label) ?: run {
            val labels = stack.joinToString(prefix = "[", postfix = "]") { it.label ?: "<unlabeled>" }
            throw BytecodeCompileException(
                "Bytecode compile error: break label '${stmt.label}' not found in $labels",
                stmt.pos
            )
        }
        val ctx = stack[targetIndex]
        val value = stmt.resultExpr?.let { compileStatementValueOrFallback(it) }
        if (ctx.resultSlot != null) {
            val objValue = value?.let { ensureObjSlot(it) } ?: run {
                val slot = allocSlot()
                val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
                builder.emit(Opcode.CONST_OBJ, voidId, slot)
                updateSlotType(slot, SlotType.OBJ)
                CompiledValue(slot, SlotType.OBJ)
            }
            builder.emit(Opcode.MOVE_OBJ, objValue.slot, ctx.resultSlot)
        } else if (value != null) {
            ensureObjSlot(value)
        }
        emitIteratorCancel(stack, targetIndex)
        val trueId = builder.addConst(BytecodeConst.Bool(true))
        builder.emit(Opcode.CONST_BOOL, trueId, ctx.breakFlagSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.breakLabel)))
        return CompiledValue(ctx.breakFlagSlot, SlotType.BOOL)
    }

    private fun compileContinue(stmt: net.sergeych.lyng.ContinueStatement): CompiledValue? {
        val stack = loopStack.toList()
        val targetIndex = findLoopContextIndex(stmt.label) ?: return null
        val ctx = stack[targetIndex]
        if (targetIndex < stack.lastIndex) {
            emitIteratorCancel(stack, targetIndex + 1)
        }
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.continueLabel)))
        return CompiledValue(ctx.breakFlagSlot, SlotType.BOOL)
    }

    private fun compileReturn(stmt: net.sergeych.lyng.ReturnStatement): CompiledValue? {
        val value = stmt.resultExpr?.let { compileStatementValueOrFallback(it) } ?: run {
            val slot = allocSlot()
            val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
            builder.emit(Opcode.CONST_OBJ, voidId, slot)
            updateSlotType(slot, SlotType.OBJ)
            CompiledValue(slot, SlotType.OBJ)
        }
        val label = stmt.label
        if (label == null || returnLabels.contains(label)) {
            builder.emit(Opcode.RET, value.slot)
        } else {
            val labelId = builder.addConst(BytecodeConst.StringVal(label))
            builder.emit(Opcode.RET_LABEL, labelId, value.slot)
        }
        return value
    }

    private fun compileThrow(stmt: net.sergeych.lyng.ThrowStatement): CompiledValue? {
        val value = compileStatementValueOrFallback(stmt.throwExpr) ?: return null
        val objValue = ensureObjSlot(value)
        val posId = builder.addConst(BytecodeConst.PosVal(stmt.pos))
        builder.emit(Opcode.THROW, posId, objValue.slot)
        return objValue
    }

    private fun emitExtensionPropertyDecl(
        stmt: net.sergeych.lyng.ExtensionPropertyDeclStatement
    ): CompiledValue {
        val constId = builder.addConst(
            BytecodeConst.ExtensionPropertyDecl(
                stmt.extTypeName,
                stmt.property,
                stmt.visibility,
                stmt.setterVisibility,
                stmt.getterTypeDecl,
                stmt.setterTypeDecl
            )
        )
        val slot = allocSlot()
        builder.emit(Opcode.DECL_EXT_PROPERTY, constId, slot)
        updateSlotType(slot, SlotType.OBJ)
        return CompiledValue(slot, SlotType.OBJ)
    }

    private fun resetAddrCache() {
        addrSlotByScopeSlot.clear()
    }

    private fun ensureScopeAddr(scopeSlot: Int): Int {
        val existing = addrSlotByScopeSlot[scopeSlot]
        val addrSlot = existing ?: run {
            val created = nextAddrSlot++
            addrSlotByScopeSlot[scopeSlot] = created
            created
        }
        builder.emit(Opcode.RESOLVE_SCOPE_SLOT, scopeSlot, addrSlot)
        return addrSlot
    }

    private fun emitLoadFromAddr(addrSlot: Int, dstSlot: Int, type: SlotType) {
        when (type) {
            SlotType.INT -> builder.emit(Opcode.LOAD_INT_ADDR, addrSlot, dstSlot)
            SlotType.REAL -> builder.emit(Opcode.LOAD_REAL_ADDR, addrSlot, dstSlot)
            SlotType.BOOL -> builder.emit(Opcode.LOAD_BOOL_ADDR, addrSlot, dstSlot)
            SlotType.OBJ -> builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, dstSlot)
            else -> builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, dstSlot)
        }
    }

    private fun emitStoreToAddr(srcSlot: Int, addrSlot: Int, type: SlotType) {
        when (type) {
            SlotType.INT -> builder.emit(Opcode.STORE_INT_ADDR, srcSlot, addrSlot)
            SlotType.REAL -> builder.emit(Opcode.STORE_REAL_ADDR, srcSlot, addrSlot)
            SlotType.BOOL -> builder.emit(Opcode.STORE_BOOL_ADDR, srcSlot, addrSlot)
            SlotType.OBJ -> builder.emit(Opcode.STORE_OBJ_ADDR, srcSlot, addrSlot)
            else -> builder.emit(Opcode.STORE_OBJ_ADDR, srcSlot, addrSlot)
        }
    }

    private fun emitMove(value: CompiledValue, dstSlot: Int) {
        val srcSlot = value.slot
        if (srcSlot == dstSlot) return
        val srcIsScope = srcSlot < scopeSlotCount
        val dstIsScope = dstSlot < scopeSlotCount
        if (value.type != SlotType.UNKNOWN) {
            if (srcIsScope && !dstIsScope) {
                val addrSlot = ensureScopeAddr(srcSlot)
                emitLoadFromAddr(addrSlot, dstSlot, value.type)
                propagateObjClass(value.type, srcSlot, dstSlot)
                propagateExactCallableObj(value.type, srcSlot, dstSlot)
                propagateExactLambdaRef(value.type, srcSlot, dstSlot)
                return
            }
            if (dstIsScope) {
                val addrSlot = ensureScopeAddr(dstSlot)
                emitStoreToAddr(srcSlot, addrSlot, value.type)
                propagateObjClass(value.type, srcSlot, dstSlot)
                propagateExactCallableObj(value.type, srcSlot, dstSlot)
                propagateExactLambdaRef(value.type, srcSlot, dstSlot)
                return
            }
        }
        when (value.type) {
            SlotType.INT -> builder.emit(Opcode.MOVE_INT, srcSlot, dstSlot)
            SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, srcSlot, dstSlot)
            SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, srcSlot, dstSlot)
            SlotType.OBJ -> builder.emit(Opcode.MOVE_OBJ, srcSlot, dstSlot)
            else -> builder.emit(Opcode.BOX_OBJ, srcSlot, dstSlot)
        }
        propagateObjClass(value.type, srcSlot, dstSlot)
        propagateExactCallableObj(value.type, srcSlot, dstSlot)
        propagateExactLambdaRef(value.type, srcSlot, dstSlot)
    }

    private fun propagateObjClass(type: SlotType, srcSlot: Int, dstSlot: Int) {
        if (type == SlotType.OBJ || type == SlotType.UNKNOWN) {
            val cls = slotObjClass[srcSlot]
            if (cls != null) {
                slotObjClass[dstSlot] = cls
                if (stableObjSlots.contains(srcSlot)) {
                    stableObjSlots.add(dstSlot)
                } else {
                    stableObjSlots.remove(dstSlot)
                }
            } else {
                slotObjClass.remove(dstSlot)
                stableObjSlots.remove(dstSlot)
            }
            val elementClass = listElementClassBySlot[srcSlot]
            if (elementClass != null) {
                listElementClassBySlot[dstSlot] = elementClass
            } else {
                listElementClassBySlot.remove(dstSlot)
            }
        } else {
            slotObjClass.remove(dstSlot)
            listElementClassBySlot.remove(dstSlot)
        }
    }

    private fun propagateExactLambdaRef(type: SlotType, srcSlot: Int, dstSlot: Int) {
        if (type == SlotType.OBJ || type == SlotType.UNKNOWN) {
            trackExactLambdaAtSlot(dstSlot, exactLambdaRefBySlot[srcSlot])
        } else {
            trackExactLambdaAtSlot(dstSlot, null)
        }
    }

    private fun propagateExactCallableObj(type: SlotType, srcSlot: Int, dstSlot: Int) {
        if (type == SlotType.OBJ || type == SlotType.UNKNOWN) {
            trackExactCallableObjAtSlot(dstSlot, exactCallableObjBySlot[srcSlot])
        } else {
            trackExactCallableObjAtSlot(dstSlot, null)
        }
    }

    private fun setPos(pos: Pos?) {
        currentPos = pos
        builder.setPos(pos)
    }

    private fun callSitePos(): Pos = currentPos ?: Pos.builtIn

    private fun refPosOrCurrent(ref: ObjRef): Pos {
        val refPos = when (ref) {
            is LocalVarRef -> ref.pos()
            is LocalSlotRef -> ref.pos()
            is QualifiedThisRef -> ref.pos()
            is ImplicitThisMethodCallRef -> ref.pos()
            is StatementRef -> ref.statement.pos
            else -> null
        }
        return refPos ?: callSitePos()
    }

    private fun compileRefWithFallback(ref: ObjRef, forceType: SlotType?, pos: Pos): CompiledValue? {
        setPos(pos)
        var compiled = compileRef(ref)
        if (compiled != null) {
            if (forceType == null) return compiled
            if (compiled.type == forceType) return compiled
            if (forceType == SlotType.BOOL) {
                val converted = when (compiled.type) {
                    SlotType.INT -> {
                        val dst = allocSlot()
                        builder.emit(Opcode.INT_TO_BOOL, compiled.slot, dst)
                        updateSlotType(dst, SlotType.BOOL)
                        CompiledValue(dst, SlotType.BOOL)
                    }
                    SlotType.OBJ -> {
                        val dst = allocSlot()
                        builder.emit(Opcode.OBJ_TO_BOOL, compiled.slot, dst)
                        updateSlotType(dst, SlotType.BOOL)
                        CompiledValue(dst, SlotType.BOOL)
                    }
                    else -> null
                }
                if (converted != null) return converted
            }
            if (compiled.type == SlotType.UNKNOWN) {
                compiled = null
            }
        }
        if (ref is LocalVarRef || ref is LocalSlotRef || ref is FastLocalVarRef) {
            val name = when (ref) {
                is LocalVarRef -> ref.name
                is FastLocalVarRef -> ref.name
                is LocalSlotRef -> ref.name
            }
            val refKind = ref::class.simpleName ?: "LocalRef"
            val loopKeys = loopSlotOverrides.keys.sorted().joinToString(prefix = "[", postfix = "]")
            val localKeys = localSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
            val scopeKeys = scopeSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
            val info = " ref=$refKind loopSlots=$loopKeys localSlots=$localKeys scopeSlots=$scopeKeys"
            throw BytecodeCompileException("Unresolved name '$name'.$info", pos)
        }
        val refInfo = when (ref) {
            is FieldRef -> "FieldRef(${ref.name})"
            else -> ref::class.simpleName ?: "UnknownRef"
        }
        val extra = if (ref is LocalVarRef) {
            val names = scopeSlotNameMap.values.joinToString(prefix = "[", postfix = "]")
            " scopeSlots=$names"
        } else {
            ""
        }
        throw BytecodeCompileException(
            "Bytecode compile error: unsupported expression ($refInfo)$extra",
            pos
        )
    }

    private fun compileScopeSlotObj(ref: ObjRef): CompiledValue? {
        val slot = when (ref) {
            is LocalSlotRef -> resolveSlot(ref)
            is LocalVarRef -> scopeSlotIndexByName[ref.name]
            is FastLocalVarRef -> scopeSlotIndexByName[ref.name]
            else -> null
        } ?: return null
        if (slot >= scopeSlotCount) return null
        val addrSlot = ensureScopeAddr(slot)
        val local = allocSlot()
        emitLoadFromAddr(addrSlot, local, SlotType.OBJ)
        updateSlotType(local, SlotType.OBJ)
        return CompiledValue(local, SlotType.OBJ)
    }

    private fun resolveReceiverClass(ref: ObjRef): ObjClass? {
        return when (ref) {
            is LocalSlotRef -> {
                if (knownObjectNames.contains(ref.name)) {
                    return nameObjClass[ref.name] ?: ObjDynamic.type
                }
                val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId
                val ownerSlot = ref.captureOwnerSlot ?: ref.slot
                val slot = resolveSlot(ref)
                val fromSlot = slot?.let { slotObjClass[it] }
                fromSlot
                    ?: slot?.let { typeDeclForSlot(it) }?.let { resolveClassFromTypeDecl(it) }
                    ?: slotTypeByScopeId[ownerScopeId]?.get(ownerSlot)
                    ?: slotTypeDeclByScopeId[ownerScopeId]?.get(ownerSlot)?.let { resolveClassFromTypeDecl(it) }
                    ?: nameObjClass[ref.name]
                    ?: resolveTypeNameClass(ref.name)
                    ?: slotInitClassByKey[ScopeSlotKey(ownerScopeId, ownerSlot)]
                    ?: run {
                        val match = slotInitClassByKey.entries.firstOrNull { (key, _) ->
                            val name = localSlotInfoMap[key]?.name ?: scopeSlotNameMap[key]
                            name == ref.name
                        }
                        match?.value
                    }
            }
            is LocalVarRef -> {
                if (knownObjectNames.contains(ref.name)) {
                    return nameObjClass[ref.name] ?: ObjDynamic.type
                }
                val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
                if (fromSlot != null) return fromSlot
                val fromDirectTypeDecl = resolveDirectNameSlot(ref.name)
                    ?.let { typeDeclForSlot(it.slot) }
                    ?.let { resolveClassFromTypeDecl(it) }
                if (fromDirectTypeDecl != null) return fromDirectTypeDecl
                val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key
                key?.let {
                    slotTypeByScopeId[it.scopeId]?.get(it.slot)
                        ?: slotTypeDeclByScopeId[it.scopeId]?.get(it.slot)?.let { decl -> resolveClassFromTypeDecl(decl) }
                        ?: slotInitClassByKey[it]
                } ?: nameObjClass[ref.name]
                    ?: resolveTypeNameClass(ref.name)
            }
            is FastLocalVarRef -> {
                if (knownObjectNames.contains(ref.name)) {
                    return nameObjClass[ref.name] ?: ObjDynamic.type
                }
                val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
                if (fromSlot != null) return fromSlot
                val fromDirectTypeDecl = resolveDirectNameSlot(ref.name)
                    ?.let { typeDeclForSlot(it.slot) }
                    ?.let { resolveClassFromTypeDecl(it) }
                if (fromDirectTypeDecl != null) return fromDirectTypeDecl
                val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key
                key?.let {
                    slotTypeByScopeId[it.scopeId]?.get(it.slot)
                        ?: slotTypeDeclByScopeId[it.scopeId]?.get(it.slot)?.let { decl -> resolveClassFromTypeDecl(decl) }
                        ?: slotInitClassByKey[it]
                } ?: nameObjClass[ref.name]
                    ?: resolveTypeNameClass(ref.name)
            }
            is BoundLocalVarRef -> slotObjClass[ref.slotIndex()]
            is QualifiedThisRef -> resolveTypeNameClass(ref.typeName)
            is ListLiteralRef -> ObjList.type
            is MapLiteralRef -> ObjMap.type
            is RangeRef -> ObjRange.type
            is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverClass(it.ref) }
            is ConstRef -> when (ref.constValue) {
                is ObjList -> ObjList.type
                is ObjMap -> ObjMap.type
                is ObjRange -> ObjRange.type
                is ObjString -> ObjString.type
                is ObjRegex -> ObjRegex.type
                is ObjInt -> ObjInt.type
                is ObjReal -> ObjReal.type
                is ObjBool -> ObjBool.type
                is ObjChar -> ObjChar.type
                else -> null
            }
            is UnaryOpRef -> when (ref.op) {
                UnaryOp.NOT -> ObjBool.type
                UnaryOp.POSITIVE -> resolveReceiverClass(ref.a)
                UnaryOp.NEGATE -> when (val operandClass = resolveReceiverClass(ref.a)) {
                    ObjInt.type -> ObjInt.type
                    ObjReal.type -> ObjReal.type
                    else -> inferMethodCallReturnClass(operandClass, "negate")
                }
                UnaryOp.BITNOT -> when (val operandClass = resolveReceiverClass(ref.a)) {
                    ObjInt.type -> ObjInt.type
                    else -> inferMethodCallReturnClass(operandClass, "bitNot")
                }
            }
            is CastRef -> resolveTypeRefClass(ref.castTypeRef())
                ?: resolveReceiverClass(ref.castValueRef())
            is FieldRef -> {
                val targetClass = resolveReceiverClass(ref.target) ?: return null
                inferFieldReturnClass(targetClass, ref.name)
            }
            is MethodCallRef -> {
                val targetClass = resolveReceiverClass(ref.receiver) ?: return null
                if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
                    ObjRegex.type
                } else {
                    inferMethodCallReturnClass(targetClass, ref.name)
                }
            }
            is CallRef -> inferCallReturnClass(ref)
            else -> null
        }
    }

    private fun resolveClassFromTypeDecl(typeDecl: TypeDecl): ObjClass? {
        return when (typeDecl) {
            is TypeDecl.Simple -> {
                resolveTypeNameClass(typeDecl.name) ?: nameObjClass[typeDecl.name]?.let { cls ->
                    if (cls == ObjClassType) ObjDynamic.type else cls
                }
            }
            is TypeDecl.Generic -> {
                resolveTypeNameClass(typeDecl.name) ?: nameObjClass[typeDecl.name]?.let { cls ->
                    if (cls == ObjClassType) ObjDynamic.type else cls
                }
            }
            is TypeDecl.Ellipsis -> resolveClassFromTypeDecl(typeDecl.elementType)
            else -> null
        }
    }

    private fun isKnownClassReceiver(ref: ObjRef): Boolean {
        return when (ref) {
            is LocalVarRef -> {
                val name = ref.name
                val directSlot = resolveDirectNameSlot(name)?.slot
                if (directSlot != null) return isNamedClassBinding(name, directSlot)
                if (nameObjClass[name] == ObjClassType) return true
                if (localSlotIndexByName.containsKey(name)) return false
                if (localSlotInfoMap.values.any { it.name == name }) return false
                isUnboundClassName(name)
            }
            is LocalSlotRef -> {
                val name = ref.name
                if (slotObjClass[ref.slot] != null || exactCallableObjBySlot[ref.slot] != null) {
                    return isNamedClassBinding(name, ref.slot)
                }
                val directSlot = resolveDirectNameSlot(name)?.slot
                if (directSlot != null) return isNamedClassBinding(name, directSlot)
                if (nameObjClass[name] == ObjClassType) return true
                if (localSlotIndexByName.containsKey(name)) return false
                if (localSlotInfoMap.values.any { it.name == name }) return false
                isUnboundClassName(name)
            }
            is FastLocalVarRef -> {
                val name = ref.name
                val directSlot = resolveDirectNameSlot(name)?.slot
                if (directSlot != null) return isNamedClassBinding(name, directSlot)
                if (nameObjClass[name] == ObjClassType) return true
                if (localSlotIndexByName.containsKey(name)) return false
                if (localSlotInfoMap.values.any { it.name == name }) return false
                isUnboundClassName(name)
            }
            else -> false
        }
    }

    private fun shouldUseClassScopeReceiver(
        ref: ObjRef,
        receiver: CompiledValue,
        receiverClass: ObjClass
    ): Boolean {
        val name = when (ref) {
            is LocalVarRef -> ref.name
            is LocalSlotRef -> ref.name
            is FastLocalVarRef -> ref.name
            else -> null
        }
        if (name != null && isKnownObjectBinding(name)) return false
        return isKnownClassReceiver(ref) &&
            (isClassSlot(receiver.slot) || receiverClass == ObjClassType || isClassNameRef(ref, receiverClass))
    }

    private fun isNamedClassBinding(name: String, slot: Int): Boolean {
        if (isClassSlot(slot)) return true
        if (isKnownObjectBinding(name)) return false
        return knownClassNames.contains(name) || classFieldTypesByName.containsKey(name)
    }

    private fun isUnboundClassName(name: String): Boolean {
        if (isKnownObjectBinding(name)) return false
        return knownClassNames.contains(name) || classFieldTypesByName.containsKey(name)
    }

    private fun isKnownObjectBinding(name: String): Boolean {
        return knownObjectNames.contains(name)
    }

    private fun isClassNameRef(ref: ObjRef, receiverClass: ObjClass): Boolean {
        if (receiverClass !is ObjInstanceClass) return false
        val name = when (ref) {
            is LocalVarRef -> ref.name
            is LocalSlotRef -> ref.name
            is FastLocalVarRef -> ref.name
            else -> return false
        }
        return name == receiverClass.className && isKnownClassReceiver(ref)
    }

    private fun isClassSlot(slot: Int): Boolean =
        slotObjClass[slot] == ObjClassType || exactCallableObjBySlot[slot] is ObjClass


    private fun isThisReceiver(ref: ObjRef): Boolean {
        return when (ref) {
            is LocalSlotRef -> ref.name == "this"
            is QualifiedThisRef -> true
            else -> false
        }
    }

    private fun isAllowedObjectMember(memberName: String): Boolean {
        return when (memberName) {
            "toString",
            "toInspectString",
            "let",
            "also",
            "apply",
            "run" -> true
            else -> false
        }
    }

    private fun refSlot(ref: LocalSlotRef): Int = ref.slot
    private fun refScopeId(ref: LocalSlotRef): Int = ref.scopeId

    private fun isLoopVarRef(ref: LocalSlotRef): Boolean {
        return loopVarKeys.contains(ScopeSlotKey(refScopeId(ref), refSlot(ref)))
    }

    private fun isLoopVarSlot(slot: Int): Boolean = loopVarSlots.contains(slot)

    private fun emitLoopVarReassignError(name: String, pos: Pos): Int {
        val msgId = builder.addConst(BytecodeConst.StringVal("can't reassign loop variable $name"))
        val msgSlot = allocSlot()
        builder.emit(Opcode.CONST_OBJ, msgId, msgSlot)
        val posId = builder.addConst(BytecodeConst.PosVal(pos))
        builder.emit(Opcode.THROW, posId, msgSlot)
        return msgSlot
    }

    private fun emitImmutableLocalReassignError(name: String, pos: Pos): Int {
        val msgId = builder.addConst(BytecodeConst.StringVal("can't reassign val $name"))
        val msgSlot = allocSlot()
        builder.emit(Opcode.CONST_OBJ, msgId, msgSlot)
        val posId = builder.addConst(BytecodeConst.PosVal(pos))
        builder.emit(Opcode.THROW, posId, msgSlot)
        return msgSlot
    }

    private fun assignOpMethodName(op: BinOp): String? = when (op) {
        BinOp.PLUS -> "plusAssign"
        BinOp.MINUS -> "minusAssign"
        BinOp.STAR -> "mulAssign"
        BinOp.SLASH -> "divAssign"
        BinOp.PERCENT -> "modAssign"
        else -> null
    }

    private fun knownBuiltinAssignOpSupport(cls: ObjClass, op: BinOp): Boolean? = when (cls) {
        ObjList.type -> op == BinOp.PLUS || op == BinOp.MINUS
        ObjObservableList.type -> op == BinOp.PLUS || op == BinOp.MINUS
        ObjSet.type -> op == BinOp.PLUS
        ObjMap.type -> op == BinOp.PLUS
        ObjRingBuffer.type -> op == BinOp.PLUS
        else -> null
    }

    private fun shouldUseAssignOpForImmutableLocal(slot: Int, targetType: SlotType, op: BinOp): Boolean {
        return when (targetType) {
            SlotType.INT, SlotType.REAL, SlotType.BOOL -> false
            SlotType.OBJ, SlotType.UNKNOWN -> {
                val cls = slotObjClass[slot] ?: return true
                val methodName = assignOpMethodName(op)
                if (methodName != null && cls.getInstanceMemberOrNull(methodName, includeStatic = false) != null) {
                    return true
                }
                knownBuiltinAssignOpSupport(cls, op) ?: true
            }
        }
    }
    private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
    private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
    private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op

    private fun resolveReceiverClassForScopeCollection(ref: ObjRef): ObjClass? {
        return when (ref) {
            is LocalSlotRef -> {
                val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId
                val ownerSlot = ref.captureOwnerSlot ?: ref.slot
                resolveLocalSlotByRefOrName(ref)?.let { slotObjClass[it] }
                    ?: slotTypeByScopeId[ownerScopeId]?.get(ownerSlot)
                    ?: slotInitClassByKey[ScopeSlotKey(ownerScopeId, ownerSlot)]
                    ?: nameObjClass[ref.name]
                    ?: resolveTypeNameClass(ref.name)
            }
            is LocalVarRef -> {
                val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key
                key?.let {
                    slotTypeByScopeId[it.scopeId]?.get(it.slot)
                        ?: slotInitClassByKey[it]
                } ?: nameObjClass[ref.name]
                    ?: resolveTypeNameClass(ref.name)
            }
            is FastLocalVarRef -> {
                val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key
                key?.let {
                    slotTypeByScopeId[it.scopeId]?.get(it.slot)
                        ?: slotInitClassByKey[it]
                } ?: nameObjClass[ref.name]
                    ?: resolveTypeNameClass(ref.name)
            }
            is BoundLocalVarRef -> slotObjClass[ref.slotIndex()]
            is QualifiedThisRef -> resolveTypeNameClass(ref.typeName)
            is ListLiteralRef -> ObjList.type
            is MapLiteralRef -> ObjMap.type
            is RangeRef -> ObjRange.type
            is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverClassForScopeCollection(it.ref) }
            is ConstRef -> when (ref.constValue) {
                is ObjList -> ObjList.type
                is ObjMap -> ObjMap.type
                is ObjRange -> ObjRange.type
                is ObjString -> ObjString.type
                is ObjRegex -> ObjRegex.type
                is ObjInt -> ObjInt.type
                is ObjReal -> ObjReal.type
                is ObjBool -> ObjBool.type
                is ObjChar -> ObjChar.type
                else -> null
            }
            is CastRef -> resolveTypeRefClass(ref.castTypeRef())
                ?: resolveReceiverClassForScopeCollection(ref.castValueRef())
            is FieldRef -> {
                val targetClass = resolveReceiverClassForScopeCollection(ref.target) ?: return null
                inferFieldReturnClass(targetClass, ref.name)
            }
            is MethodCallRef -> {
                val targetClass = resolveReceiverClassForScopeCollection(ref.receiver) ?: return null
                if (targetClass == ObjString.type && ref.name == "re" && ref.args.isEmpty() && !ref.isOptional) {
                    ObjRegex.type
                } else {
                    inferMethodCallReturnClass(targetClass, ref.name)
                }
            }
            is CallRef -> inferCallReturnClass(ref)
            else -> null
        }
    }

    private fun resolveTypeRefClass(ref: ObjRef): ObjClass? {
        return when (ref) {
            is ConstRef -> ref.constValue as? ObjClass
            is TypeDeclRef -> when (val decl = ref.decl()) {
                is TypeDecl.Simple -> {
                    resolveTypeNameClass(decl.name) ?: nameObjClass[decl.name]?.let { cls ->
                        if (cls == ObjClassType) ObjDynamic.type else cls
                    }
                }
                is TypeDecl.Generic -> {
                    resolveTypeNameClass(decl.name) ?: nameObjClass[decl.name]?.let { cls ->
                        if (cls == ObjClassType) ObjDynamic.type else cls
                    }
                }
                else -> null
            }
            is LocalSlotRef -> {
                resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name]?.let { cls ->
                    if (cls == ObjClassType) ObjDynamic.type else cls
                }
            }
            is LocalVarRef -> {
                resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name]?.let { cls ->
                    if (cls == ObjClassType) ObjDynamic.type else cls
                }
            }
            is FastLocalVarRef -> {
                resolveTypeNameClass(ref.name) ?: nameObjClass[ref.name]?.let { cls ->
                    if (cls == ObjClassType) ObjDynamic.type else cls
                }
            }
            is QualifiedThisRef -> resolveTypeNameClass(ref.typeName)
            else -> null
        }
    }

    private fun resolveTypeNameClass(name: String): ObjClass? {
        val shortName = name.substringAfterLast('.')
        return when (shortName) {
            "Object", "Obj" -> Obj.rootObjectType
            "String" -> ObjString.type
            "Int" -> ObjInt.type
            "Real" -> ObjReal.type
            "Bool" -> ObjBool.type
            "Char" -> ObjChar.type
            "List" -> ObjList.type
            "ImmutableList" -> ObjImmutableList.type
            "Map" -> ObjMap.type
            "ImmutableMap" -> ObjImmutableMap.type
            "Set" -> ObjSet.type
            "ImmutableSet" -> ObjImmutableSet.type
            "Range", "IntRange" -> ObjRange.type
            "Iterator" -> ObjIterator
            "Iterable" -> ObjIterable
            "Collection" -> ObjCollection
            "Array" -> ObjArray
            "Deferred" -> ObjDeferred.type
            "CompletableDeferred" -> ObjCompletableDeferred.type
            "Mutex" -> ObjMutex.type
            "Flow" -> ObjFlow.type
            "FlowBuilder" -> ObjFlowBuilder.type
            "Regex" -> ObjRegex.type
            "RegexMatch" -> ObjRegexMatch.type
            "MapEntry" -> ObjMapEntry.type
            "Observable" -> ObjObservable
            "Subscription" -> ObjSubscription.type
            "ListChange" -> ObjListChange.type
            "ListSet" -> ObjListSetChange.type
            "ListInsert" -> ObjListInsertChange.type
            "ListRemove" -> ObjListRemoveChange.type
            "ListClear" -> ObjListClearChange.type
            "ListReorder" -> ObjListReorderChange.type
            "ObservableList" -> ObjObservableList.type
            "ChangeRejectionException" -> ObjChangeRejectionExceptionClass
            "Instant" -> ObjInstant.type
            "Date" -> ObjDate.type
            "DateTime" -> ObjDateTime.type
            "Duration" -> ObjDuration.type
            "Exception" -> ObjException.Root
            "Class" -> ObjClassType
            "Callable" -> Statement.type
            else -> null
        }
    }

    private fun inferCallReturnClass(ref: CallRef): ObjClass? {
        fun exactLambdaReturnClass(slot: Int): ObjClass? =
            exactLambdaRefBySlot[slot]?.inferredReturnClass

        fun callableReturnClassFromSlot(slot: Int): ObjClass? {
            exactLambdaReturnClass(slot)?.let { return it }
            typeDeclForSlot(slot)?.let { decl ->
                val functionDecl = decl as? TypeDecl.Function
                if (functionDecl != null) {
                    resolveClassFromTypeDecl(functionDecl.returnType)?.let { return it }
                }
            }
            return null
        }

        fun callableResultClassOrNull(
            directReturnClass: ObjClass?,
            directTypeDecl: TypeDecl?,
            nameClass: ObjClass?,
            typeNameFallback: String?
        ): ObjClass? {
            if (directReturnClass != null) return directReturnClass
            if (directTypeDecl is TypeDecl.Function) {
                return null
            }
            if (nameClass == ObjClassType) {
                return typeNameFallback?.let { resolveTypeNameClass(it) } ?: ObjDynamic.type
            }
            if (nameClass == Statement.type) {
                return null
            }
            return nameClass ?: typeNameFallback?.let { resolveTypeNameClass(it) }
        }

        return when (val target = ref.target) {
            is LocalSlotRef -> {
                val mappedSlot = resolveLocalSlotByRefOrName(target)
                callableResultClassOrNull(
                    directReturnClass = mappedSlot?.let { callableReturnClassFromSlot(it) }
                        ?: exactLambdaRefByScopeId[target.scopeId]?.get(target.slot)?.inferredReturnClass
                        ?: callableReturnTypeByScopeId[target.scopeId]?.get(target.slot),
                    directTypeDecl = mappedSlot?.let { typeDeclForSlot(it) }
                        ?: slotTypeDeclByScopeId[target.scopeId]?.get(target.slot),
                    nameClass = nameObjClass[target.name],
                    typeNameFallback = target.name
                )
            }
            is LocalVarRef -> {
                val directSlot = resolveDirectNameSlot(target.name)?.slot
                callableResultClassOrNull(
                    directReturnClass = directSlot?.let { callableReturnClassFromSlot(it) }
                        ?: callableReturnTypeByName[target.name],
                    directTypeDecl = directSlot?.let { typeDeclForSlot(it) },
                    nameClass = nameObjClass[target.name],
                    typeNameFallback = target.name
                )
            }
            is FastLocalVarRef -> {
                val directSlot = resolveDirectNameSlot(target.name)?.slot
                callableResultClassOrNull(
                    directReturnClass = directSlot?.let { callableReturnClassFromSlot(it) }
                        ?: callableReturnTypeByName[target.name],
                    directTypeDecl = directSlot?.let { typeDeclForSlot(it) },
                    nameClass = nameObjClass[target.name],
                    typeNameFallback = target.name
                )
            }
            is BoundLocalVarRef -> callableReturnClassFromSlot(target.slotIndex())
            is ConstRef -> target.constValue as? ObjClass
            else -> null
        }
    }

    private fun inferMethodCallReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
        when (targetClass) {
            ObjDate.type -> {
                return when (name) {
                    "toIsoString", "toSortableString", "toString" -> ObjString.type
                    "toDateTime", "atStartOfDay" -> ObjDateTime.type
                    "addDays", "addMonths", "addYears", "today", "parseIso" -> ObjDate.type
                    "daysUntil", "daysSince" -> ObjInt.type
                    else -> inferMethodCallReturnClass(name)
                }
            }
            ObjInstant.type -> {
                return when (name) {
                    "toDateTime" -> ObjDateTime.type
                    "toDate" -> ObjDate.type
                    else -> inferMethodCallReturnClass(name)
                }
            }
            ObjDateTime.type -> {
                return when (name) {
                    "toDate" -> ObjDate.type
                    "toInstant" -> ObjInstant.type
                    "toUTC", "toTimeZone", "parseRFC3339", "addYears", "addMonths" -> ObjDateTime.type
                    else -> inferMethodCallReturnClass(name)
                }
            }
            else -> return inferMethodCallReturnClass(name)
        }
    }

    private fun inferMethodCallReturnClass(name: String): ObjClass? = when (name) {
        "map",
        "mapNotNull",
        "filter",
        "filterNotNull",
        "drop",
        "take",
        "flatMap",
        "flatten",
        "sorted",
        "sortedBy",
        "sortedWith",
        "reversed",
        "toList",
        "shuffle",
        "shuffled" -> ObjList.type
        "dropLast" -> ObjFlow.type
        "takeLast" -> ObjRingBuffer.type
        "iterator" -> ObjIterator
        "count" -> ObjInt.type
        "toSet" -> ObjSet.type
        "toImmutableSet" -> ObjImmutableSet.type
        "toMap" -> ObjMap.type
        "toImmutableMap" -> ObjImmutableMap.type
        "observable" -> ObjObservableList.type
        "beforeChange", "onChange" -> ObjSubscription.type
        "changes" -> ObjFlow.type
        "joinToString" -> ObjString.type
        "now",
        "truncateToSecond",
        "truncateToMinute",
        "truncateToMillisecond" -> ObjInstant.type
        "today",
        "parseIso" -> ObjDate.type
        "daysUntil",
        "daysSince" -> ObjInt.type
        "toTimeZone",
        "toUTC",
        "parseRFC3339",
        "addHours",
        "addMinutes",
        "addSeconds" -> ObjDateTime.type
        "toInstant" -> ObjInstant.type
        "toRFC3339",
        "toSortableString",
        "toJsonString",
        "decodeUtf8",
        "toDump",
        "toString" -> ObjString.type
        "startsWith",
        "matches" -> ObjBool.type
        "toInt",
        "toEpochSeconds" -> ObjInt.type
        "toImmutableList" -> ObjImmutableList.type
        "toImmutable" -> Obj.rootObjectType
        "toMutable" -> ObjMutableBuffer.type
        "seq" -> ObjFlow.type
        "encode" -> ObjBitBuffer.type
        "assertThrows" -> ObjException.Root
        else -> null
    }

    private fun inferFieldReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
        if (targetClass == null) return null
        if (targetClass == ObjDynamic.type) return ObjDynamic.type
        classFieldTypesByName[targetClass.className]?.get(name)?.let { cls ->
            if (isDelegateClass(cls)) {
                return null
            }
            return cls
        }
        enumEntriesByName[targetClass.className]?.let { entries ->
            return when {
                name == "entries" -> ObjList.type
                name == "name" -> ObjString.type
                name == "ordinal" -> ObjInt.type
                entries.contains(name) -> targetClass
                else -> null
            }
        }
        if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) {
            return ObjInstant.type
        }
        if (targetClass == ObjDate.type) {
            return when (name) {
                "year",
                "month",
                "day",
                "dayOfMonth",
                "dayOfWeek",
                "dayOfYear",
                "lengthOfMonth",
                "lengthOfYear" -> ObjInt.type
                "isLeapYear" -> ObjBool.type
                else -> null
            }
        }
        if (targetClass == ObjString.type && name == "re") {
            return ObjRegex.type
        }
        if (targetClass == ObjInt.type || targetClass == ObjReal.type) {
            return when (name) {
                "day",
                "days",
                "hour",
                "hours",
                "minute",
                "minutes",
                "second",
                "seconds",
                "millisecond",
                "milliseconds",
                "microsecond",
                "microseconds" -> ObjDuration.type
                else -> null
            }
        }
        if (targetClass == ObjDuration.type) {
            return when (name) {
                "days",
                "hours",
                "minutes",
                "seconds",
                "milliseconds",
                "microseconds" -> ObjReal.type
                else -> null
            }
        }
        if (targetClass == ObjInstant.type) {
            return when (name) {
                "epochSeconds",
                "epochWholeSeconds" -> ObjInt.type
                "truncateToSecond",
                "truncateToMinute",
                "truncateToMillisecond" -> ObjInstant.type
                else -> null
            }
        }
        if (targetClass == ObjDateTime.type) {
            return when (name) {
                "date" -> ObjDate.type
                "year",
                "month",
                "day",
                "hour",
                "minute",
                "second",
                "dayOfWeek",
                "nanosecond" -> ObjInt.type
                "timeZone" -> ObjString.type
                else -> null
            }
        }
        if (targetClass == ObjException.Root || targetClass.allParentsSet.contains(ObjException.Root)) {
            return when (name) {
                "message" -> ObjString.type
                "stackTrace" -> ObjList.type
                else -> null
            }
        }
        if (targetClass == ObjRegex.type && name == "pattern") {
            return ObjString.type
        }
        return null
    }

    private fun queueExtensionCallableNames(receiverClass: ObjClass, memberName: String) {
        if (!useScopeSlots && globalSlotInfo.isEmpty()) return
        for (receiverName in extensionReceiverTypeNames(receiverClass)) {
            val name = extensionCallableName(receiverName, memberName)
            if (allowedScopeNames == null || allowedScopeNames.contains(name)) {
                pendingScopeNameRefs.add(name)
            }
        }
    }

    private fun queueExtensionPropertyNames(receiverClass: ObjClass, memberName: String) {
        if (!useScopeSlots && globalSlotInfo.isEmpty()) return
        for (receiverName in extensionReceiverTypeNames(receiverClass)) {
            val getter = extensionPropertyGetterName(receiverName, memberName)
            if (allowedScopeNames == null || allowedScopeNames.contains(getter)) {
                pendingScopeNameRefs.add(getter)
            }
            val setter = extensionPropertySetterName(receiverName, memberName)
            if (allowedScopeNames == null || allowedScopeNames.contains(setter)) {
                pendingScopeNameRefs.add(setter)
            }
        }
    }
    private fun unaryOperand(ref: UnaryOpRef): ObjRef = ref.a
    private fun unaryOp(ref: UnaryOpRef): UnaryOp = ref.op
    private fun assignTarget(ref: AssignRef): LocalSlotRef? = ref.target as? LocalSlotRef
    private fun assignValue(ref: AssignRef): ObjRef = ref.value
    private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn

    private fun noteScopeSlotRef(slot: Int, pos: Pos) {
        if (slot >= scopeSlotCount) return
        val key = scopeKeyByIndex.getOrNull(slot) ?: return
        if (!scopeSlotRefPosByKey.containsKey(key)) {
            scopeSlotRefPosByKey[key] = pos
        }
    }

    private fun resolveSlot(ref: LocalSlotRef): Int? {
        loopSlotOverrides[ref.name]?.let { return it }
        val scopeId = refScopeId(ref)
        if (!ref.isDelegated && isModuleSlot(scopeId, ref.name)) {
            val key = ScopeSlotKey(scopeId, refSlot(ref))
            val localIndex = localSlotIndexByKey[key]
            if (localIndex != null) return scopeSlotCount + localIndex
            scopeSlotMap[key]?.let {
                if (!scopeSlotRefPosByKey.containsKey(key)) {
                    scopeSlotRefPosByKey[key] = ref.pos()
                }
                return it
            }
            scopeSlotIndexByName[ref.name]?.let {
                if (!scopeSlotRefPosByKey.containsKey(key)) {
                    scopeSlotRefPosByKey[key] = ref.pos()
                }
                return it
            }
        }
        if (ref.captureOwnerScopeId != null) {
            val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
            val localIndex = localSlotIndexByKey[scopeKey]
            if (localIndex != null) {
                if (localSlotCaptures.getOrNull(localIndex) == true) {
                    return scopeSlotCount + localIndex
                }
            }
            val resolved = scopeSlotMap[scopeKey]
            if (resolved != null) {
                if (!scopeSlotRefPosByKey.containsKey(scopeKey)) {
                    scopeSlotRefPosByKey[scopeKey] = ref.pos()
                }
            }
            if (resolved != null) return resolved
            resolveCapturedOwnerSlot(ref)?.let { return it }
            return null
        }
        if (ref.isDelegated) {
            val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
            val localIndex = localSlotIndexByKey[localKey]
            if (localIndex != null) return scopeSlotCount + localIndex
        }
        val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
        val localIndex = localSlotIndexByKey[localKey]
        if (localIndex != null) return scopeSlotCount + localIndex
        val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
        val resolved = scopeSlotMap[scopeKey]
        if (resolved != null) {
            if (!scopeSlotRefPosByKey.containsKey(scopeKey)) {
                scopeSlotRefPosByKey[scopeKey] = ref.pos()
            }
        }
        if (resolved != null) return resolved
        localSlotIndexByName[ref.name]?.let { return scopeSlotCount + it }
        return null
    }

    private fun resolveLocalSlotByRefOrName(ref: LocalSlotRef): Int? {
        return resolveSlot(ref)
            ?: ref.name.takeIf { it.isNotEmpty() }?.let { name -> resolveDirectNameSlot(name)?.slot }
    }

    private fun resolveCapturedOwnerScopeSlot(ref: LocalSlotRef): Int? {
        val ownerScopeId = ref.captureOwnerScopeId ?: return null
        val ownerSlot = ref.captureOwnerSlot ?: return null
        val key = ScopeSlotKey(ownerScopeId, ownerSlot)
        return scopeSlotMap[key]
    }

    private fun resolveCapturedOwnerSlot(ref: LocalSlotRef): Int? {
        val ownerScopeId = ref.captureOwnerScopeId ?: return null
        val ownerSlot = ref.captureOwnerSlot ?: return null
        val key = ScopeSlotKey(ownerScopeId, ownerSlot)
        localSlotIndexByKey[key]?.let { return scopeSlotCount + it }
        return scopeSlotMap[key]
    }

    private fun updateSlotType(slot: Int, type: SlotType) {
        if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
        if (type == SlotType.UNKNOWN) {
            slotTypes.remove(slot)
            listElementClassBySlot.remove(slot)
        } else {
            slotTypes[slot] = type
            if (type != SlotType.OBJ) {
                listElementClassBySlot.remove(slot)
            }
        }
    }

    private fun slotTypeFromClass(cls: ObjClass?): SlotType? {
        return when (cls) {
            ObjInt.type -> SlotType.INT
            ObjReal.type -> SlotType.REAL
            ObjBool.type -> SlotType.BOOL
            else -> null
        }
    }

    private fun elementClassFromValue(value: CompiledValue): ObjClass? {
        val cls = when (value.type) {
            SlotType.INT -> ObjInt.type
            SlotType.REAL -> ObjReal.type
            SlotType.BOOL -> ObjBool.type
            SlotType.OBJ, SlotType.UNKNOWN -> slotObjClass[value.slot]
        }
        return when (cls) {
            ObjInt.type, ObjReal.type, ObjString.type, ObjBool.type -> cls
            else -> null
        }
    }

    private fun elementClassFromConst(value: Obj): ObjClass? {
        return when (value) {
            is ObjInt -> ObjInt.type
            is ObjReal -> ObjReal.type
            is ObjString -> ObjString.type
            is ObjBool -> ObjBool.type
            else -> null
        }
    }

    private fun listElementClassFromDecl(decl: TypeDecl): ObjClass? {
        return when (decl) {
            is TypeDecl.Generic -> {
                if (decl.name != "List" || decl.args.size != 1) return null
                val arg = decl.args.first()
                val cls = when (arg) {
                    is TypeDecl.Simple -> resolveTypeNameClass(arg.name)
                    is TypeDecl.Generic -> resolveTypeNameClass(arg.name)
                    else -> null
                }
                when (cls) {
                    ObjInt.type, ObjReal.type, ObjString.type, ObjBool.type -> cls
                    else -> null
                }
            }
            is TypeDecl.Simple -> when (decl.name.substringAfterLast('.')) {
                "Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
                "String" -> ObjChar.type
                else -> null
            }
            else -> null
        }
    }

    private fun listElementClassFromReceiverRef(ref: ObjRef): ObjClass? {
        return when (ref) {
            is LocalSlotRef -> {
                val scopeId = refScopeId(ref)
                val slot = refSlot(ref)
                val decl = slotTypeDeclByScopeId[scopeId]?.get(slot) ?: return null
                listElementClassFromDecl(decl)
            }
            is ImplicitThisMemberRef -> {
                val ownerClass = ref.preferredThisTypeName()?.let { resolveTypeNameClass(it) } ?: return null
                val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null
                when (fieldClass.className) {
                    "Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
                    "String" -> ObjChar.type
                    else -> null
                }
            }
            is ThisFieldSlotRef -> {
                val ownerClass = currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) } ?: return null
                val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null
                when (fieldClass.className) {
                    "Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
                    "String" -> ObjChar.type
                    else -> null
                }
            }
            is QualifiedThisFieldSlotRef -> {
                val ownerClass = resolveTypeNameClass(ref.receiverTypeName()) ?: return null
                val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null
                when (fieldClass.className) {
                    "Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
                    "String" -> ObjChar.type
                    else -> null
                }
            }
            is FieldRef -> {
                val fieldClass = resolveReceiverClass(ref) ?: return null
                when (fieldClass.className) {
                    "Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
                    "String" -> ObjChar.type
                    else -> listElementClassFromDecl(TypeDecl.Simple(fieldClass.className, false))
                }
            }
            else -> {
                val receiverClass = resolveReceiverClass(ref) ?: return null
                when (receiverClass.className) {
                    "Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
                    "String" -> ObjChar.type
                    else -> null
                }
            }
        }
    }

    private fun annotateIndexedReceiverSlot(slot: Int, receiverClass: ObjClass?) {
        if (receiverClass == null) return
        slotObjClass[slot] = receiverClass
        when (receiverClass.className) {
            "Buffer", "MutableBuffer", "BitBuffer" -> listElementClassBySlot[slot] = ObjInt.type
            "String" -> listElementClassBySlot[slot] = ObjChar.type
        }
    }

    private fun indexElementClass(receiverSlot: Int, targetRef: ObjRef): ObjClass? {
        listElementClassBySlot[receiverSlot]?.let { return it }
        listElementClassFromReceiverRef(targetRef)?.let { return it }
        val receiverClass = resolveReceiverClass(targetRef) ?: return null
        return inferFieldReturnClass(receiverClass, "getAt")
    }

    private fun indexElementSlotType(receiverSlot: Int, targetRef: ObjRef): SlotType? =
        slotTypeFromClass(indexElementClass(receiverSlot, targetRef))

    private fun prepareCompilation(stmt: Statement) {
        builder = CmdBuilder()
        nextSlot = 0
        nextAddrSlot = 0
        slotTypes.clear()
        slotObjClass.clear()
        nameObjClass.clear()
        listElementClassBySlot.clear()
        if (knownNameObjClass.isNotEmpty()) {
            nameObjClass.putAll(knownNameObjClass)
        }
        slotInitClassByKey.clear()
        scopeSlotMap.clear()
        scopeSlotNameMap.clear()
        scopeSlotMutableMap.clear()
        localSlotInfoMap.clear()
        localSlotIndexByKey.clear()
        localSlotIndexByName.clear()
        captureSlotKeys.clear()
        forcedObjSlots.clear()
        loopSlotOverrides.clear()
        scopeSlotIndexByName.clear()
        pendingScopeNameRefs.clear()
        scopeKeyByIndex = emptyArray()
        localSlotNames = emptyArray()
        localSlotMutables = BooleanArray(0)
        localSlotDelegated = BooleanArray(0)
        localSlotCaptures = BooleanArray(0)
        localSlotKeyByIndex = emptyArray()
        declaredLocalKeys.clear()
        localRangeRefs.clear()
        intLoopVarNames.clear()
        loopVarKeys.clear()
        loopVarSlots.clear()
        valueFnRefs.clear()
        exactLambdaRefBySlot.clear()
        exactCallableObjBySlot.clear()
        activeInlineLambdas.clear()
        addrSlotByScopeSlot.clear()
        loopStack.clear()
        if (slotTypeByScopeId.isNotEmpty()) {
            for ((scopeId, slots) in slotTypeByScopeId) {
                for ((slotIndex, cls) in slots) {
                    slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
                }
            }
        }
        if (slotTypeDeclByScopeId.isNotEmpty()) {
            for ((scopeId, slots) in slotTypeDeclByScopeId) {
                for ((slotIndex, decl) in slots) {
                    if (decl.isNullable) continue
                    val key = ScopeSlotKey(scopeId, slotIndex)
                    if (slotInitClassByKey.containsKey(key)) continue
                    val cls = when (decl) {
                        is TypeDecl.Simple -> resolveTypeNameClass(decl.name)
                        is TypeDecl.Generic -> resolveTypeNameClass(decl.name)
                        else -> null
                    }
                    when (cls) {
                        ObjInt.type, ObjReal.type, ObjString.type, ObjBool.type -> {
                            slotInitClassByKey[key] = cls
                        }
                    }
                }
            }
        }
        if (allowLocalSlots) {
            collectLoopVarNames(stmt)
        }
        collectScopeSlots(stmt)
        collectExactLambdaModuleCaptures()
        if (allowLocalSlots) {
            collectLoopSlotPlans(stmt, 0)
        }
        if (allowLocalSlots && forcedLocalSlots.isNotEmpty() && forcedLocalScopeId != null) {
            for ((name, slotIndex) in forcedLocalSlots) {
                val key = ScopeSlotKey(forcedLocalScopeId, slotIndex)
                if (!localSlotInfoMap.containsKey(key)) {
                    localSlotInfoMap[key] = LocalSlotInfo(name, isMutable = false, isDelegated = false)
                }
            }
        }
        if (allowLocalSlots && forcedLocalSlotInfo.isNotEmpty() && forcedLocalScopeId != null) {
            for ((name, info) in forcedLocalSlotInfo) {
                val key = ScopeSlotKey(forcedLocalScopeId, info.index)
                if (!localSlotInfoMap.containsKey(key)) {
                    localSlotInfoMap[key] = LocalSlotInfo(name, info.isMutable, info.isDelegated)
                }
            }
        }
        if (allowLocalSlots && valueFnRefs.isNotEmpty() && lambdaCaptureEntriesByRef.isNotEmpty()) {
            for (ref in valueFnRefs) {
                val entries = lambdaCaptureEntriesByRef[ref] ?: continue
                for (entry in entries) {
                    if (entry.ownerKind == CaptureOwnerFrameKind.LOCAL) {
                        val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
                        if (!localSlotInfoMap.containsKey(key)) {
                            localSlotInfoMap[key] = LocalSlotInfo(
                                entry.ownerName,
                                entry.ownerIsMutable,
                                entry.ownerIsDelegated
                            )
                        }
                        continue
                    }
                    if (entry.ownerKind == CaptureOwnerFrameKind.MODULE) {
                        val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
                        if (!scopeSlotMap.containsKey(key)) {
                            scopeSlotMap[key] = scopeSlotMap.size
                        }
                        if (!scopeSlotNameMap.containsKey(key)) {
                            scopeSlotNameMap[key] = entry.ownerName
                        }
                        if (!scopeSlotMutableMap.containsKey(key)) {
                            scopeSlotMutableMap[key] = entry.ownerIsMutable
                        }
                    }
                }
            }
        }
        if (pendingScopeNameRefs.isNotEmpty()) {
            if (useScopeSlots) {
                val existingNames = HashSet<String>(scopeSlotNameMap.values)
                var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1
                for (name in pendingScopeNameRefs) {
                    if (!existingNames.add(name)) continue
                    maxSlotIndex += 1
                    val key = ScopeSlotKey(0, maxSlotIndex)
                    scopeSlotMap[key] = scopeSlotMap.size
                    scopeSlotNameMap[key] = name
                }
            } else if (globalSlotInfo.isNotEmpty() && globalSlotScopeId != null) {
                for (name in pendingScopeNameRefs) {
                    val info = globalSlotInfo[name] ?: continue
                    val key = ScopeSlotKey(globalSlotScopeId, info.index)
                    if (!localSlotInfoMap.containsKey(key)) {
                        localSlotInfoMap[key] = LocalSlotInfo(name, info.isMutable, info.isDelegated)
                    }
                    captureSlotKeys.add(key)
                }
            }
        }
        if (slotInitClassByKey.isNotEmpty() && scopeSlotMap.isNotEmpty()) {
            for ((key, index) in scopeSlotMap) {
                val type = slotTypeFromClass(slotInitClassByKey[key]) ?: continue
                slotTypes[index] = type
            }
        }
        scopeSlotCount = scopeSlotMap.size
        scopeSlotIndices = IntArray(scopeSlotCount)
        scopeSlotNames = arrayOfNulls(scopeSlotCount)
        scopeSlotIsModule = BooleanArray(scopeSlotCount)
        scopeSlotRequiresPreparedBinding = BooleanArray(scopeSlotCount)
        scopeSlotRefPos = arrayOfNulls(scopeSlotCount)
        scopeSlotMutables = BooleanArray(scopeSlotCount) { true }
        scopeKeyByIndex = arrayOfNulls(scopeSlotCount)
        for ((key, index) in scopeSlotMap) {
            val name = scopeSlotNameMap[key]
            scopeSlotIndices[index] = key.slot
            scopeSlotNames[index] = name
            scopeSlotIsModule[index] = moduleScopeId != null && key.scopeId == moduleScopeId
            scopeSlotRequiresPreparedBinding[index] = name != null && preparedModuleBindingNames.contains(name)
            scopeSlotRefPos[index] = name?.let { scopeRefPosByName[it] } ?: scopeSlotRefPosByKey[key]
            scopeSlotMutableMap[key]?.let { scopeSlotMutables[index] = it }
            scopeKeyByIndex[index] = key
        }
        if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) {
            val moduleId = moduleScopeId
            if (moduleId != null) {
                val moduleEntries = ArrayList<Map.Entry<ScopeSlotKey, LocalSlotInfo>>()
                val nonModuleEntries = ArrayList<Map.Entry<ScopeSlotKey, LocalSlotInfo>>()
                for (entry in localSlotInfoMap.entries) {
                    if (entry.key.scopeId == moduleId) {
                        moduleEntries.add(entry)
                    } else {
                        nonModuleEntries.add(entry)
                    }
                }
                if (moduleEntries.isNotEmpty()) {
                    val maxModuleIndex = moduleEntries.maxOf { it.key.slot }
                    val size = maxModuleIndex + 1 + nonModuleEntries.size
                    val names = arrayOfNulls<String>(size)
                    val mutables = BooleanArray(size)
                    val delegated = BooleanArray(size)
                    val used = BooleanArray(size)
                    for (entry in moduleEntries) {
                        val idx = entry.key.slot
                        localSlotIndexByKey[entry.key] = idx
                        if (!localSlotIndexByName.containsKey(entry.value.name)) {
                            localSlotIndexByName[entry.value.name] = idx
                        }
                        names[idx] = entry.value.name
                        mutables[idx] = entry.value.isMutable
                        delegated[idx] = entry.value.isDelegated
                        used[idx] = true
                    }
                    var next = maxModuleIndex + 1
                    for (entry in nonModuleEntries) {
                        while (next < size && used[next]) {
                            next += 1
                        }
                        val idx = next
                        localSlotIndexByKey[entry.key] = idx
                        if (!localSlotIndexByName.containsKey(entry.value.name)) {
                            localSlotIndexByName[entry.value.name] = idx
                        }
                        names[idx] = entry.value.name
                        mutables[idx] = entry.value.isMutable
                        delegated[idx] = entry.value.isDelegated
                        used[idx] = true
                        next += 1
                    }
                    localSlotNames = names
                    localSlotMutables = mutables
                    localSlotDelegated = delegated
                } else {
                    val names = ArrayList<String?>(localSlotInfoMap.size)
                    val mutables = BooleanArray(localSlotInfoMap.size)
                    val delegated = BooleanArray(localSlotInfoMap.size)
                    var index = 0
                    for ((key, info) in localSlotInfoMap) {
                        localSlotIndexByKey[key] = index
                        if (!localSlotIndexByName.containsKey(info.name)) {
                            localSlotIndexByName[info.name] = index
                        }
                        names.add(info.name)
                        mutables[index] = info.isMutable
                        delegated[index] = info.isDelegated
                        index += 1
                    }
                    localSlotNames = names.toTypedArray()
                    localSlotMutables = mutables
                    localSlotDelegated = delegated
                }
            } else {
                val names = ArrayList<String?>(localSlotInfoMap.size)
                val mutables = BooleanArray(localSlotInfoMap.size)
                val delegated = BooleanArray(localSlotInfoMap.size)
                var index = 0
                for ((key, info) in localSlotInfoMap) {
                    localSlotIndexByKey[key] = index
                    if (!localSlotIndexByName.containsKey(info.name)) {
                        localSlotIndexByName[info.name] = index
                    }
                    names.add(info.name)
                    mutables[index] = info.isMutable
                    delegated[index] = info.isDelegated
                    index += 1
                }
                localSlotNames = names.toTypedArray()
                localSlotMutables = mutables
                localSlotDelegated = delegated
            }
        }
        if (allowLocalSlots) {
            localSlotKeyByIndex = arrayOfNulls(localSlotNames.size)
            for ((key, index) in localSlotIndexByKey) {
                if (index in localSlotKeyByIndex.indices) {
                    localSlotKeyByIndex[index] = key
                }
            }
        }
        preloadExactLambdaRefs()
        if (allowLocalSlots && captureSlotKeys.isNotEmpty() && slotInitClassByKey.isNotEmpty()) {
            val scopeSlotsBase = scopeSlotMap.size
            for (key in captureSlotKeys) {
                val localIndex = localSlotIndexByKey[key] ?: continue
                val type = slotTypeFromClass(slotInitClassByKey[key]) ?: continue
                slotTypes[scopeSlotsBase + localIndex] = type
            }
        }
        localSlotCaptures = BooleanArray(localSlotNames.size)
        if (captureSlotKeys.isNotEmpty()) {
            for (key in captureSlotKeys) {
                val localIndex = localSlotIndexByKey[key] ?: continue
                val slot = scopeSlotCount + localIndex
                localSlotCaptures[localIndex] = true
                if (!slotTypes.containsKey(slot)) {
                    forcedObjSlots.add(slot)
                    slotTypes[slot] = SlotType.OBJ
                }
            }
        }
        if (allowLocalSlots && valueFnRefs.isNotEmpty() && lambdaCaptureEntriesByRef.isNotEmpty()) {
            val declaredLocalNames = run {
                val names = LinkedHashSet<String>()
                if (declaredLocalKeys.isNotEmpty()) {
                    for (key in declaredLocalKeys) {
                        localSlotInfoMap[key]?.name?.let { names.add(it) }
                    }
                }
                if (forcedLocalSlots.isNotEmpty()) {
                    for (name in forcedLocalSlots.keys) {
                        names.add(name)
                    }
                }
                names
            }
            for (ref in valueFnRefs) {
                val entries = lambdaCaptureEntriesByRef[ref] ?: continue
                for (entry in entries) {
                    if (entry.ownerKind != CaptureOwnerFrameKind.LOCAL) continue
                    val name = entry.ownerName
                    if (name.isEmpty()) continue
                    if (declaredLocalNames.contains(name)) continue
                    val localIndex = localSlotIndexByName[name] ?: continue
                    val slot = scopeSlotCount + localIndex
                    localSlotCaptures[localIndex] = true
                    if (!slotTypes.containsKey(slot)) {
                        forcedObjSlots.add(slot)
                        slotTypes[slot] = SlotType.OBJ
                    }
                }
            }
        }
        for (i in localSlotNames.indices) {
            if (localSlotCaptures.getOrNull(i) != true) continue
            val name = localSlotNames[i] ?: continue
            localSlotIndexByName[name] = i
        }
        if (scopeSlotCount > 0) {
            for ((key, index) in scopeSlotMap) {
                val name = scopeSlotNameMap[key] ?: continue
                if (!scopeSlotIndexByName.containsKey(name)) {
                    scopeSlotIndexByName[name] = index
                }
            }
        }
        if (slotInitClassByKey.isNotEmpty()) {
            for ((key, cls) in slotInitClassByKey) {
                val name = localSlotInfoMap[key]?.name ?: scopeSlotNameMap[key]
                if (name != null) {
                    nameObjClass[name] = cls
                }
                val localIndex = localSlotIndexByKey[key]
                val slot = when {
                    localIndex != null -> scopeSlotCount + localIndex
                    else -> scopeSlotMap[key]
                }
                if (slot != null) {
                    slotObjClass[slot] = cls
                }
            }
        }
        preloadExactCallableNames()
        if (loopVarKeys.isNotEmpty()) {
            for (key in loopVarKeys) {
                val localIndex = localSlotIndexByKey[key]
                if (localIndex != null) {
                    loopVarSlots.add(scopeSlotCount + localIndex)
                    continue
                }
                scopeSlotMap[key]?.let { loopVarSlots.add(it) }
            }
        }
        nextSlot = scopeSlotCount + localSlotNames.size
    }

    private fun collectScopeSlots(stmt: Statement) {
        if (stmt is BytecodeStatement) {
            collectScopeSlots(stmt.original)
            return
        }
        when (stmt) {
            is ExpressionStatement -> collectScopeSlotsRef(stmt.ref)
            is BlockStatement -> {
                for (child in stmt.statements()) {
                    collectScopeSlots(child)
                }
            }
            is net.sergeych.lyng.InlineBlockStatement -> {
                for (child in stmt.statements()) {
                    collectScopeSlots(child)
                }
            }
            is VarDeclStatement -> {
                val slotIndex = stmt.slotIndex
                val scopeId = stmt.scopeId ?: 0
                val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer)
                if (cls != null) {
                    nameObjClass[stmt.name] = cls
                    if (slotIndex != null) {
                        slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
                    }
                }
                if (allowLocalSlots && slotIndex != null && (stmt.actualExtern || !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = false))) {
                    val key = ScopeSlotKey(scopeId, slotIndex)
                    declaredLocalKeys.add(key)
                    if (!localSlotInfoMap.containsKey(key)) {
                        localSlotInfoMap[key] = LocalSlotInfo(stmt.name, stmt.isMutable, isDelegated = false)
                    }
                    if (!stmt.isMutable) {
                        extractDeclaredRange(stmt.initializer)?.let { range ->
                            localRangeRefs[key] = range
                        }
                    }
                } else if (slotIndex != null) {
                    val key = ScopeSlotKey(scopeId, slotIndex)
                    if (!scopeSlotMap.containsKey(key)) {
                        scopeSlotMap[key] = scopeSlotMap.size
                    }
                    if (!scopeSlotNameMap.containsKey(key)) {
                        scopeSlotNameMap[key] = stmt.name
                    }
                }
                stmt.initializer?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.FunctionDeclStatement -> {
                val slotIndex = stmt.spec.slotIndex
                val scopeId = stmt.spec.scopeId ?: 0
                if (slotIndex != null) {
                    val key = ScopeSlotKey(scopeId, slotIndex)
                    if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId, stmt.spec.name, isDelegated = false)) {
                        if (!localSlotInfoMap.containsKey(key)) {
                            localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
                        }
                    } else {
                        if (!scopeSlotMap.containsKey(key)) {
                            scopeSlotMap[key] = scopeSlotMap.size
                        }
                        if (!scopeSlotNameMap.containsKey(key)) {
                            scopeSlotNameMap[key] = stmt.spec.name
                        }
                    }
                }
            }
            is DelegatedVarDeclStatement -> {
                val slotIndex = stmt.slotIndex
                val scopeId = stmt.scopeId ?: 0
                if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = true)) {
                    val key = ScopeSlotKey(scopeId, slotIndex)
                    declaredLocalKeys.add(key)
                    if (!localSlotInfoMap.containsKey(key)) {
                        localSlotInfoMap[key] = LocalSlotInfo(stmt.name, stmt.isMutable, isDelegated = true)
                    }
                } else if (slotIndex != null) {
                    val key = ScopeSlotKey(scopeId, slotIndex)
                    if (!scopeSlotMap.containsKey(key)) {
                        scopeSlotMap[key] = scopeSlotMap.size
                    }
                    if (!scopeSlotNameMap.containsKey(key)) {
                        scopeSlotNameMap[key] = stmt.name
                    }
                }
                collectScopeSlots(stmt.initializer)
            }
            is IfStatement -> {
                collectScopeSlots(stmt.condition)
                collectScopeSlots(stmt.ifBody)
                stmt.elseBody?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.ForInStatement -> {
                if (allowLocalSlots) {
                    val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
                    if (loopSlotIndex != null && !shouldUseScopeSlotFor(stmt.loopScopeId)) {
                        val key = ScopeSlotKey(stmt.loopScopeId, loopSlotIndex)
                        declaredLocalKeys.add(key)
                        if (!localSlotInfoMap.containsKey(key)) {
                            localSlotInfoMap[key] = LocalSlotInfo(stmt.loopVarName, isMutable = true, isDelegated = false)
                        }
                    }
                }
                collectScopeSlots(stmt.source)
                collectScopeSlots(stmt.body)
                stmt.elseStatement?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.WhileStatement -> {
                collectScopeSlots(stmt.condition)
                collectScopeSlots(stmt.body)
                stmt.elseStatement?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.DoWhileStatement -> {
                collectScopeSlots(stmt.body)
                collectScopeSlots(stmt.condition)
                stmt.elseStatement?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.WhenStatement -> {
                collectScopeSlots(stmt.value)
                for (case in stmt.cases) {
                    for (cond in case.conditions) {
                        collectScopeSlots(cond.expr)
                    }
                    collectScopeSlots(case.block)
                }
                stmt.elseCase?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.TryStatement -> {
                collectScopeSlots(stmt.body)
                for (catchBlock in stmt.catches) {
                    if (allowLocalSlots) {
                        val block = catchBlock.block as? BlockStatement
                        val catchSlotIndex = block?.slotPlan?.get(catchBlock.catchVarName)
                        if (block != null && catchSlotIndex != null && !shouldUseScopeSlotFor(block.scopeId)) {
                            val key = ScopeSlotKey(block.scopeId, catchSlotIndex)
                            declaredLocalKeys.add(key)
                            if (!localSlotInfoMap.containsKey(key)) {
                                localSlotInfoMap[key] = LocalSlotInfo(
                                    catchBlock.catchVarName,
                                    isMutable = false,
                                    isDelegated = false
                                )
                            }
                        }
                    }
                    collectScopeSlots(catchBlock.block)
                }
                stmt.finallyClause?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.BreakStatement -> {
                stmt.resultExpr?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.ReturnStatement -> {
                stmt.resultExpr?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.ClassStaticFieldInitStatement -> {
                stmt.initializer?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.InstanceFieldInitStatement -> {
                stmt.initializer?.let { collectScopeSlots(it) }
            }
            is net.sergeych.lyng.InstanceDelegatedInitStatement -> {
                collectScopeSlots(stmt.initializer)
            }
            is net.sergeych.lyng.ThrowStatement -> {
                collectScopeSlots(stmt.throwExpr)
            }
            else -> {}
        }
    }

    private fun collectLoopSlotPlans(stmt: Statement, scopeDepth: Int) {
        if (stmt is BytecodeStatement) {
            collectLoopSlotPlans(stmt.original, scopeDepth)
            return
        }
        when (stmt) {
            is net.sergeych.lyng.ForInStatement -> {
                val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
                if (loopSlotIndex != null) {
                    loopVarKeys.add(ScopeSlotKey(stmt.loopScopeId, loopSlotIndex))
                }
                collectLoopSlotPlans(stmt.source, scopeDepth)
                val loopDepth = scopeDepth + 1
                collectLoopSlotPlans(stmt.body, loopDepth)
                stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) }
            }
            is net.sergeych.lyng.WhileStatement -> {
                collectLoopSlotPlans(stmt.condition, scopeDepth)
                val loopDepth = scopeDepth + 1
                collectLoopSlotPlans(stmt.body, loopDepth)
                stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) }
            }
            is net.sergeych.lyng.DoWhileStatement -> {
                val loopDepth = scopeDepth + 1
                collectLoopSlotPlans(stmt.body, loopDepth)
                collectLoopSlotPlans(stmt.condition, loopDepth)
                stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) }
            }
            is BlockStatement -> {
                val nextDepth = scopeDepth + 1
                for (child in stmt.statements()) {
                    collectLoopSlotPlans(child, nextDepth)
                }
            }
            is net.sergeych.lyng.InlineBlockStatement -> {
                for (child in stmt.statements()) {
                    collectLoopSlotPlans(child, scopeDepth)
                }
            }
            is IfStatement -> {
                collectLoopSlotPlans(stmt.condition, scopeDepth)
                collectLoopSlotPlans(stmt.ifBody, scopeDepth)
                stmt.elseBody?.let { collectLoopSlotPlans(it, scopeDepth) }
            }
            is VarDeclStatement -> {
                stmt.initializer?.let { collectLoopSlotPlans(it, scopeDepth) }
            }
            is ExpressionStatement -> {
                // no-op
            }
            is net.sergeych.lyng.BreakStatement -> {
                stmt.resultExpr?.let { collectLoopSlotPlans(it, scopeDepth) }
            }
            is net.sergeych.lyng.ReturnStatement -> {
                stmt.resultExpr?.let { collectLoopSlotPlans(it, scopeDepth) }
            }
            is net.sergeych.lyng.ThrowStatement -> {
                collectLoopSlotPlans(stmt.throwExpr, scopeDepth)
            }
            else -> {}
        }
    }

    private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
        if (moduleScopeId != null && scopeId != moduleScopeId) return false
        return isPreparedScopeName(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)
            return
        }
        when (stmt) {
            is net.sergeych.lyng.ForInStatement -> {
                if (stmt.constRange != null) {
                    intLoopVarNames.add(stmt.loopVarName)
                }
                collectLoopVarNames(stmt.source)
                collectLoopVarNames(stmt.body)
                stmt.elseStatement?.let { collectLoopVarNames(it) }
            }
            is net.sergeych.lyng.WhileStatement -> {
                collectLoopVarNames(stmt.condition)
                collectLoopVarNames(stmt.body)
                stmt.elseStatement?.let { collectLoopVarNames(it) }
            }
            is net.sergeych.lyng.DoWhileStatement -> {
                collectLoopVarNames(stmt.body)
                collectLoopVarNames(stmt.condition)
                stmt.elseStatement?.let { collectLoopVarNames(it) }
            }
            is net.sergeych.lyng.InlineBlockStatement -> {
                for (child in stmt.statements()) {
                    collectLoopVarNames(child)
                }
            }
            is BlockStatement -> {
                for (child in stmt.statements()) {
                    collectLoopVarNames(child)
                }
            }
            is VarDeclStatement -> {
                stmt.initializer?.let { collectLoopVarNames(it) }
            }
            is IfStatement -> {
                collectLoopVarNames(stmt.condition)
                collectLoopVarNames(stmt.ifBody)
                stmt.elseBody?.let { collectLoopVarNames(it) }
            }
            is ExpressionStatement -> collectLoopVarNamesRef(stmt.ref)
            is net.sergeych.lyng.BreakStatement -> {
                stmt.resultExpr?.let { collectLoopVarNames(it) }
            }
            is net.sergeych.lyng.ReturnStatement -> {
                stmt.resultExpr?.let { collectLoopVarNames(it) }
            }
            is net.sergeych.lyng.ThrowStatement -> {
                collectLoopVarNames(stmt.throwExpr)
            }
            is net.sergeych.lyng.TryStatement -> {
                collectLoopVarNames(stmt.body)
                for (catchBlock in stmt.catches) {
                    collectLoopVarNames(catchBlock.block)
                }
                stmt.finallyClause?.let { collectLoopVarNames(it) }
            }
            else -> {}
        }
    }

    private fun collectLoopVarNamesRef(ref: ObjRef) {
        when (ref) {
            is BinaryOpRef -> {
                collectLoopVarNamesRef(binaryLeft(ref))
                collectLoopVarNamesRef(binaryRight(ref))
            }
            is UnaryOpRef -> collectLoopVarNamesRef(unaryOperand(ref))
            is AssignRef -> collectLoopVarNamesRef(assignValue(ref))
            is AssignOpRef -> {
                collectLoopVarNamesRef(ref.target)
                collectLoopVarNamesRef(ref.value)
            }
            is IncDecRef -> collectLoopVarNamesRef(ref.target)
            is ConditionalRef -> {
                collectLoopVarNamesRef(ref.condition)
                collectLoopVarNamesRef(ref.ifTrue)
                collectLoopVarNamesRef(ref.ifFalse)
            }
            is ElvisRef -> {
                collectLoopVarNamesRef(ref.left)
                collectLoopVarNamesRef(ref.right)
            }
            is FieldRef -> collectLoopVarNamesRef(ref.target)
            is IndexRef -> {
                collectLoopVarNamesRef(ref.targetRef)
                collectLoopVarNamesRef(ref.indexRef)
            }
            is RangeRef -> {
                ref.left?.let { collectLoopVarNamesRef(it) }
                ref.right?.let { collectLoopVarNamesRef(it) }
                ref.step?.let { collectLoopVarNamesRef(it) }
            }
            else -> {}
        }
    }

    private fun collectScopeSlotsRef(ref: ObjRef) {
        when (ref) {
            is LocalSlotRef -> {
                val scopeId = refScopeId(ref)
                val key = ScopeSlotKey(scopeId, refSlot(ref))
                if (ref.captureOwnerScopeId != null) {
                    if (allowLocalSlots) {
                        if (!localSlotInfoMap.containsKey(key)) {
                            localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
                        }
                    }
                    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 (!localSlotInfoMap.containsKey(key)) {
                        localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
                    }
                    return
                }
                if (!scopeSlotMap.containsKey(key)) {
                    scopeSlotMap[key] = scopeSlotMap.size
                }
                if (!scopeSlotNameMap.containsKey(key)) {
                    scopeSlotNameMap[key] = ref.name
                }
                if (!scopeSlotMutableMap.containsKey(key)) {
                    scopeSlotMutableMap[key] = ref.isMutable
                }
            }
            is LocalVarRef -> {}
            is BinaryOpRef -> {
                collectScopeSlotsRef(binaryLeft(ref))
                collectScopeSlotsRef(binaryRight(ref))
            }
            is UnaryOpRef -> collectScopeSlotsRef(unaryOperand(ref))
            is CastRef -> {
                collectScopeSlotsRef(ref.castValueRef())
                collectScopeSlotsRef(ref.castTypeRef())
            }
            is LogicalAndRef -> {
                collectScopeSlotsRef(ref.left())
                collectScopeSlotsRef(ref.right())
            }
            is LogicalOrRef -> {
                collectScopeSlotsRef(ref.left())
                collectScopeSlotsRef(ref.right())
            }
            is AssignRef -> {
                val target = assignTarget(ref)
                if (target != null) {
                    val scopeId = refScopeId(target)
                    val key = ScopeSlotKey(scopeId, refSlot(target))
                    if (target.captureOwnerScopeId != null) {
                        if (allowLocalSlots) {
                            if (!localSlotInfoMap.containsKey(key)) {
                                localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
                            }
                        }
                        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 (!localSlotInfoMap.containsKey(key)) {
                                localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
                            }
                        } else {
                            if (!scopeSlotMap.containsKey(key)) {
                                scopeSlotMap[key] = scopeSlotMap.size
                            }
                            if (!scopeSlotNameMap.containsKey(key)) {
                                scopeSlotNameMap[key] = target.name
                            }
                            if (!scopeSlotMutableMap.containsKey(key)) {
                                scopeSlotMutableMap[key] = target.isMutable
                            }
                        }
                    }
                } else {
                    collectScopeSlotsRef(ref.target)
                }
                collectScopeSlotsRef(assignValue(ref))
            }
            is AssignOpRef -> {
                collectScopeSlotsRef(ref.target)
                collectScopeSlotsRef(ref.value)
            }
            is ValueFnRef -> {
                valueFnRefs.add(ref)
            }
            is AssignIfNullRef -> {
                collectScopeSlotsRef(ref.target)
                collectScopeSlotsRef(ref.value)
            }
            is IncDecRef -> collectScopeSlotsRef(ref.target)
            is ConditionalRef -> {
                collectScopeSlotsRef(ref.condition)
                collectScopeSlotsRef(ref.ifTrue)
                collectScopeSlotsRef(ref.ifFalse)
            }
            is ElvisRef -> {
                collectScopeSlotsRef(ref.left)
                collectScopeSlotsRef(ref.right)
            }
            is FieldRef -> {
                val receiverClass = resolveReceiverClassForScopeCollection(ref.target)
                if (receiverClass != null) {
                    val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
                    val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
                    if (fieldId == null && methodId == null) {
                        queueExtensionPropertyNames(receiverClass, ref.name)
                    }
                }
                collectScopeSlotsRef(ref.target)
            }
            is IndexRef -> {
                collectScopeSlotsRef(ref.targetRef)
                collectScopeSlotsRef(ref.indexRef)
            }
            is RangeRef -> {
                ref.left?.let { collectScopeSlotsRef(it) }
                ref.right?.let { collectScopeSlotsRef(it) }
                ref.step?.let { collectScopeSlotsRef(it) }
            }
            is ClassOperatorRef -> {
                collectScopeSlotsRef(ref.target)
            }
            is ListLiteralRef -> {
                for (entry in ref.entries()) {
                    when (entry) {
                        is net.sergeych.lyng.ListEntry.Element -> collectScopeSlotsRef(entry.ref)
                        is net.sergeych.lyng.ListEntry.Spread -> collectScopeSlotsRef(entry.ref)
                    }
                }
            }
            is ImplicitThisMethodCallRef -> {
                if (ref.slotId() == null) {
                    val typeName = ref.preferredThisTypeName()
                    if (typeName != null) {
                        pendingScopeNameRefs.add(extensionCallableName(typeName, ref.methodName()))
                    }
                }
                collectScopeSlotsArgs(ref.arguments())
            }
            is ImplicitThisMemberRef -> {
                if ((ref.fieldId ?: -1) < 0 && (ref.methodId ?: -1) < 0) {
                    val typeName = ref.preferredThisTypeName()
                    if (typeName != null) {
                        pendingScopeNameRefs.add(extensionPropertyGetterName(typeName, ref.name))
                    }
                }
            }
            is MapLiteralRef -> {
                for (entry in ref.entries()) {
                    when (entry) {
                        is net.sergeych.lyng.obj.MapLiteralEntry.Named -> collectScopeSlotsRef(entry.value)
                        is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> collectScopeSlotsRef(entry.ref)
                    }
                }
            }
            is CallRef -> {
                collectScopeSlotsRef(ref.target)
                collectScopeSlotsArgs(ref.args)
            }
            is MethodCallRef -> {
                val receiverClass = resolveReceiverClassForScopeCollection(ref.receiver)
                if (receiverClass != null) {
                    val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
                    if (methodId == null) {
                        queueExtensionCallableNames(receiverClass, ref.name)
                    }
                }
                collectScopeSlotsRef(ref.receiver)
                collectScopeSlotsArgs(ref.args)
            }
            is StatementRef -> {
                collectScopeSlots(ref.statement)
            }
            is ThisMethodSlotCallRef -> {
                collectScopeSlotsArgs(ref.arguments())
            }
            else -> {}
        }
    }

    private fun collectScopeSlotsArgs(args: List<ParsedArgument>) {
        for (arg in args) {
            val stmt = arg.value
            if (stmt is ExpressionStatement) {
                collectScopeSlotsRef(stmt.ref)
            }
        }
    }

    private fun extractRangeRef(source: Statement): RangeRef? {
        val target = if (source is BytecodeStatement) source.original else source
        val expr = target as? ExpressionStatement ?: return null
        val ref = expr.ref as? RangeRef ?: return null
        return if (ref.step != null) null else ref
    }

    private fun emitIterableForIn(
        stmt: net.sergeych.lyng.ForInStatement,
        sourceValue: CompiledValue,
        wantResult: Boolean,
        loopSlotId: Int,
        breakFlagSlot: Int,
        needsBreakFlag: Boolean,
        hasRealWiden: Boolean,
        realWidenSlots: Set<Int>,
    ): Int? {
        val sourceObj = ensureObjSlot(sourceValue)
        val typeId = builder.addConst(BytecodeConst.ObjRef(ObjIterable))
        val typeSlot = allocSlot()
        builder.emit(Opcode.CONST_OBJ, typeId, typeSlot)
        builder.emit(Opcode.ASSERT_IS, sourceObj.slot, typeSlot)

        val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
        val iteratorMethodId = iterableMethods["iterator"]
            ?: throw BytecodeCompileException("Missing member id for Iterable.iterator", stmt.pos)
        val iteratorMethods = ObjIterator.instanceMethodIdMap(includeAbstract = true)
        val hasNextMethodId = iteratorMethods["hasNext"]
            ?: throw BytecodeCompileException("Missing member id for Iterator.hasNext", stmt.pos)
        val nextMethodId = iteratorMethods["next"]
            ?: throw BytecodeCompileException("Missing member id for Iterator.next", stmt.pos)

        val iterSlot = allocSlot()
        builder.emit(Opcode.CALL_MEMBER_SLOT, sourceObj.slot, iteratorMethodId, 0, 0, iterSlot)
        builder.emit(Opcode.ITER_PUSH, iterSlot)

        if (needsBreakFlag) {
            val falseId = builder.addConst(BytecodeConst.Bool(false))
            builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)
        }
        val resultSlot = if (wantResult) {
            val slot = allocSlot()
            val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
            builder.emit(Opcode.CONST_OBJ, voidId, slot)
            slot
        } else {
            null
        }

        val loopLabel = builder.label()
        val continueLabel = builder.label()
        val endLabel = builder.label()
        builder.mark(loopLabel)

        val hasNextSlot = allocSlot()
        builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, hasNextMethodId, 0, 0, hasNextSlot)
        val condSlot = allocSlot()
        builder.emit(Opcode.OBJ_TO_BOOL, hasNextSlot, condSlot)
        builder.emit(
            Opcode.JMP_IF_FALSE,
            listOf(CmdBuilder.Operand.IntVal(condSlot), CmdBuilder.Operand.LabelRef(endLabel))
        )

        val nextSlot = allocSlot()
        builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, nextMethodId, 0, 0, nextSlot)
        val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN))
        emitMove(CompiledValue(nextObj.slot, SlotType.OBJ), loopSlotId)
        updateSlotType(loopSlotId, SlotType.OBJ)
        updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)

        loopStack.addLast(
            LoopContext(
                stmt.label,
                endLabel,
                continueLabel,
                breakFlagSlot,
                resultSlot,
                hasIterator = true
            )
        )
        val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
        if (hasRealWiden) {
            applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
        }
        loopStack.removeLast()
        if (wantResult) {
            val bodyObj = ensureObjSlot(bodyValue)
            builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
        }
        builder.mark(continueLabel)
        if (hasRealWiden) {
            emitLoopRealCoercions(realWidenSlots)
        }
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))

        builder.mark(endLabel)
        if (needsBreakFlag) {
            val afterPop = builder.label()
            builder.emit(
                Opcode.JMP_IF_TRUE,
                listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterPop))
            )
            builder.emit(Opcode.ITER_POP)
            builder.mark(afterPop)
        } else {
            builder.emit(Opcode.ITER_POP)
        }
        if (stmt.elseStatement != null) {
            val afterElse = if (needsBreakFlag) builder.label() else null
            if (needsBreakFlag) {
                builder.emit(
                    Opcode.JMP_IF_TRUE,
                    listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse!!))
                )
            }
            val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
            if (wantResult) {
                val elseObj = ensureObjSlot(elseValue)
                builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot!!)
            }
            if (needsBreakFlag) {
                builder.mark(afterElse!!)
            }
        }
        return resultSlot ?: breakFlagSlot
    }

    private fun emitRangeObject(startValue: CompiledValue, endValue: CompiledValue, ref: RangeRef): CompiledValue {
        val startObj = ensureObjSlot(startValue)
        val endObj = ensureObjSlot(endValue)
        val inclusiveSlot = allocSlot()
        val inclusiveId = builder.addConst(BytecodeConst.Bool(ref.isEndInclusive))
        builder.emit(Opcode.CONST_BOOL, inclusiveId, inclusiveSlot)
        val descendingSlot = allocSlot()
        val descendingId = builder.addConst(BytecodeConst.Bool(ref.isDescending))
        builder.emit(Opcode.CONST_BOOL, descendingId, descendingSlot)
        val stepSlot = allocSlot()
        builder.emit(Opcode.CONST_NULL, stepSlot)
        updateSlotType(stepSlot, SlotType.OBJ)
        val dst = allocSlot()
        builder.emit(Opcode.MAKE_RANGE, startObj.slot, endObj.slot, inclusiveSlot, descendingSlot, stepSlot, dst)
        updateSlotType(dst, SlotType.OBJ)
        slotObjClass[dst] = ObjRange.type
        return CompiledValue(dst, SlotType.OBJ)
    }

    private fun isDynamicIntRangeCandidate(ref: RangeRef): Boolean {
        val left = ref.left ?: return false
        val right = ref.right ?: return false
        return isIntLikeRef(left) && isIntLikeRef(right)
    }

    private fun isIntLikeRef(ref: ObjRef): Boolean {
        if (inferNumericKind(ref) == NumericKind.INT) {
            return true
        }
        return when (ref) {
            is ConstRef -> ref.constValue is ObjInt
            is LocalSlotRef,
            is LocalVarRef,
            is FastLocalVarRef,
            is BoundLocalVarRef,
            is CallRef,
            is MethodCallRef,
            is FieldRef,
            is CastRef,
            is StatementRef -> resolveReceiverClass(ref) == ObjInt.type
            is ThisMethodSlotCallRef,
            is ImplicitThisMethodCallRef,
            is ThisFieldSlotRef,
            is ImplicitThisMemberRef -> resolveReceiverClassForScopeCollection(ref) == ObjInt.type
            is UnaryOpRef -> ref.op == UnaryOp.NEGATE && isIntLikeRef(unaryOperand(ref))
            is BinaryOpRef -> when (binaryOp(ref)) {
                BinOp.PLUS,
                BinOp.MINUS,
                BinOp.STAR,
                BinOp.SLASH,
                BinOp.PERCENT,
                BinOp.BAND,
                BinOp.BXOR,
                BinOp.BOR,
                BinOp.SHL,
                BinOp.SHR -> isIntLikeRef(binaryLeft(ref)) && isIntLikeRef(binaryRight(ref))
                else -> false
            }
            else -> false
        }
    }

    private fun coerceToLoopInt(value: CompiledValue): CompiledValue? {
        return when (value.type) {
            SlotType.INT -> value
            SlotType.OBJ -> {
                val isExactInt = isExactNonNullSlotClassOrTemp(value.slot, ObjInt.type)
                val isStableIntObj = slotObjClass[value.slot] == ObjInt.type && isStablePrimitiveSourceSlot(value.slot)
                if (!isExactInt && !isStableIntObj) return null
                val objSlot = value.slot
                val intSlot = allocSlot()
                builder.emit(Opcode.UNBOX_INT_OBJ, objSlot, intSlot)
                updateSlotType(intSlot, SlotType.INT)
                CompiledValue(intSlot, SlotType.INT)
            }
            SlotType.UNKNOWN -> null
            else -> null
        }
    }

    private fun coerceToArithmeticInt(ref: ObjRef, value: CompiledValue): CompiledValue? {
        if (value.type == SlotType.INT) return value
        val refSuggestsInt = isIntLikeRef(ref) || inferNumericKind(ref) == NumericKind.INT
        val stableNonTemp = !isTempSlot(value.slot) && isStablePrimitiveSourceSlot(value.slot)
        return when (value.type) {
            SlotType.UNKNOWN -> {
                if (!refSuggestsInt) return null
                updateSlotType(value.slot, SlotType.INT)
                CompiledValue(value.slot, SlotType.INT)
            }
            SlotType.OBJ -> {
                if (!refSuggestsInt && !stableNonTemp) return null
                coerceToLoopInt(value)?.let { return it }
                if (!refSuggestsInt) return null
                val intSlot = allocSlot()
                builder.emit(Opcode.UNBOX_INT_OBJ, emitAssertObjSlotIsInt(value.slot), intSlot)
                updateSlotType(intSlot, SlotType.INT)
                CompiledValue(intSlot, SlotType.INT)
            }
            else -> null
        }
    }

    private fun emitAssertObjSlotIsInt(slot: Int): Int {
        val typeId = builder.addConst(BytecodeConst.ObjRef(ObjInt.type))
        val typeSlot = allocSlot()
        builder.emit(Opcode.CONST_OBJ, typeId, typeSlot)
        builder.emit(Opcode.ASSERT_IS, slot, typeSlot)
        return slot
    }

    private fun extractDeclaredRange(stmt: Statement?): RangeRef? {
        if (stmt == null) return null
        val target = if (stmt is BytecodeStatement) stmt.original else stmt
        val expr = target as? ExpressionStatement ?: return null
        val ref = expr.ref
        if (ref is RangeRef) return ref
        if (ref is ConstRef) {
            val range = ref.constValue as? ObjRange ?: return null
            if (range.step != null && !range.step.isNull) return null
            val start = range.start as? ObjInt ?: return null
            val end = range.end as? ObjInt ?: return null
            val left = ConstRef(start.asReadonly)
            val right = ConstRef(end.asReadonly)
            return RangeRef(left, right, range.isEndInclusive, isDescending = range.isDescending)
        }
        return null
    }

    private fun emitIntForLoopCheck(iSlot: Int, stopSlot: Int, descendingSlot: Int, endLabel: CmdBuilder.Label) {
        val descendingLabel = builder.label()
        val afterCheckLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(
                CmdBuilder.Operand.IntVal(descendingSlot),
                CmdBuilder.Operand.LabelRef(descendingLabel)
            )
        )
        builder.emit(
            Opcode.JMP_IF_GTE_INT,
            listOf(
                CmdBuilder.Operand.IntVal(iSlot),
                CmdBuilder.Operand.IntVal(stopSlot),
                CmdBuilder.Operand.LabelRef(endLabel)
            )
        )
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(afterCheckLabel)))
        builder.mark(descendingLabel)
        builder.emit(
            Opcode.JMP_IF_LTE_INT,
            listOf(
                CmdBuilder.Operand.IntVal(iSlot),
                CmdBuilder.Operand.IntVal(stopSlot),
                CmdBuilder.Operand.LabelRef(endLabel)
            )
        )
        builder.mark(afterCheckLabel)
    }

    private fun emitIntForLoopStep(iSlot: Int, descendingSlot: Int) {
        val descendingLabel = builder.label()
        val afterStepLabel = builder.label()
        builder.emit(
            Opcode.JMP_IF_TRUE,
            listOf(
                CmdBuilder.Operand.IntVal(descendingSlot),
                CmdBuilder.Operand.LabelRef(descendingLabel)
            )
        )
        builder.emit(Opcode.INC_INT, iSlot)
        builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(afterStepLabel)))
        builder.mark(descendingLabel)
        builder.emit(Opcode.DEC_INT, iSlot)
        builder.mark(afterStepLabel)
    }

    private fun extractRangeFromLocal(source: Statement): RangeRef? {
        val target = if (source is BytecodeStatement) source.original else source
        val expr = target as? ExpressionStatement ?: return null
        val localRef = expr.ref as? LocalSlotRef ?: return null
        val key = ScopeSlotKey(refScopeId(localRef), refSlot(localRef))
        return localRangeRefs[key]
    }

    private fun extractTypedRangeLocal(source: Statement): LocalSlotRef? {
        return null
    }

    private data class ScopeSlotKey(val scopeId: Int, val slot: Int)
}
