fix: seed typeMap for Dart local var initialized by a constructor call - #2567
Conversation
var svc = UserService(repo); never seeded a typeMap entry for svc, unlike every other language extractor's constructor-call-initializer convention, so a later call through it (svc.createUser()) could never resolve and the call edge was silently dropped. Adds handleDartLocalVarTypeMap / handle_dart_local_var_type_map, seeding a function-scoped entry for both tree-sitter-dart grammar shapes (native's clean value: call_expression, and WASM's sibling identifier + selector layout). Requires extracting a shared findEnclosingDartSignatureFromBody / find_enclosing_dart_signature_from_body helper, since a function's signature and body are sibling nodes rather than nested, so a body descendant can't reach its own signature via a simple ancestor walk. No doc updates needed — internal bug fix to an existing extractor, no language/feature/architecture surface change. docs check acknowledged. Closes #2474 Impact: 5 functions changed, 8 affected
Greptile SummaryThe PR adds function-scoped Dart type-map inference for local variables initialized by bare constructor calls, allowing subsequent calls through those locals to resolve.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
Source["Dart local declaration"] --> Parse{"Parser engine"}
Parse -->|WASM selector layout| TS["TypeScript extractor"]
Parse -->|Native call_expression layout| Rust["Rust extractor"]
TS --> Gate{"Capitalized bare callee?"}
Rust --> Gate
Gate -->|Yes| TypeMap["Seed function-scoped typeMap entry"]
Gate -->|No| Skip["Leave typeMap unchanged"]
TypeMap --> Resolve["Resolve later receiver calls"]
Reviews (3): Last reviewed commit: "fix: lower Dart local-var constructor ty..." | Re-trigger Greptile |
Codegraph Impact Analysis5 functions changed → 10 callers affected across 3 files
|
Greptile finding on PR #2567: since Dart lets a constructor call omit new, an ordinary lowercase factory function call (var svc = makeService();) was indistinguishable from a real constructor call (var svc = UserService();) at the call_expression level, so it was wrongly seeded as if svc's type were the literal function name makeService, corrupting later receiver-typed resolution for calls through that local. Gates the seeding on the callee being capitalized, matching Dart's own type-naming convention and this codebase's existing precedent for the identical ambiguity in javascript.ts/javascript.rs. Uses a plain ASCII check on both sides (TS /^[A-Z]/, Rust is_ascii_uppercase()) rather than a full-Unicode comparison, avoiding the astral-plane/titlecase engine-divergence risk #2396 already found in the fuller heuristic. docs check acknowledged. Impact: 1 functions changed, 2 affected
…idual gap Follow-up to Greptile's second finding on PR #2567 (a capitalized ordinary function is still indistinguishable from a constructor call). Empirically confirmed via a new dual-engine integration test that a wrong guess currently drops (not misroutes) the receiver call's edge, because resolveByReceiver / resolve_call_targets_core both skip the untyped direct-qualified fallback whenever any typeMap entry exists for the receiver, right or wrong. This is a pre-existing, language-agnostic property of the shared resolver, not something this fix introduces, and fully closing it needs either a shared-resolver change or a same-file cross-check that requires refactoring dart.ts's single-pass walker into a two-pass design first -- both out of scope here. Filed as follow-up issue #2568. Lowers the heuristic's confidence from 1.0 to 0.7, matching the same tier JS/TS's own capitalization-based Foo.create() factory heuristic already uses for the identical class of uncertainty. Replaces the integration test's incorrect "safety net" assumption (which turned out not to hold once verified empirically) with an honest regression test locking in the one guarantee that does hold: the wrong guess never fabricates an edge to a nonexistent node. docs check acknowledged. Impact: 1 functions changed, 2 affected
|
Addressed both findings in the latest commit (fd438c6): Finding 1 (bare lowercase call → constructor type): fixed by gating the seeding on the callee being capitalized, matching Dart's own type-naming convention. Finding 2 (uppercase ordinary function → constructor type): this one is real and I verified it empirically rather than arguing it away — added a dual-engine integration test with a capitalized top-level function ( Fully closing this needs either (a) a shared-resolver change so a failed type-aware lookup falls through to the untyped fallback, which has blast radius across every language using this cascade and needs its own precision/recall validation, or (b) a same-file "is this name already a known ordinary function?" cross-check in the Dart extractor, which requires first refactoring For this PR, I lowered the heuristic's confidence from 1.0 to 0.7, matching the identical capitalization-based uncertainty tier this codebase already accepts for JS/TS's |
Summary
var svc = UserService(repo);never seeded a typeMap entry forsvc, unlike every other language extractor's identical "assign a constructor call to a local variable" convention (e.g. JS/TS'shandleVarDeclaratorTypeMap). A later call through that local (svc.createUser()) could therefore never resolve via the typeMap, and the call edge was silently dropped.handleDartLocalVarTypeMap(TS,src/extractors/dart.ts) and its mirrorhandle_dart_local_var_type_map(Rust,crates/codegraph-core/src/extractors/dart.rs), wired toinitialized_variable_definition. Seeds a function-scoped typeMap entry (confidence 1.0) when the initializer is a bare constructor call.tree-sitter-dartgrammars needed separate handling:value: call_expression.value:is a field marker on two different children (the bare callee identifier and the trailing callselector) —childForFieldName('value')only returns the first match, so an earlier version of this fix wrongly took the "native" branch and bailed out before reaching the correct sibling-based lookup. Fixed by gating on the value field's type (=== 'call_expression'), not merely its presence.findEnclosingDartSignatureFromBody/find_enclosing_dart_signature_from_bodyhelper (refactored out offindEnclosingDartParamListForCall/find_enclosing_dart_param_list_for_call) since a Dart function's signature and body are sibling nodes, not nested — a body descendant (like a local variable declaration) can't reach its own signature via a simple ancestor walk.Deliberately does not attempt to resolve a local variable shadowing a same-named class field (tracked separately as #2478 — already noted in this file's existing doc comments as out of scope for this fix).
Test plan
dart.rs(local_var_constructor_call_typingmodule) — seeding, scoping across functions, non-constructor-call no-op, class-method scoping, issue reprotests/parsers/dart.test.ts(#2474describe block) — same coverage, WASM enginetests/integration/issue-2474-dart-local-var-constructor-call-typing.test.ts— asserts the actual call edge resolves viabuildGraph, bothwasmandnativenpx tsc --noEmit -p .,npm run lint, fullnpm test(5464 passed)cargo fmt -- --check,cargo clippy --lib -- -D warnings,cargo test --lib(1107 passed)Closes #2474