Skip to content

merge(upstream): reconcile #2157–#2172 in the native resolver - #345

Merged
bompus merged 21 commits into
fork/consolidatedfrom
reconcile/upstream-2172
Sep 30, 2026
Merged

bompus merged 21 commits into
fork/consolidatedfrom
reconcile/upstream-2172

Conversation

@bompus

@bompus bompus commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Merge upstream colbymchenry#2157 through colbymchenry#2172 at 2e2b98c9, before the Kotlin scanner patch in colbymchenry#2173. Calls through declared Java/C# members and destructured factory results now reach their declared targets, and call-site scope rules reject unrelated methods in Kotlin, Swift, Scala, Rust and Go. Flow JavaScript, FastEndpoints, Vapor closures and nested/lazy React Router routes receive the upstream extraction changes.

The fork ports resolver changes into its native kernel and keeps the source-preserving Kotlin workarounds. Name-only survivors keep the fork's 0.7 ceiling. The merge commit preserves upstream ancestry for fast-forward integration on operator direction.

Validation:

  • Golden dumps re-baselined and reviewed. Changes include framework attribution, C# default visibility, and removal of out-of-scope call guesses. A regression preserves legitimate nested Rust functions.
  • Full suite: 471 files, 6,754 passed, 34 skipped; exit 0 and no native worker crash signatures.
  • Precision gates: slim, vite, javalin, petclinic and exposed all exit 0. Added regressions cover explicit Java fields shadowed by locals, qualified external types, imported Kotlin return chains, nested factory returns, route boundaries, constructor line offsets, nested and multiline call positions, framework/native confidence ordering, and production calls that must not guess test helpers. Bounded Kotlin receiver inference follows declared property factories and compatible extensions, rejects shadowed factories/scope functions, and keeps uncertain imported return hypotheses below the trust line.
  • Native-only edge comparison holds reconciled TypeScript extraction/framework code constant: slim 0 lost/0 gained; vite 5/2; javalin 99/93; petclinic 0/0; exposed 1,649/1,066. Named corpus controls preserve Javalin fluent validation and SQL column/literal extensions. Kotlin gains include declared receiver and DSL calls; removed calls include standard-library and unrelated-owner guesses. The global DSL receiver heuristic can retain unproven targets, which remain subject to the fork confidence ceiling. Detailed edge deltas are retained for review. No new performance claim is made.
  • Existing precision controls do not cover every changed call shape. C#/Rust/Scala/Swift changes have focused regression coverage; dedicated real-corpus precision controls remain a follow-up.

README rows checked: upstream merge point, parser/name-resolution comparison, dispatch and endpoint/navigation coverage, supported language list, ASP.NET/Vapor/React Router framework rows, and measured results. The merge point and coverage text are updated with matching site pages. Historical measured rows retain their stated baseline; their previously deferred re-measure remains pending. Corpus numbers imported in upstream changelog entries describe upstream measurements, not fresh fork measurements.

colbymchenry and others added 18 commits September 30, 2026 11:23
…nry#2157)

Only `<builder>.METHOD(…, use: handler)` registrations were read. Now also:
- trailing-closure routes, `app.get("hello", ":name") { req in … }`, and
  `app.webSocket("chat") { req, ws in … }` (method WS), `routes.on(.GET, "x")
  { … }` — statement-start only, so `if let v = req.parameters.get("x") {`
  and an HTTP client's `req.client.get("https://…") { … }` don't count;
- `routes.on(.POST, "x", body: .collect(…), use: handler)`, with only
  unlabeled string arguments as path segments.

