Commit Graph

12 Commits

Author SHA1 Message Date
Anders Fugmann
7ea8558c25 Kotlin: converge delegated-property accessor synthetic thisRef locations onto K2
For `val x by Delegate()`, the compiler synthesises getter/setter bodies that
forward to the delegate's `getValue`/`setValue`, passing a synthetic receiver
argument (the enclosing `this`, or `null` for top-level/extension delegates).

Under the K1 frontend (test-kotlin1, `-language-version 1.9`) these synthetic
receiver arguments were given a bogus location `1:9:1:12`: their IR offsets
(8..11) are not real source offsets for the argument, and `findPsiElement`
resolves them to whatever sits at file offset 8, which for these tests is the
line-1 `import`. This is meaningless and makes the receivers un-searchable by
location.

The K2 frontend (test-kotlin2, default) already produces the intuitive result:
the synthetic member receiver is located at the DELEGATE EXPRESSION (e.g.
`by ResourceDelegate()` -> `ResourceDelegate()`), and a receiver-less `null`
argument gets the whole-file location `0:0:0:0`. K2 output is the canonical
target for this unification (it is also compiler-version-independent).

This change adds `getDelegatedAccessorSyntheticArgumentLocation`, gated on:
  - the enclosing declaration being an IrFunction with origin
    DELEGATED_PROPERTY_ACCESSOR (member/top-level accessors only),
  - the element being either the accessor's dispatch/extension receiver
    `IrGetValue` or a null `CodeQLIrConst`, and
  - the element offsets lying outside the enclosing `KtProperty` text range
    (so genuine in-source expressions are never rehomed).
It returns the delegate expression's location for the `this`/receiver case and
the whole-file location for the `null` case, mirroring K2. It is wired at the
three sites that extract these synthetic args (extractThisAccess, the
IrGetValue variable/extension-receiver path, and the null case of
extractConstant).

The helper relies on PSI (getPsi2Ir), so it is a no-op under K2: test-kotlin2
output is unchanged (verified). Only test-kotlin1 converges.

Trade-offs / scope:
  - LOCAL delegated properties (declared inside a function body) are a distinct
    mechanism: their get/set is inlined into the enclosing function (no
    DELEGATED_PROPERTY_ACCESSOR origin) and the residual divergence there is an
    end-offset difference, not the `1:9:1:12` bug. They are deliberately left
    for a separate commit.
  - The `$delegate` backing-variable initializer/type-access spans still differ
    between suites (e.g. `4:18` vs `4:21`); that is an unrelated mechanism and
    is not touched here.
  - The outside-KtProperty-range gate could in principle false-negative for a
    delegated property declared at the very top of a file (offsets <= 11); this
    is preferred to over-correcting real expressions and is not observed in the
    test corpus.

Relearned test-kotlin1 expected updated (exprs, methods; incl. PrintAst).
Verified via full dual-suite relearn (CI-faithful: 2.3.20/lang-1.9 for tk1,
default/2.4.0 for tk2, database consistency checks): all 3333 tests pass, only
the three delegates-related tk1 files change, every changed row is a pure
relocation of a synthetic receiver from `1:9:1:12` to the K2 target, and
test-kotlin2 is byte-for-byte unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-12 00:31:25 +02:00
Anders Fugmann
18a6bde156 Kotlin: converge delegated-property accessor body expression locations onto the delegate expression
The synthesised body of a delegated-property accessor (`get`/`getValue`/
`setValue`/`invoke` calls, the `<prop>$delegate` access, associated type
accesses and property-reference classes) carries the source range of the
whole `KtPropertyDelegate` node. The K1 frontend's range starts at the
`by` keyword; K2 starts at the delegate expression itself
(e.g. `lazy { ... }`), three columns later. The `by` keyword is syntactic
glue in the property declaration, not part of the expression being
evaluated, so K2's narrower range is the more intuitive one. Adopt it for
both frontends. Example:

  get / getValue / invoke ...  6:24:9:9  ->  6:27:9:9

Add a scoped offset remap on `FileTrapWriter`: while extracting a
`DELEGATED_PROPERTY_ACCESSOR` body, any location whose offsets exactly
equal the `by`-inclusive delegate range is emitted with the delegate
expression's range instead. The range is recovered from the enclosing
`KtProperty`'s PSI (`delegate.expression`), which is available under K1;
under K2 there is no PSI so the remap is inactive and the raw offsets
already exclude `by`. Matching the full delegate range exactly means only
these synthesised body expressions are affected.

