diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 9524f3c..ad1a93f 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -23,8 +23,8 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [x] Revisit `containsDelegatedRefs` guard for `DelegatedVarDeclStatement`. - [x] Ensure delegate binding uses explicit `Statement` objects (no inline suspend lambdas). - [x] Keep JVM tests green before commit. -- [ ] Step 6: Map literal spread in bytecode. - - [ ] Replace `MapLiteralEntry.Spread` bytecode exception with runtime `putAll`/merge logic. +- [x] Step 6: Map literal spread in bytecode. + - [x] Replace `MapLiteralEntry.Spread` bytecode exception with runtime `putAll`/merge logic. - [ ] Step 7: Class-scope member refs in bytecode. - [ ] Support `ClassScopeMemberRef` without scope-map fallback. - [ ] Step 8: ObjDynamic member access in bytecode. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 2833bc7..939e515 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -529,7 +529,35 @@ class BytecodeCompiler( builder.emit(Opcode.SET_INDEX, dst, keySlot, value.slot) } is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> { - throw BytecodeCompileException("Map spread is not supported in bytecode", Pos.builtIn) + 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) } } }