Skip to content

Commit 7b1da21

Browse files
Kotlin: exclude the by keyword from delegated-property locations
For a delegated property `var x: Int by ResourceDelegate()` the two frontend paths anchor the synthesised delegate locations differently: - K1 (-language-version 1.9) starts them at the `by` keyword. - K2 (default) starts them at the delegate expression itself (e.g. `ResourceDelegate()`), three columns later (`by ` excluded). The `by` keyword is syntactic glue joining the property to its delegate, not part of the expression that is evaluated and stored, so K2's narrower range is the more intuitive one. An existing remap already converged the delegated-accessor *body* expressions onto the K2 span; this commit converges the two remaining sites. 1. The `$delegate` backing field. `getDelegateFieldLocation` recovers the delegate expression's range from the enclosing `KtProperty` via PSI (available under K1; null under K2, where the raw offsets already exclude `by`) and is used both for the field's declaration location (extractField) and for its initializer assignment / lhs access (extractFieldInitializer's declLocId). It fires only for a delegated property's own backing field (`isDelegated` and `backingField === f`). 2. Local delegated properties. The `provideDelegate(...)` call and its `KProperty` argument in the delegate variable's initializer span the whole `by <expr>` range under K1; the `IrLocalDelegatedProperty` branch now applies the same `scopedOffsetRemap` used for accessor bodies while extracting that variable. The shared PSI walk is factored into `getEnclosingKtProperty` and a `KtProperty`-keyed `getDelegateExpressionOffsetRemap` overload. Result: test-kotlin1 and test-kotlin2 exprs/delegatedProperties.expected and exprs/funcExprs.expected are now byte-identical. exprs/exprs.expected and methods/exprs.expected have their delegate rows converged (remaining diffs in those two files are unrelated mechanisms: implicit-cast nodes and the property-type / setter-parameter spans, addressed separately). All 3333 tests pass in both suites (K1 2.3.20 / lang 1.9 and K2 2.4.0 / lang 2.0). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5dc4830 commit 7b1da21

5 files changed

Lines changed: 140 additions & 86 deletions

