Fix do-while scoping and module pseudo-symbol

This commit is contained in:
Sergey Chernov 2026-01-30 16:48:21 +03:00
parent 3210205061
commit d6e1e74b48
4 changed files with 81 additions and 10 deletions

View File

@ -223,6 +223,11 @@ class Compiler(
}
private fun resolveIdentifierRef(name: String, pos: Pos): ObjRef {
if (name == "__PACKAGE__") {
resolutionSink?.reference(name, pos)
val value = ObjString(packageName ?: "unknown").asReadonly
return ConstRef(value)
}
if (name == "this") {
resolutionSink?.reference(name, pos)
return LocalVarRef(name, pos)
@ -3176,10 +3181,13 @@ class Compiler(
val label = getLabel()?.also { cc.labels += it }
val loopSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++)
slotPlanStack.add(loopSlotPlan)
var conditionSlotPlan: SlotPlan = loopSlotPlan
val (canBreak, parsedBody) = try {
cc.parseLoop {
if (cc.current().type == Token.Type.LBRACE) {
parseLoopBlock()
val (blockStmt, blockPlan) = parseLoopBlockWithPlan()
conditionSlotPlan = blockPlan
blockStmt
} else {
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement")
}
@ -3196,7 +3204,7 @@ class Compiler(
throw ScriptError(tWhile.pos, "Expected 'while' after do body")
ensureLparen()
slotPlanStack.add(loopSlotPlan)
slotPlanStack.add(conditionSlotPlan)
val condition = try {
parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected condition after 'while'")
} finally {
@ -3212,7 +3220,7 @@ class Compiler(
cc.previous()
null
}
val loopPlanSnapshot = slotPlanIndices(loopSlotPlan)
val loopPlanSnapshot = slotPlanIndices(conditionSlotPlan)
return DoWhileStatement(body, condition, elseStatement, label, loopPlanSnapshot, body.pos)
}
@ -3819,6 +3827,35 @@ class Compiler(
}
}
private suspend fun parseLoopBlockWithPlan(): Pair<Statement, SlotPlan> {
val startPos = cc.currentPos()
val t = cc.next()
if (t.type != Token.Type.LBRACE)
throw ScriptError(t.pos, "Expected block body start: {")
resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null)
val blockSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++)
slotPlanStack.add(blockSlotPlan)
val capturePlan = CapturePlan(blockSlotPlan)
capturePlanStack.add(capturePlan)
val block = try {
parseScript()
} finally {
capturePlanStack.removeLast()
slotPlanStack.removeLast()
}
val planSnapshot = slotPlanIndices(blockSlotPlan)
val stmt = BlockStatement(block, planSnapshot, capturePlan.captures.toList(), startPos)
val wrapped = wrapBytecode(stmt)
val t1 = cc.next()
if (t1.type != Token.Type.RBRACE)
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
val range = MiniRange(startPos, t1.pos)
lastParsedBlockRange = range
miniSink?.onBlock(MiniBlock(range))
resolutionSink?.exitScope(t1.pos)
return wrapped to blockSlotPlan
}
private suspend fun parseVarDeclaration(
isMutable: Boolean,
visibility: Visibility,

View File

@ -347,6 +347,13 @@ open class Scope(
open operator fun get(name: String): ObjRecord? {
if (name == "this") return thisObj.asReadonly
if (name == "__PACKAGE__") {
var s: Scope? = this
while (s != null) {
if (s is ModuleScope) return s.packageNameObj
s = s.parent
}
}
// 1. Prefer direct locals/bindings declared in this frame
tryGetLocalRecord(this, name, currentClassCtx)?.let { return it }

View File

@ -175,6 +175,9 @@ class BytecodeCompiler(
return when (ref) {
is ConstRef -> compileConst(ref.constValue)
is LocalSlotRef -> {
if (ref.name == "__PACKAGE__") {
return compileNameLookup(ref.name)
}
if (!allowLocalSlots) return null
if (ref.isDelegated) return null
if (ref.name.isEmpty()) return null
@ -201,6 +204,9 @@ class BytecodeCompiler(
CompiledValue(mapped, resolved)
}
is LocalVarRef -> {
if (ref.name == "__PACKAGE__") {
return compileNameLookup(ref.name)
}
if (allowLocalSlots) {
if (!forceScopeSlots) {
scopeSlotIndexByName[ref.name]?.let { slot ->
@ -2775,18 +2781,34 @@ class BytecodeCompiler(
val loopLabel = builder.label()
val continueLabel = builder.label()
val endLabel = builder.label()
val useLoopScope = stmt.loopSlotPlan.isNotEmpty()
val breakLabel = if (useLoopScope) builder.label() else endLabel
val planId = if (useLoopScope) {
builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan, emptyList()))
} else {
-1
}
builder.mark(loopLabel)
if (useLoopScope) {
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
}
loopStack.addLast(
LoopContext(
stmt.label,
endLabel,
breakLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
)
val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
val bodyTarget = if (stmt.body is BytecodeStatement) stmt.body.original else stmt.body
val bodyValue = if (useLoopScope && bodyTarget is BlockStatement) {
emitInlineBlock(bodyTarget, wantResult)
} else {
compileStatementValueOrFallback(stmt.body, wantResult)
} ?: return null
loopStack.removeLast()
if (wantResult) {
val bodyObj = ensureObjSlot(bodyValue)
@ -2795,10 +2817,20 @@ class BytecodeCompiler(
builder.mark(continueLabel)
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
if (condition.type != SlotType.BOOL) return null
if (useLoopScope) {
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
}
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
)
if (useLoopScope) {
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(breakLabel)
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
}
builder.mark(endLabel)
if (stmt.elseStatement != null) {

View File

@ -2008,7 +2008,6 @@ class ScriptTest {
}
@Ignore("incremental enable")
@Test
fun testMethodCallLastBlockWithEllipsis() = runTest {
eval(
@ -2027,7 +2026,6 @@ class ScriptTest {
}
@Ignore("incremental enable")
@Test
fun nationalCharsTest() = runTest {
eval(
@ -2051,7 +2049,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun doWhileSimpleTest() = runTest {
eval(
@ -2066,7 +2063,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testFailDoWhileSample1() = runTest {
eval(
@ -2081,7 +2077,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testForContinue() = runTest {
eval(