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 |