Skip to content

Commit f8f1472

Browse files
Kotlin: span assignment-desugared setter calls through the assigned value
An assignment to a property, delegated property or `@JvmStatic` property (`lhs = rhs`) desugars to a setter call. The two frontends anchor that synthesised call differently: - K1 records the call (and its synthetic implicit-`this` receiver and any synthetic reflection arguments) at the assignment's left-hand side only (`varResource0 = 3` -> `37:9:37:20`, `curValue = value` -> `29:17:29:24`). - K2 spans the whole assignment, through the assigned value (`37:9:37:24`, `29:17:29:32`). The setter call *is* the desugaring of the whole assignment, so K2's whole-assignment span is the more intuitive, information-preserving one: it keeps the call on the source construct it represents rather than dropping the right-hand side. This matches the existing array-`set` EQ-origin widening precedent, which already widens K1 onto the whole-assignment span. We converge K1 onto the K2 span with a scoped offset remap set only while extracting the setter call, keyed on the call's exact offset pair. Because the compiler gives the synthetic implicit-`this` receiver and the synthetic reflection arguments the same offsets as the call, the single remap widens them along with the `MethodCall` node, while real source arguments (the assigned value itself, e.g. the `2` / `3` / `value`) keep their own offsets and are unaffected. The remap fires only for an `IrStatementOrigin.EQ` call whose last value argument (the assigned value) ends past the call's own end offset, so it is a no-op under K2 (where the call already spans the assigned value) and for every non-assignment call. Tradeoff: the direction is fixed (K1 -> K2) because K1 cannot, from its raw IR, recover the whole-assignment span for these synthetic children; K2 produces it natively. That is acceptable here because the whole-assignment span is the more correct one on the merits and is already the established convention for array `set`. Expected updates (K1 only; K2 already emitted these): - library-tests/exprs/exprs.expected (delegated + direct property setters) - library-tests/generic-instance-methods/test.expected (setStored) - library-tests/jvmstatic-annotation/test.expected (@JvmStatic setters) Both suites relearned; all tests pass. Divergence 798 -> 758 rows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86bfc022-3ecc-4746-ba23-3b76c4e4c3e4
1 parent 8267d33 commit f8f1472

4 files changed

Lines changed: 77 additions & 21 deletions

File tree

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4986,6 +4986,40 @@ open class KotlinFileExtractor(
49864986
else -> false
49874987
}
49884988

4989+
/**
4990+
* For a property/delegated-property setter [IrCall] desugared from an assignment
4991+
* `lhs = rhs` (origin [IrStatementOrigin.EQ]), returns the end offset of the assigned
4992+
* value (the call's last value argument) when it extends past the call's own end offset,
4993+
* or null otherwise.
4994+
*
4995+
* The K1 frontend records such a setter call (and its synthetic implicit-`this` receiver
4996+
* and synthetic reflection arguments) at the assignment's left-hand side only
4997+
* (`varResource0 = 3` -> `37:9:37:20`), whereas K2 spans the whole assignment through the
4998+
* assigned value (`37:9:37:24`). The setter call *is* the desugaring of the whole
4999+
* assignment, so K2's span is the more intuitive one; we converge K1 onto it. Returns null
5000+
* under K2 (where the call already spans the assigned value, so no widening is needed) and
5001+
* for any non-assignment call.
5002+
*/
5003+
private fun getSetterCallAssignedValueEndOffset(c: IrCall): Int? {
5004+
if (c.origin != IrStatementOrigin.EQ) return null
5005+
val n = c.codeQlValueArgumentsCount
5006+
if (n == 0) return null
5007+
val assignedValue = c.codeQlGetValueArgument(n - 1) ?: return null
5008+
val valueEnd = assignedValue.endOffset
5009+
if (
5010+
valueEnd == UNDEFINED_OFFSET ||
5011+
valueEnd == SYNTHETIC_OFFSET ||
5012+
c.startOffset == UNDEFINED_OFFSET ||
5013+
c.startOffset == SYNTHETIC_OFFSET ||
5014+
c.endOffset == UNDEFINED_OFFSET ||
5015+
c.endOffset == SYNTHETIC_OFFSET
5016+
) {
5017+
return null
5018+
}
5019+
if (valueEnd <= c.endOffset) return null
5020+
return valueEnd
5021+
}
5022+
49895023
private fun extractCall(
49905024
c: IrCall,
49915025
callable: Label<out DbCallable>,
@@ -5977,7 +6011,29 @@ open class KotlinFileExtractor(
59776011
}
59786012
}
59796013
else -> {
5980-
extractMethodAccess(target, true, true)
6014+
// A property/delegated-property setter call desugared from an assignment
6015+
// `lhs = rhs` is anchored by K1 at the left-hand side only, while K2 spans the
6016+
// whole assignment through the assigned value. Widen the K1 span to match by
6017+
// remapping the call's exact offset pair (which its synthetic implicit-`this`
6018+
// receiver and synthetic reflection arguments also carry) onto one that runs
6019+
// through the assigned value. Real source arguments have their own offsets and
6020+
// are unaffected; the remap is a no-op under K2 (helper returns null).
6021+
val assignedValueEnd = getSetterCallAssignedValueEndOffset(c)
6022+
if (assignedValueEnd != null) {
6023+
val previousRemap = tw.scopedOffsetRemap
6024+
tw.scopedOffsetRemap =
6025+
Pair(
6026+
Pair(c.startOffset, c.endOffset),
6027+
Pair(c.startOffset, assignedValueEnd)
6028+
)
6029+
try {
6030+
extractMethodAccess(target, true, true)
6031+
} finally {
6032+
tw.scopedOffsetRemap = previousRemap
6033+
}
6034+
} else {
6035+
extractMethodAccess(target, true, true)
6036+
}
59816037
}
59826038
}
59836039
}

