Skip to content

Residual native/wasm divergence on db/connection.ts survives #2235's typeMap-scoping fix #2397

Description

@carlos-alm

Context

#2235 diagnosed (and this repo's own self-build was the original repro for) a typeMap-collision bug: two different functions in the same file declaring a same-named local/parameter collide under the flat, per-file, name-only typeMap bare key. #2235's fix adds a function-scoped key (${callerName}::${name}) that both engines now seed and consult before the bare fallback, plus fixes ReturnType<typeof fn>-style generic wrappers being extracted as if their own name were a real type. That fix is verified correct and complete for the general scoping-collision case (see the new tests in tests/parsers/javascript.test.ts, tests/integration/issue-2235-typemap-scoping-collision.test.ts, and the Rust unit tests in crates/codegraph-core/src/extractors/javascript.rs).

But: the specific real-world repro that motivated #2235 is still divergent after the fix

Rebuilding this repo's own src/ with both engines (node dist/cli.js build src --engine wasm|native --no-incremental) after #2235's fix lands:

=== wasm: openReadonlyOrFail ===
openReadonlyOrFail|BetterSqlite3Database|receiver|1.0|
openReadonlyOrFail|BetterSqlite3Database.pragma|calls|0.6|ts-native
openReadonlyOrFail|BetterSqlite3Database.prepare|calls|0.6|ts-native
openReadonlyOrFail|NativeDbProxy.pragma|calls|0.8|cha
openReadonlyOrFail|NativeDbProxy.prepare|calls|0.8|cha
...
=== native: openReadonlyOrFail ===
openReadonlyOrFail|LockedDatabase|receiver|0.9|
...

native still resolves openReadonlyOrFail's local db to LockedDatabase (wrong — db is as unknown as BetterSqlite3Database at that line) instead of BetterSqlite3Database, and emits no calls edges to prepare/pragma at all — exactly the behavior #2235 originally reported.

Why #2235's fix doesn't reach this case

openReadonlyOrFail's own local (const db = new Database(dbPath, { readonly: true }) as unknown as BetterSqlite3Database;, src/db/connection.ts:389) is an as-cast, not a type annotation or a bare new X() — neither engine's handleVarDeclaratorTypeMap/handle_var_declarator_type_map has a branch for as_expression values, so this line contributes nothing to the typeMap in either engine (confirmed by direct extraction dump — no openReadonlyOrFail::db scoped entry exists in either engine's typeMap). #2235's scoped key can only disambiguate a collision when the colliding function's OWN declaration is itself extractable; it can't help when the local declaration is un-extractable to begin with.

With no scoped entry, resolution for openReadonlyOrFail's db.pragma()/db.prepare() falls through to the bare "db" key (or, when that's also empty/wrong, a global proximity fallback at confidence 0.6). The bare key's value depends on which other function in the file wins it — and that's where the engines diverge:

  • src/db/connection.ts:178export function closeDb(db: LockedDatabase): void {} seeds bare "db" = "LockedDatabase" @ 0.9 (a genuine, correctly-scoped-but-irrelevant-here entry) in both engines.
  • src/db/connection.ts:607 / :673const db = openReadonlyOrFail(customDbPath, busyTimeoutMs); triggers the Phase 8.2 return-type-propagation branch (handleCallExprTypeMap / handle_var_declarator_type_map's call_expression branch), which looks up openReadonlyOrFail's own return type (): BetterSqlite3Database at :380, confidence 1.0) and propagates it onto that call site's own db local.

wasm ends up with "BetterSqlite3Database" winning the bare key (by whatever confidence/insertion-order the propagation branch produces there); native does not — the two engines are seeding the bare "db" key with genuinely different final values from this same propagation mechanism. This is an insertion-order/confidence discrepancy in the return-type-propagation branch itself, not a scoping problem — #2235's own scope note flagged auditing this as a separate, optional avenue distinct from the scoping fix it committed to.

Suggested follow-up

  1. Instrument/compare both engines' raw type_map/return_type_map output for src/db/connection.ts end-to-end (not just the final DB edges) to find exactly where the propagated confidence or dedup/insertion order first diverges between handleCallExprTypeMap's resolveCallExprReturnType(valueN, typeMap, returnTypeMap, 0) (TS) and the equivalent inline block in handle_var_declarator_type_map (Rust, crates/codegraph-core/src/extractors/javascript.rs around the same_file_entry/propagated block).
  2. Separately (lower priority, larger scope): consider whether as unknown as X / as X casts should seed a typeMap entry at all (currently silently dropped in both engines) — that would make openReadonlyOrFail's own scoped key correct at the source and stop depending on bare-key propagation luck entirely. This overlaps with typeMap collisions across same-named locals in different functions cause native/wasm divergence (residual gap after #1949) #2235's ReturnType<typeof fn> fix in spirit (both are about not letting an unresolvable/complex annotation-or-cast either poison or starve the typeMap) but is a distinct extraction gap on its own.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions