Skip to content

Commit 240103e

Browse files
Kotlin: converge implicit primary constructor locations onto the K2-native class span
The K1 and K2 frontends record different source locations for the compiler-synthesised primary constructor of a class that declares no primary constructor in source. K2 uses the class declaration's raw IR offsets, which include any leading modifier keywords, while K1's raw offsets start at the `class` keyword and omit the modifiers. Since K1 (unlike K2) retains the PSI, we recover the modifier-inclusive span from the enclosing KtClassOrObject so K1 matches the K2-native span. Example, for `open class C0<V> {}` on line 11 (the `open` modifier is at column 1, the `class` keyword at column 6): before (K1): generics.kt:11:6:11:19 | C0 | C0() after (K1): generics.kt:11:1:11:19 | C0 | C0() (matches K2) The fix is deliberately narrow and leaves all other constructors untouched: - explicit primary constructors (`class C1(val t: T)`, `class C2()`) keep their own parameter-list location, which both frontends already agree on; - explicit secondary constructors keep their own location (only the `isPrimary` constructor is adjusted); - specialised/parameterised copies of a generic constructor (`typeSubstitution != null`) are excluded, so they do not gain a spurious source location and appear in source-filtered queries. Relearned test-kotlin1 (K1) and test-kotlin2 (K2): all 3333 tests pass with database-consistency checks. Only test-kotlin1 expected files change (K2 output is unchanged, as K2 already emits these spans natively). No previously matching row diverges; net K1-vs-K2 divergence decreases on every affected file, with generics, generic-inner-classes and modifiers now fully identical. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e37d89b commit 240103e

9 files changed

Lines changed: 66 additions & 28 deletions

File tree

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
5353
import org.jetbrains.kotlin.name.FqName
5454
import org.jetbrains.kotlin.types.Variance
5555
import org.jetbrains.kotlin.util.OperatorNameConventions
56+
import org.jetbrains.kotlin.psi.KtClass
57+
import org.jetbrains.kotlin.psi.KtClassOrObject
5658
import org.jetbrains.kotlin.psi.KtProperty
5759
import org.jetbrains.kotlin.psi.KtPropertyAccessor
5860
import org.jetbrains.kotlin.psi.KtVariableDeclaration
@@ -2462,6 +2464,7 @@ open class KotlinFileExtractor(
24622464
val locId =
24632465
overriddenAttributes?.sourceLoc
24642466
?: (if (usesK2) getPsiBasedLocation(f) else null)
2467+
?: (if (f.symbol is IrConstructorSymbol && typeSubstitution == null) getPsiBasedImplicitConstructorLocation(f) else null)
24652468
?: getLocation(f, classTypeArgsIncludingOuterClasses)
24662469

24672470
if (f.symbol is IrConstructorSymbol) {
@@ -3038,6 +3041,41 @@ open class KotlinFileExtractor(
30383041
return tw.getLocation(declStart, declEnd)
30393042
}
30403043

3044+
/**
3045+
* Returns the PSI-based location for the *implicit* (compiler-synthesised)
3046+
* primary constructor of [f]'s enclosing class, spanning the full
3047+
* [KtClassOrObject] text range (including any leading modifiers such as `open`).
3048+
*
3049+
* When a class has no primary constructor written in source (for example
3050+
* `open class C0<V> {}`), the compiler synthesises one. The K2 frontend records
3051+
* that constructor with the class declaration's raw IR offsets, which include
3052+
* the leading modifier keywords. The K1 frontend's raw offsets for the same
3053+
* synthesised constructor start at the `class` keyword and omit the modifiers.
3054+
* K1 retains the PSI, so we recover the modifier-inclusive span from the
3055+
* enclosing [KtClassOrObject] to match K2.
3056+
*
3057+
* This deliberately does *not* apply to an *explicit* primary constructor
3058+
* (e.g. `class C1(val t: T)` or `class C2()`): those keep their own
3059+
* parameter-list location, which both frontends already record identically from
3060+
* raw IR offsets. It returns null when the [KtFile] is unavailable (as under K2,
3061+
* where the raw offsets already carry the class span) or when the enclosing
3062+
* class declares an explicit primary constructor.
3063+
*/
3064+
private fun getPsiBasedImplicitConstructorLocation(f: IrFunction): Label<DbLocation>? {
3065+
if ((f as? IrConstructor)?.isPrimary != true) return null
3066+
val c = f.parentAsClass
3067+
val startOffset = c.startOffset
3068+
if (startOffset < 0) return null
3069+
val file = currentIrFile ?: return null
3070+
val ktClass = getPsi2Ir()?.getKtFile(file)
3071+
?.findElementAt(startOffset)
3072+
?.let { leaf -> generateSequence(leaf) { it.parent }.filterIsInstance<KtClassOrObject>().firstOrNull() }
3073+
?: return null
3074+
// An explicit primary constructor keeps its own (parameter-list) location.
3075+
if ((ktClass as? KtClass)?.primaryConstructor != null) return null
3076+
return tw.getLocation(ktClass.startOffset, ktClass.endOffset)
3077+
}
3078+
30413079
private fun extractVariable(
30423080
v: IrVariable,
30433081
callable: Label<out DbCallable>,

java/ql/test-kotlin1/library-tests/controlflow/basic/bbStmts.expected

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
| Test.kt:3:8:80:1 | Entry | 0 | Test.kt:3:8:80:1 | Entry |
2-
| Test.kt:3:8:80:1 | Entry | 1 | Test.kt:3:8:80:1 | { ... } |
3-
| Test.kt:3:8:80:1 | Entry | 2 | Test.kt:3:1:80:1 | Before super(...) |
4-
| Test.kt:3:8:80:1 | Entry | 3 | Test.kt:3:1:80:1 | super(...) |
5-
| Test.kt:3:8:80:1 | Entry | 4 | Test.kt:3:1:80:1 | After super(...) |
6-
| Test.kt:3:8:80:1 | Entry | 5 | Test.kt:3:8:80:1 | { ... } |
7-
| Test.kt:3:8:80:1 | Entry | 6 | Test.kt:3:8:80:1 | After { ... } |
8-
| Test.kt:3:8:80:1 | Entry | 7 | Test.kt:3:8:80:1 | Normal Exit |
9-
| Test.kt:3:8:80:1 | Entry | 8 | Test.kt:3:8:80:1 | Exit |
1+
| Test.kt:3:1:80:1 | Entry | 0 | Test.kt:3:1:80:1 | Entry |
2+
| Test.kt:3:1:80:1 | Entry | 1 | Test.kt:3:8:80:1 | { ... } |
3+
| Test.kt:3:1:80:1 | Entry | 2 | Test.kt:3:1:80:1 | Before super(...) |
4+
| Test.kt:3:1:80:1 | Entry | 3 | Test.kt:3:1:80:1 | super(...) |
5+
| Test.kt:3:1:80:1 | Entry | 4 | Test.kt:3:1:80:1 | After super(...) |
6+
| Test.kt:3:1:80:1 | Entry | 5 | Test.kt:3:8:80:1 | { ... } |
7+
| Test.kt:3:1:80:1 | Entry | 6 | Test.kt:3:8:80:1 | After { ... } |
8+
| Test.kt:3:1:80:1 | Entry | 7 | Test.kt:3:1:80:1 | Normal Exit |
9+
| Test.kt:3:1:80:1 | Entry | 8 | Test.kt:3:1:80:1 | Exit |
1010
| Test.kt:4:2:79:2 | Entry | 0 | Test.kt:4:2:79:2 | Entry |
1111
| Test.kt:4:2:79:2 | Entry | 1 | Test.kt:4:13:79:2 | { ... } |
1212
| Test.kt:4:2:79:2 | Entry | 2 | Test.kt:5:3:5:16 | var ...; |

java/ql/test-kotlin1/library-tests/controlflow/basic/getASuccessor.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#select
2+
| Test.kt:3:1:80:1 | Entry | Constructor | Test.kt:3:8:80:1 | { ... } | BlockStmt |
3+
| Test.kt:3:1:80:1 | Normal Exit | Constructor | Test.kt:3:1:80:1 | Exit | Constructor |
24
| Test.kt:3:1:80:1 | super(...) | SuperConstructorInvocationStmt | Test.kt:3:8:80:1 | { ... } | BlockStmt |
3-
| Test.kt:3:8:80:1 | Entry | Constructor | Test.kt:3:8:80:1 | { ... } | BlockStmt |
4-
| Test.kt:3:8:80:1 | Normal Exit | Constructor | Test.kt:3:8:80:1 | Exit | Constructor |
5+
| Test.kt:3:8:80:1 | { ... } | BlockStmt | Test.kt:3:1:80:1 | Normal Exit | Constructor |
56
| Test.kt:3:8:80:1 | { ... } | BlockStmt | Test.kt:3:1:80:1 | super(...) | SuperConstructorInvocationStmt |
6-
| Test.kt:3:8:80:1 | { ... } | BlockStmt | Test.kt:3:8:80:1 | Normal Exit | Constructor |
77
| Test.kt:4:2:79:2 | Entry | Method | Test.kt:4:13:79:2 | { ... } | BlockStmt |
88
| Test.kt:4:2:79:2 | Normal Exit | Method | Test.kt:4:2:79:2 | Exit | Method |
99
| Test.kt:4:13:79:2 | { ... } | BlockStmt | Test.kt:5:3:5:16 | var ...; | LocalVariableDeclStmt |

java/ql/test-kotlin1/library-tests/exprs/exprs.expected

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,11 +1474,11 @@
14741474
| exprs.kt:170:9:170:17 | r2.height | exprs.kt:165:1:172:1 | foo | VarAccess |
14751475
| exprs.kt:170:9:170:21 | ...=... | exprs.kt:165:1:172:1 | foo | AssignExpr |
14761476
| exprs.kt:170:21:170:21 | 3 | exprs.kt:165:1:172:1 | foo | IntegerLiteral |
1477-
| exprs.kt:174:1:176:1 | 0 | exprs.kt:174:6:176:1 | Direction | IntegerLiteral |
1478-
| exprs.kt:174:1:176:1 | Direction | exprs.kt:174:6:176:1 | Direction | TypeAccess |
1479-
| exprs.kt:174:1:176:1 | Enum<Direction> | exprs.kt:174:6:176:1 | Direction | TypeAccess |
1480-
| exprs.kt:174:1:176:1 | new Enum(...) | exprs.kt:174:6:176:1 | Direction | ClassInstanceExpr |
1481-
| exprs.kt:174:1:176:1 | null | exprs.kt:174:6:176:1 | Direction | NullLiteral |
1477+
| exprs.kt:174:1:176:1 | 0 | exprs.kt:174:1:176:1 | Direction | IntegerLiteral |
1478+
| exprs.kt:174:1:176:1 | Direction | exprs.kt:174:1:176:1 | Direction | TypeAccess |
1479+
| exprs.kt:174:1:176:1 | Enum<Direction> | exprs.kt:174:1:176:1 | Direction | TypeAccess |
1480+
| exprs.kt:174:1:176:1 | new Enum(...) | exprs.kt:174:1:176:1 | Direction | ClassInstanceExpr |
1481+
| exprs.kt:174:1:176:1 | null | exprs.kt:174:1:176:1 | Direction | NullLiteral |
14821482
| exprs.kt:175:5:175:10 | ...=... | exprs.kt:0:0:0:0 | <clinit> | KtInitializerAssignExpr |
14831483
| exprs.kt:175:5:175:10 | Direction | exprs.kt:0:0:0:0 | <clinit> | TypeAccess |
14841484
| exprs.kt:175:5:175:10 | Direction | exprs.kt:0:0:0:0 | <clinit> | TypeAccess |

java/ql/test-kotlin1/library-tests/generic-inner-classes/test.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ paramTypes
3333
| file:///!unknown-binary-location/testuser/OuterNotGeneric$InnerGeneric.class:0:0:0:0 | InnerGeneric<String> | String |
3434
constructors
3535
| KotlinUser.kt:3:1:18:1 | User |
36-
| OuterGeneric.kt:3:8:21:1 | OuterGeneric |
37-
| OuterGeneric.kt:5:16:9:3 | InnerNotGeneric |
36+
| OuterGeneric.kt:3:1:21:1 | OuterGeneric |
37+
| OuterGeneric.kt:5:3:9:3 | InnerNotGeneric |
3838
| OuterGeneric.kt:13:5:13:21 | InnerGeneric |
3939
| OuterGeneric.kt:15:5:15:25 | InnerGeneric |
40-
| OuterNotGeneric.kt:3:8:11:1 | OuterNotGeneric |
41-
| OuterNotGeneric.kt:5:16:9:3 | InnerGeneric |
40+
| OuterNotGeneric.kt:3:1:11:1 | OuterNotGeneric |
41+
| OuterNotGeneric.kt:5:3:9:3 | InnerGeneric |
4242
| file:///!unknown-binary-location/testuser/OuterGeneric$InnerGeneric.class:0:0:0:0 | InnerGeneric<String> |
4343
| file:///!unknown-binary-location/testuser/OuterGeneric$InnerGeneric.class:0:0:0:0 | InnerGeneric<String> |
4444
| file:///!unknown-binary-location/testuser/OuterGeneric$InnerNotGeneric.class:0:0:0:0 | InnerNotGeneric<> |

java/ql/test-kotlin1/library-tests/generics/generics.expected

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ parameterizedType
2929
function
3030
| generics.kt:3:1:5:1 | f0 | f0(int,java.lang.Object) |
3131
| generics.kt:7:1:9:1 | f1 | f1(int,java.lang.Object) |
32-
| generics.kt:11:6:11:19 | C0 | C0() |
32+
| generics.kt:11:1:11:19 | C0 | C0() |
3333
| generics.kt:13:15:13:24 | C1 | C1(java.lang.Object) |
3434
| generics.kt:13:16:13:23 | getT | getT() |
3535
| generics.kt:14:5:14:19 | f1 | f1(java.lang.Object) |
@@ -40,7 +40,7 @@ function
4040
| generics.kt:36:1:40:1 | BoundedTest | BoundedTest() |
4141
| generics.kt:38:5:38:25 | m | m(java.lang.CharSequence,java.lang.CharSequence) |
4242
| generics.kt:42:1:54:1 | Outer | Outer() |
43-
| generics.kt:43:11:47:5 | Inner1 | Inner1() |
43+
| generics.kt:43:5:47:5 | Inner1 | Inner1() |
4444
| generics.kt:44:9:46:9 | fn1 | fn1(java.lang.Object,java.lang.Object,java.lang.Object,java.lang.Object) |
4545
| generics.kt:49:5:53:5 | Nested1 | Nested1() |
4646
| generics.kt:50:9:52:9 | fn2 | fn2(java.lang.Object,java.lang.Object) |

java/ql/test-kotlin1/library-tests/methods/constructors.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
| delegates.kt:8:32:11:5 | | delegates.kt:8:32:11:5 | new KMutableProperty1<MyClass,String>(...) { ... } | file://:0:0:0:0 | void |
77
| delegates.kt:8:66:11:5 | | delegates.kt:8:66:11:5 | new Function3<KProperty<?>,String,String,Unit>(...) { ... } | file://:0:0:0:0 | void |
88
| enumClass.kt:1:21:1:32 | EnumClass | enumClass.kt:1:1:4:1 | EnumClass | file://:0:0:0:0 | void |
9-
| enumClass.kt:6:6:16:1 | EnumWithFunctions | enumClass.kt:6:1:16:1 | EnumWithFunctions | file://:0:0:0:0 | void |
9+
| enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:6:1:16:1 | EnumWithFunctions | file://:0:0:0:0 | void |
1010
| enumClass.kt:8:3:11:4 | VAL | enumClass.kt:8:3:11:4 | VAL | file://:0:0:0:0 | void |
1111
| methods2.kt:7:1:10:1 | Class2 | methods2.kt:7:1:10:1 | Class2 | file://:0:0:0:0 | void |
1212
| methods3.kt:5:1:7:1 | Class3 | methods3.kt:5:1:7:1 | Class3 | file://:0:0:0:0 | void |

java/ql/test-kotlin1/library-tests/methods/methods.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ constructors
6767
| delegates.kt:8:32:11:5 | new KMutableProperty1<MyClass,String>(...) { ... } | delegates.kt:8:32:11:5 | | |
6868
| delegates.kt:8:66:11:5 | new Function3<KProperty<?>,String,String,Unit>(...) { ... } | delegates.kt:8:66:11:5 | | |
6969
| enumClass.kt:1:1:4:1 | EnumClass | enumClass.kt:1:21:1:32 | EnumClass | EnumClass(int) |
70-
| enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:6:6:16:1 | EnumWithFunctions | EnumWithFunctions() |
70+
| enumClass.kt:6:1:16:1 | EnumWithFunctions | enumClass.kt:6:1:16:1 | EnumWithFunctions | EnumWithFunctions() |
7171
| enumClass.kt:8:3:11:4 | VAL | enumClass.kt:8:3:11:4 | VAL | VAL() |
7272
| methods2.kt:7:1:10:1 | Class2 | methods2.kt:7:1:10:1 | Class2 | Class2() |
7373
| methods3.kt:5:1:7:1 | Class3 | methods3.kt:5:1:7:1 | Class3 | Class3() |

java/ql/test-kotlin1/library-tests/modifiers/modifiers.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
| modifiers.kt:1:1:29:1 | X | Class | public |
2-
| modifiers.kt:1:6:29:1 | X | Constructor | public |
2+
| modifiers.kt:1:1:29:1 | X | Constructor | public |
33
| modifiers.kt:2:13:2:17 | getA$private | Method | final |
44
| modifiers.kt:2:13:2:17 | getA$private | Method | private |
55
| modifiers.kt:2:13:2:21 | a | Field | final |
@@ -22,7 +22,7 @@
2222
| modifiers.kt:5:5:5:34 | d | Property | public |
2323
| modifiers.kt:7:5:9:5 | Nested | Class | final |
2424
| modifiers.kt:7:5:9:5 | Nested | Class | protected |
25-
| modifiers.kt:7:15:9:5 | Nested | Constructor | public |
25+
| modifiers.kt:7:5:9:5 | Nested | Constructor | public |
2626
| modifiers.kt:8:16:8:25 | getE | Method | final |
2727
| modifiers.kt:8:16:8:25 | getE | Method | public |
2828
| modifiers.kt:8:16:8:29 | e | Field | final |
@@ -74,7 +74,7 @@
7474
| modifiers.kt:32:5:32:32 | foo | Method | public |
7575
| modifiers.kt:35:1:41:1 | LateInit | Class | final |
7676
| modifiers.kt:35:1:41:1 | LateInit | Class | public |
77-
| modifiers.kt:35:8:41:1 | LateInit | Constructor | public |
77+
| modifiers.kt:35:1:41:1 | LateInit | Constructor | public |
7878
| modifiers.kt:36:22:36:40 | getTest0$private | Method | final |
7979
| modifiers.kt:36:22:36:40 | getTest0$private | Method | private |
8080
| modifiers.kt:36:22:36:40 | setTest0$private | Method | final |

0 commit comments

Comments
 (0)