Skip to content

fix(native): add missing factory-method type-map heuristic - #2533

Merged
carlos-alm merged 3 commits into
mainfrom
fix/issue-2396
Aug 16, 2026
Merged

fix(native): add missing factory-method type-map heuristic#2533
carlos-alm merged 3 commits into
mainfrom
fix/issue-2396

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Closes #2396

Root cause

TS's handleCallExprTypeMap (src/extractors/javascript.ts) seeds a factory-method heuristic: for const x = Foo.create() (any capitalized-object member-expression call, not just Object.create), x is typed as Foo at confidence 0.7 — a common static-factory pattern (const conn = Database.create()).

The Rust mirror, handle_var_declarator_type_map (crates/codegraph-core/src/extractors/javascript.rs), only implemented the narrower Object.create({...}) branch (seed_object_create_entries) with no general equivalent — a real dual-engine parity gap, not just a missing test. The same source file could produce a receiver-type edge on WASM but not on native, or vice versa, for this pattern.

Fix

Adds the same heuristic to the Rust side, gated identically to TS: call_expression whose function is a member_expression, whose object is an identifier starting with an uppercase letter and not in JS_BUILTIN_GLOBALS, at confidence 0.7 — using push_scoped_type_map_entry (from #2235) for consistent scoping with the rest of the function.

No explicit exclusion of Object.create was needed: "Object" is itself in JS_BUILTIN_GLOBALS, and the new branch only fires on a member_expression callee, which is mutually exclusive with the existing identifier-callee return-type-propagation branch right above it (a call's function field is one node, never both kinds) — so no priority-ordering logic was needed between them either.

Test plan

  • Rust: 3 new unit tests in crates/codegraph-core/src/extractors/javascript.rs mirroring the existing TS tests (seeds at 0.7, ignores lowercase receivers, ignores Object.create/other builtins). Verified the core test fails without the fix (temporarily reverted the source, confirmed failure, restored).
  • 2 new cross-engine parity tests in tests/engines/parity.test.ts (native vs WASM), since this is specifically a native/WASM divergence — verified these fail against a native addon built from the pre-fix source (rebuilt+reinstalled the addon twice to prove it), confirming this is a genuine dual-engine bug fix, not just new coverage on one side.
  • cargo test: 1037 passed. cargo clippy --all-targets -- -D warnings and cargo fmt --check clean.
  • npm test: 5325 passed. npm run lint clean.
  • codegraph diff-impact --staged -T: no function-level TS changes detected (Rust source + Rust/TS test files only).

TS's handleCallExprTypeMap seeds a factory-method heuristic: for any
const x = Foo.create() call (member-expression callee, capitalized
identifier object, not a builtin global), x is typed as Foo at confidence
0.7 — a common static-factory pattern. The Rust mirror,
handle_var_declarator_type_map, only implemented the narrower
Object.create({...}) branch with no general equivalent, so the same source
file could produce a receiver-type edge on one engine but not the other.

Adds the same heuristic to the Rust side, gated identically (member_expression
callee, uppercase-starting identifier object, not in JS_BUILTIN_GLOBALS) and
using push_scoped_type_map_entry for #2235-consistent scoping. No explicit
exclusion of Object.create is needed — "Object" is itself in
JS_BUILTIN_GLOBALS, and the new branch is mutually exclusive with the
existing identifier-callee return-type-propagation branch by construction
(a call's function field is one node, never both kinds).

docs check acknowledged
@greptile-apps

greptile-apps Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR restores native/WASM parity for JavaScript factory-method type inference and fixes the previously reported Unicode edge cases.

  • Adds the native Foo.create() type-map heuristic at confidence 0.7.
  • Matches TypeScript’s first-UTF-16-unit capitalization check for astral and titlecase identifiers.
  • Adds native unit and cross-engine parity coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
crates/codegraph-core/src/extractors/javascript.rs Adds the missing factory-method heuristic and faithfully mirrors TypeScript’s UTF-16 capitalization behavior, resolving both prior findings.
tests/engines/parity.test.ts Adds native/WASM parity assertions for factory-method inference and excluded receiver cases.

Reviews (3): Last reviewed commit: "fix(native): match JS's toLowerCase-diff..." | Re-trigger Greptile

Comment thread crates/codegraph-core/src/extractors/javascript.rs Outdated
Greptile's review of the #2396 fix correctly flagged that the new factory
heuristic's capitalization check examined the full Unicode scalar
(chars().next().is_uppercase()), while TS's objName[0] !== objName[0].toLowerCase()
operates on the first UTF-16 code unit. For an astral-plane leading character
(code point > U+FFFF), JS string indexing yields a lone surrogate that never
case-folds, so TS's check is always false there — Rust's full-scalar check
would incorrectly recognize such identifiers as uppercase, diverging from
WASM for this parity-sensitive heuristic.

Adds starts_with_uppercase_like_js, replicating the UTF-16-code-unit
semantics precisely (treating any surrogate code unit as non-uppercase,
matching JS), and uses it in place of the naive scalar check.

docs check acknowledged
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review — fixed the UTF-16-code-unit vs full-Unicode-scalar mismatch you flagged. Added starts_with_uppercase_like_js to replicate JS's objName[0] !== objName[0].toLowerCase() semantics exactly, including the lone-surrogate case for astral-plane leading characters, plus regression tests for both a BMP non-ASCII uppercase letter and an astral uppercase letter.

Comment thread crates/codegraph-core/src/extractors/javascript.rs
…2396)

Greptile's second review round on #2396 flagged that char::is_uppercase()
and "does lowercasing change this character" — what JS's
objName[0] !== objName[0].toLowerCase() actually implements — disagree for
Unicode titlecase letters (category Lt, e.g. 'Dž'): is_uppercase() is false
for them, but they lowercase to a different character, so TS's heuristic
fires while Rust's didn't.

starts_with_uppercase_like_js now compares the lowercased code point against
the original directly (c.to_lowercase().ne(iter::once(c))) instead of
checking is_uppercase(), matching JS's actual condition rather than an
approximation of it.

docs check acknowledged
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai please re-review — fixed the titlecase-letter mismatch you flagged. starts_with_uppercase_like_js now compares against the lowercased form directly (matching JS's actual objName[0] !== objName[0].toLowerCase() condition) instead of using char::is_uppercase(), which excludes Unicode category Lt (titlecase) letters. Added a regression test using 'Dž' (U+01C5).

@carlos-alm
carlos-alm merged commit 4cb243f into main Aug 16, 2026
42 checks passed
@carlos-alm
carlos-alm deleted the fix/issue-2396 branch August 16, 2026 03:49
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rust engine missing factory-method type-map heuristic (const x = Foo.create() → type Foo)

1 participant