Skip to content

fix(extractor): recognize inline-new expression as receiver type in extractReceiverName#1415

Merged
carlos-alm merged 8 commits into
mainfrom
fix/inline-new-receiver-1396
Jun 9, 2026
Merged

fix(extractor): recognize inline-new expression as receiver type in extractReceiverName#1415
carlos-alm merged 8 commits into
mainfrom
fix/inline-new-receiver-1396

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • extractReceiverName in src/extractors/javascript.ts now detects new_expression and parenthesized_expression wrapping a new_expression as receiver nodes, returning the constructor name directly (e.g. 'Dog') instead of the raw expression text (e.g. '(new Dog(\\'Rex\\'))').
  • For (new Dog('Rex')).bark() or new Dog().bark(), the receiver recorded for the bark call is now 'Dog' rather than the raw text. The resolver finds Dog.bark via the direct qualified method lookup path without needing the text-based regex heuristic that was handling these cases in call-resolver.ts.
  • Both query and walk extraction paths benefit from the change (verified via tests/engines/query-walk-parity.test.ts).

Test plan

  • npx vitest run tests/integration/prototype-method-resolution.test.ts — all 3 tests pass (including the inline-new receiver test)
  • npx vitest run tests/engines/query-walk-parity.test.ts — all 14 tests pass (query and walk paths both produce receiver: 'Dog')
  • npx vitest run tests/benchmarks/resolution/resolution-benchmark.test.ts — 171 tests pass
  • Full integration + parsers + engines test suite — only the pre-existing C# parity failures remain (fix(parity): C# native engine resolves static receiver calls that WASM misses #1372)
  • npm run lint — no new warnings

Closes #1396

…xtractReceiverName

When the object of a member call is a `new_expression` (e.g. `new Dog().bark()`)
or a parenthesized `new_expression` (e.g. `(new Dog('Rex')).bark()`),
`extractReceiverName` now returns the constructor name (e.g. `'Dog'`) directly
instead of the raw node text (e.g. `'(new Dog(\'Rex\'))')`).

This lets the resolver reach the direct qualified method lookup path
(`Dog.bark`) without relying on the text-based regex heuristic that was
handling these expressions in `call-resolver.ts`.

Closes #1396
@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves call resolution accuracy for inline new-expression receivers in JavaScript/TypeScript and adds var-declared instance type inference for C#. Both the TypeScript extractor and its Rust mirror are updated in parity.

  • extractReceiverName (JS): now detects new_expression and parenthesized_expression(new_expression) nodes, returning the constructor name directly (e.g. 'Dog') so the resolver can find Dog.bark without the text-based regex heuristic in call-resolver.ts; the regex is retained as a safety-net fallback with an updated comment.
  • C# var inference (TS + Rust): extractVarInitType / extract_var_init_type extracts the constructor name from var x = new Foo() initializers, filling the type map entry that was previously missing for implicit_type declarations.
  • Tests & benchmarks: new unit tests for the C# type-map, updated recall threshold (0.8 → 0.9) validated by the benchmark suite.

Confidence Score: 5/5

The core logic changes are narrow and well-tested; the JS extractor change and C# var-inference additions are both backed by new unit tests and a passing benchmark suite with a raised recall threshold.

All changes follow established patterns in the codebase, the TS and Rust implementations are in parity, the regex fallback is correctly preserved as a safety net, and the test plan covers the key resolution paths. The only observations are two unreachable branches (dead code) present identically in both the TS and Rust implementations, which do not affect correctness.

The unreachable object_creation_expression-as-direct-declarator-child branch in both src/extractors/csharp.ts and crates/codegraph-core/src/extractors/csharp.rs is worth cleaning up for clarity, but does not affect behavior.

Important Files Changed