java/ql/test-kotlin1/library-tests/exprs/exprs.expected

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@
9090
| delegatedProperties.kt:20:17:20:28 | <get-varResource1>(...) | delegatedProperties.kt:18:5:40:5 | fn | MethodCall |
9191
| delegatedProperties.kt:20:17:20:28 | Object | delegatedProperties.kt:18:5:40:5 | fn | TypeAccess |
9292
| delegatedProperties.kt:20:17:20:28 | new (...) | delegatedProperties.kt:18:5:40:5 | fn | ClassInstanceExpr |
93-
| delegatedProperties.kt:21:9:21:20 | <set-varResource1>(...) | delegatedProperties.kt:18:5:40:5 | fn | MethodCall |
94-
| delegatedProperties.kt:21:9:21:20 | Object | delegatedProperties.kt:18:5:40:5 | fn | TypeAccess |
95-
| delegatedProperties.kt:21:9:21:20 | new (...) | delegatedProperties.kt:18:5:40:5 | fn | ClassInstanceExpr |
93+
| delegatedProperties.kt:21:9:21:24 | <set-varResource1>(...) | delegatedProperties.kt:18:5:40:5 | fn | MethodCall |
94+
| delegatedProperties.kt:21:9:21:24 | Object | delegatedProperties.kt:18:5:40:5 | fn | TypeAccess |
95+
| delegatedProperties.kt:21:9:21:24 | new (...) | delegatedProperties.kt:18:5:40:5 | fn | ClassInstanceExpr |
9696
| delegatedProperties.kt:21:24:21:24 | 2 | delegatedProperties.kt:18:5:40:5 | fn | IntegerLiteral |
9797
| delegatedProperties.kt:23:9:23:31 | String | file://:0:0:0:0 | <none> | TypeAccess |
9898
| delegatedProperties.kt:23:9:23:31 | name$delegate | delegatedProperties.kt:18:5:40:5 | fn | LocalVariableDeclExpr |
@@ -140,8 +140,8 @@
140140
| delegatedProperties.kt:28:50:28:71 | ? ... | file://:0:0:0:0 | <none> | WildcardTypeAccess |
141141
| delegatedProperties.kt:28:50:28:71 | KProperty<?> | file://:0:0:0:0 | <none> | TypeAccess |
142142
| delegatedProperties.kt:28:74:28:83 | int | file://:0:0:0:0 | <none> | TypeAccess |
143-
| delegatedProperties.kt:29:17:29:24 | setCurValue(...) | delegatedProperties.kt:28:22:30:13 | setValue | MethodCall |
144-
| delegatedProperties.kt:29:17:29:24 | this | delegatedProperties.kt:28:22:30:13 | setValue | ThisAccess |
143+
| delegatedProperties.kt:29:17:29:32 | setCurValue(...) | delegatedProperties.kt:28:22:30:13 | setValue | MethodCall |
144+
| delegatedProperties.kt:29:17:29:32 | this | delegatedProperties.kt:28:22:30:13 | setValue | ThisAccess |
145145
| delegatedProperties.kt:29:28:29:32 | value | delegatedProperties.kt:28:22:30:13 | setValue | VarAccess |
146146
| delegatedProperties.kt:33:9:33:76 | int | file://:0:0:0:0 | <none> | TypeAccess |
147147
| delegatedProperties.kt:33:9:33:76 | readOnly$delegate | delegatedProperties.kt:18:5:40:5 | fn | LocalVariableDeclExpr |
@@ -198,8 +198,8 @@
198198
| delegatedProperties.kt:36:9:36:29 | println(...) | delegatedProperties.kt:18:5:40:5 | fn | MethodCall |
199199
| delegatedProperties.kt:36:17:36:28 | getVarResource0(...) | delegatedProperties.kt:18:5:40:5 | fn | MethodCall |
200200
| delegatedProperties.kt:36:17:36:28 | this | delegatedProperties.kt:18:5:40:5 | fn | ThisAccess |
201-
| delegatedProperties.kt:37:9:37:20 | setVarResource0(...) | delegatedProperties.kt:18:5:40:5 | fn | MethodCall |
202-
| delegatedProperties.kt:37:9:37:20 | this | delegatedProperties.kt:18:5:40:5 | fn | ThisAccess |
201+
| delegatedProperties.kt:37:9:37:24 | setVarResource0(...) | delegatedProperties.kt:18:5:40:5 | fn | MethodCall |
202+
| delegatedProperties.kt:37:9:37:24 | this | delegatedProperties.kt:18:5:40:5 | fn | ThisAccess |
203203
| delegatedProperties.kt:37:24:37:24 | 3 | delegatedProperties.kt:18:5:40:5 | fn | IntegerLiteral |
204204
| delegatedProperties.kt:39:9:39:51 | int | file://:0:0:0:0 | <none> | TypeAccess |
205205
| delegatedProperties.kt:39:9:39:51 | varResource2$delegate | delegatedProperties.kt:18:5:40:5 | fn | LocalVariableDeclExpr |

