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>
This commit is contained in:
Anders Fugmann
2026-07-11 20:15:14 +02:00
parent 79948931ab
commit 0de7f77ebc
4 changed files with 61 additions and 30 deletions

View File

@@ -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<DbArrayaccess>().also { arrayAccessId ->
@@ -6006,7 +6020,15 @@ open class KotlinFileExtractor(
val exprParent = parent.expr(e, callable)
val assignId = tw.getFreshIdLabel<DbAssignexpr>()
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,

View File

@@ -361,6 +361,15 @@ open class FileTrapWriter(
fun getLocation(e: IrElement): Label<DbLocation> {
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<DbLocation> {
return getLocation(getStartOffset(e), endOffset)
}
/**
* Gets a label for the location corresponding to `startOffset` and `endOffset` within this
* file.

View File

@@ -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 |

View File

@@ -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 |