Skip to content

fix(parity): infer var-typed local types from new-expression initializers in C##1424

Merged
carlos-alm merged 10 commits into
mainfrom
fix/csharp-static-receiver-parity
Jun 10, 2026
Merged

fix(parity): infer var-typed local types from new-expression initializers in C##1424
carlos-alm merged 10 commits into
mainfrom
fix/csharp-static-receiver-parity

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • Root cause: Both WASM and native C# extractors skipped var/implicit_type variable declarations entirely. var service = new UserService(repo) never added service → UserService to the typeMap, so service.AddUser() couldn't be resolved to UserService.AddUser.
  • WASM fix (src/extractors/csharp.ts): Add extractVarInitType that infers the constructor type from var x = new Foo() initializers — syncs source with pre-existing dist logic that had drifted out of sync.
  • Native fix (crates/codegraph-core/src/extractors/csharp.rs): Mirror the same logic, enabling the Rust engine to emit the same calls/receiver edges as WASM.
  • Dev infrastructure (src/infrastructure/native.ts): Add CODEGRAPH_NATIVE_ADDON_PATH env-var override to loadNative() for local development workflows where the published npm binary cannot be loaded (its dylib install-name points to the CI build path).

Closes #1418.

Test plan

  • CODEGRAPH_NATIVE_ADDON_PATH=... npx vitest run tests/integration/build-parity.test.ts — 8/8 pass (previously 2 failed with edge/role divergence)
  • npx vitest run tests/parsers/csharp.test.ts — 13/13 pass
  • Full test suite with env var: 176 test files pass (1 skipped, unrelated)

To run parity tests locally, first rebuild the native addon:

npx napi build --manifest-path crates/codegraph-core/Cargo.toml --output-dir crates/codegraph-core --release
export CODEGRAPH_NATIVE_ADDON_PATH="$(pwd)/crates/codegraph-core/index.node"
npx vitest run tests/integration/build-parity.test.ts

Before: native 68 edges vs WASM 78 edges — instance-method calls via var-typed locals missing
After: native 74 edges vs WASM 78 edges — parity on calls/receiver edges for instance method dispatch through var-typed locals

…ion guard (#1407)

Adds class-scope.js to the JS resolution benchmark. The fixture contains
a class with a bare flush() call inside run() and a Processor.flush method
that must NOT be the target of that call — JS bare calls are lexically
scoped to the module, never the class.

The 1.0 precision floor for the JS fixture acts as the enforcement
mechanism: if the call.receiver guard in resolveByMethodOrGlobal is ever
removed, the resolver would emit Processor.run → Processor.flush as a
false positive, immediately failing CI.

Closes #1407
…alizers in C#

Both the WASM and native C# extractors were skipping variable declarations
with `var`/`implicit_type` type nodes entirely, so `var service = new
UserService(repo)` never added `service → UserService` to the typeMap.  The
call-edge resolver therefore could not resolve `service.AddUser()` or
`service.GetUser()` to the qualified methods on `UserService`.

The dist copy of the WASM extractor already had the fix (extractVarInitType),
but the source had drifted out of sync.  This commit re-introduces the logic
in both engines:

WASM (TypeScript): add `extractVarInitType(declarator)` that walks the
variable_declarator children looking for an `object_creation_expression` (or
one inside an `equals_value_clause`), then reads the `type` field via
`extractCSharpTypeName`.  `handleCSharpVarDecl` now sets `isVar` for
`implicit_type | var_keyword` and calls `extractVarInitType` in that branch.

Native (Rust): mirror the same logic in `extract_var_init_type` and update
`match_csharp_type_map` to drive it when the type node is `var_keyword` or
`implicit_type`.

Infrastructure: add CODEGRAPH_NATIVE_ADDON_PATH env-var override to
`loadNative()` for local development workflows where the published npm binary
can't be loaded (its dylib install-name points to the CI build path).  Tests
now pass with CODEGRAPH_NATIVE_ADDON_PATH set to the locally rebuilt binary.

Fixes: #1418 (build-parity test failure — native vs WASM C# edge/role
divergence on `var`-typed local variables instantiated with `new`).
@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This commit resolves the stale PR reference comments that were flagged in the previous review round — #1407 is corrected to #1422 in the benchmark history annotation and to #1422/#1424 in the call-resolver test header.

  • tests/benchmarks/resolution/resolution-benchmark.test.ts: updates the JS threshold changelog comment to credit class-scope.js to the correct PR (#1422).
  • tests/unit/call-resolver.test.ts: updates the file-header doc-comment to reference both #1422 (where class-scope.js landed on main) and #1424 (this PR), matching the resolution in the prior review thread.

Confidence Score: 5/5

Safe to merge — both changed files contain only comment corrections with no functional code impact.

Both changes are single-line comment fixes updating stale PR reference numbers. No logic, test assertions, or runtime behaviour is modified.

No files require special attention.

Important Files Changed

Filename Overview
tests/benchmarks/resolution/resolution-benchmark.test.ts Single-line comment fix: updates stale PR reference from #1407 to #1422 in the JS threshold history annotation. No functional change.
tests/unit/call-resolver.test.ts Single-line comment fix: updates stale PR reference from #1407 to #1422/#1424 in the file header doc-comment. No functional change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["PR #1424 — comment-ref fix commit"] --> B["tests/benchmarks/resolution/\nresolution-benchmark.test.ts"]
    A --> C["tests/unit/\ncall-resolver.test.ts"]
    B --> D["#1407 → #1422\n(bare-call guard PR reference)"]
    C --> E["#1407 → #1422/#1424\n(dual-PR attribution)"]
Loading

Reviews (11): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

// define-property.js and accessorGetter→accessorTarget.accessMethod in define-property-accessor.js,
// total expected now 35.
// total expected now 35. multi-class.js adds 4 class-scoped typeMap edges → 39.
// #1407 adds class-scope.js (bare-call guard), +1 → total 40.

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 The comment attributes class-scope.js to PR #1407, but that file is introduced in this PR (#1424). The reference pattern used elsewhere in this comment block (e.g. #1335) refers to PR numbers, so this looks like a copy-paste from a draft. Suggest updating to the actual PR number so the history trail stays accurate.

Suggested change
// #1407 adds class-scope.js (bare-call guard), +1 → total 40.
// #1424 adds class-scope.js (bare-call guard), +1 → total 40.

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 comment from #1407 to #1424 (the current PR). The reference was a copy-paste error from a draft.

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 — the comment now correctly references #1422, which is the PR that landed class-scope.js on main. The merge conflict resolution had previously reset to main's stale #1407 reference; the new commit corrects it to #1422/#1424 as appropriate.

Comment on lines +74 to +78
} catch (err) {
_loadError = err as Error;
_cached = null;
return null;
}

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 When the dev-override path is set but fails, the function returns null with no diagnostic output. Every other load-failure path in this file sets _loadError, and callers surface the message through getNative(). Here the error is recorded but there's no debug() call, which means a developer who misspells the path or points to a stale binary will see native silently unavailable with no log hint — even when DEBUG is enabled.

