Fix bytecode loop locals and class member resolution

This commit is contained in:
Sergey Chernov 2026-01-30 22:27:48 +03:00
parent 2e9e0921bf
commit ffb22d0875
5 changed files with 93 additions and 24 deletions

View File

@ -23,5 +23,6 @@ sealed class CodeContext {
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() { class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
val pendingInitializations = mutableMapOf<String, Pos>() val pendingInitializations = mutableMapOf<String, Pos>()
val declaredMembers = mutableSetOf<String>() val declaredMembers = mutableSetOf<String>()
var slotPlanId: Int? = null
} }
} }

View File

@ -295,6 +295,14 @@ class Compiler(
} }
val slotLoc = lookupSlotLocation(name, includeModule = false) val slotLoc = lookupSlotLocation(name, includeModule = false)
if (slotLoc != null) { if (slotLoc != null) {
val classCtx = codeContexts.lastOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
if (slotLoc.depth > 0 &&
classCtx?.slotPlanId == slotLoc.scopeId &&
classCtx.declaredMembers.contains(name)
) {
resolutionSink?.referenceMember(name, pos)
return ImplicitThisMemberRef(name, pos)
}
captureLocalRef(name, slotLoc, pos)?.let { ref -> captureLocalRef(name, slotLoc, pos)?.let { ref ->
resolutionSink?.reference(name, pos) resolutionSink?.reference(name, pos)
return ref return ref
@ -868,6 +876,20 @@ class Compiler(
is ContinueStatement -> false is ContinueStatement -> false
is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false
is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr)
is WhenStatement -> {
containsUnsupportedForBytecode(target.value) ||
target.cases.any { case ->
case.conditions.any { cond ->
when (cond) {
is WhenEqualsCondition -> containsUnsupportedForBytecode(cond.expr)
is WhenInCondition -> containsUnsupportedForBytecode(cond.expr)
is WhenIsCondition -> false
else -> true
}
} || containsUnsupportedForBytecode(case.block)
} ||
(target.elseCase?.let { containsUnsupportedForBytecode(it) } ?: false)
}
else -> true else -> true
} }
} }
@ -2962,6 +2984,18 @@ class Compiler(
"Bad class declaration: expected ')' at the end of the primary constructor" "Bad class declaration: expected ')' at the end of the primary constructor"
) )
val classSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++)
classCtx?.slotPlanId = classSlotPlan.id
constructorArgsDeclaration?.params?.forEach { param ->
val mutable = param.accessType?.isMutable ?: false
declareSlotNameIn(classSlotPlan, param.name, mutable, isDelegated = false)
}
constructorArgsDeclaration?.params?.forEach { param ->
if (param.accessType != null) {
classCtx?.declaredMembers?.add(param.name)
}
}
// Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )? // Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )?
data class BaseSpec(val name: String, val args: List<ParsedArgument>?) data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
@ -2983,12 +3017,6 @@ class Compiler(
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
pushInitScope() pushInitScope()
constructorArgsDeclaration?.params?.forEach { param ->
if (param.accessType != null) {
classCtx?.declaredMembers?.add(param.name)
}
}
// Robust body detection: peek next non-whitespace token; if it's '{', consume and parse the body // Robust body detection: peek next non-whitespace token; if it's '{', consume and parse the body
var classBodyRange: MiniRange? = null var classBodyRange: MiniRange? = null
val bodyInit: Statement? = run { val bodyInit: Statement? = run {
@ -3026,12 +3054,7 @@ class Compiler(
} }
// parse body // parse body
val bodyStart = next.pos val bodyStart = next.pos
val classSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++)
slotPlanStack.add(classSlotPlan) slotPlanStack.add(classSlotPlan)
constructorArgsDeclaration?.params?.forEach { param ->
val mutable = param.accessType?.isMutable ?: false
declareSlotNameIn(classSlotPlan, param.name, mutable, isDelegated = false)
}
resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos) resolutionSink?.declareClass(nameToken.value, baseSpecs.map { it.name }, startPos)
resolutionSink?.enterScope(ScopeKind.CLASS, startPos, nameToken.value, baseSpecs.map { it.name }) resolutionSink?.enterScope(ScopeKind.CLASS, startPos, nameToken.value, baseSpecs.map { it.name })
constructorArgsDeclaration?.params?.forEach { param -> constructorArgsDeclaration?.params?.forEach { param ->
@ -3583,7 +3606,8 @@ class Compiler(
} }
miniSink?.onEnterFunction(node) miniSink?.onEnterFunction(node)
return inCodeContext(CodeContext.Function(name, implicitThisMembers = extTypeName != null)) { val implicitThisMembers = extTypeName != null || (parentContext is CodeContext.ClassBody && !isStatic)
return inCodeContext(CodeContext.Function(name, implicitThisMembers = implicitThisMembers)) {
cc.labels.add(name) cc.labels.add(name)
outerLabel?.let { cc.labels.add(it) } outerLabel?.let { cc.labels.add(it) }
@ -3629,12 +3653,10 @@ class Compiler(
cc.nextNonWhitespace() // consume '=' cc.nextNonWhitespace() // consume '='
if (cc.peekNextNonWhitespace().value == "return") if (cc.peekNextNonWhitespace().value == "return")
throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function") throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function")
val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression") val exprStmt = parseExpression()
// Shorthand function returns the expression value ?: throw ScriptError(cc.currentPos(), "Expected function body expression")
object : Statement() { // Shorthand function returns the expression value.
override val pos: Pos = expr.pos exprStmt
override suspend fun execute(scope: Scope): Obj = expr.execute(scope)
}
} else { } else {
parseBlock() parseBlock()
} }

View File

@ -26,6 +26,7 @@ import net.sergeych.lyng.Pos
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.ToBoolStatement import net.sergeych.lyng.ToBoolStatement
import net.sergeych.lyng.VarDeclStatement import net.sergeych.lyng.VarDeclStatement
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.WhenCondition import net.sergeych.lyng.WhenCondition
import net.sergeych.lyng.WhenEqualsCondition import net.sergeych.lyng.WhenEqualsCondition
import net.sergeych.lyng.WhenInCondition import net.sergeych.lyng.WhenInCondition
@ -1467,6 +1468,13 @@ class BytecodeCompiler(
builder.emit(realOp, left, rhs.slot, out) builder.emit(realOp, left, rhs.slot, out)
CompiledValue(out, SlotType.REAL) CompiledValue(out, SlotType.REAL)
} }
SlotType.OBJ -> {
if (objOp == null) return null
val leftObj = allocSlot()
builder.emit(Opcode.BOX_OBJ, out, leftObj)
builder.emit(objOp, leftObj, rhs.slot, out)
CompiledValue(out, SlotType.OBJ)
}
else -> null else -> null
} }
} }
@ -1483,6 +1491,13 @@ class BytecodeCompiler(
builder.emit(realOp, out, right, out) builder.emit(realOp, out, right, out)
CompiledValue(out, SlotType.REAL) CompiledValue(out, SlotType.REAL)
} }
SlotType.OBJ -> {
if (objOp == null) return null
val leftObj = allocSlot()
builder.emit(Opcode.BOX_OBJ, out, leftObj)
builder.emit(objOp, leftObj, rhs.slot, out)
CompiledValue(out, SlotType.OBJ)
}
else -> null else -> null
} }
} }
@ -2578,6 +2593,18 @@ class BytecodeCompiler(
usedOverride = true usedOverride = true
slot slot
} }
val loopDeclId = if (usedOverride) {
builder.addConst(
BytecodeConst.LocalDecl(
stmt.loopVarName,
true,
Visibility.Public,
isTransient = false
)
)
} else {
-1
}
try { try {
if (range == null && rangeRef == null && typedRangeLocal == null) { if (range == null && rangeRef == null && typedRangeLocal == null) {
@ -2623,6 +2650,9 @@ class BytecodeCompiler(
builder.emit(Opcode.MOVE_OBJ, nextObj.slot, loopSlotId) builder.emit(Opcode.MOVE_OBJ, nextObj.slot, loopSlotId)
updateSlotType(loopSlotId, SlotType.OBJ) updateSlotType(loopSlotId, SlotType.OBJ)
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ) updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
if (usedOverride) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
}
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
@ -2719,6 +2749,9 @@ class BytecodeCompiler(
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId) builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
if (usedOverride) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
}
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
stmt.label, stmt.label,
@ -2785,6 +2818,9 @@ class BytecodeCompiler(
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId) builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
if (usedOverride) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
}
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
stmt.label, stmt.label,

View File

@ -2288,6 +2288,13 @@ class ImplicitThisMemberRef(
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
val th = scope.thisObj val th = scope.thisObj
if (th is ObjClass) {
return th.readField(scope, name)
}
if (th != null && th !is ObjInstance) {
return th.readField(scope, name)
}
// member slots on this instance // member slots on this instance
if (th is ObjInstance) { if (th is ObjInstance) {
// private member access for current class context // private member access for current class context
@ -2333,6 +2340,15 @@ class ImplicitThisMemberRef(
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
val th = scope.thisObj val th = scope.thisObj
if (th is ObjClass) {
th.writeField(scope, name, newValue)
return
}
if (th != null && th !is ObjInstance) {
th.writeField(scope, name, newValue)
return
}
// member slots on this instance // member slots on this instance
if (th is ObjInstance) { if (th is ObjInstance) {
val key = th.objClass.publicMemberResolution[name] ?: name val key = th.objClass.publicMemberResolution[name] ?: name

View File

@ -3725,7 +3725,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable")
@Test @Test
fun testExceptionSerializationPlain() = runTest { fun testExceptionSerializationPlain() = runTest {
eval( eval(
@ -4704,7 +4703,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable: expression-body methods not resolved yet")
@Test @Test
fun testFunMiniDeclaration() = runTest { fun testFunMiniDeclaration() = runTest {
eval( eval(
@ -4793,7 +4791,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable: ctor params in superclass call not resolved yet")
@Test @Test
fun testExceptionToString() = runTest { fun testExceptionToString() = runTest {
eval( eval(
@ -4926,7 +4923,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable: capture of static var inside run block not resolved")
@Test @Test
fun realWorldCaptureProblem() = runTest { fun realWorldCaptureProblem() = runTest {
eval( eval(
@ -5120,7 +5116,6 @@ class ScriptTest {
) )
} }
@Ignore("incremental enable: for-in over String in disasm sample not yet supported")
@Test @Test
fun testForInIterableDisasm() = runTest { fun testForInIterableDisasm() = runTest {
val scope = Script.newScope() val scope = Script.newScope()
@ -5148,7 +5143,6 @@ class ScriptTest {
println("[DEBUG_LOG] type(\"153\")=${r2.inspect(scope)}") println("[DEBUG_LOG] type(\"153\")=${r2.inspect(scope)}")
} }
@Ignore("incremental enable: for-in bytecode over iterable returns 0")
@Test @Test
fun testForInIterableBytecode() = runTest { fun testForInIterableBytecode() = runTest {
val result = eval( val result = eval(