java/ql/test-kotlin1/library-tests/generic-instance-methods/test.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ calls
77
| Test.java:27:5:27:24 | getter(...) | Test.java:16:22:16:25 | user | Test.java:14:14:14:17 | Test | Generic2.class:0:0:0:0 | getter | Generic2.class:0:0:0:0 | Generic2<? super String> |
88
| test.kt:5:32:5:46 | identity(...) | test.kt:5:3:5:46 | identity2 | test.kt:1:1:13:1 | Generic | test.kt:6:3:6:35 | identity | test.kt:1:1:13:1 | Generic |
99
| test.kt:7:21:7:26 | getStored(...) | test.kt:7:3:7:26 | getter | test.kt:1:1:13:1 | Generic | test.kt:3:3:3:12 | getStored | test.kt:1:1:13:1 | Generic |
10-
| test.kt:8:26:8:31 | setStored(...) | test.kt:8:3:8:41 | setter | test.kt:1:1:13:1 | Generic | test.kt:3:3:3:12 | setStored | test.kt:1:1:13:1 | Generic |
10+
| test.kt:8:26:8:39 | setStored(...) | test.kt:8:3:8:41 | setter | test.kt:1:1:13:1 | Generic | test.kt:3:3:3:12 | setStored | test.kt:1:1:13:1 | Generic |
1111
| test.kt:11:44:11:70 | privateid(...) | test.kt:11:3:11:70 | callPrivateId | test.kt:1:1:13:1 | Generic | file:///!unknown-binary-location/Generic.class:0:0:0:0 | privateid | file:///!unknown-binary-location/Generic.class:0:0:0:0 | Generic<String> |
1212
| test.kt:18:3:18:35 | identity(...) | test.kt:15:1:28:1 | user | test.kt:0:0:0:0 | TestKt | file:///!unknown-binary-location/Generic.class:0:0:0:0 | identity | file:///!unknown-binary-location/Generic.class:0:0:0:0 | Generic<String> |
1313
| test.kt:19:3:19:36 | identity2(...) | test.kt:15:1:28:1 | user | test.kt:0:0:0:0 | TestKt | file:///!unknown-binary-location/Generic.class:0:0:0:0 | identity2 | file:///!unknown-binary-location/Generic.class:0:0:0:0 | Generic<String> |