File tree

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

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,7 +1610,7 @@ open class KotlinFileExtractor(
16101610

16111611
val expr = initializer.expression
16121612

1613-
val declLocId = tw.getLocation(f)
1613+
val declLocId = (f as? IrField)?.let { getDelegateFieldLocation(it) } ?: tw.getLocation(f)
16141614
extractExpressionStmt(
16151615
declLocId,
16161616
blockAndFunctionId.first,
@@ -2677,6 +2677,7 @@ open class KotlinFileExtractor(
26772677
?.owner
26782678
?.takeUnless { it.isDelegated }
26792679
?.let { getPsiBasedLocation(it) }
2680+
?: getDelegateFieldLocation(f)
26802681
?: tw.getLocation(f)
26812682
return extractField(
26822683
id,
@@ -3454,11 +3455,18 @@ open class KotlinFileExtractor(
34543455
private fun getDelegateExpressionOffsetRemap(
34553456
f: IrFunction
34563457
): Pair<Pair<Int, Int>, Pair<Int, Int>>? {
3457-
val file = currentIrFile ?: return null
3458-
val psiElement = getPsi2Ir()?.findPsiElement(f, file) ?: return null
3459-
val ktProperty = generateSequence(psiElement) { it.parent }
3460-
.filterIsInstance<KtProperty>().firstOrNull()
3461-
?: return null
3458+
return getEnclosingKtProperty(f)?.let { getDelegateExpressionOffsetRemap(it) }
3459+
}
3460+
3461+
/**
3462+
* Maps the whole `by <expr>` delegate range (which the K1 frontend records for the
3463+
* synthesised delegate expressions) onto the delegate expression's own range (the `by`
3464+
* keyword excluded, as K2 records natively). Returns null when [ktProperty] has no delegate
3465+
* or the delegate has no expression.
3466+
*/
3467+
private fun getDelegateExpressionOffsetRemap(
3468+
ktProperty: KtProperty
3469+
): Pair<Pair<Int, Int>, Pair<Int, Int>>? {
34623470
val delegate = ktProperty.delegate ?: return null
34633471
val expression = delegate.expression ?: return null
34643472
return Pair(
@@ -3467,6 +3475,40 @@ open class KotlinFileExtractor(
34673475
)
34683476
}
34693477

3478+
/**
3479+
* Returns the [KtProperty] enclosing the PSI element that back-maps from IR element [e],
3480+
* or null when no PSI is available (as under K2, where [getKtFile]/[findPsiElement] return
3481+
* null) or [e] is not within a property.
3482+
*/
3483+
private fun getEnclosingKtProperty(e: IrElement): KtProperty? {
3484+
val file = currentIrFile ?: return null
3485+
val psiElement = getPsi2Ir()?.findPsiElement(e, file) ?: return null
3486+
return generateSequence(psiElement) { it.parent }
3487+
.filterIsInstance<KtProperty>().firstOrNull()
3488+
}
3489+
3490+
/**
3491+
* Returns the PSI-based location for a delegated property's `$delegate` backing field, or
3492+
* null to leave the raw IR offset in place.
3493+
*
3494+
* The `$delegate` field stores the delegate object, so its natural location is the delegate
3495+
* expression (e.g. `ResourceDelegate()` in `by ResourceDelegate()`). The K1 frontend's raw
3496+
* offset for this field starts at the `by` keyword; K2 starts at the delegate expression.
3497+
* The `by` keyword is syntactic glue, not part of the stored expression, so K2's narrower
3498+
* range is the more intuitive one.
3499+
*
3500+
* This fires only for a delegated property's own backing field (`isDelegated` and
3501+
* `backingField === f`), and returns null under K2 (no PSI) and for specialised/binary
3502+
* extractions (no PSI), where the raw offset already excludes the `by` keyword.
3503+
*/
3504+
private fun getDelegateFieldLocation(f: IrField): Label<DbLocation>? {
3505+
val property = f.correspondingPropertySymbol?.owner ?: return null
3506+
if (!property.isDelegated || property.backingField !== f) return null
3507+
val ktProperty = getEnclosingKtProperty(property) ?: return null
3508+
val expression = ktProperty.delegate?.expression ?: return null
3509+
return tw.getLocation(expression.startOffset, expression.endOffset)
3510+
}
3511+
34703512
/**
34713513
* Reconstructs the location of an `if`/`when` branch [b] (of the enclosing [IrWhen] [w])
34723514
* from the branch's own condition/result offsets, or null to leave the raw location in
@@ -3673,7 +3715,19 @@ open class KotlinFileExtractor(
36733715
// This is not expected to happen, as the plugin hooks into the pipeline before IR lowering.
36743716
logger.errorElement("Local delegated property is missing delegate", s)
36753717
} else {
3676-
extractVariable(delegate, callable, blockId, 0)
3718+
// The synthesised `provideDelegate(...)` call and its `KProperty`
3719+
// argument in the delegate variable's initializer span the whole
3720+
// `by <expr>` range under K1; remap them onto the delegate
3721+
// expression (the `by` keyword excluded, as K2 records natively).
3722+
val delegateRemap =
3723+
getEnclosingKtProperty(s)?.let { getDelegateExpressionOffsetRemap(it) }
3724+
val previousRemap = tw.scopedOffsetRemap
3725+
if (delegateRemap != null) tw.scopedOffsetRemap = delegateRemap
3726+
try {
3727+
extractVariable(delegate, callable, blockId, 0)
3728+
} finally {
3729+
tw.scopedOffsetRemap = previousRemap
3730+
}
36773731
tw.writeKtProperties(propId, s.name.asString())
36783732
tw.writeHasLocation(propId, locId)
36793733
tw.writeKtPropertyDelegates(propId, useVariable(delegate))

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ delegatedProperties
44
| delegatedProperties.kt:23:9:23:31 | name | name | local | delegatedProperties.kt:23:9:23:31 | Map<String,Object> name$delegate | delegatedProperties.kt:23:29:23:31 | map |
55
| delegatedProperties.kt:33:9:33:76 | readOnly | readOnly | local | delegatedProperties.kt:33:9:33:76 | ReadWriteProperty<Object,Integer> readOnly$delegate | delegatedProperties.kt:33:30:33:47 | resourceDelegate(...) |
66
| delegatedProperties.kt:34:9:34:48 | readWrite | readWrite | local | delegatedProperties.kt:34:9:34:48 | ReadWriteProperty<Object,Integer> readWrite$delegate | delegatedProperties.kt:34:31:34:48 | resourceDelegate(...) |
7-
| delegatedProperties.kt:39:9:39:51 | varResource2 | varResource2 | local | delegatedProperties.kt:39:9:39:51 | ResourceDelegate varResource2$delegate | delegatedProperties.kt:39:31:39:51 | provideDelegate(...) |
8-
| delegatedProperties.kt:42:5:42:47 | varResource0 | varResource0 | non-local | delegatedProperties.kt:42:27:42:47 | varResource0$delegate | delegatedProperties.kt:42:30:42:47 | new ResourceDelegate(...) |
9-
| delegatedProperties.kt:66:5:66:50 | delegatedToMember1 | delegatedToMember1 | non-local | delegatedProperties.kt:66:33:66:50 | delegatedToMember1$delegate | delegatedProperties.kt:66:36:66:50 | ...::... |
10-
| delegatedProperties.kt:67:5:67:53 | delegatedToMember2 | delegatedToMember2 | non-local | delegatedProperties.kt:67:33:67:53 | delegatedToMember2$delegate | delegatedProperties.kt:67:36:67:53 | ...::... |
11-
| delegatedProperties.kt:69:5:69:56 | delegatedToExtMember1 | delegatedToExtMember1 | non-local | delegatedProperties.kt:69:36:69:56 | delegatedToExtMember1$delegate | delegatedProperties.kt:69:39:69:56 | ...::... |
12-
| delegatedProperties.kt:70:5:70:59 | delegatedToExtMember2 | delegatedToExtMember2 | non-local | delegatedProperties.kt:70:36:70:59 | delegatedToExtMember2$delegate | delegatedProperties.kt:70:39:70:59 | ...::... |
13-
| delegatedProperties.kt:72:5:72:56 | delegatedToBaseClass1 | delegatedToBaseClass1 | non-local | delegatedProperties.kt:72:36:72:56 | delegatedToBaseClass1$delegate | delegatedProperties.kt:72:39:72:56 | ...::... |
14-
| delegatedProperties.kt:73:5:73:56 | delegatedToBaseClass2 | delegatedToBaseClass2 | non-local | delegatedProperties.kt:73:36:73:56 | delegatedToBaseClass2$delegate | delegatedProperties.kt:73:39:73:56 | ...::... |
15-
| delegatedProperties.kt:75:5:75:78 | delegatedToAnotherClass1 | delegatedToAnotherClass1 | non-local | delegatedProperties.kt:75:39:75:78 | delegatedToAnotherClass1$delegate | delegatedProperties.kt:75:42:75:78 | ...::... |
16-
| delegatedProperties.kt:77:5:77:49 | delegatedToTopLevel | delegatedToTopLevel | non-local | delegatedProperties.kt:77:34:77:49 | delegatedToTopLevel$delegate | delegatedProperties.kt:77:37:77:49 | ...::... |
17-
| delegatedProperties.kt:79:5:79:38 | max | max | non-local | delegatedProperties.kt:79:18:79:38 | max$delegate | delegatedProperties.kt:79:21:79:38 | ...::... |
7+
| delegatedProperties.kt:39:9:39:51 | varResource2 | varResource2 | local | delegatedProperties.kt:39:9:39:51 | ResourceDelegate varResource2$delegate | delegatedProperties.kt:39:34:39:51 | provideDelegate(...) |
8+
| delegatedProperties.kt:42:5:42:47 | varResource0 | varResource0 | non-local | delegatedProperties.kt:42:30:42:47 | varResource0$delegate | delegatedProperties.kt:42:30:42:47 | new ResourceDelegate(...) |
9+
| delegatedProperties.kt:66:5:66:50 | delegatedToMember1 | delegatedToMember1 | non-local | delegatedProperties.kt:66:36:66:50 | delegatedToMember1$delegate | delegatedProperties.kt:66:36:66:50 | ...::... |
10+
| delegatedProperties.kt:67:5:67:53 | delegatedToMember2 | delegatedToMember2 | non-local | delegatedProperties.kt:67:36:67:53 | delegatedToMember2$delegate | delegatedProperties.kt:67:36:67:53 | ...::... |
11+
| delegatedProperties.kt:69:5:69:56 | delegatedToExtMember1 | delegatedToExtMember1 | non-local | delegatedProperties.kt:69:39:69:56 | delegatedToExtMember1$delegate | delegatedProperties.kt:69:39:69:56 | ...::... |
12+
| delegatedProperties.kt:70:5:70:59 | delegatedToExtMember2 | delegatedToExtMember2 | non-local | delegatedProperties.kt:70:39:70:59 | delegatedToExtMember2$delegate | delegatedProperties.kt:70:39:70:59 | ...::... |
13+
| delegatedProperties.kt:72:5:72:56 | delegatedToBaseClass1 | delegatedToBaseClass1 | non-local | delegatedProperties.kt:72:39:72:56 | delegatedToBaseClass1$delegate | delegatedProperties.kt:72:39:72:56 | ...::... |
14+
| delegatedProperties.kt:73:5:73:56 | delegatedToBaseClass2 | delegatedToBaseClass2 | non-local | delegatedProperties.kt:73:39:73:56 | delegatedToBaseClass2$delegate | delegatedProperties.kt:73:39:73:56 | ...::... |
15+
| delegatedProperties.kt:75:5:75:78 | delegatedToAnotherClass1 | delegatedToAnotherClass1 | non-local | delegatedProperties.kt:75:42:75:78 | delegatedToAnotherClass1$delegate | delegatedProperties.kt:75:42:75:78 | ...::... |
16+
| delegatedProperties.kt:77:5:77:49 | delegatedToTopLevel | delegatedToTopLevel | non-local | delegatedProperties.kt:77:37:77:49 | delegatedToTopLevel$delegate | delegatedProperties.kt:77:37:77:49 | ...::... |
17+
| delegatedProperties.kt:79:5:79:38 | max | max | non-local | delegatedProperties.kt:79:21:79:38 | max$delegate | delegatedProperties.kt:79:21:79:38 | ...::... |
1818
| delegatedProperties.kt:82:9:82:54 | delegatedToMember3 | delegatedToMember3 | local | delegatedProperties.kt:82:9:82:54 | KMutableProperty0<Integer> delegatedToMember3$delegate | delegatedProperties.kt:82:40:82:54 | ...::... |
19-
| delegatedProperties.kt:87:1:87:46 | extDelegated | extDelegated | non-local | delegatedProperties.kt:87:31:87:46 | extDelegated$delegateMyClass | delegatedProperties.kt:87:34:87:46 | ...::... |
19+
| delegatedProperties.kt:87:1:87:46 | extDelegated | extDelegated | non-local | delegatedProperties.kt:87:34:87:46 | extDelegated$delegateMyClass | delegatedProperties.kt:87:34:87:46 | ...::... |
2020
delegatedPropertyTypes
2121
| delegatedProperties.kt:6:9:9:9 | prop1 | file://:0:0:0:0 | int | file://<external>/Lazy.class:0:0:0:0 | Lazy<Integer> |
2222
| delegatedProperties.kt:19:9:19:51 | varResource1 | file://:0:0:0:0 | int | delegatedProperties.kt:45:1:51:1 | ResourceDelegate |

0 commit comments

Comments
 (0)