mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 20:12:11 +08:00
Issue #2805: in a Kotlin test class with `private fun repository():
RealRepo`, `repository().pending(1)` went to a test double's
`FakeRepo.pending` in another package, and `val repo = repository();
repo.pending(1)` to the interface `Repo.pending`. The extractor records a
call on an unqualified call's result by the outer method's bare name (a
capitalized inner, `Foo().bar()`, is kept as a chain), so the name matcher
took whichever `pending` came first; and the local's type was read only
from a constructor or an annotation, so the receiver's name guessed it.
The name matcher now reads such a call where it is written
(`repository().pending(1)`, `this.repository()…`, `nullable()?.pending()`)
and finds the inner function the way Kotlin does: a method of the calling
class, of a class it inherits from or one around it, read from their
declarations, else a top-level function the file can name. The call is
resolved on the type that function's signature declares, including what
the type inherits and extensions on it, or links nothing when the type
lacks the method: the #750 rule, as for `Foo.factory().bar()`. A local set
from such a call alone (`val repo = repository()`) takes that type for
the existing typed-local path. Edge cases the A/B surfaced:
- a nested type is the one the function names, `Request.Builder` or a
plain `Builder` written in `Request`, matched by whole names
(`MockResponse.Builder` is no `Response.Builder`);
- an extension is the one the calling file can name (Exposed declares
`Expression<T>.year()` in three modules);
- a type from outside the project, a call inside an extension on one,
and an extension the index keeps unattached (on a nullable receiver, or
`fun Dns.Call.x()` filed under `Dns`) keep the name match as before.
Validation:
- New kotlin-call-result-receiver test (9 cases): 7 fail on main, the two
that guard unchanged behavior pass on both; all pass with the fix on the
kernel and on the wasm path. Mutation checks confirm the owner-path,
scoped-nested-type, whole-segment and extension-visibility rules are
each exercised.
- A/B on main e32021f6, calls by call site, nodes identical:
okhttp c317930: 82 retargeted, 176 gained, 3 dropped, 516 confidence
only; JetBrains/Exposed 09f814c: 152 retargeted, 138 gained, 1 dropped,
1353 confidence only; nowinandroid 2982e63: 1 confidence only. Controls
gin (Go), nest (TypeScript) and mybatis-3 (Java): byte-identical.
- Triage: retargets go from name guesses to the declared type
(`request().post()` → `Request.Builder.post`, `varchar(…).index()` →
`Column.index`, `rowNumber().over()` → `RowNumber.over`,
`newReader().readInt()` → `Hpack.Reader.readInt`); gains are locals now
typed (`val connection = connect(peer)`); the 4 drops are Kotlin's
`apply` scope function, which went to a test helper's or a Gradle
plugin's `apply`. Every retarget group was checked against the source.
- Cost: whole index, median of 3 interleaved: okhttp 10.5 s -> 10.9 s,
Exposed 17.7 s -> 16.8 s, within noise.
Fixes #2805
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>