Updates test-kotlin1 expected only (test-kotlin2 unchanged).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-11 14:09:36 +02:00
Anders Fugmann
b2f94da372 Kotlin: converge delegated property accessor locations onto K2 property span
For local and member delegated properties, the synthesised accessor
(`<get-prop>`/`<set-prop>`) and its generated wrapper class/constructor
were anchored by the K1 frontend at the delegate expression rather than
at the property declaration. K2 (raw IR) anchors them at the property
`val`/`var` keyword through the end of the delegate, i.e. the whole
`KtProperty` span.

Adopt the K2 span for both frontends so the extractor emits identical
locations regardless of the supplied language version. Example:

  <get-prop1>  6:24:9:9  ->  6:9:9:9

Add `getPsiBasedDelegatedAccessorLocation`, which walks from the
accessor's PSI up to the enclosing `KtProperty` and returns its span.
It returns null when there is no PSI (K2) or no enclosing property, so
K2 keeps its native locations and non-delegated callables are
unaffected. Wire it into `extractFunction`'s location chain and into
`extractGeneratedClass` (guarded on `DELEGATED_PROPERTY_ACCESSOR`) so the
generated class/constructor sorts before the method, matching K2.

Updates test-kotlin1 expected only (test-kotlin2 unchanged).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-11 13:15:35 +02:00
Anders Fugmann
240103e7b3 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>
2026-07-11 10:12:41 +02:00
Anders Fugmann
e37d89b849 Kotlin: converge default property-accessor locations onto the K2-native spans
Synthesised and bare `get`/`set` accessors were extracted with different
source locations depending on the frontend:

    val typedProp: Int = 3            // getTypedProp
        K1: 5:5:5:17  (val..name)     K2: 5:5:5:22  (val..type)

    val defaultGetter = 7
        get                           // getDefaultGetter
        K1: 19:5:19:21 (property head) K2: 20:13:20:15 (`get` keyword)

Under K2 the extractor has no PSI back-mapping for these accessors
(`getKtFile` returns null), so it cannot reproduce K1's property-name-end
span; K2 instead falls back to the raw IR offsets. Rather than converge on a
value K2 cannot produce, K1 is made to match the K2-native spans via the PSI:

  * a bare `get`/`set` keyword now points at the keyword token; and
  * a fully synthesised accessor now spans the property signature
    (`val`/`var` .. type annotation, or .. name when untyped), excluding the
    initialiser.

Explicit-body accessors (`get() = 5`) are unaffected: they are located at
their body and never take this override.

Only K1 output changes; the test-kotlin2 (K2) expected files are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-11 01:46:59 +02:00
Anders Fugmann
b8a16923ba kotlin-extractor: converge local variable entity locations across language versions
The local variable declaration expression (extractVariableExpr) computed its
location via getPsiBasedLocation(v as IrElement). The `as IrElement` cast forced
overload resolution to the generic getPsiBasedLocation(IrElement), which resolves
the PSI element straight from the IR element's raw source offsets via
PsiSourceManager.findPsiElement. Those offsets differ between the two frontend
language versions:

  fun f(param: Int) {
      val local1 = 2 + 3   // line 6: `        val local1 = 2 + 3`
  }

- With -language-version 1.9 the IrVariable offsets cover only the name, so
  findPsiElement returns the name leaf and the variable is located at 6:13:6:18.
- With -language-version 2.0 the IrVariable offsets cover the whole declaration,
  so it is located at 6:9:6:26 (the `val` keyword through the initialiser).

The IrVariable-specific overload getPsiBasedLocation(IrVariable) is frontend
stable: it finds the leaf at the variable's start offset, walks up to the
enclosing KtVariableDeclaration and returns the span from the `val`/`var` keyword
to the end of the declaration. Using it for the entity location makes both
language versions emit the full declaration span 6:9:6:26.

This is the same helper already used for the enclosing localvariabledeclstmt
location; this change applies it to the variable entity (localvars / the
localvariabledeclexpr) as well, so the two are consistent.

Effect: 12 test-kotlin1 expected files move toward the test-kotlin2 output (all
strictly reduce the tk1-vs-tk2 difference; e.g. variables 16->4, reflection
111->67, exprs 1759->1363 diff lines). test-kotlin2 output is unchanged (the
overload produces the same span there as the generic one did). Both suites pass
all 3333 tests with --check-databases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Also update the enhanced-nullability Kotlin integration test expected: its
local variable declaration expression now spans the val/var keyword to the
initialiser (user.kt:2:7:2:7 -> user.kt:2:3:2:16), consistent with the
library-test convergence.
2026-07-10 22:35:20 +02:00
Anders Fugmann
7eb71807cd fix property and accessor locations using PSI
K1 IrProperty.startOffset includes leading modifiers (private, abstract,
lateinit, annotations) in the span start; K2 already starts at val/var.
Walk the PSI tree from p.startOffset to the enclosing KtProperty, then use
valOrVarKeyword.startOffset as the declaration start, giving a consistent
start in both K1 and K2.

Two related but distinct locations are derived from the KtProperty:

- The property itself spans val/var through the end of the full
  declaration (KtProperty.endOffset), including an explicit getter/setter
  body on a following line. This is getPsiBasedLocation(IrProperty).
- Synthesised accessors (DEFAULT_PROPERTY_ACCESSOR origin) span val/var
  through the end of the property name (KtProperty.nameIdentifier.endOffset)
  via getPsiBasedAccessorLocation, applied through accessorOverride().
  Explicit getter/setter bodies keep their own independently computed
  location.

This makes K1 accessor locations match K2 and gives each synthesised
accessor a precise span, rather than the property's full declaration span.

Example (properties.kt line 3, "var modifiableInt = 1"):
  property  modifiableInt     -> 3:5:3:25   (val/var .. end of "= 1")
  accessor  getModifiableInt  -> 3:5:3:21   (val/var .. end of name)
  accessor  setModifiableInt  -> 3:5:3:21

Because accessor locations appear wherever accessors are reported, this
refinement updates many expected files (property listings, modifiers,
methods, reflection, control-flow and expression dumps). Every change is a
location-coordinate change only: no result tuple is added or removed.

The PSI-based location is restricted to unspecialised extractions
(classTypeArgsIncludingOuterClasses.isNullOrEmpty()). Specialised generic
instances (e.g. C<String>.prop) continue to use the binary whole-file
location returned by getLocation(p, typeArgs), preserving the existing
behaviour that keeps them absent from fromSource() queries.

The visibility merge in extractFunction is extended to accept an
overriddenAttributes parameter from the caller; the internal fake-override
visibility adjustment (DescriptorVisibilities.PUBLIC for Java binary Object
methods) is merged with any caller-supplied attributes so that neither
overrides the other silently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-10 19:59:14 +02:00
Anders Fugmann
19092fbed7 kotlin-extractor: fix K1 local variable locations to span from val/var to initializer
In K1 mode, IrVariable.startOffset points to the name identifier and
IrVariable.endOffset is also at the name end, giving a location that
covers only the variable name (e.g. "local1") rather than the full
declaration ("val local1 = 2 + 3").

In K2 mode the IR offsets already point from the val/var keyword to the
end of the initializer, matching the intuitive source span.

Fix this for K1 by adding an IrVariable-specific overload of
getPsiBasedLocation that:
1. finds the leaf PSI element at v.startOffset (the name identifier)
2. walks up to the enclosing KtVariableDeclaration (KtProperty)
3. uses the val/var keyword position as the start offset to exclude
   any leading annotations from the span

This matches the K2 IR behavior and gives a more complete location for
variable declarations when used in CodeQL queries.

The fix applies to both K1 and K2 since the overload is unconditional;
for K2 the walk-up gives the same result as before.

Expected updates in test-kotlin1:
- variables: local variable locations now span from val/var to initializer
- exprs, stmts, methods, modifiers, reflection: same local variable span
- controlflow/basic: CFG node references updated for new variable spans
  (the "var ...;" node now starts at the val/var keyword)

Class member and top-level properties (IrProperty, not IrVariable) are
not affected by this change and retain their existing location behaviour.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-10 19:59:13 +02:00
Anders Fugmann
86d9c349ec Kotlin: Accept test changes 2026-01-28 10:11:21 +01:00
Ian Lynagh
08be35fc2c Kotlin: Add a test for constructors 2024-09-27 11:21:23 +01:00
Ian Lynagh
918bee07dd Kotlin: Accept Kotlin 1 test changes 1.9.0 -> 2.0.0 2024-06-03 15:26:51 +01:00
Ian Lynagh
f48cc1a526 Kotlin: Move tests from test/kotlin to test-kotlin1
Matches test-kotlin2
2023-11-21 15:28:12 +00:00