From 0de7f77ebc4e0f6bd70b5c9a13a0d0f603f6d7e3 Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Sat, 11 Jul 2026 20:15:14 +0200 Subject: [PATCH] Converge indexed array-assignment locations onto K2 (span through the assigned value) For an indexed array assignment `a[i] = v` (and its compound forms `a[i] op= v`), the K1 and K2 frontends disagree on the source location of both the synthesised array-access node (`...[...]`) and the assignment node (`...=...`/`...op=...`): - K1 records the end offset at the left-hand side array access, e.g. `a1[0] = a1[0]` gets `12:3:12:7` (just `a1[0]`). - K2 spans through the assigned value, e.g. `12:3:12:15` (the whole `a1[0] = a1[0]`). Decision: adopt the K2 span. An assignment expression should cover its whole source range (left-hand side through the right-hand value), consistent with how ordinary (non-indexed) assignments are already located and with the Java extractor's array-store locations. This makes location-based queries (and any `...=...`/`...[...]` overlap reasoning) behave the same regardless of frontend. Implementation: the set operation is desugared to an array `set` call. Two code paths build these nodes, and both used the raw call/block end offset: - simple `a[i] = v` -> the `Array.set` IrCall branch in `extractCall` - compound `a[i] op= v` -> `tryExtractArrayUpdate` Each now widens the end offset to the assigned value's end via a small offset-based `correctedEndOffset(rawEnd, valueEnd)` helper (extracted from the existing `correctedEndOffset(IrExpression)` so the two share one rule). The widening only fires when the value's end lies past the raw end, so it is a no-op under K2 (where the raw end already spans the value) and ignores undefined/synthetic offsets. A new `TrapWriter.getLocation(e, endOffset)` overload preserves the existing IrCall start-offset adjustment while overriding just the end. Tradeoff: the change is deliberately scoped to the two array-assignment paths rather than globally rewriting how set-call locations are derived, to avoid perturbing unrelated expressions. Only the `test-kotlin1` array expected files change; `test-kotlin2` is unaffected, and the two suites' `arrayAccesses` and `assignExprs` expected files are now byte-identical. All 3333 tests pass in both K1 and K2 modes (with database-consistency checks). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 30 +++++++++++-- .../src/main/kotlin/TrapWriter.kt | 9 ++++ .../arrays/arrayAccesses.expected | 44 +++++++++---------- .../library-tests/arrays/assignExprs.expected | 8 ++-- 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 80fc938861f..530318ec3b0 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -3196,11 +3196,21 @@ open class KotlinFileExtractor( is IrSetField -> e.value.endOffset else -> UNDEFINED_OFFSET } + return correctedEndOffset(e.endOffset, valueEnd) + } + + /** + * Returns [rawEnd], or [valueEnd] when the latter is a valid offset lying past [rawEnd]. This + * widens an assignment's raw end offset (which the K1 frontend records at the left-hand side) + * to include its right-hand value; under K2 the raw end already spans the value, so [valueEnd] + * is not past [rawEnd] and this is a no-op. Undefined/synthetic [valueEnd] offsets are ignored. + */ + private fun correctedEndOffset(rawEnd: Int, valueEnd: Int): Int { return if ( - valueEnd != UNDEFINED_OFFSET && valueEnd != SYNTHETIC_OFFSET && valueEnd > e.endOffset + valueEnd != UNDEFINED_OFFSET && valueEnd != SYNTHETIC_OFFSET && valueEnd > rawEnd ) valueEnd - else e.endOffset + else rawEnd } /** @@ -5232,7 +5242,11 @@ open class KotlinFileExtractor( if (array != null && arrayIdx != null && assignedValue != null) { - val locId = tw.getLocation(c) + // The K1 frontend records the set-operation's end offset at the left-hand + // side array access (`a[i]`), whereas K2 spans through the assigned value. + // Widen the end to include the assigned value so both frontends agree. + val locId = + tw.getLocation(c, correctedEndOffset(c.endOffset, assignedValue.endOffset)) extractAssignExpr(c.type, locId, parent, idx, callable, enclosingStmt) .also { assignId -> tw.getFreshIdLabel().also { arrayAccessId -> @@ -6006,7 +6020,15 @@ open class KotlinFileExtractor( val exprParent = parent.expr(e, callable) val assignId = tw.getFreshIdLabel() val type = useType(arrayVarInitializer.type) - val locId = tw.getLocation(e) + // The K1 frontend records the compound-assignment block's + // end offset at the left-hand side array access (`a[i]`), + // whereas K2 spans through the right-hand value. Widen the + // end to include the value so both frontends agree. + val locId = + tw.getLocation( + e, + correctedEndOffset(e.endOffset, updateRhs.endOffset) + ) tw.writeExprsKotlinType(assignId, type.kotlinResult.id) extractExprContext( assignId, diff --git a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt index bae3188f05c..92a16c55724 100644 --- a/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt +++ b/java/kotlin-extractor/src/main/kotlin/TrapWriter.kt @@ -361,6 +361,15 @@ open class FileTrapWriter( fun getLocation(e: IrElement): Label { return getLocation(getStartOffset(e), getEndOffset(e)) } + /** + * Gets a label for the location starting at `e`'s (adjusted) start offset and ending at the + * given `endOffset`. This preserves the IrCall start-offset adjustment that [getLocation] + * applies, while allowing the caller to override the end offset (for example to widen an + * assignment's span to include its right-hand value). + */ + fun getLocation(e: IrElement, endOffset: Int): Label { + return getLocation(getStartOffset(e), endOffset) + } /** * Gets a label for the location corresponding to `startOffset` and `endOffset` within this * file. diff --git a/java/ql/test-kotlin1/library-tests/arrays/arrayAccesses.expected b/java/ql/test-kotlin1/library-tests/arrays/arrayAccesses.expected index 986bdeed21f..6217a2650e2 100644 --- a/java/ql/test-kotlin1/library-tests/arrays/arrayAccesses.expected +++ b/java/ql/test-kotlin1/library-tests/arrays/arrayAccesses.expected @@ -1,22 +1,22 @@ -| arrayGetsSets.kt:12:3:12:7 | ...[...] | arrayGetsSets.kt:12:3:12:7 | ...=... | int[] | arrayGetsSets.kt:12:3:12:4 | a1 | arrayGetsSets.kt:12:6:12:6 | 0 | -| arrayGetsSets.kt:12:11:12:15 | ...[...] | arrayGetsSets.kt:12:3:12:7 | ...=... | int | arrayGetsSets.kt:12:11:12:12 | a1 | arrayGetsSets.kt:12:14:12:14 | 0 | -| arrayGetsSets.kt:13:3:13:7 | ...[...] | arrayGetsSets.kt:13:3:13:7 | ...=... | short[] | arrayGetsSets.kt:13:3:13:4 | a2 | arrayGetsSets.kt:13:6:13:6 | 0 | -| arrayGetsSets.kt:13:11:13:15 | ...[...] | arrayGetsSets.kt:13:3:13:7 | ...=... | short | arrayGetsSets.kt:13:11:13:12 | a2 | arrayGetsSets.kt:13:14:13:14 | 0 | -| arrayGetsSets.kt:14:3:14:7 | ...[...] | arrayGetsSets.kt:14:3:14:7 | ...=... | byte[] | arrayGetsSets.kt:14:3:14:4 | a3 | arrayGetsSets.kt:14:6:14:6 | 0 | -| arrayGetsSets.kt:14:11:14:15 | ...[...] | arrayGetsSets.kt:14:3:14:7 | ...=... | byte | arrayGetsSets.kt:14:11:14:12 | a3 | arrayGetsSets.kt:14:14:14:14 | 0 | -| arrayGetsSets.kt:15:3:15:7 | ...[...] | arrayGetsSets.kt:15:3:15:7 | ...=... | long[] | arrayGetsSets.kt:15:3:15:4 | a4 | arrayGetsSets.kt:15:6:15:6 | 0 | -| arrayGetsSets.kt:15:11:15:15 | ...[...] | arrayGetsSets.kt:15:3:15:7 | ...=... | long | arrayGetsSets.kt:15:11:15:12 | a4 | arrayGetsSets.kt:15:14:15:14 | 0 | -| arrayGetsSets.kt:16:3:16:7 | ...[...] | arrayGetsSets.kt:16:3:16:7 | ...=... | float[] | arrayGetsSets.kt:16:3:16:4 | a5 | arrayGetsSets.kt:16:6:16:6 | 0 | -| arrayGetsSets.kt:16:11:16:15 | ...[...] | arrayGetsSets.kt:16:3:16:7 | ...=... | float | arrayGetsSets.kt:16:11:16:12 | a5 | arrayGetsSets.kt:16:14:16:14 | 0 | -| arrayGetsSets.kt:17:3:17:7 | ...[...] | arrayGetsSets.kt:17:3:17:7 | ...=... | double[] | arrayGetsSets.kt:17:3:17:4 | a6 | arrayGetsSets.kt:17:6:17:6 | 0 | -| arrayGetsSets.kt:17:11:17:15 | ...[...] | arrayGetsSets.kt:17:3:17:7 | ...=... | double | arrayGetsSets.kt:17:11:17:12 | a6 | arrayGetsSets.kt:17:14:17:14 | 0 | -| arrayGetsSets.kt:18:3:18:7 | ...[...] | arrayGetsSets.kt:18:3:18:7 | ...=... | boolean[] | arrayGetsSets.kt:18:3:18:4 | a7 | arrayGetsSets.kt:18:6:18:6 | 0 | -| arrayGetsSets.kt:18:11:18:15 | ...[...] | arrayGetsSets.kt:18:3:18:7 | ...=... | boolean | arrayGetsSets.kt:18:11:18:12 | a7 | arrayGetsSets.kt:18:14:18:14 | 0 | -| arrayGetsSets.kt:19:3:19:7 | ...[...] | arrayGetsSets.kt:19:3:19:7 | ...=... | char[] | arrayGetsSets.kt:19:3:19:4 | a8 | arrayGetsSets.kt:19:6:19:6 | 0 | -| arrayGetsSets.kt:19:11:19:15 | ...[...] | arrayGetsSets.kt:19:3:19:7 | ...=... | char | arrayGetsSets.kt:19:11:19:12 | a8 | arrayGetsSets.kt:19:14:19:14 | 0 | -| arrayGetsSets.kt:20:3:20:7 | ...[...] | arrayGetsSets.kt:20:3:20:7 | ...=... | Object[] | arrayGetsSets.kt:20:3:20:4 | a9 | arrayGetsSets.kt:20:6:20:6 | 0 | -| arrayGetsSets.kt:20:11:20:15 | ...[...] | arrayGetsSets.kt:20:3:20:7 | ...=... | Object | arrayGetsSets.kt:20:11:20:12 | a9 | arrayGetsSets.kt:20:14:20:14 | 0 | -| arrayGetsSets.kt:32:3:32:7 | ...[...] | arrayGetsSets.kt:32:3:32:7 | ...+=... | int | arrayGetsSets.kt:32:3:32:4 | a1 | arrayGetsSets.kt:32:6:32:6 | 0 | -| arrayGetsSets.kt:38:3:38:7 | ...[...] | arrayGetsSets.kt:38:3:38:7 | .../=... | long | arrayGetsSets.kt:38:3:38:4 | a4 | arrayGetsSets.kt:38:6:38:6 | 0 | -| arrayGetsSets.kt:39:3:39:7 | ...[...] | arrayGetsSets.kt:39:3:39:7 | ...-=... | float | arrayGetsSets.kt:39:3:39:4 | a5 | arrayGetsSets.kt:39:6:39:6 | 0 | -| arrayGetsSets.kt:40:3:40:7 | ...[...] | arrayGetsSets.kt:40:3:40:7 | ...*=... | double | arrayGetsSets.kt:40:3:40:4 | a6 | arrayGetsSets.kt:40:6:40:6 | 0 | +| arrayGetsSets.kt:12:3:12:15 | ...[...] | arrayGetsSets.kt:12:3:12:15 | ...=... | int[] | arrayGetsSets.kt:12:3:12:4 | a1 | arrayGetsSets.kt:12:6:12:6 | 0 | +| arrayGetsSets.kt:12:11:12:15 | ...[...] | arrayGetsSets.kt:12:3:12:15 | ...=... | int | arrayGetsSets.kt:12:11:12:12 | a1 | arrayGetsSets.kt:12:14:12:14 | 0 | +| arrayGetsSets.kt:13:3:13:15 | ...[...] | arrayGetsSets.kt:13:3:13:15 | ...=... | short[] | arrayGetsSets.kt:13:3:13:4 | a2 | arrayGetsSets.kt:13:6:13:6 | 0 | +| arrayGetsSets.kt:13:11:13:15 | ...[...] | arrayGetsSets.kt:13:3:13:15 | ...=... | short | arrayGetsSets.kt:13:11:13:12 | a2 | arrayGetsSets.kt:13:14:13:14 | 0 | +| arrayGetsSets.kt:14:3:14:15 | ...[...] | arrayGetsSets.kt:14:3:14:15 | ...=... | byte[] | arrayGetsSets.kt:14:3:14:4 | a3 | arrayGetsSets.kt:14:6:14:6 | 0 | +| arrayGetsSets.kt:14:11:14:15 | ...[...] | arrayGetsSets.kt:14:3:14:15 | ...=... | byte | arrayGetsSets.kt:14:11:14:12 | a3 | arrayGetsSets.kt:14:14:14:14 | 0 | +| arrayGetsSets.kt:15:3:15:15 | ...[...] | arrayGetsSets.kt:15:3:15:15 | ...=... | long[] | arrayGetsSets.kt:15:3:15:4 | a4 | arrayGetsSets.kt:15:6:15:6 | 0 | +| arrayGetsSets.kt:15:11:15:15 | ...[...] | arrayGetsSets.kt:15:3:15:15 | ...=... | long | arrayGetsSets.kt:15:11:15:12 | a4 | arrayGetsSets.kt:15:14:15:14 | 0 | +| arrayGetsSets.kt:16:3:16:15 | ...[...] | arrayGetsSets.kt:16:3:16:15 | ...=... | float[] | arrayGetsSets.kt:16:3:16:4 | a5 | arrayGetsSets.kt:16:6:16:6 | 0 | +| arrayGetsSets.kt:16:11:16:15 | ...[...] | arrayGetsSets.kt:16:3:16:15 | ...=... | float | arrayGetsSets.kt:16:11:16:12 | a5 | arrayGetsSets.kt:16:14:16:14 | 0 | +| arrayGetsSets.kt:17:3:17:15 | ...[...] | arrayGetsSets.kt:17:3:17:15 | ...=... | double[] | arrayGetsSets.kt:17:3:17:4 | a6 | arrayGetsSets.kt:17:6:17:6 | 0 | +| arrayGetsSets.kt:17:11:17:15 | ...[...] | arrayGetsSets.kt:17:3:17:15 | ...=... | double | arrayGetsSets.kt:17:11:17:12 | a6 | arrayGetsSets.kt:17:14:17:14 | 0 | +| arrayGetsSets.kt:18:3:18:15 | ...[...] | arrayGetsSets.kt:18:3:18:15 | ...=... | boolean[] | arrayGetsSets.kt:18:3:18:4 | a7 | arrayGetsSets.kt:18:6:18:6 | 0 | +| arrayGetsSets.kt:18:11:18:15 | ...[...] | arrayGetsSets.kt:18:3:18:15 | ...=... | boolean | arrayGetsSets.kt:18:11:18:12 | a7 | arrayGetsSets.kt:18:14:18:14 | 0 | +| arrayGetsSets.kt:19:3:19:15 | ...[...] | arrayGetsSets.kt:19:3:19:15 | ...=... | char[] | arrayGetsSets.kt:19:3:19:4 | a8 | arrayGetsSets.kt:19:6:19:6 | 0 | +| arrayGetsSets.kt:19:11:19:15 | ...[...] | arrayGetsSets.kt:19:3:19:15 | ...=... | char | arrayGetsSets.kt:19:11:19:12 | a8 | arrayGetsSets.kt:19:14:19:14 | 0 | +| arrayGetsSets.kt:20:3:20:15 | ...[...] | arrayGetsSets.kt:20:3:20:15 | ...=... | Object[] | arrayGetsSets.kt:20:3:20:4 | a9 | arrayGetsSets.kt:20:6:20:6 | 0 | +| arrayGetsSets.kt:20:11:20:15 | ...[...] | arrayGetsSets.kt:20:3:20:15 | ...=... | Object | arrayGetsSets.kt:20:11:20:12 | a9 | arrayGetsSets.kt:20:14:20:14 | 0 | +| arrayGetsSets.kt:32:3:32:12 | ...[...] | arrayGetsSets.kt:32:3:32:12 | ...+=... | int | arrayGetsSets.kt:32:3:32:4 | a1 | arrayGetsSets.kt:32:6:32:6 | 0 | +| arrayGetsSets.kt:38:3:38:13 | ...[...] | arrayGetsSets.kt:38:3:38:13 | .../=... | long | arrayGetsSets.kt:38:3:38:4 | a4 | arrayGetsSets.kt:38:6:38:6 | 0 | +| arrayGetsSets.kt:39:3:39:13 | ...[...] | arrayGetsSets.kt:39:3:39:13 | ...-=... | float | arrayGetsSets.kt:39:3:39:4 | a5 | arrayGetsSets.kt:39:6:39:6 | 0 | +| arrayGetsSets.kt:40:3:40:14 | ...[...] | arrayGetsSets.kt:40:3:40:14 | ...*=... | double | arrayGetsSets.kt:40:3:40:4 | a6 | arrayGetsSets.kt:40:6:40:6 | 0 | diff --git a/java/ql/test-kotlin1/library-tests/arrays/assignExprs.expected b/java/ql/test-kotlin1/library-tests/arrays/assignExprs.expected index da09855b1e0..5f8fda311e2 100644 --- a/java/ql/test-kotlin1/library-tests/arrays/assignExprs.expected +++ b/java/ql/test-kotlin1/library-tests/arrays/assignExprs.expected @@ -1,4 +1,4 @@ -| arrayGetsSets.kt:32:3:32:7 | ...+=... | += | int[] | arrayGetsSets.kt:32:3:32:7 | ...[...] | int | arrayGetsSets.kt:32:12:32:12 | 1 | int | -| arrayGetsSets.kt:38:3:38:7 | .../=... | /= | long[] | arrayGetsSets.kt:38:3:38:7 | ...[...] | long | arrayGetsSets.kt:38:12:38:13 | 1 | long | -| arrayGetsSets.kt:39:3:39:7 | ...-=... | -= | float[] | arrayGetsSets.kt:39:3:39:7 | ...[...] | float | arrayGetsSets.kt:39:12:39:13 | 1.0 | float | -| arrayGetsSets.kt:40:3:40:7 | ...*=... | *= | double[] | arrayGetsSets.kt:40:3:40:7 | ...[...] | double | arrayGetsSets.kt:40:12:40:14 | 1.0 | double | +| arrayGetsSets.kt:32:3:32:12 | ...+=... | += | int[] | arrayGetsSets.kt:32:3:32:12 | ...[...] | int | arrayGetsSets.kt:32:12:32:12 | 1 | int | +| arrayGetsSets.kt:38:3:38:13 | .../=... | /= | long[] | arrayGetsSets.kt:38:3:38:13 | ...[...] | long | arrayGetsSets.kt:38:12:38:13 | 1 | long | +| arrayGetsSets.kt:39:3:39:13 | ...-=... | -= | float[] | arrayGetsSets.kt:39:3:39:13 | ...[...] | float | arrayGetsSets.kt:39:12:39:13 | 1.0 | float | +| arrayGetsSets.kt:40:3:40:14 | ...*=... | *= | double[] | arrayGetsSets.kt:40:3:40:14 | ...[...] | double | arrayGetsSets.kt:40:12:40:14 | 1.0 | double |