kotlin-extractor: converge backing-field locations across language versions

Anchor a property backing field's location on its property declaration (the
`val`/`var` keyword to the end of the declaration) rather than the raw IR
offset. The raw `IrField` offset is inconsistent between frontends: under
`-language-version 1.9` it includes leading modifiers in the span start, while
under 2.0 it starts at the `val`/`var` keyword.

Example: `private val privateProp: Int = 0`

  before (lang 1.9):  properties.kt:35:5:35:32  | int privateProp;  (col 5 = `private`)
  after  (lang 1.9):  properties.kt:35:13:35:32 | int privateProp;  (col 13 = `val`)
  lang 2.0 (unchanged): properties.kt:35:13:35:32 | int privateProp;

The property entity already uses this PSI-based anchor (getPsiBasedLocation),
so the field now matches its own property location, which is what the 2.0
frontend already emits.

Delegated properties are excluded via `isDelegated`: their field is the
`$delegate` storage, whose location is the delegate expression rather than the
property declaration, and is converged separately.

This is a no-op for `-language-version 2.0` (only test-kotlin1 expected files
change); the two suites' backing-field and field-type-access locations now
agree.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Anders Fugmann
2026-07-10 21:52:45 +02:00
parent 327eedcc9b
commit 7e79e693f4
10 changed files with 61 additions and 49 deletions

View File