Routes main → this branch: SwiftPackageIndex-Server 13→34, vapor 6→265
(its tests), swiftchat 0→1, SteamPress 27→27, penny-bot 0→0.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…g slash (colbymchenry#2158)

- FastEndpoints: an endpoint class (`: Endpoint<…>`, `EndpointWithoutRequest`,
  `Ep.…`) declares `Get(…)` / `Post(…)` in `Configure()`; each becomes a route
  linked to the class's own HandleAsync / ExecuteAsync (the bare-member scope
  from colbymchenry#2151 keeps two `List` endpoints apart). A constant path
  (`CreateContributorRequest.Route`) is read in postExtract from wherever the
  class declares it; `$"/{nameof(Project)}s"` reads as `/Projects`.
- `app.MapGet("api/todos", …)` is named `/api/todos`.

A/B: ardalis/CleanArchitecture 0 → 24 routes (+24 edges); eShopOnWeb and
eShop only re-key routes whose path gained a leading slash; realworld and
jasontaylordev/CleanArchitecture byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…chenry#2159)

The README said every bridge hop carries `metadata.synthesizedBy`, but only
the synthesized channels (rn-event-channel, fabric-native-impl) did; a
resolver-made hop (Swift↔ObjC, RN legacy / TurboModules, Expo Modules JS)
carried only `resolvedBy: 'framework'`, and `expo-module-extract` named
nothing that exists. The framework loop now adds `metadata.framework` (the
resolver's name: swift-objc-bridge, react-native-bridge, expo-modules-js,
fabric-view, rails, laravel, …) and the README says what each kind carries.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…mchenry#2160)

Read at the site:
- a later link of a chain (the extractor keeps one receiver level; the line
  shows the dot) reaches a member whose owner the receiver chain is named
  after (call / type arguments dropped: `proc("x").call()` → proc,
  `checker.value.onWrite` → checker), or a same-file member; never a package
  object's function unless it is a Scala 3 `extension` method;
- a name the enclosing def binds (parameter, val/var/def, lambda / for param)
  is only a def inside that function;
- otherwise a member of the types around it, an anonymous subclass's bases
  (`new OptionParser[C]("x") { head(…) }`), their extends/with supertypes
  (source-read heads), same file, or an imported object (`import Foo._`,
  `import Foo.{bar}`); a value import (`import builder._`) leaves the file
  unjudged.

A/B (edges removed / added): cats -1693/+678, sttp -887/+271, munit
-265/+175, scopt -245/+78, os-lib -115/+43.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
)