Filename Overview
src/extractors/javascript.ts Adds new_expression and parenthesized_expression(new_expression) handling to extractReceiverName; logic is correct and delegates to the existing extractNewExprTypeName helper.
src/domain/graph/builder/call-resolver.ts Comment-only update to the inline-new regex fallback; now accurately describes the block as a belt-and-suspenders safety net rather than the primary handler.
src/extractors/csharp.ts Adds extractVarInitType to handle var x = new Foo() declarations; the direct object_creation_expression child branch is dead code per the C# grammar but is harmless.
crates/codegraph-core/src/extractors/csharp.rs Rust mirror of the C# type-map changes; the object_creation_expression-as-direct-child branch is also dead code per the grammar but matches the TS implementation.
tests/parsers/csharp.test.ts Two new tests cover both var-declared and explicitly-typed local variable type-map entries; assertions are precise.
tests/benchmarks/resolution/resolution-benchmark.test.ts C# recall threshold raised from 0.8 → 0.9 with a clear explanatory comment; change is backed by benchmark results mentioned in the PR description.
tests/search/embedding-regression.test.ts Expands network-error skip logic to cover HTTP/2 and additional errno codes; the NodeJS.ErrnoException cast is safe since only the code property is read.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["call_expression\ne.g. (new Dog('Rex')).bark()"] --> B["extractCallInfo\n→ extractMemberExprCallInfo"]
    B --> C["extractReceiverName(objNode)"]
    C --> D{objNode.type?}
    D -- "identifier / this / super" --> E["return objNode.text"]
    D -- "new_expression" --> F["extractNewExprTypeName(objNode)\n→ 'Dog'"]
    D -- "parenthesized_expression" --> G["loop children\nfind new_expression child"]
    G --> H["extractNewExprTypeName(child)\n→ 'Dog'"]
    D -- "other" --> I["return objNode.text\n(raw text fallback)"]
    F --> J["receiver = 'Dog'"]
    H --> J
    J --> K["call-resolver.ts\ntypeName = typeMap.get('Dog') or lookup"]
    I --> L["Belt-and-suspenders regex\n/^\\(?\\s*new\\s+([A-Z_$]…)/\n(catches future unhandled node types)"]
    L --> K
    K --> M["lookup.byName('Dog.bark')\n→ resolved method node"]
Loading

Fix All in Claude Code

Reviews (11): Last reviewed commit: "Merge branch 'main' into fix/inline-new-..." | Re-trigger Greptile

