From d91b3899aefffe89bcea58cd708b5067a34327f6 Mon Sep 17 00:00:00 2001 From: Anders Fugmann Date: Mon, 13 Jul 2026 17:50:12 +0200 Subject: [PATCH] Kotlin: converge synthetic property-access locations to the K2 signature span For a property such as `val prop: Int = 1`, the compiler generates synthetic `this.prop` accesses (inside the default getter and the primary-constructor field initialiser). The K1 and K2 frontends give these synthetic accesses different source spans: * K1 runs the span through the initialiser: `variables.kt:3:5:3:21` * K2 stops at the end of the type signature: `variables.kt:3:5:3:17` The property *declaration* location is `3:21` under both frontends and is left untouched; only these synthetic *access* expressions diverged. The K2 signature span (val/var keyword to the end of the type, or to the end of the name when the type is inferred) is the more intuitive location: it points at the property's signature rather than incidentally swallowing the initialiser expression, so a `this.prop` read is not reported as spanning code it does not evaluate. We adopt the K2 span and reproduce it under K1 from PSI. New helper `getPsiBasedPropertySignatureAccessLocation` returns the signature span only when the access resolves (via `findPsiElement`) to the enclosing `KtProperty`, which is true exactly for these synthetic property-declaration-anchored accesses. It returns null under K2 (no PSI is available there, and the raw IR offset already gives the signature span) and for every ordinary source-written access (whose PSI is the reference expression, not the whole declaration), so canonical K2 output and normal accesses are unaffected. It is wired into the `IrGetField` read path and into `extractThisAccess`. Effect: tk1 `variables/variables.expected` becomes byte-identical to tk2; `variables/variableAccesses.expected` drops to only the separate implicit-`this` call-receiver span divergence; genericExprTypes/exprs/methods synthetic property-access rows all move from the through-initialiser span onto the K2 signature span. All 3333 tests pass in both suites; test-kotlin2 is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/main/kotlin/KotlinFileExtractor.kt | 28 ++++++++++++++++++- .../classes/genericExprTypes.expected | 8 +++--- .../library-tests/exprs/exprs.expected | 22 +++++++-------- .../library-tests/methods/exprs.expected | 4 +-- .../variables/variableAccesses.expected | 6 ++-- .../variables/variables.expected | 4 +-- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index 320cae7ff09..806f03ec61a 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -3099,6 +3099,31 @@ open class KotlinFileExtractor( return tw.getLocation(declStart, declEnd) } + /** + * When a *synthetic* property access (e.g. the `this.prop` read the compiler generates + * inside a default getter or a primary-constructor field initialiser) carries IR offsets + * that span the whole property declaration, returns the property *signature* span (the + * `val`/`var` keyword to the end of the type, or to the end of the name when the type is + * inferred), matching the location the K2 frontend records for the same synthetic access; + * otherwise returns null. + * + * K1 and K2 offset these synthetic accesses differently: K1 runs through the initialiser + * (`val prop: Int = 1` -> `3:5:3:21`) while K2 stops at the type (`3:5:3:17`). Under K1 the + * access resolves (via [findPsiElement]) to the enclosing [KtProperty], from which we recover + * the K2 signature span. Returns null under K2 (no PSI; the raw offset already gives the + * signature span) and for every ordinary, source-written access, whose PSI is the reference + * expression rather than the whole property declaration. + */ + private fun getPsiBasedPropertySignatureAccessLocation(e: IrElement): Label? { + val file = currentIrFile ?: return null + val psi2Ir = getPsi2Ir() ?: return null + val ktProperty = psi2Ir.findPsiElement(e, file) as? KtProperty ?: return null + val declStart = ktProperty.valOrVarKeyword.startOffset + val declEnd = (ktProperty.typeReference ?: ktProperty.nameIdentifier)?.endOffset + ?: return null + return tw.getLocation(declStart, declEnd) + } + // Matches the K1 frontend's synthetic name for the temporary holding the subject of a // destructuring declaration (`val (a, b) = subject`), which is `tmp_container`. K2 names // the same temporary with the special name ``. @@ -6975,7 +7000,7 @@ open class KotlinFileExtractor( is IrGetField -> { val exprParent = parent.expr(e, callable) val owner = tryReplaceAndroidSyntheticField(e.symbol.owner) - val locId = tw.getLocation(e) + val locId = getPsiBasedPropertySignatureAccessLocation(e) ?: tw.getLocation(e) val fieldType = if (isAnnotationClassField(owner)) kClassToJavaClass(e.type) else e.type extractVariableAccess( @@ -7543,6 +7568,7 @@ open class KotlinFileExtractor( ) { val containingDeclaration = declarationStack.peek().first val locId = getDelegatedAccessorSyntheticArgumentLocation(e) + ?: getPsiBasedPropertySignatureAccessLocation(e) ?: getPsiBasedLocation(e) ?: tw.getLocation(e) if ( diff --git a/java/ql/test-kotlin1/library-tests/classes/genericExprTypes.expected b/java/ql/test-kotlin1/library-tests/classes/genericExprTypes.expected index 88cbe352200..05ed2b7e12e 100644 --- a/java/ql/test-kotlin1/library-tests/classes/genericExprTypes.expected +++ b/java/ql/test-kotlin1/library-tests/classes/genericExprTypes.expected @@ -7,22 +7,22 @@ | generic_anonymous.kt:1:26:1:33 | this | Generic | | generic_anonymous.kt:1:26:1:33 | this.t | T | | generic_anonymous.kt:3:3:5:3 | ...=... | new Object(...) { ... } | -| generic_anonymous.kt:3:3:5:3 | this | Generic | | generic_anonymous.kt:3:3:5:3 | x | new Object(...) { ... } | | generic_anonymous.kt:3:11:3:15 | T | T | | generic_anonymous.kt:3:11:3:15 | new Object(...) { ... } | new Object(...) { ... } | +| generic_anonymous.kt:3:11:3:15 | this | Generic | +| generic_anonymous.kt:3:11:3:15 | this.x | new Object(...) { ... } | | generic_anonymous.kt:3:11:5:3 | T | T | | generic_anonymous.kt:3:11:5:3 | new Object(...) { ... } | new Object(...) { ... } | -| generic_anonymous.kt:3:11:5:3 | this.x | new Object(...) { ... } | | generic_anonymous.kt:3:19:5:3 | | new Object(...) { ... } | | generic_anonymous.kt:3:19:5:3 | Object | Object | | generic_anonymous.kt:3:19:5:3 | new (...) | new Object(...) { ... } | | generic_anonymous.kt:4:7:4:16 | T | T | +| generic_anonymous.kt:4:7:4:16 | this | new Object(...) { ... } | +| generic_anonymous.kt:4:7:4:16 | this.member | T | | generic_anonymous.kt:4:7:4:20 | ...=... | T | | generic_anonymous.kt:4:7:4:20 | T | T | | generic_anonymous.kt:4:7:4:20 | member | T | -| generic_anonymous.kt:4:7:4:20 | this | new Object(...) { ... } | -| generic_anonymous.kt:4:7:4:20 | this.member | T | | generic_anonymous.kt:4:20:4:20 | Generic | Generic | | generic_anonymous.kt:4:20:4:20 | Generic.this | Generic | | generic_anonymous.kt:4:20:4:20 | getT(...) | T | diff --git a/java/ql/test-kotlin1/library-tests/exprs/exprs.expected b/java/ql/test-kotlin1/library-tests/exprs/exprs.expected index 7b5c952d6f3..60915f59a4d 100644 --- a/java/ql/test-kotlin1/library-tests/exprs/exprs.expected +++ b/java/ql/test-kotlin1/library-tests/exprs/exprs.expected @@ -119,14 +119,14 @@ | 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: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 | delegatedProperties.kt:26:13:26:24 | getCurValue | ThisAccess | -| delegatedProperties.kt:26:13:26:28 | this | delegatedProperties.kt:26:13:26:24 | setCurValue | ThisAccess | -| delegatedProperties.kt:26:13:26:28 | this.curValue | delegatedProperties.kt:26:13:26:24 | getCurValue | VarAccess | | 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 | @@ -281,6 +281,8 @@ | 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 | DelegatedPropertiesKt | delegatedProperties.kt:60:1:60:20 | getTopLevelInt | TypeAccess | +| delegatedProperties.kt:60:1:60:20 | DelegatedPropertiesKt.topLevelInt | delegatedProperties.kt:60:1:60:20 | getTopLevelInt | 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 | @@ -288,10 +290,8 @@ | 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 | getTopLevelInt | 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 | getTopLevelInt | 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 | @@ -1545,11 +1545,11 @@ | exprs.kt:186:23:186:27 | Color | exprs.kt:184:1:187:1 | enums | TypeAccess | | exprs.kt:186:23:186:27 | Color.GREEN | exprs.kt:184:1:187:1 | enums | VarAccess | | exprs.kt:192:5:192:10 | int | file://:0:0:0:0 | | TypeAccess | +| exprs.kt:192:5:192:10 | this | exprs.kt:192:5:192:10 | getA1 | ThisAccess | +| exprs.kt:192:5:192:10 | this.a1 | exprs.kt:192:5:192:10 | getA1 | VarAccess | | exprs.kt:192:5:192:14 | ...=... | exprs.kt:191:1:199:1 | Class1 | KtInitializerAssignExpr | | exprs.kt:192:5:192:14 | a1 | exprs.kt:191:1:199:1 | Class1 | VarAccess | | exprs.kt:192:5:192:14 | int | file://:0:0:0:0 | | TypeAccess | -| exprs.kt:192:5:192:14 | this | exprs.kt:192:5:192:10 | getA1 | ThisAccess | -| exprs.kt:192:5:192:14 | this.a1 | exprs.kt:192:5:192:10 | getA1 | VarAccess | | exprs.kt:192:14:192:14 | 1 | exprs.kt:191:1:199:1 | Class1 | IntegerLiteral | | exprs.kt:193:13:198:5 | Object | file://:0:0:0:0 | | TypeAccess | | exprs.kt:194:9:194:18 | a2 | exprs.kt:193:13:198:5 | getObject | LocalVariableDeclExpr | @@ -1558,11 +1558,11 @@ | exprs.kt:195:16:197:9 | Interface1 | exprs.kt:193:13:198:5 | getObject | TypeAccess | | exprs.kt:195:16:197:9 | new (...) | exprs.kt:193:13:198:5 | getObject | ClassInstanceExpr | | exprs.kt:196:13:196:26 | String | file://:0:0:0:0 | | TypeAccess | +| exprs.kt:196:13:196:26 | this | exprs.kt:196:13:196:26 | getA3 | ThisAccess | +| exprs.kt:196:13:196:26 | this.a3 | exprs.kt:196:13:196:26 | getA3 | VarAccess | | exprs.kt:196:13:196:49 | ...=... | exprs.kt:195:16:197:9 | | KtInitializerAssignExpr | | exprs.kt:196:13:196:49 | String | file://:0:0:0:0 | | TypeAccess | | exprs.kt:196:13:196:49 | a3 | exprs.kt:195:16:197:9 | | VarAccess | -| exprs.kt:196:13:196:49 | this | exprs.kt:196:13:196:26 | getA3 | ThisAccess | -| exprs.kt:196:13:196:49 | this.a3 | exprs.kt:196:13:196:26 | getA3 | VarAccess | | exprs.kt:196:31:196:32 | getA1(...) | exprs.kt:195:16:197:9 | | MethodCall | | exprs.kt:196:31:196:32 | this | exprs.kt:195:16:197:9 | | ThisAccess | | exprs.kt:196:31:196:37 | ... + ... | exprs.kt:195:16:197:9 | | AddExpr | @@ -4295,10 +4295,10 @@ | samConversion.kt:59:12:59:12 | 1 | samConversion.kt:57:9:60:1 | test | IntegerLiteral | | samConversion.kt:59:14:59:14 | 2 | samConversion.kt:57:9:60:1 | test | IntegerLiteral | | samConversion.kt:63:5:63:9 | int | file://:0:0:0:0 | | TypeAccess | +| samConversion.kt:63:5:63:9 | this | samConversion.kt:63:5:63:9 | getX | ThisAccess | +| samConversion.kt:63:5:63:9 | this.x | samConversion.kt:63:5:63:9 | getX | VarAccess | | samConversion.kt:63:5:63:13 | ...=... | samConversion.kt:62:1:64:1 | PropertyRefsTest | KtInitializerAssignExpr | | samConversion.kt:63:5:63:13 | int | file://:0:0:0:0 | | TypeAccess | -| samConversion.kt:63:5:63:13 | this | samConversion.kt:63:5:63:9 | getX | ThisAccess | -| samConversion.kt:63:5:63:13 | this.x | samConversion.kt:63:5:63:9 | getX | VarAccess | | samConversion.kt:63:5:63:13 | x | samConversion.kt:62:1:64:1 | PropertyRefsTest | VarAccess | | samConversion.kt:63:13:63:13 | 1 | samConversion.kt:62:1:64:1 | PropertyRefsTest | IntegerLiteral | | samConversion.kt:67:5:67:37 | int | file://:0:0:0:0 | | TypeAccess | diff --git a/java/ql/test-kotlin1/library-tests/methods/exprs.expected b/java/ql/test-kotlin1/library-tests/methods/exprs.expected index 256ca4cbfbe..26cc729fada 100644 --- a/java/ql/test-kotlin1/library-tests/methods/exprs.expected +++ b/java/ql/test-kotlin1/library-tests/methods/exprs.expected @@ -1,3 +1,5 @@ +| clinit.kt:3:1:3:20 | ClinitKt | TypeAccess | +| 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 | @@ -6,8 +8,6 @@ | 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 | 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 | ClinitKt.topLevelInt | VarAccess | | clinit.kt:3:1:3:24 | int | TypeAccess | diff --git a/java/ql/test-kotlin1/library-tests/variables/variableAccesses.expected b/java/ql/test-kotlin1/library-tests/variables/variableAccesses.expected index b29bc063a2b..5623638949d 100644 --- a/java/ql/test-kotlin1/library-tests/variables/variableAccesses.expected +++ b/java/ql/test-kotlin1/library-tests/variables/variableAccesses.expected @@ -1,11 +1,11 @@ varAcc +| variables.kt:3:5:3:17 | this.prop | | variables.kt:3:5:3:21 | prop | -| variables.kt:3:5:3:21 | this.prop | | variables.kt:7:17:7:22 | local1 | | variables.kt:9:17:9:22 | local2 | | variables.kt:10:22:10:26 | param | | variables.kt:11:17:11:22 | local3 | -| variables.kt:15:1:15:21 | VariablesKt.topLevel | +| variables.kt:15:1:15:17 | VariablesKt.topLevel | | variables.kt:15:1:15:21 | VariablesKt.topLevel | | variables.kt:21:11:21:18 | o | | variables.kt:21:11:21:18 | o | @@ -20,7 +20,7 @@ extensionReceiverAcc | variables.kt:32:9:32:12 | this | | variables.kt:33:9:33:12 | this | instAcc -| variables.kt:3:5:3:21 | this | +| variables.kt:3:5:3:17 | this | | variables.kt:21:11:21:18 | this | | variables.kt:26:9:26:9 | this | | variables.kt:26:11:26:15 | this | diff --git a/java/ql/test-kotlin1/library-tests/variables/variables.expected b/java/ql/test-kotlin1/library-tests/variables/variables.expected index 0022c22ed8b..1f562ccceac 100644 --- a/java/ql/test-kotlin1/library-tests/variables/variables.expected +++ b/java/ql/test-kotlin1/library-tests/variables/variables.expected @@ -13,8 +13,8 @@ isFinal | variables.kt:8:9:8:26 | int local2 | non-final | | variables.kt:10:9:10:26 | int local3 | final | compileTimeConstant +| variables.kt:3:5:3:17 | this.prop | | variables.kt:3:5:3:21 | prop | -| variables.kt:3:5:3:21 | this.prop | | variables.kt:7:17:7:22 | local1 | -| variables.kt:15:1:15:21 | VariablesKt.topLevel | +| variables.kt:15:1:15:17 | VariablesKt.topLevel | | variables.kt:15:1:15:21 | VariablesKt.topLevel |