diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index e042fd3..b2f9170 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -35,10 +35,12 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [ ] Step 10: Bytecode for declaration statements in module scripts. - [ ] Support `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement` in bytecode compilation. - [ ] Decide whether to compile declarations into module bytecode or keep a mixed execution path. + - [x] Ensure module object member refs compile as instance access (not class-scope). - [ ] Step 11: Destructuring assignment bytecode. - [ ] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback. - [ ] Step 12: Optional member assign-ops and inc/dec in bytecode. - [ ] Support `a?.b += 1` and `a?.b++` for `FieldRef` targets. + - [x] Fix post-inc return value for object slots stored in scope frames. ## Notes diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt index 7c05428..70f9640 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClassDeclStatement.kt @@ -28,4 +28,9 @@ class ClassDeclStatement( override suspend fun execute(scope: Scope): Obj { return delegate.execute(scope) } + + override suspend fun callOn(scope: Scope): Obj { + val target = scope.parent ?: scope + return delegate.execute(target) + } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index f004f04..970efff 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -165,6 +165,7 @@ class Compiler( private val classScopeCallableMembersByClassName: MutableMap> = mutableMapOf() private val encodedPayloadTypeByScopeId: MutableMap> = mutableMapOf() private val encodedPayloadTypeByName: MutableMap = mutableMapOf() + private val objectDeclNames: MutableSet = mutableSetOf() private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) { val plan = moduleSlotPlan() ?: return @@ -1536,7 +1537,12 @@ class Compiler( allowedScopeNames = modulePlan.keys, moduleScopeId = moduleSlotPlan()?.id, slotTypeByScopeId = slotTypeByScopeId, - knownNameObjClass = knownClassMapForBytecode() + knownNameObjClass = knownClassMapForBytecode(), + knownObjectNames = objectDeclNames, + classFieldTypesByName = classFieldTypesByName, + enumEntriesByName = enumEntriesByName, + callableReturnTypeByScopeId = callableReturnTypeByScopeId, + callableReturnTypeByName = callableReturnTypeByName ) as BytecodeStatement unwrapped to bytecodeStmt.bytecodeFunction() } else { @@ -1830,7 +1836,12 @@ class Compiler( allowedScopeNames = allowedScopeNames, moduleScopeId = moduleSlotPlan()?.id, slotTypeByScopeId = slotTypeByScopeId, - knownNameObjClass = knownClassMapForBytecode() + knownNameObjClass = knownClassMapForBytecode(), + knownObjectNames = objectDeclNames, + classFieldTypesByName = classFieldTypesByName, + enumEntriesByName = enumEntriesByName, + callableReturnTypeByScopeId = callableReturnTypeByScopeId, + callableReturnTypeByName = callableReturnTypeByName ) } @@ -1861,7 +1872,12 @@ class Compiler( allowedScopeNames = allowedScopeNames, moduleScopeId = moduleSlotPlan()?.id, slotTypeByScopeId = slotTypeByScopeId, - knownNameObjClass = knownNames + knownNameObjClass = knownNames, + knownObjectNames = objectDeclNames, + classFieldTypesByName = classFieldTypesByName, + enumEntriesByName = enumEntriesByName, + callableReturnTypeByScopeId = callableReturnTypeByScopeId, + callableReturnTypeByName = callableReturnTypeByName ) } @@ -1899,6 +1915,9 @@ class Compiler( is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) is ExtensionPropertyDeclStatement -> false + is ClassDeclStatement -> false + is FunctionDeclStatement -> false + is EnumDeclStatement -> false is TryStatement -> { containsUnsupportedForBytecode(target.body) || target.catches.any { containsUnsupportedForBytecode(it.block) } || @@ -5660,6 +5679,9 @@ class Compiler( if (declaredName != null) { resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken!!.pos) declareLocalName(declaredName, isMutable = false) + if (codeContexts.lastOrNull() is CodeContext.Module) { + objectDeclNames.add(declaredName) + } if (outerClassName != null) { val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody outerCtx?.classScopeMembers?.add(declaredName) @@ -6612,11 +6634,17 @@ class Compiler( val declKind = if (parentContext is CodeContext.ClassBody) SymbolKind.MEMBER else SymbolKind.FUNCTION resolutionSink?.declareSymbol(name, declKind, isMutable = false, pos = nameStartPos, isOverride = isOverride) if (parentContext is CodeContext.ClassBody && extTypeName == null) { - parentContext.declaredMembers.add(name) - if (!parentContext.memberMethodIds.containsKey(name)) { - parentContext.memberMethodIds[name] = parentContext.nextMethodId++ + if (isStatic) { + parentContext.classScopeMembers.add(name) + registerClassScopeMember(parentContext.name, name) + registerClassScopeCallableMember(parentContext.name, name) + } else { + parentContext.declaredMembers.add(name) + if (!parentContext.memberMethodIds.containsKey(name)) { + parentContext.memberMethodIds[name] = parentContext.nextMethodId++ + } + memberMethodId = parentContext.memberMethodIds[name] } - memberMethodId = parentContext.memberMethodIds[name] } if (declKind != SymbolKind.MEMBER) { declareLocalName(name, isMutable = false) @@ -7752,7 +7780,10 @@ class Compiler( } if (declaringClassNameCaptured != null && extTypeName == null) { - if (isDelegate || isProperty) { + if (isStatic) { + classCtx?.classScopeMembers?.add(name) + registerClassScopeMember(declaringClassNameCaptured, name) + } else if (isDelegate || isProperty) { if (classCtx != null) { if (!classCtx.memberMethodIds.containsKey(name)) { classCtx.memberMethodIds[name] = classCtx.nextMethodId++ diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt index b90ca74..45cbf61 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/EnumDeclStatement.kt @@ -27,4 +27,9 @@ class EnumDeclStatement( override suspend fun execute(scope: Scope): Obj { return delegate.execute(scope) } + + override suspend fun callOn(scope: Scope): Obj { + val target = scope.parent ?: scope + return delegate.execute(target) + } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt index cd49fd7..bbf3d08 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt @@ -27,4 +27,9 @@ class FunctionDeclStatement( override suspend fun execute(scope: Scope): Obj { return delegate.execute(scope) } + + override suspend fun callOn(scope: Scope): Obj { + val target = scope.parent ?: scope + return delegate.execute(target) + } } 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 baf058f..b5b58a0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -28,6 +28,11 @@ class BytecodeCompiler( private val moduleScopeId: Int? = null, private val slotTypeByScopeId: Map> = emptyMap(), private val knownNameObjClass: Map = emptyMap(), + private val knownObjectNames: Set = emptySet(), + private val classFieldTypesByName: Map> = emptyMap(), + private val enumEntriesByName: Map> = emptyMap(), + private val callableReturnTypeByScopeId: Map> = emptyMap(), + private val callableReturnTypeByName: Map = emptyMap(), ) { private var builder = CmdBuilder() private var nextSlot = 0 @@ -54,7 +59,8 @@ class BytecodeCompiler( private val localRangeRefs = LinkedHashMap() private val slotTypes = mutableMapOf() private val slotObjClass = mutableMapOf() - private val nameObjClass = mutableMapOf() + private val nameObjClass = knownNameObjClass.toMutableMap() + private val knownClassNames = knownNameObjClass.keys.toSet() private val slotInitClassByKey = mutableMapOf() private val intLoopVarNames = LinkedHashSet() private val loopStack = ArrayDeque() @@ -148,6 +154,54 @@ class BytecodeCompiler( localSlotMutables ) } + is net.sergeych.lyng.ClassDeclStatement -> { + val value = emitStatementCall(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, + localSlotNames, + localSlotMutables + ) + } + is net.sergeych.lyng.FunctionDeclStatement -> { + val value = emitStatementCall(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, + localSlotNames, + localSlotMutables + ) + } + is net.sergeych.lyng.EnumDeclStatement -> { + val value = emitStatementCall(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, + localSlotNames, + localSlotMutables + ) + } else -> null } } @@ -1689,6 +1743,25 @@ class BytecodeCompiler( } return value } + if (isKnownClassReceiver(target.target)) { + 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] @@ -1887,28 +1960,105 @@ class BytecodeCompiler( } ?: return compileEvalRef(ref) val fieldTarget = ref.target as? FieldRef if (fieldTarget != null) { - if (fieldTarget.isOptional) return compileEvalRef(ref) 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 receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null - val current = allocSlot() - val result = allocSlot() - val rhs = compileRef(ref.value) ?: return compileEvalRef(ref) 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, current) + builder.emit(objOp, current, rhs.slot, result) + builder.mark(endLabel) updateSlotType(result, SlotType.OBJ) return CompiledValue(result, SlotType.OBJ) } - throw BytecodeCompileException( - "Member assignment requires compile-time receiver type: ${fieldTarget.name}", - Pos.builtIn - ) + val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name] + val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name] + if (fieldId == null && methodId == null && isKnownClassReceiver(fieldTarget.target)) { + 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, current) + builder.emit(objOp, current, rhs.slot, 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, current) + builder.emit(objOp, current, rhs.slot, 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) { @@ -2190,6 +2340,30 @@ class BytecodeCompiler( val encodedMethodId = encodeMemberId(receiverClass, methodId) val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val dst = allocSlot() + if (fieldId == null && methodId == null && isKnownClassReceiver(ref.target)) { + 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) @@ -2507,13 +2681,15 @@ class BytecodeCompiler( 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(boxed, SlotType.OBJ) + CompiledValue(old, SlotType.OBJ) } else { val result = allocSlot() val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ @@ -3092,6 +3268,34 @@ class BytecodeCompiler( builder.mark(endLabel) return CompiledValue(dst, SlotType.OBJ) } + if (isKnownClassReceiver(ref.receiver)) { + 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) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null + setPos(callPos) + builder.emit(Opcode.CALL_SLOT, memberSlot, args.base, encodedCount, dst) + return CompiledValue(dst, SlotType.OBJ) + } val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name) ?: throw BytecodeCompileException( "Unknown member ${ref.name} on ${receiverClass.className}", @@ -3476,6 +3680,17 @@ class BytecodeCompiler( throw BytecodeCompileException("Unsupported statement in bytecode: $stmtName", stmt.pos) } + private fun emitStatementCall(stmt: Statement): CompiledValue { + val constId = builder.addConst(BytecodeConst.ObjRef(stmt)) + val calleeSlot = allocSlot() + builder.emit(Opcode.CONST_OBJ, constId, calleeSlot) + updateSlotType(calleeSlot, SlotType.OBJ) + val dst = allocSlot() + builder.emit(Opcode.CALL_SLOT, calleeSlot, 0, 0, dst) + updateSlotType(dst, SlotType.OBJ) + return CompiledValue(dst, SlotType.OBJ) + } + private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { val target = if (stmt is BytecodeStatement) stmt.original else stmt setPos(target.pos) @@ -3509,9 +3724,9 @@ class BytecodeCompiler( is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) - is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target) - is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target) - is net.sergeych.lyng.EnumDeclStatement -> emitStatementEval(target) + is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target) + is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target) + is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.TryStatement -> emitTry(target, true) is net.sergeych.lyng.WhenStatement -> compileWhen(target, true) is net.sergeych.lyng.BreakStatement -> compileBreak(target) @@ -3536,6 +3751,9 @@ class BytecodeCompiler( is VarDeclStatement -> emitVarDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is IfStatement -> compileIfStatement(target) + is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target) + is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target) + is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.ForInStatement -> { val resultSlot = emitForIn(target, false) ?: return null CompiledValue(resultSlot, SlotType.OBJ) @@ -4986,6 +5204,9 @@ class BytecodeCompiler( private fun resolveReceiverClass(ref: ObjRef): ObjClass? { return when (ref) { is LocalSlotRef -> { + if (knownObjectNames.contains(ref.name)) { + return nameObjClass[ref.name] ?: ObjDynamic.type + } val slot = resolveSlot(ref) val fromSlot = slot?.let { slotObjClass[it] } fromSlot @@ -5002,6 +5223,9 @@ class BytecodeCompiler( } } 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 key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key @@ -5046,6 +5270,14 @@ class BytecodeCompiler( } } + private fun isKnownClassReceiver(ref: ObjRef): Boolean { + return when (ref) { + is LocalVarRef -> knownClassNames.contains(ref.name) && !knownObjectNames.contains(ref.name) + is LocalSlotRef -> knownClassNames.contains(ref.name) && !knownObjectNames.contains(ref.name) + else -> false + } + } + private fun isAllowedObjectMember(memberName: String): Boolean { return when (memberName) { "toString", @@ -5149,8 +5381,12 @@ class BytecodeCompiler( private fun inferCallReturnClass(ref: CallRef): ObjClass? { return when (val target = ref.target) { - is LocalSlotRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name) - is LocalVarRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name) + is LocalSlotRef -> callableReturnTypeByScopeId[target.scopeId]?.get(target.slot) + ?: nameObjClass[target.name] + ?: resolveTypeNameClass(target.name) + is LocalVarRef -> callableReturnTypeByName[target.name] + ?: nameObjClass[target.name] + ?: resolveTypeNameClass(target.name) is ConstRef -> target.constValue as? ObjClass else -> null } @@ -5214,6 +5450,16 @@ class BytecodeCompiler( 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 { return it } + 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 } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index d829a8e..05eb0d6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -45,6 +45,11 @@ class BytecodeStatement private constructor( moduleScopeId: Int? = null, slotTypeByScopeId: Map> = emptyMap(), knownNameObjClass: Map = emptyMap(), + knownObjectNames: Set = emptySet(), + classFieldTypesByName: Map> = emptyMap(), + enumEntriesByName: Map> = emptyMap(), + callableReturnTypeByScopeId: Map> = emptyMap(), + callableReturnTypeByName: Map = emptyMap(), ): Statement { if (statement is BytecodeStatement) return statement val hasUnsupported = containsUnsupportedStatement(statement) @@ -63,7 +68,12 @@ class BytecodeStatement private constructor( allowedScopeNames = allowedScopeNames, moduleScopeId = moduleScopeId, slotTypeByScopeId = slotTypeByScopeId, - knownNameObjClass = knownNameObjClass + knownNameObjClass = knownNameObjClass, + knownObjectNames = knownObjectNames, + classFieldTypesByName = classFieldTypesByName, + enumEntriesByName = enumEntriesByName, + callableReturnTypeByScopeId = callableReturnTypeByScopeId, + callableReturnTypeByName = callableReturnTypeByName ) val compiled = compiler.compileStatement(nameHint, statement) val fn = compiled ?: throw BytecodeCompileException( diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 9fd3a81..f01b626 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -240,7 +240,9 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd() "${typeObj.inspect(frame.ensureScope())} is not the class instance" ) if (!obj.isInstanceOf(clazz)) { - frame.ensureScope().raiseClassCastError("expected ${clazz.className}, got ${obj.objClass.className}") + frame.ensureScope().raiseClassCastError( + "Cannot cast ${obj.objClass.className} to ${clazz.className}" + ) } return } @@ -1296,6 +1298,14 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm type = ObjRecord.Type.Property ) ) + val getterName = extensionPropertyGetterName(decl.extTypeName, decl.property.name) + val getterWrapper = ObjExtensionPropertyGetterCallable(decl.property.name, decl.property) + frame.ensureScope().addItem(getterName, false, getterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun) + if (decl.property.setter != null) { + val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name) + val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property) + frame.ensureScope().addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun) + } frame.setObj(slot, decl.property) return } @@ -1449,7 +1459,12 @@ class CmdGetMemberSlot( } } else null } ?: frame.ensureScope().raiseSymbolNotFound("member") - val name = rec.memberName ?: "" + val rawName = rec.memberName ?: "" + val name = if (receiver is ObjInstance && rawName.contains("::")) { + rawName.substringAfterLast("::") + } else { + rawName + } suspend fun autoCallIfMethod(resolved: ObjRecord, recv: Obj): Obj { return if (resolved.type == ObjRecord.Type.Fun && !resolved.isAbstract) { resolved.value.invoke(frame.ensureScope(), resolved.receiver ?: recv, Arguments.EMPTY, resolved.declaringClass) @@ -1498,12 +1513,17 @@ class CmdSetMemberSlot( } } else null } ?: frame.ensureScope().raiseSymbolNotFound("member") - val name = rec.memberName ?: "" + val rawName = rec.memberName ?: "" + val name = if (receiver is ObjInstance && rawName.contains("::")) { + rawName.substringAfterLast("::") + } else { + rawName + } if (receiver is ObjQualifiedView) { receiver.writeField(frame.ensureScope(), name, frame.slotToObj(valueSlot)) return } - frame.ensureScope().assign(rec, name, frame.slotToObj(valueSlot)) + receiver.writeField(frame.ensureScope(), name, frame.slotToObj(valueSlot)) return } } @@ -2383,10 +2403,10 @@ class CmdFrame( val direct = record.value if (direct is FrameSlotRef) return direct.read() val name = fn.scopeSlotNames[slot] + if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) { + return target.resolve(record, name) + } if (direct !== ObjUnset) { - if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) { - return target.resolve(record, name) - } return direct } if (name == null) return record.value @@ -2405,10 +2425,10 @@ class CmdFrame( if (direct is FrameSlotRef) return direct.read() val slotId = addrScopeSlots[addrSlot] val name = fn.scopeSlotNames.getOrNull(slotId) + if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) { + return target.resolve(record, name) + } if (direct !== ObjUnset) { - if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) { - return target.resolve(record, name) - } return direct } if (name == null) return record.value