fixed wrong line report on throw statement

This commit is contained in:
Sergey Chernov 2025-11-21 17:18:59 +01:00
parent a229f227e1
commit 72c6dc2bde
3 changed files with 104 additions and 10 deletions

View File

@ -389,7 +389,7 @@ class Compiler(
} }
"throw" -> { "throw" -> {
val s = parseThrowStatement() val s = parseThrowStatement(t.pos)
operand = StatementRef(s) operand = StatementRef(s)
} }
@ -916,7 +916,7 @@ class Compiler(
"class" -> parseClassDeclaration() "class" -> parseClassDeclaration()
"enum" -> parseEnumDeclaration() "enum" -> parseEnumDeclaration()
"try" -> parseTryStatement() "try" -> parseTryStatement()
"throw" -> parseThrowStatement() "throw" -> parseThrowStatement(id.pos)
"when" -> parseWhenStatement() "when" -> parseWhenStatement()
else -> { else -> {
// triples // triples
@ -1080,15 +1080,26 @@ class Compiler(
} }
} }
private suspend fun parseThrowStatement(): Statement { private suspend fun parseThrowStatement(start: Pos): Statement {
val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected") val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected")
return statement { // Important: bind the created statement to the position of the `throw` keyword so that
var errorObject = throwStatement.execute(this) // any raised error reports the correct source location.
if (errorObject is ObjString) return statement(start) { sc ->
errorObject = ObjException(this, errorObject.value) var errorObject = throwStatement.execute(sc)
if (errorObject is ObjException) // Rebind error scope to the throw-site position so ScriptError.pos is accurate
raiseError(errorObject) val throwScope = sc.createChildScope(pos = start)
else raiseError("this is not an exception object: $errorObject") errorObject = when (errorObject) {
is ObjString -> ObjException(throwScope, errorObject.value)
is ObjException -> ObjException(
errorObject.exceptionClass,
throwScope,
errorObject.message,
errorObject.extraData,
errorObject.useStackTrace
)
else -> throwScope.raiseError("this is not an exception object: $errorObject")
}
throwScope.raiseError(errorObject as ObjException)
} }
} }

View File

@ -3519,4 +3519,21 @@ class ScriptTest {
""".trimIndent()).toString()) """.trimIndent()).toString())
} }
@Test
fun testThrowReportsSource() = runTest {
try {
eval(
"""
// line 1
// line 2
throw "the test"
""".trimIndent()
)
} catch (se: ScriptError) {
println(se.message)
// Pos.line is zero-based
assertEquals(2, se.pos.line)
}
}
} }

View File

@ -0,0 +1,66 @@
/*
* Copyright 2025 Sergey S. Chernov
*/
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScriptError
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.fail
class ThrowSourcePosJvmTest {
private fun assertThrowLine(code: String, expectedLine: Int) {
try {
runBlocking { Scope().eval(code) }
fail("Expected ScriptError to be thrown, but nothing was thrown")
} catch (se: ScriptError) {
println(se.message)
assertEquals(expectedLine, se.pos.line)
}
}
@Test
fun simpleThrow_afterComments_reportsCorrectLine() {
val code = """
// line 1
// line 2
throw "simple"
""".trimIndent()
// zero-based line index
assertThrowLine(code, 2)
}
@Test
fun inlineThrow_withLeadingSpaces_reportsCorrectLine() {
val code = """
val x = 1
throw "boom"
""".trimIndent()
// throw is on the 2nd line (zero-based index 1)
assertThrowLine(code, 1)
}
@Test
fun throwInsideBlock_reportsCorrectLine() {
val code = """
if( true ) {
// comment
throw "boom"
}
""".trimIndent()
// throw is on the 3rd line of the snippet (zero-based index 2)
assertThrowLine(code, 2)
}
@Test
fun throwAsExpression_reportsCorrectLine() {
val code = """
val x = null
val y = x ?: throw "npe-like"
""".trimIndent()
// throw is on the 2nd line (zero-based index 1)
assertThrowLine(code, 1)
}
}