Step 25: emit DECL_EXEC for declarations
This commit is contained in:
parent
8314127fdb
commit
0caa9849cf
@ -19,18 +19,18 @@ package net.sergeych.lyng
|
|||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
class ClassDeclStatement(
|
class ClassDeclStatement(
|
||||||
private val delegate: Statement,
|
val executable: DeclExecutable,
|
||||||
private val startPos: Pos,
|
private val startPos: Pos,
|
||||||
val declaredName: String?,
|
val declaredName: String?,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return delegate.execute(scope)
|
return executable.execute(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val target = scope.parent ?: scope
|
val target = scope.parent ?: scope
|
||||||
return delegate.execute(target)
|
return executable.execute(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5784,7 +5784,7 @@ class Compiler(
|
|||||||
return enumClass
|
return enumClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return EnumDeclStatement(enumDeclStatement, stmtPos)
|
return EnumDeclStatement(StatementDeclExecutable(enumDeclStatement), stmtPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parseObjectDeclaration(isExtern: Boolean = false): Statement {
|
private suspend fun parseObjectDeclaration(isExtern: Boolean = false): Statement {
|
||||||
@ -5965,7 +5965,7 @@ class Compiler(
|
|||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ClassDeclStatement(declStatement, startPos, className)
|
return ClassDeclStatement(StatementDeclExecutable(declStatement), startPos, className)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false): Statement {
|
private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false): Statement {
|
||||||
@ -6372,7 +6372,7 @@ class Compiler(
|
|||||||
return newClass
|
return newClass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClassDeclStatement(classDeclStatement, startPos, qualifiedName)
|
ClassDeclStatement(StatementDeclExecutable(classDeclStatement), startPos, qualifiedName)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -7186,7 +7186,7 @@ class Compiler(
|
|||||||
return annotatedFnBody
|
return annotatedFnBody
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val declaredFn = FunctionDeclStatement(fnCreateStatement, start)
|
val declaredFn = FunctionDeclStatement(StatementDeclExecutable(fnCreateStatement), start)
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
currentInitScope += declaredFn
|
currentInitScope += declaredFn
|
||||||
NopStatement
|
NopStatement
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
interface DeclExecutable {
|
||||||
|
suspend fun execute(scope: Scope): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatementDeclExecutable(private val delegate: Statement) : DeclExecutable {
|
||||||
|
override suspend fun execute(scope: Scope): Obj = delegate.execute(scope)
|
||||||
|
}
|
||||||
@ -19,17 +19,17 @@ package net.sergeych.lyng
|
|||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
class EnumDeclStatement(
|
class EnumDeclStatement(
|
||||||
private val delegate: Statement,
|
val executable: DeclExecutable,
|
||||||
private val startPos: Pos,
|
private val startPos: Pos,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return delegate.execute(scope)
|
return executable.execute(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val target = scope.parent ?: scope
|
val target = scope.parent ?: scope
|
||||||
return delegate.execute(target)
|
return executable.execute(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,17 +19,17 @@ package net.sergeych.lyng
|
|||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
class FunctionDeclStatement(
|
class FunctionDeclStatement(
|
||||||
private val delegate: Statement,
|
val executable: DeclExecutable,
|
||||||
private val startPos: Pos,
|
private val startPos: Pos,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return delegate.execute(scope)
|
return executable.execute(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val target = scope.parent ?: scope
|
val target = scope.parent ?: scope
|
||||||
return delegate.execute(target)
|
return executable.execute(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4077,6 +4077,23 @@ class BytecodeCompiler(
|
|||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun emitDeclExec(stmt: Statement): CompiledValue {
|
||||||
|
val executable = when (stmt) {
|
||||||
|
is net.sergeych.lyng.ClassDeclStatement -> stmt.executable
|
||||||
|
is net.sergeych.lyng.FunctionDeclStatement -> stmt.executable
|
||||||
|
is net.sergeych.lyng.EnumDeclStatement -> stmt.executable
|
||||||
|
else -> throw BytecodeCompileException(
|
||||||
|
"Bytecode compile error: unsupported declaration ${stmt::class.simpleName}",
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val constId = builder.addConst(BytecodeConst.DeclExec(executable))
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(Opcode.DECL_EXEC, constId, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
|
||||||
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
|
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
|
||||||
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
setPos(target.pos)
|
setPos(target.pos)
|
||||||
@ -4110,9 +4127,9 @@ class BytecodeCompiler(
|
|||||||
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
|
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
|
||||||
is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
|
is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
|
||||||
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
|
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
|
||||||
is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target)
|
is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target)
|
||||||
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target)
|
is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target)
|
||||||
is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target)
|
is net.sergeych.lyng.EnumDeclStatement -> emitDeclExec(target)
|
||||||
is net.sergeych.lyng.TryStatement -> emitTry(target, true)
|
is net.sergeych.lyng.TryStatement -> emitTry(target, true)
|
||||||
is net.sergeych.lyng.WhenStatement -> compileWhen(target, true)
|
is net.sergeych.lyng.WhenStatement -> compileWhen(target, true)
|
||||||
is net.sergeych.lyng.BreakStatement -> compileBreak(target)
|
is net.sergeych.lyng.BreakStatement -> compileBreak(target)
|
||||||
@ -4143,9 +4160,9 @@ class BytecodeCompiler(
|
|||||||
is VarDeclStatement -> emitVarDecl(target)
|
is VarDeclStatement -> emitVarDecl(target)
|
||||||
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
|
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
|
||||||
is IfStatement -> compileIfStatement(target)
|
is IfStatement -> compileIfStatement(target)
|
||||||
is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target)
|
is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target)
|
||||||
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target)
|
is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target)
|
||||||
is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target)
|
is net.sergeych.lyng.EnumDeclStatement -> emitDeclExec(target)
|
||||||
is net.sergeych.lyng.ForInStatement -> {
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
val resultSlot = emitForIn(target, false) ?: return null
|
val resultSlot = emitForIn(target, false) ?: return null
|
||||||
CompiledValue(resultSlot, SlotType.OBJ)
|
CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
|
|||||||
@ -34,6 +34,7 @@ sealed class BytecodeConst {
|
|||||||
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
|
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
|
||||||
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
|
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
|
||||||
data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst()
|
data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst()
|
||||||
|
data class DeclExec(val executable: net.sergeych.lyng.DeclExecutable) : BytecodeConst()
|
||||||
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
|
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
|
||||||
data class ExtensionPropertyDecl(
|
data class ExtensionPropertyDecl(
|
||||||
val extTypeName: String,
|
val extTypeName: String,
|
||||||
|
|||||||
@ -157,6 +157,7 @@ class CmdBuilder {
|
|||||||
Opcode.PUSH_TRY ->
|
Opcode.PUSH_TRY ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
||||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
||||||
|
Opcode.DECL_EXEC,
|
||||||
Opcode.ASSIGN_DESTRUCTURE ->
|
Opcode.ASSIGN_DESTRUCTURE ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||||
@ -409,6 +410,7 @@ class CmdBuilder {
|
|||||||
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
||||||
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
|
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
|
||||||
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
|
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
|
||||||
|
Opcode.DECL_EXEC -> CmdDeclExec(operands[0], operands[1])
|
||||||
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
||||||
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])
|
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])
|
||||||
|
|||||||
@ -194,6 +194,7 @@ object CmdDisassembler {
|
|||||||
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclExec -> Opcode.DECL_EXEC to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||||
@ -265,6 +266,7 @@ object CmdDisassembler {
|
|||||||
Opcode.PUSH_TRY ->
|
Opcode.PUSH_TRY ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
||||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
||||||
|
Opcode.DECL_EXEC,
|
||||||
Opcode.ASSIGN_DESTRUCTURE ->
|
Opcode.ASSIGN_DESTRUCTURE ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||||
|
|||||||
@ -1314,6 +1314,22 @@ class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CmdDeclExec(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||||
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
|
frame.syncFrameToScope(useRefs = true)
|
||||||
|
}
|
||||||
|
val decl = frame.fn.constants[constId] as? BytecodeConst.DeclExec
|
||||||
|
?: error("DECL_EXEC expects DeclExec at $constId")
|
||||||
|
val result = decl.executable.execute(frame.ensureScope())
|
||||||
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
|
frame.syncScopeToFrame()
|
||||||
|
}
|
||||||
|
frame.storeObjResult(slot, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cmd() {
|
class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureDecl
|
val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureDecl
|
||||||
|
|||||||
@ -158,6 +158,7 @@ enum class Opcode(val code: Int) {
|
|||||||
STORE_BOOL_ADDR(0xB9),
|
STORE_BOOL_ADDR(0xB9),
|
||||||
THROW(0xBB),
|
THROW(0xBB),
|
||||||
RETHROW_PENDING(0xBC),
|
RETHROW_PENDING(0xBC),
|
||||||
|
DECL_EXEC(0xBD),
|
||||||
ITER_PUSH(0xBF),
|
ITER_PUSH(0xBF),
|
||||||
ITER_POP(0xC0),
|
ITER_POP(0xC0),
|
||||||
ITER_CANCEL(0xC1),
|
ITER_CANCEL(0xC1),
|
||||||
|
|||||||
@ -24,10 +24,13 @@ import net.sergeych.lyng.ScriptError
|
|||||||
import net.sergeych.lyng.Source
|
import net.sergeych.lyng.Source
|
||||||
import net.sergeych.lyng.eval
|
import net.sergeych.lyng.eval
|
||||||
import net.sergeych.lyng.toSource
|
import net.sergeych.lyng.toSource
|
||||||
|
import net.sergeych.lyng.bytecode.CmdDisassembler
|
||||||
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
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.assertFailsWith
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class BytecodeRecentOpsTest {
|
class BytecodeRecentOpsTest {
|
||||||
@ -243,6 +246,23 @@ class BytecodeRecentOpsTest {
|
|||||||
assertTrue(disasm.contains("DECL_DELEGATED"), disasm)
|
assertTrue(disasm.contains("DECL_DELEGATED"), disasm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun moduleDeclsAvoidCallableCallSlots() = runTest {
|
||||||
|
val script = """
|
||||||
|
class A {}
|
||||||
|
fun f() { 1 }
|
||||||
|
enum E { one }
|
||||||
|
""".trimIndent()
|
||||||
|
val compiled = Compiler.compile(script.toSource(), Script.defaultImportManager)
|
||||||
|
val field = Script::class.java.getDeclaredField("moduleBytecode")
|
||||||
|
field.isAccessible = true
|
||||||
|
val moduleFn = field.get(compiled) as? CmdFunction
|
||||||
|
assertNotNull(moduleFn, "module bytecode missing")
|
||||||
|
val disasm = CmdDisassembler.disassemble(moduleFn)
|
||||||
|
assertTrue(!disasm.contains("CALL_SLOT"), disasm)
|
||||||
|
assertTrue(disasm.contains("DECL_EXEC"), disasm)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun unionMemberDispatchSubtype() = runTest {
|
fun unionMemberDispatchSubtype() = runTest {
|
||||||
eval(
|
eval(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user