Skip to content

Commit 5146b01

Browse files
Kotlin: narrow plain-assignment LHS VarAccess to the target identifier under K2
For a plain assignment `x = v`, the K2/FIR frontend records the set operation's end offset past the assigned value, so the left-hand-side `VarAccess` was located over the whole `x = v` rather than just the target identifier `x`. K1's set-value end offset already stops at the identifier, so the two frontends diverged (and this cascaded into the controlflow tests). A `VarAccess` should be located at the variable reference, not the whole assignment, so K1's identifier-only span is canonical. `getPlainSetValueLhsIdentifierLocation` narrows the LHS location to the target identifier for a plain `IrSetValue`, deriving the identifier end as `startOffset + name.length`. This is applied only when the target name is an unquoted simple identifier (not special, not a hard keyword, no backtick-requiring characters), so backtick-quoted names, keyword names and qualified receivers all fall back to the raw location. Gated on K2, since under K1 the raw location is already identifier-only. Relearned both suites (3333 tests each); only K2 expected files change, all narrowing LHS `VarAccess` locations to match K1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86bfc022-3ecc-4746-ba23-3b76c4e4c3e4
1 parent f8f1472 commit 5146b01

6 files changed

Lines changed: 159 additions & 72 deletions

File tree

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

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6403,6 +6403,89 @@ open class KotlinFileExtractor(
64036403
else null
64046404
}
64056405

