From 2e814a7b22aaad23850331197f08bdccabcb9d85 Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Sun, 12 Jul 2026 11:26:32 +0200 Subject: [PATCH] Kotlin: anchor synthetic exhaustive-when throw to the `when` An exhaustive `when` expression (one with no explicit `else` that the compiler proves total) gets a synthetic `throw NoWhenBranchMatchedException(...)` as its fallback branch. The two frontends locate that synthetic call differently: * K2 gives the call the source offsets of the enclosing `when` expression, so the throw (and the `new NoWhenBranchMatchedException`) are located at the `when` block, e.g. `6:3:9:3`. * K1 leaves the synthetic call with undefined offsets, so the extractor emits a `0:0:0:0` (no-source) location for both nodes. A real source location is strictly more useful than none, and anchoring the implicit fallback to the `when` it belongs to is the intuitive choice, so we adopt the K2 behaviour for both frontends. `extractCall` now records the enclosing `when`'s location while extracting its branches (`currentSyntheticWhenLocation`) and uses it as the fallback for the `noWhenBranchMatchedException` builtin whenever the synthetic call itself has undefined offsets. This only changes K1: under K2 the call already carries valid offsets, so `tw.getLocation(c)` is used unchanged and K2 output is byte-identical. Relearned both suites: all 3333 tests pass and the only changed row is test-kotlin1/library-tests/no-when-branch-found, which now matches test-kotlin2 exactly (`0:0:0:0` -> `6:3:9:3`). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 22 +++++++++++++++++-- .../no-when-branch-found/test.expected | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 708600f6b22..762132add39 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -96,6 +96,13 @@ open class KotlinFileExtractor( // argument of that property's forwarding get/set, which under K1 gets a bogus location. private var currentLocalDelegatedProperty: IrLocalDelegatedProperty? = null + // Set only while extracting the branches of an `IrWhen`. Holds that `when`'s source + // location, used as a fallback location for the compiler-generated + // `throw NoWhenBranchMatchedException(...)` of an exhaustive `when`. Under K2 that + // synthetic call carries the `when`'s offsets, but under K1 it has undefined offsets + // (yielding a `0:0:0:0` location); this makes both frontends anchor it to the `when`. + private var currentSyntheticWhenLocation: Label? = null + private inline fun with(kind: String, element: IrElement, f: () -> T): T { val name = when (element) { @@ -5042,7 +5049,12 @@ open class KotlinFileExtractor( } isBuiltinCallInternal(c, "noWhenBranchMatchedException") -> { kotlinNoWhenBranchMatchedConstructor?.let { - val locId = tw.getLocation(c) + // Under K1 this synthetic call has undefined offsets (a `0:0:0:0` + // location); fall back to the enclosing `when`'s location, matching K2. + val locId = + if (c.startOffset == UNDEFINED_OFFSET || c.endOffset == UNDEFINED_OFFSET) + currentSyntheticWhenLocation ?: tw.getLocation(c) + else tw.getLocation(c) val thrownType = useSimpleTypeClass(it.parentAsClass, listOf(), false) val stmtParent = stmtExprParent.stmt(c, callable) val throwId = tw.getFreshIdLabel() @@ -6834,7 +6846,13 @@ open class KotlinFileExtractor( tw.writeStmts_whenbranch(bId, id, i, callable) tw.writeHasLocation(bId, bLocId) extractExpressionExpr(b.condition, callable, bId, 0, bId) - extractExpressionStmt(b.result, callable, bId, 1) + val prevSyntheticWhenLocation = currentSyntheticWhenLocation + currentSyntheticWhenLocation = locId + try { + extractExpressionStmt(b.result, callable, bId, 1) + } finally { + currentSyntheticWhenLocation = prevSyntheticWhenLocation + } if (b is IrElseBranch) { tw.writeWhen_branch_else(bId) } diff --git a/java/ql/test-kotlin1/library-tests/no-when-branch-found/test.expected b/java/ql/test-kotlin1/library-tests/no-when-branch-found/test.expected index f441807d2f5..a4fcaba215a 100644 --- a/java/ql/test-kotlin1/library-tests/no-when-branch-found/test.expected +++ b/java/ql/test-kotlin1/library-tests/no-when-branch-found/test.expected @@ -1 +1 @@ -| test.kt:0:0:0:0 | throw ... | test.kt:0:0:0:0 | new NoWhenBranchMatchedException(...) | +| test.kt:6:3:9:3 | throw ... | test.kt:6:3:9:3 | new NoWhenBranchMatchedException(...) |