Skip to content

Commit b2f94da

Browse files
Kotlin: converge delegated property accessor locations onto K2 property span
For local and member delegated properties, the synthesised accessor (`<get-prop>`/`<set-prop>`) and its generated wrapper class/constructor were anchored by the K1 frontend at the delegate expression rather than at the property declaration. K2 (raw IR) anchors them at the property `val`/`var` keyword through the end of the delegate, i.e. the whole `KtProperty` span. Adopt the K2 span for both frontends so the extractor emits identical locations regardless of the supplied language version. Example: <get-prop1> 6:24:9:9 -> 6:9:9:9 Add `getPsiBasedDelegatedAccessorLocation`, which walks from the accessor's PSI up to the enclosing `KtProperty` and returns its span. It returns null when there is no PSI (K2) or no enclosing property, so K2 keeps its native locations and non-delegated callables are unaffected. Wire it into `extractFunction`'s location chain and into `extractGeneratedClass` (guarded on `DELEGATED_PROPERTY_ACCESSOR`) so the generated class/constructor sorts before the method, matching K2. Updates test-kotlin1 expected only (test-kotlin2 unchanged). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 240103e commit b2f94da

7 files changed

Lines changed: 708 additions & 661 deletions

File tree

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

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2463,6 +2463,7 @@ open class KotlinFileExtractor(
24632463

24642464
val locId =
24652465
overriddenAttributes?.sourceLoc
2466+
?: (if (f.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) getPsiBasedDelegatedAccessorLocation(f) else null)
24662467
?: (if (usesK2) getPsiBasedLocation(f) else null)
24672468
?: (if (f.symbol is IrConstructorSymbol && typeSubstitution == null) getPsiBasedImplicitConstructorLocation(f) else null)
24682469
?: getLocation(f, classTypeArgsIncludingOuterClasses)
@@ -3076,6 +3077,42 @@ open class KotlinFileExtractor(
30763077
return tw.getLocation(ktClass.startOffset, ktClass.endOffset)
30773078
}
30783079

3080+
/**
3081+
* Returns the PSI-based location for the synthesised getter/setter of a
3082+
* *delegated* property (origin `DELEGATED_PROPERTY_ACCESSOR`), spanning the
3083+
* enclosing property declaration from its `val`/`var` keyword through the end of
3084+
* the delegate expression.
3085+
*
3086+
* For a delegated property such as `val prop1: Int by lazy { ... }` the compiler
3087+
* synthesises a getter (and, for `var`, a setter) whose body forwards to the
3088+
* delegate's `getValue`/`setValue`. The K2 frontend records these accessors with
3089+
* raw IR offsets spanning the whole property declaration (the `val`/`var` keyword
3090+
* through the delegate expression). The K1 frontend's raw offsets (and its
3091+
* [findPsiElement]-based location) instead cover only the delegate expression, so
3092+
* the two frontends disagree on the accessor's start column.
3093+
*
3094+
* K1 retains the PSI, so we recover the K2-native span from the enclosing
3095+
* [KtProperty]: the [findPsiElement] mapping resolves the accessor to a node
3096+
* within the property's delegate, from which we walk up to the [KtProperty].
3097+
* [KtProperty.valOrVarKeyword] then gives the start (excluding any leading
3098+
* modifiers, matching the default-accessor and property spans) and
3099+
* [KtProperty.endOffset] gives the end (past the delegate expression).
3100+
*
3101+
* This is deliberately not gated on [usesK2]: it must apply to the K1 frontends
3102+
* (which retain PSI) to converge them onto the K2-native span, and it returns null
3103+
* under K2 (where [findPsiElement] finds no PSI and the raw offsets already carry
3104+
* this span). It applies uniformly to both getter and setter, and to both
3105+
* member-level and local delegated properties.
3106+
*/
3107+
private fun getPsiBasedDelegatedAccessorLocation(f: IrFunction): Label<DbLocation>? {
3108+
val file = currentIrFile ?: return null
3109+
val psiElement = getPsi2Ir()?.findPsiElement(f, file) ?: return null
3110+
val ktProperty = generateSequence(psiElement) { it.parent }
3111+
.filterIsInstance<KtProperty>().firstOrNull()
3112+
?: return null
3113+
return tw.getLocation(ktProperty.valOrVarKeyword.startOffset, ktProperty.endOffset)
3114+
}
3115+
30793116
private fun extractVariable(
30803117
v: IrVariable,
30813118
callable: Label<out DbCallable>,
@@ -9372,11 +9409,21 @@ open class KotlinFileExtractor(
93729409
with("generated class", localFunction) {
93739410
val ids = getLocallyVisibleFunctionLabels(localFunction)
93749411

9412+
// For a delegated property's synthesised accessor the generated wrapper class
9413+
// (and its constructor and super-call) must share the accessor's declaration
9414+
// span so that, like the K2 frontend, the constructor still sorts before the
9415+
// getter/setter rather than after it (the raw K1 offset would place the class
9416+
// at the delegate expression, past the accessor's own start column).
9417+
val classLocId =
9418+
(if (localFunction.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR)
9419+
getPsiBasedDelegatedAccessorLocation(localFunction) else null)
9420+
?: tw.getLocation(localFunction)
9421+
93759422
val id =
93769423
extractGeneratedClass(
93779424
ids,
93789425
superTypes,
9379-
tw.getLocation(localFunction),
9426+
classLocId,
93809427
localFunction,
93819428
localFunction.parent,
93829429
compilerGeneratedKindOverride = compilerGeneratedKindOverride

0 commit comments

Comments
 (0)