Skip to content

fix(csharp): strip call-site type arguments from generic calls - #2676

Open
rohit-jsfreaky wants to merge 1 commit into
Graphify-Labs:v8from
rohit-jsfreaky:fix/csharp-call-site-type-arguments
Open

fix(csharp): strip call-site type arguments from generic calls#2676
rohit-jsfreaky wants to merge 1 commit into
Graphify-Labs:v8from
rohit-jsfreaky:fix/csharp-call-site-type-arguments

Conversation

@rohit-jsfreaky

Copy link
Copy Markdown
Contributor

Fixes #2624.

Summary

A C# call written with explicit type arguments produced no calls edge, while the same method called with an inferred type argument resolved fine — so the call-site syntax decided it, not the declaration.

tree-sitter wraps a name carrying a type-argument list in a generic_name node. The C# call handler read that node verbatim, so the callee became Fetch<Payload> while its declaration is stored bare as .Fetch(); the lookup key never matched and the edge was dropped silently.

A second, independent defect sat next to it: in Bag<Payload>.Make() the receiver is a generic_name, and a receiver was only captured when it was a plain identifier — so it was dropped entirely and _resolve_csharp_member_calls had nothing to bind.

Changes

  • Add _csharp_name_without_type_args(), returning the bare name of a call-site name node. It reads the name field first and falls back to the first identifier child — the same order _csharp_collect_type_refs uses (the pinned grammar exposes no name field on generic_name; trying the field first keeps this working on versions that do).
  • Apply it to the callee in recv.M<T>(), to a generic_name receiver (Bag<Payload>.Make()), and to an unqualified call (Local<Payload>()), which previously fell through to the raw-text scan and kept the <...>.

C# only — all three call sites sit inside the tree_sitter_c_sharp branch and the helper is used nowhere else. A node that is not a generic_name returns _read_text unchanged, so non-generic calls take exactly the path they did before.

This does not loosen resolution. Stripping the type-argument list only makes the lookup key match; the call still goes through receiver typing and the god-node guard, so an ambiguous or untypable receiver still yields no edge rather than a wrong one. A qualified generic (Demo.Bag<Payload>) has no bare identifier child and keeps its previous name minus the type arguments, so nothing that used to be recorded stops being recorded.

Reproduction

The issue's five files, graphify extract . --code-only --no-cluster:

call before after
A() -> Registry.Fetch<Payload>() missing resolved
B() -> box.Read<Payload>() missing resolved
C() -> Registry.Has() (control) resolved resolved
D() -> Registry.Pick(item) (control) resolved resolved
E() -> Bag<Payload>.Make() missing resolved

2 of 5 edges before, 5 of 5 after. Two further shapes were broken by the same cause and are not in the report — both now covered: Local<Payload>("k") (unqualified, no receiver) and this.Local<Payload>("k").

Tests

Five tests appended to tests/test_csharp_member_calls.py. Each was confirmed to fail with the source change reverted and the tests kept, and to pass with it.

One is a guard rather than a repro: a generic call on a typed field must still bind to that field's type and not to a same-named method on an unrelated class (#1609), now exercised through a generic call. The constructed-generic-receiver test is split across two files deliberately — in a single file the callee resolves by an in-file label match and never reaches receiver typing, so a same-file version passes even with the receiver dropped.

uv run --no-sync pytest tests/test_csharp_member_calls.py tests/test_csharp_type_resolution.py tests/test_csharp_partial_classes.py tests/test_dotnet.py -q — 126 passed.

uv run --no-sync ruff check graphify/extractors/engine.py tests/test_csharp_member_calls.py — passed.

Full suite: 22 failed, 4304 passed, 13 skipped, against a clean v8 baseline of 22 failed, 4299 passed, 13 skipped — the same 22 in both runs, all pre-existing and platform-sensitive on this Windows machine, none touching this path. The +5 are the new tests.

graphify update . re-run per AGENTS.md: 765 files, 13,426 nodes, no errors.

Tested against the pinned tree_sitter_c_sharp 0.23.5 on Windows; other grammar versions and platforms were not exercised.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR addresses C# call-site handling when explicit type arguments are present (e.g., X.M<T>(), Bag<Payload>.Make(), Local<Payload>()). It adds a new helper _csharp_name_without_type_args that strips the type-argument list from generic_name nodes, and updates _extract_generic to use it for callee names and receivers, while also recognizing generic_name as a valid receiver/function node type alongside identifier. The test file adds a new suite of cases covering explicit type arguments on type receivers, typed local receivers, constructed generic type receivers (split across files), unqualified/this generic calls, and a check that type-argument stripping doesn't bypass receiver typing. The changed surface is limited to the C# path in graphify/extractors/engine.py and the corresponding tests in tests/test_csharp_member_calls.py.

No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 650 functions depend on the 255 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: walk_calls() — 1 callers, 16 callees

Verification — 650 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 591 function(s) in the blast radius were not formally verified this run

· 1 more finding(s) on lines outside this diff (see the check run).

@JensD-git

Copy link
Copy Markdown

Thank's again. Hope this PR will be released soon.

@JensD-git

Copy link
Copy Markdown

How did this pull request backlog come about? Is there anything we can do to help here? It's unusual for completed items to sit here for two weeks.

@rohit-jsfreaky
rohit-jsfreaky force-pushed the fix/csharp-call-site-type-arguments branch from 8750f61 to b0ad6e4 Compare August 25, 2026 13:32
@rohit-jsfreaky

Copy link
Copy Markdown
Contributor Author

Rebased onto v8 (0.9.49) — conflict resolved. It was append-only in tests/test_csharp_member_calls.py: the primary-constructor tests from #2836 and these #2624 tests both land at the end of the file, so both blocks are kept, upstream's first. engine.py rebased untouched.

I also re-checked that this is not already covered by #2911 / #2913, since those mention generic type arguments too. Those walk them in field / property / return / parameter position, which is type-reference resolution; the call-site callee and receiver naming is a separate path. The repro still fails the same way on 0.9.49 — 3 of 8 shapes resolve without this change, 8 of 8 with it.

Full suite on the rebased branch: 12 failed, 4986 passed, 43 skipped — the same 12 as a clean v8 checkout on this Windows machine (fifo / unix-socket / hermes / gemini path tests), none touching this change. All four C# suites pass (132).

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Fixes dropped C# calls edges when a call site spells explicit type arguments (Registry.Fetch<Payload>(...), Bag<Payload>.Make(...), Local<Payload>(...)), which tree-sitter wraps in a generic_name whose verbatim text (Fetch<Payload>) never matched the bare declaration name. Adds _csharp_name_without_type_args, which returns the bare identifier off a generic_name (falling back to splitting on < for qualified generics like Demo.Bag<Payload>), and routes callee names, Type.M() receivers, and unqualified/this calls through it so type-argument lists are stripped before lookup while receiver typing still applies.

No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 692 functions depend on the 280 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _extract_generic() — 18 callers, 24 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: extract_js() — 80 callers, 3 callees
  • new: extract_julia() — 17 callers, 7 callees
  • new: extract_cpp() — 27 callers, 3 callees
  • new: extract_vue() — 10 callers, 6 callees
  • new: walk() — 1 callers, 56 callees
  • …and 8 more — each is listed as a finding

Verification — 692 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 632 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

· 16 more finding(s) on lines outside this diff (see the check run).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C#: explicit type arguments at a call site (X.M<T>(...)) drop the calls edge

2 participants