Suggested change
} catch (err) {
_loadError = err as Error;
_cached = null;
return null;
}
} catch (err) {
_loadError = err as Error;
debug(`loadNative: CODEGRAPH_NATIVE_ADDON_PATH=${devOverride} failed: ${toErrorMessage(err as Error)}`);
_cached = null;
return null;
}

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 — the merge conflict resolution adopted main's version of native.ts (PR #1389), which already includes a warn() call on the failure path (more visible than debug()). The original PR's simpler CODEGRAPH_NATIVE_ADDON_PATH approach was superseded by the more complete implementation from main that uses NAPI_RS_NATIVE_LIBRARY_PATH + automatic local binary detection.

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

10 functions changed22 callers affected across 8 files

  • extract_csharp_type_name in crates/codegraph-core/src/extractors/csharp.rs:441 (2 transitive callers)
  • extract_var_init_type in crates/codegraph-core/src/extractors/csharp.rs:454 (1 transitive callers)
  • match_csharp_type_map in crates/codegraph-core/src/extractors/csharp.rs:476 (0 transitive callers)
  • isModuleScopedLanguage in src/domain/graph/builder/call-resolver.ts:43 (11 transitive callers)
  • resolveByMethodOrGlobal in src/domain/graph/builder/call-resolver.ts:84 (15 transitive callers)
  • handleCSharpVarDecl in src/extractors/csharp.ts:354 (3 transitive callers)
  • processData in tests/benchmarks/resolution/fixtures/javascript/class-scope.js:10 (1 transitive callers)
  • Processor in tests/benchmarks/resolution/fixtures/javascript/class-scope.js:14 (0 transitive callers)
  • Processor.run in tests/benchmarks/resolution/fixtures/javascript/class-scope.js:15 (0 transitive callers)
  • Processor.flush in tests/benchmarks/resolution/fixtures/javascript/class-scope.js:20 (0 transitive callers)

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

In JS/TS, bare foo() calls inside a class method are module-scoped — there
is no implicit class binding. The same-class fallback in
resolveByMethodOrGlobal was incorrectly emitting class-scoped edges for
bare calls with no module-level match (e.g. flush() inside Processor.run
resolving to Processor.flush).

Gate the fallback: skip for bare calls in JS/TS, keep for this.method()
calls and for C#/Java where bare calls are class-scoped.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 3b29844 into main Jun 10, 2026
22 checks passed
@carlos-alm carlos-alm deleted the fix/csharp-static-receiver-parity branch June 10, 2026 02:43
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 10, 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(parity): C# build-parity test fails on main — native vs WASM edge/role divergence

1 participant