From db88f07a3aa2a0eff34e051055d117f06168bc95 Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Sun, 12 Jul 2026 16:08:25 +0200 Subject: [PATCH] Kotlin: include accessor annotations in explicit accessor location An explicit property accessor may carry its own annotations, for example: val x: Int @JvmName("getX_prop") get() = 15 The K2 frontend records such an accessor with raw IR offsets that begin at the leading annotation; the K1 frontend's raw offsets start at the `get`/`set` keyword and omit the annotation. This is a pure K1 information regression: the annotation is part of the accessor declaration and the K2 span is the more faithful one, so we converge K1 onto K2. Because the annotation-inclusive start cannot be reconstructed under K2 (no PSI back-mapping) but is trivially available under K1, we recover it from the KtPropertyAccessor PSI node, whose text range begins at its modifier list. A new helper getPsiBasedAnnotatedAccessorLocation returns this span, and accessorOverride now applies it to explicit accessors (in addition to the existing synthesised-accessor handling). Guards keep the change surgical: - returns null under K2 (getKtFile unavailable; raw offsets already include the annotation), leaving K2 untouched. - returns null when the accessor declares no annotations of its own, so non-annotated explicit accessors (which already converge) are unaffected. Relearned both suites: only explicit annotated-accessor declaration rows change (K1 now matches K2). annotations/jvmName/test.expected becomes byte-identical across suites; the residual diffs in jvmstatic-annotation are pre-existing, unrelated divergences (JVM-static proxy forwarder locations and call-argument spans). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 37 +++++++++++++++++-- .../annotations/jvmName/test.expected | 2 +- .../jvmstatic-annotation/test.expected | 4 +- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index e3870ef17f3..c0562191376 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -2762,9 +2762,10 @@ open class KotlinFileExtractor( // getPsiBasedAccessorLocation): the keyword token for a bare `get`/`set`, // or the property signature for a fully synthesised accessor. fun accessorOverride(f: IrFunction) = - if (f.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) - getPsiBasedAccessorLocation(p, f)?.let { OverriddenFunctionAttributes(sourceLoc = it) } - else null + (if (f.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR) + getPsiBasedAccessorLocation(p, f) + else getPsiBasedAnnotatedAccessorLocation(p, f)) + ?.let { OverriddenFunctionAttributes(sourceLoc = it) } if (getter == null) { if (!isExternalDeclaration(p)) { @@ -3132,9 +3133,37 @@ open class KotlinFileExtractor( return tw.getLocation(ktClass.startOffset, ktClass.endOffset) } + /** + * Returns the PSI-based location for an *explicit* property accessor that carries + * its own annotation(s) (for example `@JvmName("getX_prop") get() = 15`), spanning + * from the leading annotation through the end of the accessor. + * + * The K2 frontend records such an accessor with raw IR offsets that already include + * the leading annotation; the K1 frontend's raw offsets start at the `get`/`set` + * keyword and omit the annotation. We converge K1 onto the annotation-inclusive K2 + * span by recovering it from the [KtPropertyAccessor] PSI node, whose text range + * begins at its modifier list (the annotations). + * + * Returns null under K2 (where [getKtFile] is unavailable and the raw offsets + * already carry the annotation) and for accessors that declare no annotations of + * their own (which both frontends already record identically). + */ + private fun getPsiBasedAnnotatedAccessorLocation(p: IrProperty, accessor: IrFunction): Label? { + if (p.startOffset < 0 || p.endOffset < 0) return null + val file = currentIrFile ?: return null + val ktFile = getPsi2Ir()?.getKtFile(file) ?: return null + val ktProperty = ktFile.findElementAt(p.startOffset) + ?.let { leaf -> generateSequence(leaf) { it.parent }.filterIsInstance().firstOrNull() } + ?: return null + val isGetter = p.getter?.symbol == accessor.symbol + val ktAccessor: KtPropertyAccessor = (if (isGetter) ktProperty.getter else ktProperty.setter) + ?: return null + if (ktAccessor.annotationEntries.isEmpty()) return null + return tw.getLocation(ktAccessor.startOffset, ktAccessor.endOffset) + } + /** * Returns the PSI-based location for the synthesised getter/setter of a - * *delegated* property (origin `DELEGATED_PROPERTY_ACCESSOR`), spanning the * enclosing property declaration from its `val`/`var` keyword through the end of * the delegate expression. * diff --git a/java/ql/test-kotlin1/library-tests/annotations/jvmName/test.expected b/java/ql/test-kotlin1/library-tests/annotations/jvmName/test.expected index b26dff9d334..ba6887606f2 100644 --- a/java/ql/test-kotlin1/library-tests/annotations/jvmName/test.expected +++ b/java/ql/test-kotlin1/library-tests/annotations/jvmName/test.expected @@ -1,5 +1,5 @@ | Test.java:2:17:2:17 | m | m | m | -| test.kt:4:9:4:18 | getX_prop | getX_prop | getX | +| test.kt:3:9:4:18 | getX_prop | getX_prop | getX | | test.kt:6:5:6:19 | getX | getX | getX | | test.kt:10:5:10:14 | changeY | changeY | setY | | test.kt:10:5:10:14 | y | y | getY | diff --git a/java/ql/test-kotlin1/library-tests/jvmstatic-annotation/test.expected b/java/ql/test-kotlin1/library-tests/jvmstatic-annotation/test.expected index b286f4766bd..1071f7a6e03 100644 --- a/java/ql/test-kotlin1/library-tests/jvmstatic-annotation/test.expected +++ b/java/ql/test-kotlin1/library-tests/jvmstatic-annotation/test.expected @@ -12,8 +12,8 @@ staticMembers | test.kt:31:1:47:1 | NonCompanion | test.kt:33:14:33:69 | staticMethod | Method | | test.kt:31:1:47:1 | NonCompanion | test.kt:36:14:36:35 | getStaticProp | Method | | test.kt:31:1:47:1 | NonCompanion | test.kt:36:14:36:35 | setStaticProp | Method | -| test.kt:31:1:47:1 | NonCompanion | test.kt:40:16:40:43 | getPropWithStaticGetter | Method | -| test.kt:31:1:47:1 | NonCompanion | test.kt:45:16:45:58 | setPropWithStaticSetter | Method | +| test.kt:31:1:47:1 | NonCompanion | test.kt:40:5:40:43 | getPropWithStaticGetter | Method | +| test.kt:31:1:47:1 | NonCompanion | test.kt:45:5:45:58 | setPropWithStaticSetter | Method | #select | test.kt:9:1:29:1 | HasCompanion | JavaUser.java:5:5:5:34 | staticMethod(...) | JavaUser.java:5:5:5:16 | HasCompanion | static | | test.kt:9:1:29:1 | HasCompanion | JavaUser.java:7:5:7:73 | setStaticProp(...) | JavaUser.java:7:5:7:16 | HasCompanion | static |