@@ -2594,12 +2594,24 @@ open class KotlinFileExtractor(
if (isAnnotationClassField(f)) kClassToJavaClass(f.type) else f.type
val id = useField(f)
extractAnnotations(f, id, extractAnnotationEnumTypeAccesses)
// A plain backing field's location should match its property's location
// (the `val`/`var` keyword to the end of the declaration), rather than the
// raw IR offset, which includes leading modifiers under -language-version
// 1.9 but not 2.0. Anchoring on the property keeps the two consistent.
// Delegated properties are excluded: their field is the `$delegate` storage,
// whose location is the delegate expression, not the property declaration.
val locId =
f.correspondingPropertySymbol
?.owner
?.takeUnless { it.isDelegated }
?.let { getPsiBasedLocation(it) }
?: tw.getLocation(f)
return extractField(
id,
"${f.name.asString()}$fNameSuffix",
extractType,
parentId,
tw.getLocation(f),
locId,
f.visibility,
f,
isExternalDeclaration(f),

View File

@@ -44,13 +44,7 @@ def.kt:
# 50| 1: [LocalVariableDeclStmt] var ...;
# 50| 1: [LocalVariableDeclExpr] x
# 50| 0: [IntegerLiteral] 10
# 53| 3: [FieldDeclaration] int p;
#-----| -2: (Annotations)
# 56| 1: [Annotation] Annot0k
# 0| 1: [IntegerLiteral] 0
# 53| -1: [TypeAccess] int
# 57| 0: [IntegerLiteral] 5
# 57| 4: [Method] getP
# 57| 3: [Method] getP
#-----| 1: (Annotations)
# 54| 1: [Annotation] Annot0k
# 0| 1: [IntegerLiteral] 0
@@ -59,6 +53,12 @@ def.kt:
# 57| 0: [ReturnStmt] return ...
# 57| 0: [VarAccess] DefKt.p
# 57| -1: [TypeAccess] DefKt
# 57| 4: [FieldDeclaration] int p;
#-----| -2: (Annotations)
# 56| 1: [Annotation] Annot0k
# 0| 1: [IntegerLiteral] 0
# 57| -1: [TypeAccess] int
# 57| 0: [IntegerLiteral] 5
# 57| 5: [Method] setP
#-----| 1: (Annotations)
# 55| 1: [Annotation] Annot0k

View File

@@ -34,7 +34,7 @@ annotations
| def.kt:46:21:46:28 | Annot0k | def.kt:46:21:46:39 | a | def.kt:5:1:21:60 | Annot0k |
| def.kt:54:1:54:12 | Annot0k | def.kt:57:1:57:5 | getP | def.kt:5:1:21:60 | Annot0k |
| def.kt:55:1:55:12 | Annot0k | def.kt:57:1:57:5 | setP | def.kt:5:1:21:60 | Annot0k |
| def.kt:56:1:56:14 | Annot0k | def.kt:53:1:57:23 | p | def.kt:5:1:21:60 | Annot0k |
| def.kt:56:1:56:14 | Annot0k | def.kt:57:1:57:23 | p | def.kt:5:1:21:60 | Annot0k |
| def.kt:59:5:59:21 | Annot0k | def.kt:59:5:59:28 | <this> | def.kt:5:1:21:60 | Annot0k |
| use.java:10:5:10:21 | Annot0j | use.java:14:18:14:18 | Z | Annot0j.java:1:19:1:25 | Annot0j |
| use.java:11:5:11:90 | Annot1j | use.java:14:18:14:18 | Z | Annot1j.java:1:19:1:25 | Annot1j |

View File

@@ -658,7 +658,14 @@ generic_anonymous.kt:
# 1| 0: [ReturnStmt] return ...
# 1| 0: [VarAccess] this.t
# 1| -1: [ThisAccess] this
# 3| 4: [FieldDeclaration] new Object(...) { ... } x;
# 3| 4: [Method] getX$private
# 3| 3: [TypeAccess] new Object(...) { ... }
# 3| 0: [TypeAccess] T
# 3| 5: [BlockStmt] { ... }
# 3| 0: [ReturnStmt] return ...
# 3| 0: [VarAccess] this.x
# 3| -1: [ThisAccess] this
# 3| 5: [FieldDeclaration] new Object(...) { ... } x;
# 3| -1: [TypeAccess] new Object(...) { ... }
# 3| 0: [TypeAccess] T
# 3| 0: [StmtExpr] <Stmt>
@@ -686,13 +693,6 @@ generic_anonymous.kt:
# 3| 1: [ExprStmt] <Expr>;
# 3| 0: [ClassInstanceExpr] new (...)
# 3| -3: [TypeAccess] Object
# 3| 5: [Method] getX$private
# 3| 3: [TypeAccess] new Object(...) { ... }
# 3| 0: [TypeAccess] T
# 3| 5: [BlockStmt] { ... }
# 3| 0: [ReturnStmt] return ...
# 3| 0: [VarAccess] this.x
# 3| -1: [ThisAccess] this
# 7| 6: [Method] get
# 7| 3: [TypeAccess] T
# 7| 5: [BlockStmt] { ... }

View File

@@ -7,12 +7,12 @@
| 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 | T | T |
| generic_anonymous.kt:3:3:5:3 | new Object(...) { ... } | 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: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 | <Stmt> | new Object(...) { ... } |
| generic_anonymous.kt:3:19:5:3 | Object | Object |

View File

@@ -18,9 +18,9 @@ comments
| comments.kt:95:1:95:163 | // Diagnostic Matches: % Couldn't get owner of KDoc. The comment is extracted without an owner. ...while extracting a file (comments.kt) at %comments.kt:1:1:96:0% | // Diagnostic Matches: % Couldn't get owner of KDoc. The comment is extracted without an owner. ...while extracting a file (comments.kt) at %comments.kt:1:1:96:0% |
commentOwners
| comments.kt:4:1:11:3 | /**\n * A group of *members*.\n *\n * This class has no useful logic; it's just a documentation example.\n *\n * @property name the name of this group.\n * @constructor Creates an empty group.\n */ | comments.kt:12:1:31:1 | Group |
| comments.kt:14:5:16:7 | /**\n * Members of this group.\n */ | comments.kt:17:5:17:46 | members |
| comments.kt:14:5:16:7 | /**\n * Members of this group.\n */ | comments.kt:17:13:17:23 | getMembers$private |
| comments.kt:14:5:16:7 | /**\n * Members of this group.\n */ | comments.kt:17:13:17:46 | members |
| comments.kt:14:5:16:7 | /**\n * Members of this group.\n */ | comments.kt:17:13:17:46 | members |
| comments.kt:19:5:22:7 | /**\n * Adds a [member] to this group.\n * @return the new size of the group.\n */ | comments.kt:23:5:26:5 | add |
| comments.kt:35:5:35:34 | /** Medium is in the middle */ | comments.kt:36:5:36:14 | Medium |
| comments.kt:37:5:37:23 | /** This is high */ | comments.kt:38:5:38:11 | High |

View File

@@ -15,9 +15,9 @@
| Test.kt:1:1:8:1 | TestKt | Test.kt:3:3:3:11 | getField |
| Test.kt:1:1:8:1 | TestKt | Test.kt:3:3:3:11 | setField |
| Test.kt:1:1:8:1 | TestKt | Test.kt:3:3:3:22 | field |
| Test.kt:1:1:8:1 | TestKt | Test.kt:4:3:5:25 | rawField |
| Test.kt:1:1:8:1 | TestKt | Test.kt:5:3:5:14 | getRawField |
| Test.kt:1:1:8:1 | TestKt | Test.kt:5:3:5:14 | setRawField |
| Test.kt:1:1:8:1 | TestKt | Test.kt:5:3:5:25 | rawField |
| Test.kt:1:1:8:1 | TestKt | Test.kt:6:3:6:22 | method |
| Test.kt:10:1:10:20 | FieldUsedKt | Test.kt:10:1:10:20 | FieldUsedKt |
| Test.kt:11:1:11:23 | RawFieldUsedKt | Test.kt:11:1:11:23 | RawFieldUsedKt |

View File

@@ -1,19 +1,19 @@
| modifiers.kt:1:1:29:1 | X | Class | public |
| modifiers.kt:1:6:29:1 | X | Constructor | public |
| modifiers.kt:2:5:2:21 | a | Field | final |
| modifiers.kt:2:5:2:21 | a | Field | private |
| modifiers.kt:2:13:2:17 | getA$private | Method | final |
| modifiers.kt:2:13:2:17 | getA$private | Method | private |
| modifiers.kt:2:13:2:21 | a | Field | final |
| modifiers.kt:2:13:2:21 | a | Field | private |
| modifiers.kt:2:13:2:21 | a | Property | private |
| modifiers.kt:3:5:3:23 | b | Field | final |
| modifiers.kt:3:5:3:23 | b | Field | private |
| modifiers.kt:3:15:3:19 | getB | Method | final |
| modifiers.kt:3:15:3:19 | getB | Method | protected |
| modifiers.kt:3:15:3:23 | b | Field | final |
| modifiers.kt:3:15:3:23 | b | Field | private |
| modifiers.kt:3:15:3:23 | b | Property | protected |
| modifiers.kt:4:5:4:22 | c | Field | final |
| modifiers.kt:4:5:4:22 | c | Field | private |
| modifiers.kt:4:14:4:18 | getC$main | Method | final |
| modifiers.kt:4:14:4:18 | getC$main | Method | internal |
| modifiers.kt:4:14:4:22 | c | Field | final |
| modifiers.kt:4:14:4:22 | c | Field | private |
| modifiers.kt:4:14:4:22 | c | Property | internal |
| modifiers.kt:5:5:5:9 | getD | Method | final |
| modifiers.kt:5:5:5:9 | getD | Method | public |
@@ -23,10 +23,10 @@
| modifiers.kt:7:5:9:5 | Nested | Class | final |
| modifiers.kt:7:5:9:5 | Nested | Class | protected |
| modifiers.kt:7:15:9:5 | Nested | Constructor | public |
| modifiers.kt:8:9:8:29 | e | Field | final |
| modifiers.kt:8:9:8:29 | e | Field | private |
| modifiers.kt:8:16:8:20 | getE | Method | final |
| modifiers.kt:8:16:8:20 | getE | Method | public |
| modifiers.kt:8:16:8:29 | e | Field | final |
| modifiers.kt:8:16:8:29 | e | Field | private |
| modifiers.kt:8:16:8:29 | e | Property | public |
| modifiers.kt:11:5:15:5 | fn1 | Method | final |
| modifiers.kt:11:5:15:5 | fn1 | Method | public |
@@ -75,11 +75,11 @@
| modifiers.kt:35:1:41:1 | LateInit | Class | final |
| modifiers.kt:35:1:41:1 | LateInit | Class | public |
| modifiers.kt:35:8:41:1 | LateInit | Constructor | public |
| modifiers.kt:36:5:36:40 | test0 | Field | private |
| modifiers.kt:36:22:36:30 | getTest0$private | Method | final |
| modifiers.kt:36:22:36:30 | getTest0$private | Method | private |
| modifiers.kt:36:22:36:30 | setTest0$private | Method | final |
| modifiers.kt:36:22:36:30 | setTest0$private | Method | private |
| modifiers.kt:36:22:36:40 | test0 | Field | private |
| modifiers.kt:36:22:36:40 | test0 | Property | lateinit |
| modifiers.kt:36:22:36:40 | test0 | Property | private |
| modifiers.kt:38:5:40:5 | fn | Method | final |

View File

@@ -19,15 +19,15 @@
| test.kt:7:1:22:1 | A | test.kt:7:16:7:21 | A |
| test.kt:7:1:22:1 | A | test.kt:9:3:9:14 | getAnonType |
| test.kt:7:1:22:1 | A | test.kt:9:3:11:3 | anonType |
| test.kt:7:1:22:1 | A | test.kt:13:3:15:3 | privateAnonType |
| test.kt:7:1:22:1 | A | test.kt:13:11:13:29 | getPrivateAnonType$private |
| test.kt:7:1:22:1 | A | test.kt:13:11:15:3 | privateAnonType |
| test.kt:7:1:22:1 | A | test.kt:17:3:20:3 | privateUser |
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:9:18:11:3 | |
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:10:5:10:22 | x |
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:10:14:10:18 | getX |
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:10:14:10:22 | x |
| test.kt:13:33:15:3 | new If<T>(...) { ... } | test.kt:13:33:15:3 | |
| test.kt:13:33:15:3 | new If<T>(...) { ... } | test.kt:14:5:14:22 | x |
| test.kt:13:33:15:3 | new If<T>(...) { ... } | test.kt:14:14:14:18 | getX |
| test.kt:13:33:15:3 | new If<T>(...) { ... } | test.kt:14:14:14:22 | x |
enclosingTypes
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A.class:0:0:0:0 | A<CharSequence> |
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A.class:0:0:0:0 | A<String> |

View File

@@ -17,17 +17,17 @@
| properties.kt:28:5:29:22 | overrideGetter | properties.kt:29:13:29:22 | getOverrideGetter | properties.kt:28:5:28:22 | setOverrideGetter | properties.kt:28:5:29:22 | overrideGetter | public |
| properties.kt:30:5:31:29 | overrideGetterUseField | properties.kt:31:13:31:29 | getOverrideGetterUseField | properties.kt:30:5:30:30 | setOverrideGetterUseField | properties.kt:30:5:31:29 | overrideGetterUseField | public |
| properties.kt:32:5:33:29 | useField | properties.kt:33:13:33:29 | getUseField | file://:0:0:0:0 | <none> | properties.kt:32:5:33:29 | useField | public |
| properties.kt:34:14:34:36 | lateInitVar | properties.kt:34:14:34:28 | getLateInitVar | properties.kt:34:14:34:28 | setLateInitVar | properties.kt:34:5:34:36 | lateInitVar | lateinit, public |
| properties.kt:35:13:35:32 | privateProp | properties.kt:35:13:35:27 | getPrivateProp$private | file://:0:0:0:0 | <none> | properties.kt:35:5:35:32 | privateProp | private |
| properties.kt:36:15:36:36 | protectedProp | properties.kt:36:15:36:31 | getProtectedProp | file://:0:0:0:0 | <none> | properties.kt:36:5:36:36 | protectedProp | protected |
| properties.kt:37:12:37:30 | publicProp | properties.kt:37:12:37:25 | getPublicProp | file://:0:0:0:0 | <none> | properties.kt:37:5:37:30 | publicProp | public |
| properties.kt:38:14:38:34 | internalProp | properties.kt:38:14:38:29 | getInternalProp$main | file://:0:0:0:0 | <none> | properties.kt:38:5:38:34 | internalProp | internal |
| properties.kt:67:7:67:23 | constVal | properties.kt:67:7:67:18 | getConstVal | file://:0:0:0:0 | <none> | properties.kt:67:1:67:23 | constVal | public |
| properties.kt:34:14:34:36 | lateInitVar | properties.kt:34:14:34:28 | getLateInitVar | properties.kt:34:14:34:28 | setLateInitVar | properties.kt:34:14:34:36 | lateInitVar | lateinit, public |
| properties.kt:35:13:35:32 | privateProp | properties.kt:35:13:35:27 | getPrivateProp$private | file://:0:0:0:0 | <none> | properties.kt:35:13:35:32 | privateProp | private |
| properties.kt:36:15:36:36 | protectedProp | properties.kt:36:15:36:31 | getProtectedProp | file://:0:0:0:0 | <none> | properties.kt:36:15:36:36 | protectedProp | protected |
| properties.kt:37:12:37:30 | publicProp | properties.kt:37:12:37:25 | getPublicProp | file://:0:0:0:0 | <none> | properties.kt:37:12:37:30 | publicProp | public |
| properties.kt:38:14:38:34 | internalProp | properties.kt:38:14:38:29 | getInternalProp$main | file://:0:0:0:0 | <none> | properties.kt:38:14:38:34 | internalProp | internal |
| properties.kt:67:7:67:23 | constVal | properties.kt:67:7:67:18 | getConstVal | file://:0:0:0:0 | <none> | properties.kt:67:7:67:23 | constVal | public |
| properties.kt:70:5:70:16 | prop | properties.kt:70:5:70:12 | getProp | file://:0:0:0:0 | <none> | properties.kt:70:5:70:16 | prop | public |
| properties.kt:78:1:79:13 | x | properties.kt:79:5:79:13 | getX | file://:0:0:0:0 | <none> | file://:0:0:0:0 | <none> | public |
| properties.kt:80:1:81:13 | x | properties.kt:81:5:81:13 | getX | file://:0:0:0:0 | <none> | file://:0:0:0:0 | <none> | public |
| properties.kt:84:13:84:29 | data | properties.kt:84:13:84:20 | getData$private | properties.kt:84:13:84:20 | setData$private | properties.kt:84:5:84:29 | data | private |
| properties.kt:92:13:93:18 | data | properties.kt:93:9:93:18 | getData | properties.kt:92:13:92:20 | setData$private | properties.kt:92:5:93:18 | data | private |
| properties.kt:84:13:84:29 | data | properties.kt:84:13:84:20 | getData$private | properties.kt:84:13:84:20 | setData$private | properties.kt:84:13:84:29 | data | private |
| properties.kt:92:13:93:18 | data | properties.kt:93:9:93:18 | getData | properties.kt:92:13:92:20 | setData$private | properties.kt:92:13:93:18 | data | private |
fieldDeclarations
| properties.kt:2:27:2:50 | int constructorProp; | properties.kt:2:27:2:50 | constructorProp | 0 |
| properties.kt:2:53:2:83 | int mutableConstructorProp; | properties.kt:2:53:2:83 | mutableConstructorProp | 0 |
@@ -43,12 +43,12 @@ fieldDeclarations
| properties.kt:28:5:29:22 | int overrideGetter; | properties.kt:28:5:29:22 | overrideGetter | 0 |
| properties.kt:30:5:31:29 | int overrideGetterUseField; | properties.kt:30:5:31:29 | overrideGetterUseField | 0 |
| properties.kt:32:5:33:29 | int useField; | properties.kt:32:5:33:29 | useField | 0 |
| properties.kt:34:5:34:36 | String lateInitVar; | properties.kt:34:5:34:36 | lateInitVar | 0 |
| properties.kt:35:5:35:32 | int privateProp; | properties.kt:35:5:35:32 | privateProp | 0 |
| properties.kt:36:5:36:36 | int protectedProp; | properties.kt:36:5:36:36 | protectedProp | 0 |
| properties.kt:37:5:37:30 | int publicProp; | properties.kt:37:5:37:30 | publicProp | 0 |
| properties.kt:38:5:38:34 | int internalProp; | properties.kt:38:5:38:34 | internalProp | 0 |
| properties.kt:67:1:67:23 | int constVal; | properties.kt:67:1:67:23 | constVal | 0 |
| properties.kt:34:14:34:36 | String lateInitVar; | properties.kt:34:14:34:36 | lateInitVar | 0 |
| properties.kt:35:13:35:32 | int privateProp; | properties.kt:35:13:35:32 | privateProp | 0 |
| properties.kt:36:15:36:36 | int protectedProp; | properties.kt:36:15:36:36 | protectedProp | 0 |
| properties.kt:37:12:37:30 | int publicProp; | properties.kt:37:12:37:30 | publicProp | 0 |
| properties.kt:38:14:38:34 | int internalProp; | properties.kt:38:14:38:34 | internalProp | 0 |
| properties.kt:67:7:67:23 | int constVal; | properties.kt:67:7:67:23 | constVal | 0 |
| properties.kt:70:5:70:16 | int prop; | properties.kt:70:5:70:16 | prop | 0 |
| properties.kt:84:5:84:29 | int data; | properties.kt:84:5:84:29 | data | 0 |
| properties.kt:92:5:93:18 | int data; | properties.kt:92:5:93:18 | data | 0 |
| properties.kt:84:13:84:29 | int data; | properties.kt:84:13:84:29 | data | 0 |
| properties.kt:92:13:93:18 | int data; | properties.kt:92:13:93:18 | data | 0 |