Skip to content

Commit 915d4d4

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 915d4d4

5 files changed

Lines changed: 138 additions & 84 deletions

File tree

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

Lines changed: 59 additions & 5 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,30 @@ open class KotlinFileExtractor(
34543455
private fun getDelegateExpressionOffsetRemap(
34553456
f: IrFunction
34563457
): Pair<Pair<Int, Int>, Pair<Int, Int>>? {
3458+
return getEnclosingKtProperty(f)?.let { getDelegateExpressionOffsetRemap(it) }
3459+
}
3460+
3461+
/**
3462+
* Returns the [KtProperty] enclosing the PSI element that back-maps from IR element [e],
3463+
* or null when no PSI is available (as under K2, where [getKtFile]/[findPsiElement] return
3464+
* null) or [e] is not within a property.
3465+
*/
3466+
private fun getEnclosingKtProperty(e: IrElement): KtProperty? {
34573467
val file = currentIrFile ?: return null
3458-
val psiElement = getPsi2Ir()?.findPsiElement(f, file) ?: return null
3459-
val ktProperty = generateSequence(psiElement) { it.parent }
3468+
val psiElement = getPsi2Ir()?.findPsiElement(e, file) ?: return null
3469+
return generateSequence(psiElement) { it.parent }
34603470
.filterIsInstance<KtProperty>().firstOrNull()
3461-
?: return null
3471+
}
3472+
3473+
/**
3474+
* Maps the whole `by <expr>` delegate range (which the K1 frontend records for the
3475+
* synthesised delegate expressions) onto the delegate expression's own range (the `by`
3476+
* keyword excluded, as K2 records natively). Returns null when [ktProperty] has no delegate
3477+
* or the delegate has no expression.
3478+
*/
3479+
private fun getDelegateExpressionOffsetRemap(
3480+
ktProperty: KtProperty
3481+
): Pair<Pair<Int, Int>, Pair<Int, Int>>? {
34623482
val delegate = ktProperty.delegate ?: return null
34633483
val expression = delegate.expression ?: return null
34643484
return Pair(
@@ -3467,6 +3487,28 @@ open class KotlinFileExtractor(
34673487
)
34683488
}
34693489

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)