java/ql/test-kotlin1/library-tests/jvmstatic-annotation/test.expected

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ staticMembers
3232
| test.kt:11:3:27:3 | Companion | test.kt:16:16:16:43 | setStaticProp(...) | test.kt:16:16:16:43 | HasCompanion.Companion | instance |
3333
| test.kt:11:3:27:3 | Companion | test.kt:20:18:20:45 | getPropWithStaticGetter(...) | test.kt:20:18:20:45 | HasCompanion.Companion | instance |
3434
| test.kt:11:3:27:3 | Companion | test.kt:20:26:20:45 | getPropWithStaticSetter(...) | test.kt:20:26:20:45 | this | instance |
35-
| test.kt:11:3:27:3 | Companion | test.kt:21:24:21:43 | setPropWithStaticSetter(...) | test.kt:21:24:21:43 | this | instance |
35+
| test.kt:11:3:27:3 | Companion | test.kt:21:24:21:47 | setPropWithStaticSetter(...) | test.kt:21:24:21:47 | this | instance |
3636
| test.kt:11:3:27:3 | Companion | test.kt:24:15:24:34 | getPropWithStaticGetter(...) | test.kt:24:15:24:34 | this | instance |
3737
| test.kt:11:3:27:3 | Companion | test.kt:25:18:25:60 | setPropWithStaticSetter(...) | test.kt:25:18:25:60 | HasCompanion.Companion | instance |
38-
| test.kt:11:3:27:3 | Companion | test.kt:25:35:25:54 | setPropWithStaticGetter(...) | test.kt:25:35:25:54 | this | instance |
38+
| test.kt:11:3:27:3 | Companion | test.kt:25:35:25:58 | setPropWithStaticGetter(...) | test.kt:25:35:25:58 | this | instance |
3939
| test.kt:11:3:27:3 | Companion | test.kt:52:3:52:32 | staticMethod(...) | test.kt:52:3:52:14 | Companion | instance |
4040
| test.kt:11:3:27:3 | Companion | test.kt:53:3:53:35 | nonStaticMethod(...) | test.kt:53:3:53:14 | Companion | instance |
41-
| test.kt:11:3:27:3 | Companion | test.kt:54:3:54:25 | setStaticProp(...) | test.kt:54:3:54:14 | Companion | instance |
41+
| test.kt:11:3:27:3 | Companion | test.kt:54:3:54:54 | setStaticProp(...) | test.kt:54:3:54:14 | Companion | instance |
4242
| test.kt:11:3:27:3 | Companion | test.kt:54:29:54:54 | getNonStaticProp(...) | test.kt:54:29:54:40 | Companion | instance |
43-
| test.kt:11:3:27:3 | Companion | test.kt:55:3:55:28 | setNonStaticProp(...) | test.kt:55:3:55:14 | Companion | instance |
43+
| test.kt:11:3:27:3 | Companion | test.kt:55:3:55:54 | setNonStaticProp(...) | test.kt:55:3:55:14 | Companion | instance |
4444
| test.kt:11:3:27:3 | Companion | test.kt:55:32:55:54 | getStaticProp(...) | test.kt:55:32:55:43 | Companion | instance |
45-
| test.kt:11:3:27:3 | Companion | test.kt:56:3:56:35 | setPropWithStaticGetter(...) | test.kt:56:3:56:14 | Companion | instance |
45+
| test.kt:11:3:27:3 | Companion | test.kt:56:3:56:71 | setPropWithStaticGetter(...) | test.kt:56:3:56:14 | Companion | instance |
4646
| test.kt:11:3:27:3 | Companion | test.kt:56:39:56:71 | getPropWithStaticSetter(...) | test.kt:56:39:56:50 | Companion | instance |
47-
| test.kt:11:3:27:3 | Companion | test.kt:57:3:57:35 | setPropWithStaticSetter(...) | test.kt:57:3:57:14 | Companion | instance |
47+
| test.kt:11:3:27:3 | Companion | test.kt:57:3:57:71 | setPropWithStaticSetter(...) | test.kt:57:3:57:14 | Companion | instance |
4848
| test.kt:11:3:27:3 | Companion | test.kt:57:39:57:71 | getPropWithStaticGetter(...) | test.kt:57:39:57:50 | Companion | instance |
4949
| test.kt:31:1:47:1 | NonCompanion | JavaUser.java:13:5:13:34 | staticMethod(...) | JavaUser.java:13:5:13:16 | NonCompanion | static |
5050
| test.kt:31:1:47:1 | NonCompanion | JavaUser.java:14:5:14:46 | nonStaticMethod(...) | JavaUser.java:14:5:14:25 | NonCompanion.INSTANCE | instance |
@@ -59,16 +59,16 @@ staticMembers
5959
| test.kt:31:1:47:1 | NonCompanion | test.kt:33:52:33:69 | nonStaticMethod(...) | test.kt:33:52:33:69 | NonCompanion.INSTANCE | instance |
6060
| test.kt:31:1:47:1 | NonCompanion | test.kt:34:44:34:58 | staticMethod(...) | test.kt:34:44:34:58 | NonCompanion | static |
6161
| test.kt:31:1:47:1 | NonCompanion | test.kt:40:24:40:43 | getPropWithStaticSetter(...) | test.kt:40:24:40:43 | NonCompanion.INSTANCE | instance |
62-
| test.kt:31:1:47:1 | NonCompanion | test.kt:41:22:41:41 | setPropWithStaticSetter(...) | test.kt:41:22:41:41 | NonCompanion | static |
62+
| test.kt:31:1:47:1 | NonCompanion | test.kt:41:22:41:45 | setPropWithStaticSetter(...) | test.kt:41:22:41:45 | NonCompanion | static |
6363
| test.kt:31:1:47:1 | NonCompanion | test.kt:44:13:44:32 | getPropWithStaticGetter(...) | test.kt:44:13:44:32 | NonCompanion | static |
64-
| test.kt:31:1:47:1 | NonCompanion | test.kt:45:33:45:52 | setPropWithStaticGetter(...) | test.kt:45:33:45:52 | NonCompanion.INSTANCE | instance |
64+
| test.kt:31:1:47:1 | NonCompanion | test.kt:45:33:45:56 | setPropWithStaticGetter(...) | test.kt:45:33:45:56 | NonCompanion.INSTANCE | instance |
6565
| test.kt:31:1:47:1 | NonCompanion | test.kt:60:3:60:32 | staticMethod(...) | test.kt:60:3:60:32 | NonCompanion | static |
6666
| test.kt:31:1:47:1 | NonCompanion | test.kt:61:3:61:35 | nonStaticMethod(...) | test.kt:61:3:61:14 | INSTANCE | instance |
67-
| test.kt:31:1:47:1 | NonCompanion | test.kt:62:3:62:25 | setStaticProp(...) | test.kt:62:3:62:25 | NonCompanion | static |
67+
| test.kt:31:1:47:1 | NonCompanion | test.kt:62:3:62:54 | setStaticProp(...) | test.kt:62:3:62:54 | NonCompanion | static |
6868
| test.kt:31:1:47:1 | NonCompanion | test.kt:62:29:62:54 | getNonStaticProp(...) | test.kt:62:29:62:40 | INSTANCE | instance |
69-
| test.kt:31:1:47:1 | NonCompanion | test.kt:63:3:63:28 | setNonStaticProp(...) | test.kt:63:3:63:14 | INSTANCE | instance |
69+
| test.kt:31:1:47:1 | NonCompanion | test.kt:63:3:63:54 | setNonStaticProp(...) | test.kt:63:3:63:14 | INSTANCE | instance |
7070
| test.kt:31:1:47:1 | NonCompanion | test.kt:63:32:63:54 | getStaticProp(...) | test.kt:63:32:63:54 | NonCompanion | static |
71-
| test.kt:31:1:47:1 | NonCompanion | test.kt:64:3:64:35 | setPropWithStaticGetter(...) | test.kt:64:3:64:14 | INSTANCE | instance |
71+
| test.kt:31:1:47:1 | NonCompanion | test.kt:64:3:64:71 | setPropWithStaticGetter(...) | test.kt:64:3:64:14 | INSTANCE | instance |
7272
| test.kt:31:1:47:1 | NonCompanion | test.kt:64:39:64:71 | getPropWithStaticSetter(...) | test.kt:64:39:64:50 | INSTANCE | instance |
73-
| test.kt:31:1:47:1 | NonCompanion | test.kt:65:3:65:35 | setPropWithStaticSetter(...) | test.kt:65:3:65:35 | NonCompanion | static |
73+
| test.kt:31:1:47:1 | NonCompanion | test.kt:65:3:65:71 | setPropWithStaticSetter(...) | test.kt:65:3:65:71 | NonCompanion | static |
7474
| test.kt:31:1:47:1 | NonCompanion | test.kt:65:39:65:71 | getPropWithStaticGetter(...) | test.kt:65:39:65:71 | NonCompanion | static |

0 commit comments

Comments
 (0)