Step 21: union mismatch bytecode throw

This commit is contained in:
Sergey Chernov 2026-02-09 12:13:33 +03:00
parent b49f291bff
commit 9d508e219f
3 changed files with 24 additions and 14 deletions

View File

@ -70,9 +70,9 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [x] Allow `NopStatement` in `containsUnsupportedForBytecode`. - [x] Allow `NopStatement` in `containsUnsupportedForBytecode`.
- [x] Emit `ObjVoid` directly in bytecode for `NopStatement` in statement/value contexts. - [x] Emit `ObjVoid` directly in bytecode for `NopStatement` in statement/value contexts.
- [x] Add a JVM test that exercises a code path returning `NopStatement` in bytecode (e.g., static class member decl in class body). - [x] Add a JVM test that exercises a code path returning `NopStatement` in bytecode (e.g., static class member decl in class body).
- [ ] Step 21: Union mismatch path in bytecode. - [x] Step 21: Union mismatch path in bytecode.
- [ ] Replace `UnionTypeMismatchStatement` branch with a bytecode-compilable throw path (no custom `StatementRef` that blocks bytecode). - [x] Replace `UnionTypeMismatchStatement` branch with a bytecode-compilable throw path (no custom `StatementRef` that blocks bytecode).
- [ ] Add a JVM test that forces the union mismatch at runtime and asserts the error message. - [x] Add a JVM test that forces the union mismatch at runtime and asserts the error message.
- [ ] Step 22: Delegated local slots in bytecode. - [ ] Step 22: Delegated local slots in bytecode.
- [ ] Support reads/writes/assign-ops/inc/dec for delegated locals (`LocalSlotRef.isDelegated`) in `BytecodeCompiler`. - [ ] Support reads/writes/assign-ops/inc/dec for delegated locals (`LocalSlotRef.isDelegated`) in `BytecodeCompiler`.
- [ ] Remove `containsDelegatedRefs` guard once delegated locals are bytecode-safe. - [ ] Remove `containsDelegatedRefs` guard once delegated locals are bytecode-safe.

View File

@ -4341,15 +4341,6 @@ class Compiler(
} }
} }
private class UnionTypeMismatchStatement(
private val message: String,
override val pos: Pos
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
throw ScriptError(pos, message)
}
}
private fun resolveMemberHostForUnion(typeDecl: TypeDecl, memberName: String, pos: Pos): ObjClass { private fun resolveMemberHostForUnion(typeDecl: TypeDecl, memberName: String, pos: Pos): ObjClass {
val receiverClass = when (typeDecl) { val receiverClass = when (typeDecl) {
TypeDecl.TypeAny, TypeDecl.TypeNullableAny -> Obj.rootObjectType TypeDecl.TypeAny, TypeDecl.TypeNullableAny -> Obj.rootObjectType
@ -4392,8 +4383,10 @@ class Compiler(
resolveMemberHostForUnion(option, memberName, pos) resolveMemberHostForUnion(option, memberName, pos)
} }
val unionName = typeDeclName(union) val unionName = typeDeclName(union)
val failStmt = UnionTypeMismatchStatement("value is not $unionName", pos) val errorMessage = ObjString("value is not $unionName").asReadonly
var current: ObjRef = net.sergeych.lyng.obj.StatementRef(failStmt) val throwExpr = ExpressionStatement(ConstRef(errorMessage), pos)
val throwStmt = ThrowStatement(throwExpr, pos)
var current: ObjRef = net.sergeych.lyng.obj.StatementRef(throwStmt)
for (option in options.asReversed()) { for (option in options.asReversed()) {
val typeRef = net.sergeych.lyng.obj.TypeDeclRef(option, pos) val typeRef = net.sergeych.lyng.obj.TypeDeclRef(option, pos)
val cond = BinaryOpRef(BinOp.IS, left, typeRef) val cond = BinaryOpRef(BinOp.IS, left, typeRef)

View File

@ -17,6 +17,7 @@
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler import net.sergeych.lyng.Compiler
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
@ -24,6 +25,7 @@ import net.sergeych.lyng.eval
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue import kotlin.test.assertTrue
class BytecodeRecentOpsTest { class BytecodeRecentOpsTest {
@ -221,6 +223,21 @@ class BytecodeRecentOpsTest {
) )
} }
@Test
fun unionMemberDispatchMismatch() = runTest {
val err = assertFailsWith<ExecutionError> {
eval(
"""
class A { fun who() = "A" }
class B { fun who() = "B" }
val x: A | B = 1
x.who()
""".trimIndent()
)
}
assertTrue(err.message?.contains("value is not A | B") == true)
}
@Test @Test
fun objectReceiverMemberError() = runTest { fun objectReceiverMemberError() = runTest {
val failed = try { val failed = try {