Comment on lines +2519 to +2533
// `(new Foo(...)).method()` — extract the constructor name so the resolver can
// look up `Foo.method` directly without relying on a text-based regex heuristic.
if (t === 'new_expression') {
const name = extractNewExprTypeName(objNode);
if (name) return name;
}
if (t === 'parenthesized_expression') {
for (let i = 0; i < objNode.childCount; i++) {
const child = objNode.child(i);
if (child?.type === 'new_expression') {
const name = extractNewExprTypeName(child);
if (name) return name;
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stale comment in call-resolver.ts now misleads future readers

The block at src/domain/graph/builder/call-resolver.ts lines 85–93 still says "extractReceiverName returns the raw node text for non-identifier nodes, so (new A).t() produces receiver='(new A)'" — but that is no longer true after this change. The accompanying regex fallback is now dead code for the new_expression / parenthesized_expression(new_expression) cases it was designed to cover. The comment should be updated (or the fallback removed) to keep the two sites consistent; otherwise the next developer who hits a resolution issue and reads call-resolver.ts will be chasing a phantom.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — updated the stale comment in call-resolver.ts (commit ca5065b). The comment now correctly describes the regex as a belt-and-suspenders fallback for AST node types not yet handled by extractReceiverName, rather than the primary handler for inline-new receivers. The regex is kept as a safety net for future unhandled cases.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

6 functions changed29 callers affected across 8 files

  • extract_var_init_type in crates/codegraph-core/src/extractors/csharp.rs:441 (1 transitive callers)
  • match_csharp_type_map in crates/codegraph-core/src/extractors/csharp.rs:476 (0 transitive callers)
  • resolveByMethodOrGlobal in src/domain/graph/builder/call-resolver.ts:62 (15 transitive callers)
  • handleCSharpVarDecl in src/extractors/csharp.ts:333 (3 transitive callers)
  • extractVarInitType in src/extractors/csharp.ts:350 (3 transitive callers)
  • extractReceiverName in src/extractors/javascript.ts:2675 (9 transitive callers)

…lver

The comment at lines 85-93 of call-resolver.ts described behaviour from
before extractReceiverName was taught to handle new_expression and
parenthesized_expression(new_expression) nodes. The comment said
extractReceiverName returned raw node text for those cases, which is no
longer true. Update the comment to reflect that the regex is now a
belt-and-suspenders fallback for unhandled AST node types, not the
primary handler for inline-new receivers.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

…tion_expression initializer

When a local variable is declared as `var service = new UserService(repo)`, the
tree-sitter C# grammar represents the type node as `implicit_type` (not `var_keyword`)
and places the `object_creation_expression` as a direct child of `variable_declarator`
(not nested in an `equals_value_clause`).

Previously `handleCSharpVarDecl` returned early on `implicit_type`, leaving the typeMap
without an entry for `service`. Calls like `service.AddUser()` therefore had no receiver
type and were not resolved.

Fix (WASM/TS): recognise both `implicit_type` and `var_keyword` as the var-inference
signal, then walk the `variable_declarator`'s children for a direct
`object_creation_expression` or an `equals_value_clause` containing one, and seed the
typeMap with the constructor type at confidence 0.9.

Fix (native/Rust): symmetric change to `match_csharp_type_map` plus new
`extract_var_init_type` helper, keeping both engines in parity.

Result: C# `receiver-typed` recall: 0/4 → 4/4 (100%); aggregate recall: 73.9% → 91.3%.
Threshold ratcheted from {precision: 0.5, recall: 0.2} to {precision: 0.9, recall: 0.9}.

Fixes #1402
Comment on lines +442 to +448
for i in 0..declarator.child_count() {
let child = declarator.child(i)?;
if child.kind() == "object_creation_expression" {
if let Some(t) = child.child_by_field_name("type") {
return extract_csharp_type_name(&t, source).map(|s| s.to_string());
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Early-return ? in outer loop silently truncates the search

declarator.child(i)? uses the ? operator, so if any child slot returns None the entire function returns None immediately, abandoning all remaining children. In contrast, the inner loop on line 450 correctly uses if let Some(expr) to continue past missing slots. The TypeScript mirror of this function also uses optional-chaining (child?.type) for the same reason. If tree-sitter ever produces a gap in the child list (which the Option return type explicitly allows), this outer loop would silently stop and return no type for a valid initializer that appears later in the declarator.

Suggested change
for i in 0..declarator.child_count() {
let child = declarator.child(i)?;
if child.kind() == "object_creation_expression" {
if let Some(t) = child.child_by_field_name("type") {
return extract_csharp_type_name(&t, source).map(|s| s.to_string());
}
}
for i in 0..declarator.child_count() {
let Some(child) = declarator.child(i) else { continue };
if child.kind() == "object_creation_expression" {

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d8a329d — replaced declarator.child(i)? with let Some(child) = declarator.child(i) else { continue }, matching the inner loop's pattern. Now skips None child slots instead of aborting the entire search.

…er loop

Replace `declarator.child(i)?` with `let Some(child) = ... else { continue }`
to skip None child slots rather than returning None from the entire function.
Matches the inner loop pattern and the TypeScript mirror's optional-chaining.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

…gression test

The macOS CI runner intermittently fails with ECONNRESET when downloading
the HuggingFace model. Broaden the catch in the embedding regression test
to treat connection-level errors (ECONNRESET, ETIMEDOUT, ENOTFOUND,
ECONNREFUSED) and 'terminated' worker errors the same as HTTP 429 —
mark rateLimited=true and skip the dependent tests instead of failing.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Fixed CI failure — the 'Test Node 22 (macos-latest)' failure was caused by ECONNRESET when the macOS runner downloaded the HuggingFace model in tests/search/embedding-regression.test.ts. The catch block only guarded against HTTP 429 (rate limit), but not connection-level errors. Commit 953d815 broadens the guard to also treat ECONNRESET, ETIMEDOUT, ENOTFOUND, ECONNREFUSED, and 'terminated' errors as transient network failures — these cases now set rateLimited=true and skip the dependent tests rather than failing the suite.

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 76907e6 into main Jun 9, 2026
29 checks passed
@carlos-alm carlos-alm deleted the fix/inline-new-receiver-1396 branch June 9, 2026 23:16
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 9, 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.

fix(test): prototype-method-resolution inline-new receiver test fails (pre-existing)

1 participant