From a17c88ab9437bc9e1ad42a98b6a84b611a77970b Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Mon, 13 Jul 2026 13:23:54 +0200 Subject: [PATCH] Kotlin: exclude the `by` keyword from an interface-delegation field location For `class C : Intf by ` the compiler synthesises a `$$delegate_0` field that stores the delegate ``, so its natural location is that expression. The two frontends disagreed on the field's own location: - K1 records it at the delegate expression (`intfDelegate.kt:7:26:9:1`, the `object : Intf { ... }`); while - K2 records it from the delegated supertype, so it includes the `Intf by ` glue (`7:18:9:1`). This is the interface-delegation analogue of the delegated-property `$delegate` field, where we already adopted the delegate-expression span and excluded the `by` keyword (it is syntactic glue, not part of the stored expression). We apply the same principle here and converge K2 onto K1's narrower span. `getClassDelegateFieldLocation` anchors a class-delegation field (`IrDeclarationOrigin.DELEGATE`, which has no corresponding property and would otherwise fall through to the raw IR offset) on its own initialiser expression. It uses the initialiser's raw offsets, which are available under both frontends (`7:26:9:1` in each), so it is frontend-stable: under K1 it reproduces the offset already recorded (no change) and under K2 it trims the leading `Intf by `. Relearn (both suites, CPUS=5): all 3333 tests pass. Only test-kotlin2 changes; interface-delegate/test is now byte-identical across suites (2 -> 0 divergent rows). No other file changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 25 +++++++++++++++++++ .../interface-delegate/test.expected | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 543abaf1493..8cb27040881 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -2679,6 +2679,7 @@ open class KotlinFileExtractor( ?.takeUnless { it.isDelegated } ?.let { getPsiBasedLocation(it) } ?: getDelegateFieldLocation(f) + ?: getClassDelegateFieldLocation(f) ?: tw.getLocation(f) return extractField( id, @@ -3559,6 +3560,30 @@ open class KotlinFileExtractor( return tw.getLocation(expression.startOffset, expression.endOffset) } + /** + * Returns the location for an interface-delegation `$$delegate_0` backing field, or null to + * leave the raw IR offset in place. + * + * For `class C : Intf by ` the compiler synthesises a `$$delegate_0` field that stores + * ``, so its natural location is that delegate expression. The K1 frontend's raw offset + * already spans the expression (`intfDelegate.kt:7:26:9:1`, the `object : Intf { ... }`); the + * K2 frontend's raw offset starts at the delegated supertype and so includes the `Intf by ` + * glue (`7:18:9:1`). As with a delegated property's `$delegate` field, the `by` clause is + * syntactic glue rather than part of the stored expression, so we converge on the narrower + * delegate-expression span by anchoring the field on its own initialiser. + * + * This uses the field initialiser's raw offsets (available under both frontends), so it is + * frontend-stable: under K1 it reproduces the offset already recorded, and under K2 it trims + * the leading `Intf by `. It fires only for class-delegation fields (`IrDeclarationOrigin.DELEGATE`), + * which have no corresponding property and would otherwise fall through to the raw IR offset. + */ + private fun getClassDelegateFieldLocation(f: IrField): Label? { + if (f.origin != IrDeclarationOrigin.DELEGATE) return null + val expression = f.initializer?.expression ?: return null + if (expression.startOffset < 0 || expression.endOffset < 0) return null + return tw.getLocation(expression.startOffset, expression.endOffset) + } + /** * Reconstructs the location of an `if`/`when` branch [b] (of the enclosing [IrWhen] [w]) * from the branch's own condition/result offsets, or null to leave the raw location in diff --git a/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected b/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected index f04dc01ae8b..f1c9f054e08 100644 --- a/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected +++ b/java/ql/test-kotlin2/library-tests/interface-delegate/test.expected @@ -1,5 +1,5 @@ fields -| intfDelegate.kt:7:18:9:1 | $$delegate_0 | intfDelegate.kt:7:26:9:1 | | +| intfDelegate.kt:7:26:9:1 | $$delegate_0 | intfDelegate.kt:7:26:9:1 | | #select | intfDelegate.kt:0:0:0:0 | f | intfDelegate.kt:7:1:10:1 | Concrete | | intfDelegate.kt:3:3:3:15 | f | intfDelegate.kt:1:1:5:1 | Intf |