6406+
/**
6407+
* Kotlin's hard keywords, which cannot be used as an identifier without backtick-quoting. When
6408+
* a declaration's name equals one of these, its source occurrence is the backtick-quoted form
6409+
* (e.g. `` `is` ``), so the source token is longer than the name and offset-plus-name-length
6410+
* arithmetic would misplace the identifier's end. Such names are therefore excluded from the
6411+
* plain-assignment LHS narrowing below.
6412+
*/
6413+
private val hardKeywords =
6414+
setOf(
6415+
"as",
6416+
"break",
6417+
"class",
6418+
"continue",
6419+
"do",
6420+
"else",
6421+
"false",
6422+
"for",
6423+
"fun",
6424+
"if",
6425+
"in",
6426+
"interface",
6427+
"is",
6428+
"null",
6429+
"object",
6430+
"package",
6431+
"return",
6432+
"super",
6433+
"this",
6434+
"throw",
6435+
"true",
6436+
"try",
6437+
"typealias",
6438+
"typeof",
6439+
"val",
6440+
"var",
6441+
"when",
6442+
"while"
6443+
)
6444+
6445+
/**
6446+
* Returns true when [name] is an unquoted simple Kotlin identifier: it is non-empty, starts
6447+
* with a letter or underscore, contains only letters, digits and underscores, and is not a
6448+
* hard keyword. For such a name the source occurrence contains exactly [name] with no
6449+
* backtick-quoting, so its character length matches the source token's length.
6450+
*/
6451+
private fun isUnquotedSimpleIdentifier(name: String): Boolean {
6452+
if (name.isEmpty()) return false
6453+
if (!(name[0].isLetter() || name[0] == '_')) return false
6454+
if (!name.all { it.isLetterOrDigit() || it == '_' }) return false
6455+
return name !in hardKeywords
6456+
}
6457+
6458+
/**
6459+
* For a plain assignment `x = v` (an `IrSetValue` that is not a desugared in-place update), the
6460+
* K2 frontend records the set operation's end offset past the assigned value, so
6461+
* `getLocation(e)` spans the whole `x = v` rather than just the target identifier `x`; K1's set
6462+
* end offset already stops at the identifier. This returns a location covering only the target
6463+
* identifier so both frontends agree.
6464+
*
6465+
* The identifier's end offset is derived as `startOffset + name.length`, which is safe only
6466+
* when the target's name is an unquoted simple identifier (so the source token equals the name
6467+
* with no backtick-quoting and no leading receiver). Returns null in every other case - special
6468+
* or backtick-requiring names, invalid/synthetic offsets, or an end running into the assigned
6469+
* value - so callers keep the raw location. Deliberately gated on [usesK2]: under K1 the raw
6470+
* location is already identifier-only, so this must not perturb it.
6471+
*/
6472+
private fun getPlainSetValueLhsIdentifierLocation(e: IrSetValue): Label<DbLocation>? {
6473+
if (!usesK2) return null
6474+
val start = e.startOffset
6475+
if (start == UNDEFINED_OFFSET || start == SYNTHETIC_OFFSET) return null
6476+
val name = e.symbol.owner.name
6477+
if (name.isSpecial) return null
6478+
val text = name.asString()
6479+
if (!isUnquotedSimpleIdentifier(text)) return null
6480+
val end = start + text.length
6481+
val valueStart = e.value.startOffset
6482+
if (
6483+
valueStart != UNDEFINED_OFFSET && valueStart != SYNTHETIC_OFFSET && end > valueStart
6484+
)
6485+
return null
6486+
return tw.getLocation(start, end)
6487+
}
6488+
64066489
private fun getUpdateInPlaceRHS(
64076490
origin: IrStatementOrigin?,
64086491
isExpectedLhs: (IrExpression?) -> Boolean,
@@ -7179,19 +7262,23 @@ open class KotlinFileExtractor(
71797262
// For a desugared in-place update (`v += e`) the K2 frontend records the set
71807263
// operation's end offset past the whole assignment, so `getLocation(e)` would
71817264
// span `v += e` rather than just `v`. Locate the LHS `VarAccess` at the update's
7182-
// receiver read of `v` instead, whose span is the identifier in both frontends;
7183-
// fall back to the raw location when this is not such an in-place update or the
7184-
// receiver lacks a usable source span.
7265+
// receiver read of `v` instead, whose span is the identifier in both frontends.
7266+
// For a plain assignment (`v = e`) K2 similarly spans the whole assignment, so
7267+
// narrow to the target identifier via `getPlainSetValueLhsIdentifierLocation`.
7268+
// Fall back to the raw location when neither applies or lacks a usable span.
71857269
val lhsLocId =
71867270
(e as? IrSetValue)
7187-
?.let { getUpdateInPlaceReceiver(it) }
7188-
?.takeIf {
7189-
it.startOffset != UNDEFINED_OFFSET &&
7190-
it.endOffset != UNDEFINED_OFFSET &&
7191-
it.startOffset != SYNTHETIC_OFFSET &&
7192-
it.endOffset != SYNTHETIC_OFFSET
7193-
}
7194-
?.let { tw.getLocation(it) } ?: tw.getLocation(e)
7271+
?.let { setValue ->
7272+
getUpdateInPlaceReceiver(setValue)
7273+
?.takeIf {
7274+
it.startOffset != UNDEFINED_OFFSET &&
7275+
it.endOffset != UNDEFINED_OFFSET &&
7276+
it.startOffset != SYNTHETIC_OFFSET &&
7277+
it.endOffset != SYNTHETIC_OFFSET
7278+
}
7279+
?.let { tw.getLocation(it) }
7280+
?: getPlainSetValueLhsIdentifierLocation(setValue)
7281+
} ?: tw.getLocation(e)
71957282
extractExprContext(lhsId, lhsLocId, callable, exprParent.enclosingStmt)
71967283

71977284
when (e) {

java/ql/test-kotlin2/library-tests/controlflow/basic/bbStmts.expected

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
| Test.kt:11:3:16:3 | After when ... | 1 | Test.kt:11:3:16:3 | After <Expr>; |
4747
| Test.kt:11:3:16:3 | After when ... | 2 | Test.kt:18:3:18:7 | <Expr>; |
4848
| Test.kt:11:3:16:3 | After when ... | 3 | Test.kt:18:3:18:7 | Before ...=... |
49-
| Test.kt:11:3:16:3 | After when ... | 4 | Test.kt:18:3:18:7 | z |
49+
| Test.kt:11:3:16:3 | After when ... | 4 | Test.kt:18:3:18:3 | z |
5050
| Test.kt:11:3:16:3 | After when ... | 5 | Test.kt:18:7:18:7 | 0 |
5151
| Test.kt:11:3:16:3 | After when ... | 6 | Test.kt:18:3:18:7 | ...=... |
5252
| Test.kt:11:3:16:3 | After when ... | 7 | Test.kt:18:3:18:7 | After ...=... |
@@ -65,7 +65,7 @@
6565
| Test.kt:11:7:11:11 | After ... > ... [false] | 4 | Test.kt:14:10:16:3 | { ... } |
6666
| Test.kt:11:7:11:11 | After ... > ... [false] | 5 | Test.kt:15:4:15:9 | <Expr>; |
6767
| Test.kt:11:7:11:11 | After ... > ... [false] | 6 | Test.kt:15:4:15:9 | Before ...=... |
68-
| Test.kt:11:7:11:11 | After ... > ... [false] | 7 | Test.kt:15:4:15:9 | y |
68+
| Test.kt:11:7:11:11 | After ... > ... [false] | 7 | Test.kt:15:4:15:4 | y |
6969
| Test.kt:11:7:11:11 | After ... > ... [false] | 8 | Test.kt:15:8:15:9 | 30 |
7070
| Test.kt:11:7:11:11 | After ... > ... [false] | 9 | Test.kt:15:4:15:9 | ...=... |
7171
| Test.kt:11:7:11:11 | After ... > ... [false] | 10 | Test.kt:15:4:15:9 | After ...=... |
@@ -75,14 +75,14 @@
7575
| Test.kt:11:7:11:11 | After ... > ... [true] | 1 | Test.kt:11:14:14:3 | { ... } |
7676
| Test.kt:11:7:11:11 | After ... > ... [true] | 2 | Test.kt:12:4:12:9 | <Expr>; |
7777
| Test.kt:11:7:11:11 | After ... > ... [true] | 3 | Test.kt:12:4:12:9 | Before ...=... |
78-
| Test.kt:11:7:11:11 | After ... > ... [true] | 4 | Test.kt:12:4:12:9 | y |
78+
| Test.kt:11:7:11:11 | After ... > ... [true] | 4 | Test.kt:12:4:12:4 | y |
7979
| Test.kt:11:7:11:11 | After ... > ... [true] | 5 | Test.kt:12:8:12:9 | 20 |
8080
| Test.kt:11:7:11:11 | After ... > ... [true] | 6 | Test.kt:12:4:12:9 | ...=... |
8181
| Test.kt:11:7:11:11 | After ... > ... [true] | 7 | Test.kt:12:4:12:9 | After ...=... |
8282
| Test.kt:11:7:11:11 | After ... > ... [true] | 8 | Test.kt:12:4:12:9 | After <Expr>; |
8383
| Test.kt:11:7:11:11 | After ... > ... [true] | 9 | Test.kt:13:4:13:9 | <Expr>; |
8484
| Test.kt:11:7:11:11 | After ... > ... [true] | 10 | Test.kt:13:4:13:9 | Before ...=... |
85-
| Test.kt:11:7:11:11 | After ... > ... [true] | 11 | Test.kt:13:4:13:9 | z |
85+
| Test.kt:11:7:11:11 | After ... > ... [true] | 11 | Test.kt:13:4:13:4 | z |
8686
| Test.kt:11:7:11:11 | After ... > ... [true] | 12 | Test.kt:13:8:13:9 | 10 |
8787
| Test.kt:11:7:11:11 | After ... > ... [true] | 13 | Test.kt:13:4:13:9 | ...=... |
8888
| Test.kt:11:7:11:11 | After ... > ... [true] | 14 | Test.kt:13:4:13:9 | After ...=... |
@@ -98,7 +98,7 @@
9898
| Test.kt:21:6:21:10 | After ... < ... [true] | 0 | Test.kt:21:6:21:10 | After ... < ... [true] |
9999
| Test.kt:21:6:21:10 | After ... < ... [true] | 1 | Test.kt:22:4:22:9 | <Expr>; |
100100
| Test.kt:21:6:21:10 | After ... < ... [true] | 2 | Test.kt:22:4:22:9 | Before ...=... |
101-
| Test.kt:21:6:21:10 | After ... < ... [true] | 3 | Test.kt:22:4:22:9 | y |
101+
| Test.kt:21:6:21:10 | After ... < ... [true] | 3 | Test.kt:22:4:22:4 | y |
102102
| Test.kt:21:6:21:10 | After ... < ... [true] | 4 | Test.kt:22:8:22:9 | 40 |
103103
| Test.kt:21:6:21:10 | After ... < ... [true] | 5 | Test.kt:22:4:22:9 | ...=... |
104104
| Test.kt:21:6:21:10 | After ... < ... [true] | 6 | Test.kt:22:4:22:9 | After ...=... |
@@ -107,7 +107,7 @@
107107
| Test.kt:21:6:21:10 | After ... < ... [true] | 9 | Test.kt:21:3:24:9 | After <Expr>; |
108108
| Test.kt:21:6:21:10 | After ... < ... [true] | 10 | Test.kt:27:3:27:8 | <Expr>; |
109109
| Test.kt:21:6:21:10 | After ... < ... [true] | 11 | Test.kt:27:3:27:8 | Before ...=... |
110-
| Test.kt:21:6:21:10 | After ... < ... [true] | 12 | Test.kt:27:3:27:8 | z |
110+
| Test.kt:21:6:21:10 | After ... < ... [true] | 12 | Test.kt:27:3:27:3 | z |
111111
| Test.kt:21:6:21:10 | After ... < ... [true] | 13 | Test.kt:27:7:27:8 | 10 |
112112
| Test.kt:21:6:21:10 | After ... < ... [true] | 14 | Test.kt:27:3:27:8 | ...=... |
113113
| Test.kt:21:6:21:10 | After ... < ... [true] | 15 | Test.kt:27:3:27:8 | After ...=... |
@@ -123,7 +123,7 @@
123123
| Test.kt:30:3:33:3 | After when ... | 1 | Test.kt:30:3:33:3 | After <Expr>; |
124124
| Test.kt:30:3:33:3 | After when ... | 2 | Test.kt:35:3:35:8 | <Expr>; |
125125
| Test.kt:30:3:33:3 | After when ... | 3 | Test.kt:35:3:35:8 | Before ...=... |
126-
| Test.kt:30:3:33:3 | After when ... | 4 | Test.kt:35:3:35:8 | z |
126+
| Test.kt:30:3:33:3 | After when ... | 4 | Test.kt:35:3:35:3 | z |
127127
| Test.kt:30:3:33:3 | After when ... | 5 | Test.kt:35:7:35:8 | 20 |
128128
| Test.kt:30:3:33:3 | After when ... | 6 | Test.kt:35:3:35:8 | ...=... |
129129
| Test.kt:30:3:33:3 | After when ... | 7 | Test.kt:35:3:35:8 | After ...=... |
@@ -134,14 +134,14 @@
134134
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 1 | Test.kt:30:15:33:3 | { ... } |
135135
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 2 | Test.kt:31:4:31:9 | <Expr>; |
136136
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 3 | Test.kt:31:4:31:9 | Before ...=... |
137-
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 4 | Test.kt:31:4:31:9 | y |
137+
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 4 | Test.kt:31:4:31:4 | y |
138138
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 5 | Test.kt:31:8:31:9 | 60 |
139139
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 6 | Test.kt:31:4:31:9 | ...=... |
140140
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 7 | Test.kt:31:4:31:9 | After ...=... |
141141
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 8 | Test.kt:31:4:31:9 | After <Expr>; |
142142
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 9 | Test.kt:32:4:32:9 | <Expr>; |
143143
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 10 | Test.kt:32:4:32:9 | Before ...=... |
144-
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 11 | Test.kt:32:4:32:9 | z |
144+
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 11 | Test.kt:32:4:32:4 | z |
145145
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 12 | Test.kt:32:8:32:9 | 10 |
146146
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 13 | Test.kt:32:4:32:9 | ...=... |
147147
| Test.kt:30:7:30:12 | After ... (value equals) ... [true] | 14 | Test.kt:32:4:32:9 | After ...=... |
@@ -156,21 +156,21 @@
156156
| Test.kt:38:9:38:13 | After ... > ... [false] | 1 | Test.kt:38:3:41:3 | After while (...) |
157157
| Test.kt:38:9:38:13 | After ... > ... [false] | 2 | Test.kt:43:3:43:8 | <Expr>; |
158158
| Test.kt:38:9:38:13 | After ... > ... [false] | 3 | Test.kt:43:3:43:8 | Before ...=... |
159-
| Test.kt:38:9:38:13 | After ... > ... [false] | 4 | Test.kt:43:3:43:8 | z |
159+
| Test.kt:38:9:38:13 | After ... > ... [false] | 4 | Test.kt:43:3:43:3 | z |
160160
| Test.kt:38:9:38:13 | After ... > ... [false] | 5 | Test.kt:43:7:43:8 | 30 |
161161
| Test.kt:38:9:38:13 | After ... > ... [false] | 6 | Test.kt:43:3:43:8 | ...=... |
162162
| Test.kt:38:9:38:13 | After ... > ... [false] | 7 | Test.kt:43:3:43:8 | After ...=... |
163163
| Test.kt:38:9:38:13 | After ... > ... [false] | 8 | Test.kt:43:3:43:8 | After <Expr>; |
164164
| Test.kt:38:9:38:13 | After ... > ... [false] | 9 | Test.kt:73:3:73:8 | <Expr>; |
165165
| Test.kt:38:9:38:13 | After ... > ... [false] | 10 | Test.kt:73:3:73:8 | Before ...=... |
166-
| Test.kt:38:9:38:13 | After ... > ... [false] | 11 | Test.kt:73:3:73:8 | z |
166+
| Test.kt:38:9:38:13 | After ... > ... [false] | 11 | Test.kt:73:3:73:3 | z |
167167
| Test.kt:38:9:38:13 | After ... > ... [false] | 12 | Test.kt:73:7:73:8 | 50 |
168168
| Test.kt:38:9:38:13 | After ... > ... [false] | 13 | Test.kt:73:3:73:8 | ...=... |
169169
| Test.kt:38:9:38:13 | After ... > ... [false] | 14 | Test.kt:73:3:73:8 | After ...=... |
170170
| Test.kt:38:9:38:13 | After ... > ... [false] | 15 | Test.kt:73:3:73:8 | After <Expr>; |
171171
| Test.kt:38:9:38:13 | After ... > ... [false] | 16 | Test.kt:77:3:77:8 | <Expr>; |
172172
| Test.kt:38:9:38:13 | After ... > ... [false] | 17 | Test.kt:77:3:77:8 | Before ...=... |
173-
| Test.kt:38:9:38:13 | After ... > ... [false] | 18 | Test.kt:77:3:77:8 | w |
173+
| Test.kt:38:9:38:13 | After ... > ... [false] | 18 | Test.kt:77:3:77:3 | w |
174174
| Test.kt:38:9:38:13 | After ... > ... [false] | 19 | Test.kt:77:7:77:8 | 40 |
175175
| Test.kt:38:9:38:13 | After ... > ... [false] | 20 | Test.kt:77:3:77:8 | ...=... |
176176
| Test.kt:38:9:38:13 | After ... > ... [false] | 21 | Test.kt:77:3:77:8 | After ...=... |
@@ -182,7 +182,7 @@
182182
| Test.kt:38:9:38:13 | After ... > ... [true] | 1 | Test.kt:38:16:41:3 | { ... } |
183183
| Test.kt:38:9:38:13 | After ... > ... [true] | 2 | Test.kt:39:4:39:9 | <Expr>; |
184184
| Test.kt:38:9:38:13 | After ... > ... [true] | 3 | Test.kt:39:4:39:9 | Before ...=... |
185-
| Test.kt:38:9:38:13 | After ... > ... [true] | 4 | Test.kt:39:4:39:9 | y |
185+
| Test.kt:38:9:38:13 | After ... > ... [true] | 4 | Test.kt:39:4:39:4 | y |
186186
| Test.kt:38:9:38:13 | After ... > ... [true] | 5 | Test.kt:39:8:39:9 | 10 |
187187
| Test.kt:38:9:38:13 | After ... > ... [true] | 6 | Test.kt:39:4:39:9 | ...=... |
188188
| Test.kt:38:9:38:13 | After ... > ... [true] | 7 | Test.kt:39:4:39:9 | After ...=... |
@@ -199,7 +199,7 @@
199199
| Test.kt:38:9:38:13 | After ... > ... [true] | 18 | Test.kt:40:4:40:6 | After var ...; |
200200
| Test.kt:38:9:38:13 | After ... > ... [true] | 19 | Test.kt:40:4:40:6 | <Expr>; |
201201
| Test.kt:38:9:38:13 | After ... > ... [true] | 20 | Test.kt:40:4:40:6 | Before ...=... |
202-
| Test.kt:38:9:38:13 | After ... > ... [true] | 21 | Test.kt:40:4:40:6 | x |
202+
| Test.kt:38:9:38:13 | After ... > ... [true] | 21 | Test.kt:40:4:40:4 | x |
203203
| Test.kt:38:9:38:13 | After ... > ... [true] | 22 | Test.kt:40:4:40:6 | Before dec(...) |
204204
| Test.kt:38:9:38:13 | After ... > ... [true] | 23 | Test.kt:40:4:40:6 | <unary> |
205205
| Test.kt:38:9:38:13 | After ... > ... [true] | 24 | Test.kt:40:4:40:6 | dec(...) |

0 commit comments

Comments
 (0)