From 8267d331a4911e8e192de0e8dd4be400820c1c60 Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Tue, 14 Jul 2026 19:53:44 +0200 Subject: [PATCH] Kotlin: converge default-accessor body spans onto the K2 signature span A compiler-generated default property accessor (`DEFAULT_PROPERTY_ACCESSOR`) has no source body, so the two frontends anchor its synthesised body expressions (`field = value` / `return field`, plus their ``, field and `this` sub-accesses) differently: - K1 anchors them at the whole `KtProperty`, whose end offset runs through the initialiser (`var topLevelInt: Int = 0` -> `60:1:60:24`, `var curValue = 0` -> `26:13:26:24` .. `26:28`). - K2 has no PSI for these accessors and falls back to the raw signature span, which stops at the type (`60:1:60:20`) or, when the type is inferred, at the name (`26:13:26:24`). The initialiser is not part of the accessor body (it runs in the field initialiser / ``, not in the setter), so K2's narrower signature span is the more intuitive, information-preserving choice: it keeps the accessor's synthetic expressions on the property signature and leaves the initialiser to the genuine `KtInitializerAssignExpr` / `` rows. We converge K1 onto the K2 span with a scoped offset remap set only while extracting the accessor body (mirroring the existing delegated-property-accessor remap). The remap is keyed off the accessor's corresponding property and matches the property's full IR range exactly, so: - it only rewrites the synthetic body expressions that carry that range; - the real field-initialiser rows (which share the same source text but are extracted in the initialiser context) are untouched and keep their initialiser-inclusive spans; and - it is a no-op under K2 (no PSI, so `getEnclosingKtProperty` returns null) and for any property without an initialiser past its signature. Tradeoff: this converges K1 onto a span that K2 produces natively but that K1 cannot recover without the PSI, so the direction is fixed (K1 -> K2) rather than chosen freely. That is acceptable here because the K2 span is the more correct one on the merits (the initialiser does not belong to the accessor body). Expected updates (K1 only; K2 already emitted these): - library-tests/exprs/exprs.expected (setCurValue, setTopLevelInt) - library-tests/methods/exprs.expected (clinit.kt setTopLevelInt) Both suites relearned; all tests pass. Divergence 820 -> 798 rows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 86bfc022-3ecc-4746-ba23-3b76c4e4c3e4 --- .../src/main/kotlin/KotlinFileExtractor.kt | 45 +++++++++++++++++-- .../library-tests/exprs/exprs.expected | 14 +++--- .../library-tests/methods/exprs.expected | 8 ++-- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 8d786ac9255..adcc725762a 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -2580,9 +2580,13 @@ open class KotlinFileExtractor( f ) val remap = - if (f.origin == IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR) - getDelegateExpressionOffsetRemap(f) - else null + when (f.origin) { + IrDeclarationOrigin.DELEGATED_PROPERTY_ACCESSOR -> + getDelegateExpressionOffsetRemap(f) + IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR -> + getDefaultAccessorBodyOffsetRemap(f) + else -> null + } val previousRemap = tw.scopedOffsetRemap if (remap != null) tw.scopedOffsetRemap = remap try { @@ -3581,6 +3585,41 @@ open class KotlinFileExtractor( ) } + /** + * Maps the property-declaration range (which the K1 frontend records for the synthesised + * body expressions of a compiler-generated default `get`/`set` accessor, running through the + * property initialiser) onto the property *signature* range (the `val`/`var` keyword through + * the type, or through the name when the type is inferred), which K2 records natively. + * + * A `DEFAULT_PROPERTY_ACCESSOR` has no source body, so the K1 frontend anchors its synthesised + * `field = value` / `return field` expressions (and their ``/field/`this` sub-accesses) + * at the whole [KtProperty], whose end offset runs through the initialiser + * (`var topLevelInt: Int = 0` -> `60:1:60:24`). K2 has no PSI for these accessors and falls + * back to the raw signature span, which stops at the type (`60:1:60:20`) or, when the type is + * inferred, at the name (`var curValue = 0` -> `26:13:26:24`). The initialiser is not part of + * the accessor body, so K2's narrower signature span is the more intuitive one; we converge K1 + * onto it via a scoped offset remap set only while extracting the accessor body. The remap + * matches the property range exactly, so no unrelated location is affected. + * + * Returns null under K2 (no PSI, [getEnclosingKtProperty] returns null) and when the property + * has no initialiser past the signature to exclude. + */ + private fun getDefaultAccessorBodyOffsetRemap( + f: IrFunction + ): Pair, Pair>? { + val property = (f as? IrSimpleFunction)?.correspondingPropertySymbol?.owner ?: return null + if (property.startOffset < 0 || property.endOffset < 0) return null + val ktProperty = getEnclosingKtProperty(property) ?: return null + val declEnd = ktProperty.typeReference?.endOffset + ?: ktProperty.nameIdentifier?.endOffset + ?: return null + if (property.endOffset <= declEnd) return null + return Pair( + Pair(property.startOffset, property.endOffset), + Pair(property.startOffset, declEnd) + ) + } + /** * Returns the [KtProperty] enclosing the PSI element that back-maps from IR element [e], * or null when no PSI is available (as under K2, where [getKtFile]/[findPsiElement] return diff --git a/java/ql/test-kotlin1/library-tests/exprs/exprs.expected b/java/ql/test-kotlin1/library-tests/exprs/exprs.expected index b92d94021fb..949b14ff3ec 100644 --- a/java/ql/test-kotlin1/library-tests/exprs/exprs.expected +++ b/java/ql/test-kotlin1/library-tests/exprs/exprs.expected @@ -116,18 +116,18 @@ | delegatedProperties.kt:25:64:31:9 | | delegatedProperties.kt:25:9:31:9 | resourceDelegate | StmtExpr | | delegatedProperties.kt:25:64:31:9 | ReadWriteProperty | delegatedProperties.kt:25:9:31:9 | resourceDelegate | TypeAccess | | delegatedProperties.kt:25:64:31:9 | new (...) | delegatedProperties.kt:25:9:31:9 | resourceDelegate | ClassInstanceExpr | +| delegatedProperties.kt:26:13:26:24 | ...=... | delegatedProperties.kt:26:13:26:24 | setCurValue | AssignExpr | +| delegatedProperties.kt:26:13:26:24 | | delegatedProperties.kt:26:13:26:24 | setCurValue | VarAccess | | delegatedProperties.kt:26:13:26:24 | Unit | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:26:13:26:24 | int | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:26:13:26:24 | int | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:26:13:26:24 | this | delegatedProperties.kt:26:13:26:24 | getCurValue | ThisAccess | | delegatedProperties.kt:26:13:26:24 | this | delegatedProperties.kt:26:13:26:24 | setCurValue | ThisAccess | | delegatedProperties.kt:26:13:26:24 | this.curValue | delegatedProperties.kt:26:13:26:24 | getCurValue | VarAccess | +| delegatedProperties.kt:26:13:26:24 | this.curValue | delegatedProperties.kt:26:13:26:24 | setCurValue | VarAccess | | delegatedProperties.kt:26:13:26:28 | ...=... | delegatedProperties.kt:25:64:31:9 | | KtInitializerAssignExpr | -| delegatedProperties.kt:26:13:26:28 | ...=... | delegatedProperties.kt:26:13:26:24 | setCurValue | AssignExpr | -| delegatedProperties.kt:26:13:26:28 | | delegatedProperties.kt:26:13:26:24 | setCurValue | VarAccess | | delegatedProperties.kt:26:13:26:28 | curValue | delegatedProperties.kt:25:64:31:9 | | VarAccess | | delegatedProperties.kt:26:13:26:28 | int | file://:0:0:0:0 | | TypeAccess | -| delegatedProperties.kt:26:13:26:28 | this.curValue | delegatedProperties.kt:26:13:26:24 | setCurValue | VarAccess | | delegatedProperties.kt:26:28:26:28 | 0 | delegatedProperties.kt:25:64:31:9 | | IntegerLiteral | | delegatedProperties.kt:27:22:27:88 | int | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:27:35:27:47 | Object | file://:0:0:0:0 | | TypeAccess | @@ -281,18 +281,18 @@ | delegatedProperties.kt:54:51:54:68 | KProperty | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:56:16:56:33 | ResourceDelegate | delegatedProperties.kt:54:14:57:5 | provideDelegate | TypeAccess | | delegatedProperties.kt:56:16:56:33 | new ResourceDelegate(...) | delegatedProperties.kt:54:14:57:5 | provideDelegate | ClassInstanceExpr | +| delegatedProperties.kt:60:1:60:20 | ...=... | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | AssignExpr | +| delegatedProperties.kt:60:1:60:20 | | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | VarAccess | | delegatedProperties.kt:60:1:60:20 | DelegatedPropertiesKt | delegatedProperties.kt:60:1:60:20 | getTopLevelInt | TypeAccess | +| delegatedProperties.kt:60:1:60:20 | DelegatedPropertiesKt | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | TypeAccess | | delegatedProperties.kt:60:1:60:20 | DelegatedPropertiesKt.topLevelInt | delegatedProperties.kt:60:1:60:20 | getTopLevelInt | VarAccess | +| delegatedProperties.kt:60:1:60:20 | DelegatedPropertiesKt.topLevelInt | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | VarAccess | | delegatedProperties.kt:60:1:60:20 | Unit | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:60:1:60:20 | int | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:60:1:60:20 | int | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:60:1:60:24 | ...=... | delegatedProperties.kt:0:0:0:0 | | KtInitializerAssignExpr | -| delegatedProperties.kt:60:1:60:24 | ...=... | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | AssignExpr | -| delegatedProperties.kt:60:1:60:24 | | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | VarAccess | | delegatedProperties.kt:60:1:60:24 | DelegatedPropertiesKt | delegatedProperties.kt:0:0:0:0 | | TypeAccess | -| delegatedProperties.kt:60:1:60:24 | DelegatedPropertiesKt | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | TypeAccess | | delegatedProperties.kt:60:1:60:24 | DelegatedPropertiesKt.topLevelInt | delegatedProperties.kt:0:0:0:0 | | VarAccess | -| delegatedProperties.kt:60:1:60:24 | DelegatedPropertiesKt.topLevelInt | delegatedProperties.kt:60:1:60:20 | setTopLevelInt | VarAccess | | delegatedProperties.kt:60:1:60:24 | int | file://:0:0:0:0 | | TypeAccess | | delegatedProperties.kt:60:24:60:24 | 0 | delegatedProperties.kt:0:0:0:0 | | IntegerLiteral | | delegatedProperties.kt:62:25:62:48 | ...=... | delegatedProperties.kt:62:24:62:49 | ClassWithDelegate | KtInitializerAssignExpr | diff --git a/java/ql/test-kotlin1/library-tests/methods/exprs.expected b/java/ql/test-kotlin1/library-tests/methods/exprs.expected index 26cc729fada..dfc25dc7588 100644 --- a/java/ql/test-kotlin1/library-tests/methods/exprs.expected +++ b/java/ql/test-kotlin1/library-tests/methods/exprs.expected @@ -1,14 +1,14 @@ +| clinit.kt:3:1:3:20 | ...=... | AssignExpr | +| clinit.kt:3:1:3:20 | | VarAccess | | clinit.kt:3:1:3:20 | ClinitKt | TypeAccess | +| clinit.kt:3:1:3:20 | ClinitKt | TypeAccess | +| clinit.kt:3:1:3:20 | ClinitKt.topLevelInt | VarAccess | | clinit.kt:3:1:3:20 | ClinitKt.topLevelInt | VarAccess | | clinit.kt:3:1:3:20 | Unit | TypeAccess | | clinit.kt:3:1:3:20 | int | TypeAccess | | clinit.kt:3:1:3:20 | int | TypeAccess | -| clinit.kt:3:1:3:24 | ...=... | AssignExpr | | clinit.kt:3:1:3:24 | ...=... | KtInitializerAssignExpr | -| clinit.kt:3:1:3:24 | | VarAccess | | clinit.kt:3:1:3:24 | ClinitKt | TypeAccess | -| clinit.kt:3:1:3:24 | ClinitKt | TypeAccess | -| clinit.kt:3:1:3:24 | ClinitKt.topLevelInt | VarAccess | | clinit.kt:3:1:3:24 | ClinitKt.topLevelInt | VarAccess | | clinit.kt:3:1:3:24 | int | TypeAccess | | clinit.kt:3:24:3:24 | 0 | IntegerLiteral |