tree-sitter-javascript can't parse Flow annotations; `render(): React.Node`
ended a class early (segmented-control's component had no methods). A
`.js` / `.jsx` whose leading comments carry `@flow` (not `@noflow`) now
detects as `tsx`, and the TS extractor's offset-preserving preParse blanks
Flow-only syntax for those files: exact object `{| |}`, a maybe type's `?`,
an inexact object's bare `...`, `import typeof`, `opaque type`. preParse is
hoisted before the kernel, so both paths parse the same bytes;
kernel-parity now routes .js/.jsx through detectLanguage too (RN core
Libraries: 0 diffs over 641 files). Grammar preload adds tsx whenever
JavaScript is present.

Parse errors over 400 RN-core Flow files: 10,626 (JS grammar) → 391 (TSX).
A/B edges: RN core Libraries 29,649 → 38,194 (+12,639 / −4,094, the
removals mostly `root.render(…)` → the one DrawerLayoutAndroid::render the
old parse left, and edges re-keyed onto recovered methods);
segmented-control +35/−5; express and react-native-netinfo byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ss-file (colbymchenry#2162)

The sealed-module rule only covered modules exporting nothing. Per symbol
now: a JS/TS top-level binding declared by its own statement (function,
const/let/var, class, interface, type, enum) in an ES module is reachable
from another file only if it's exported — the `export` keyword (node flag or
its line), an `export { a as b }` list, `export default x` or
`export default { a, b }`. Classic scripts, CommonJS, `declare global`,
`.d.ts`, qualified members, object-literal members (zustand store actions)
and `proto.x = function x()` are exempt. Applied at the reachability gate,
so the winner is rejected rather than another promoted.

A/B (edges removed / added): zod -229 (package `z` imports → a local `z`),
excalidraw -66 (DOM `Element` → a test helper's type), kit -50, typeorm -47
(`import { ColumnMetadata } from "typeorm"` → a fixture's local), trpc -26,
hono -8, axios -1; express byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t returns (colbymchenry#2163)

`const { getDefaultActivityRoute } = useDefaultActivity(); getDefaultActivityRoute()`
bound the name locally, which ruled out every cross-file candidate, so the
call resolved to nothing. A bare JS-family call through a name destructured
(`{ key: alias }` included) from a call's result now resolves the callee —
through the file's imports, else the same file, else the project's one
function of that name (Nuxt's `~/` alias) — checks its source returns the
key, and takes the function declared in its body, else the module-level one
it returns. A later declaration at the call's scope shadows the binding.
Returned object literals now count as exporting their names for colbymchenry#2162's
unexported-binding rule.

A/B edges added (removed): mealie +176 (-4), excalidraw +156 (`const { t } =
useI18n()`), halo +58, kit +34 (-14, `validate` now the validator's own),
elk +19; trpc byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… aliases (colbymchenry#2165)

- Route tables compose nested paths: a child's `path` is relative to the
  path-bearing routes around it (`children` of a data router, nested
  `<Route>` elements), absolute ones reset. `lazy: () => import('./x')`
  (object and JSX) renders the module's default export (`Component` export
  as fallback) via a claimed `lazy-import:` ref. `path: paths.app.root.path`
  keeps its parts on the node's signature and is named in postExtract from
  the constant's object literal (found through the file's import). The
  route keeps its extracted id; `isReactRouterRoute` accepts it. An element
  wrapped in parentheses or a guard (`<ProtectedRoute>`, `<Suspense>`,
  `*Provider`, `*Guard`) renders the first element inside.
- Import aliases: the tsconfig / jsconfig nearest the importing file that
  declares `paths` is tried before the root's, so each monorepo app resolves
  its own `@/*` / `~/*`.

A/B (edges removed / added): bulletproof-react -43/+367 (0 → 9 named,
linked routes; cross-app imports fixed), react-native-reusables -103/+193,
kit -184/+218, trpc -45/+71 (examples' `~/…` now their own), create-t3-turbo
-1/+13; excalidraw, halo, obytes, realworld byte-identical. colbymchenry#1348's nested
fixtures now expect composed paths (`/dashboard/settings`, `/data/prefs`).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…olbymchenry#2166)

`Name.method()` where `Name` reads as a type in the language's conventions
and nothing in the project (same language family) is called `Name` — JDK /
BCL / DOM / Indy types — now leaves the method-call matcher before the
capitalized-receiver and name-overlap guesses. Not applied in Go (exported
package variables), C / C++ / Rust / CUDA / Metal; in Pascal only Delphi
type prefixes (T/E/I…) and `Exception` count, since parameters and locals
are capitalized there too.

A/B (edges removed): commons-lang -1636 (`Integer.valueOf` → StringUtils
×804, `Calendar.getInstance`, `Instant.now`), horse -89 (`Exception.Create`
→ EHorseException), retrofit -79 (OkHttp `RequestBody.create` / `HttpUrl.get`),
typeorm -74 (`Object.assign` → ObjectUtils), gson -100, zod -63,
Newtonsoft.Json -57, jsoup -18; gin and cobra byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…henry#2167)

The extractors keep one receiver level, so chain links arrive as bare names.
Read at the call site:
- bare (no `.` / `::` before it): never a method — Rust reaches one only
  through `self.` / `Type::`, Go through a receiver (axum's routing
  `get(handler)` went to a cookie jar's `get`, tokio's `drop(x)` to `Drop`
  impls, colbymchenry#1861's pinned `reset()` → `Target::reset` is now the free fn);
- chained (after `.`, receiver read back through arguments, macros `name!`,
  `::` paths and Go composite literals): never a free function, and a
  standard-library method name (Option/Result/iterator/collection/string
  methods; Go's String/Error/Header/locks/reflect/time) needs a receiver
  named after its owner — project-specific names keep their match (clap's
  `flag("n").short('n')` → Arg::short, cobra's `c.Root().Name()`, gin's
  `c.Set(…)`).
The same std gate applies to the method-call matcher's unique-name guess.

A/B (edges removed / added): clap -1496/+93, tokio -1064/+235, axum
-695/+42, ripgrep -477/+32, cobra -167, serde -119/+1, bat -40/+4, gin
-32/+26.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ames stay std (colbymchenry#2168)

- tree-sitter-kotlin can't read `class Name` / (comments) / `internal
  constructor(…) : Super {` on separate lines — the class is dropped and its
  members become loose top-level functions (okio's ByteString, Kotlin
  Multiplatform's `expect` classes). The Kotlin preParse now blanks that
  constructor's modifiers / annotations / keyword to spaces, so `(…) :
  Super {` parses as the primary constructor; offsets and lines survive, and
  preParse runs before the kernel, so both paths parse the same bytes.
- With members back on their classes, a Kotlin standard-library name on an
  untyped chain link (scope functions, collection / string / conversion
  methods — not names project types commonly carry) needs a receiver named
  after its owner, in the exact / fuzzy filters and the unique-name guess.

A/B (edges removed / added): okio -1028/+1127, okhttp -979/+1145,
kotlinx-datetime -426/+515 (mostly re-keyed onto recovered classes),
koin -60/+4 (`msg.contains(…)` → a dependency map's `contains`).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…type (colbymchenry#2169)

When a receiver's type is inferred from its declaration or initializer and
the method isn't on it, the matcher used to fall through to its
capitalized-name and name-overlap guesses. If the inferred name is a type
(capitalized last segment) that nothing in the project declares — JDK /
BCL / okhttp / Django types — it now stops: the method is that outside
type's. Python's initializer pattern no longer reads a keyword argument
(`prefix=IPNetwork(…),` inside a call) as a binding of `prefix`.

A/B (edges removed): commons-lang -1296 (`append` / `length` / `charAt` on
JDK types), Newtonsoft.Json -496 (`MemoryStream.ToArray`, `ToString`),
gson -367 (`List.add`, `Map.put`), retrofit -321 (okhttp `Request.headers()`
/ `method()`), jsoup -130, okio -26, pytest -44 (pathlib / io), netbox -25,
healthchecks -9 (`TimestampSigner.sign`), flask -9, zod -1.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…bymchenry#2170)

Swift's extractor keeps one receiver level, so super.init(…), a longer
chain's removeAll() and a bare min(a, b) all arrive as bare names and
exact-matched whichever project member shared the name. A bare or self./
super. call now reaches only a member of the types around it and what they
inherit or conform to (source-read heads, UIKit ancestry, the standard
collection protocols), nearest first; a standard-named chain link needs a
receiver that names the owner, an outside owner, or an argument label only
the project's method takes; playground and test-target globals stay put.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… type (colbymchenry#2171)

C# / Java / Kotlin receivers that are fields, properties or parameters were
typed only when declared in the calling method without generics, so a call
through a field fell to the receiver-word guess: Newtonsoft's
_innerWriter.WriteValue() landed on the wrapper's own WriteValue, a
TextWriter's Write() on a test writer's, commons-lang's DateFormat field's
parse() on DateParser 284 times.

- read class-level member declarations (brace depth 1, comments skipped),
  walking base classes with their type arguments substituted, C# using
  aliases, type-parameter bounds, generic and for-each declarations
- C# interface members default public and namespace-level types internal
  (TS extractor + kernel, parity 0 diffs); interface calls resolve at all
- the receiver-word guess weighs the owner type's name only, the receiver's
  last link (a constant's type), and its head noun; .NET std names need a
  receiver named after the owner; production code never lands in a test
  suite's mock

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…#2172)

A receiver-less Kotlin call exact-matched any class's same-named method:
koin's module { } in one test went to another test class's private
module 268 times, error("…") to a Logger's error, get() inside a
definition lambda to Koin's get rather than the Scope it runs on.

A bare call now reaches a method of a class, anonymous object or extension
receiver around it (read from nodes and from the source's braces, so a
class the parser lost still counts) and what those inherit (with Android
framework ancestry), of a type the project's function types take as a
lambda receiver (plus outside types it writes extensions on), or of an
object the file imports. with/apply/run blocks and .kts scripts are left
unjudged; Kotlin's require/check/assert preconditions are the stdlib's.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@bompus
bompus merged commit 630321d into fork/consolidated Sep 30, 2026
@bompus
bompus deleted the reconcile/upstream-2172 branch September 30, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants