Skip to content

fix(csharp): walk generic type arguments in field position - #2913

Open
brobl2008 wants to merge 1 commit into
Graphify-Labs:v8from
brobl2008:fix/csharp-field-generic-type-args
Open

fix(csharp): walk generic type arguments in field position#2913
brobl2008 wants to merge 1 commit into
Graphify-Labs:v8from
brobl2008:fix/csharp-field-generic-type-args

Conversation

@brobl2008

Copy link
Copy Markdown
Contributor

Fixes the field half of #2911.

Problem

The field_declaration handler reads only the outer type name via _read_csharp_type_name and emits a single references edge, so the inner argument of Box<Widget> is never linked.

Properties, return types and parameters already walk the whole type expression through _csharp_collect_type_refs. That makes fields the odd one out — and the property_declaration handler's own comment points at exactly the siblings it was mirroring:

Use _csharp_collect_type_refs (like the Java/PHP/Kotlin siblings) so List<Widget> yields both the List field ref and the Widget generic_arg ref.

The tree_sitter_java field_declaration handler immediately below does the same thing correctly. C# fields were simply never converted.

position before after
property references[generic_arg] unchanged
return references[generic_arg] unchanged
parameter references[generic_arg] unchanged
field none references[generic_arg]

Why it matters

Two very common shapes lose their edge:

private readonly IDbContextFactory<SomeContext> _factory;  // SomeContext: no edge
private readonly Mock<IThing> _mock;                       // IThing: no edge

Classic constructor injection stores its dependency in a field, so this is the same blind spot #2829 removed from primary constructors — just relocated to where the dependency is kept. Mock-based unit tests lose their subject across an entire suite.

The failure is silent. affected returns a smaller, confident answer rather than an error or a warning, which is the same reason #2829 was worth fixing.

Fix

Route the field handler through _csharp_collect_type_refs, passing the in-scope type parameters as the skip set, mirroring the property handler exactly. The outer type still emits context="field"; arguments emit context="generic_arg". The existing csharp_field_types receiver registration is untouched.

Bonus: this also stops fields fabricating nodes for predefined types. private string _s previously created a string node, because _read_csharp_type_name returns builtins while _csharp_collect_type_refs returns early on predefined_type. That aligns fields with the builtin-not-fabricated behaviour added for primary constructors in 1eb356c.

Verification

Eight regression tests in tests/test_csharp_field_generic_args.py, covering both directions — the argument is linked, a field and a property of the same type now agree, nested arguments resolve, and type parameters and builtins are still never fabricated.

New tests on unpatched HEAD 5 of 8 fail
New tests with this patch 8 of 8 pass
C# suites (-k csharp) 108 passed, 5 skipped
Full suite, baseline 30 failed, 4,575 passed
Full suite, patched 25 failed, 4,580 passed

A set-difference of the failing test ids between the two full runs shows nothing newly broken — the delta is exactly the five new tests.

One caveat stated plainly: test_labeling.py::test_label_communities_batches_when_over_batch_size is flaky independently of this change. It fails intermittently on unpatched HEAD too (confirmed over repeated runs), so please don't read it either way.

Scope

Deliberately field-only. #2911 also covers call-site type arguments (services.AddScoped<IThing, Thing>()), but that lives in the call-resolution path and overlaps #2676, which strips call-site type arguments to repair the callee match. The two want different things from the same tokens — one discards them, the other needs them emitted — so that half is better as its own change once #2676 settles. Happy to write it.

The field_declaration handler read only the outer type name via
_read_csharp_type_name and emitted a single references edge, so the
inner argument of `Box<Widget>` was never linked. Properties, return
types and parameters already walk the whole type expression through
_csharp_collect_type_refs, which left fields the odd one out -- the
property handler's own comment even points at the Java/PHP/Kotlin
siblings it was mirroring, and the tree_sitter_java field_declaration
handler directly below does the same thing correctly.

Two very common shapes lose their edge as a result: a stored dependency
such as `IDbContextFactory<SomeContext>` loses SomeContext, and
`Mock<IThing>` loses IThing across an entire test suite. Classic
constructor injection stores its dependency in a field, so this is the
same blind spot Graphify-Labs#2829 removed from primary constructors, just moved to
where the dependency is kept. The loss is silent: `affected` returns a
smaller, confident answer rather than an error.

The fix routes the field handler through _csharp_collect_type_refs with
the in-scope type parameters as the skip set, mirroring the property
handler exactly. The outer type still emits context="field"; arguments
emit context="generic_arg".

This also stops fields fabricating nodes for predefined types --
`private string _s` previously created a `string` node, because
_read_csharp_type_name returns builtins while _csharp_collect_type_refs
returns early on predefined_type. That matches the builtin-not-fabricated
behaviour added for primary constructors in 1eb356c.

Eight regression tests cover both directions: the generic argument is
linked, a field and a property of the same type now agree, nested
arguments resolve, and type parameters and builtins are still never
fabricated. Five of the eight fail on unpatched HEAD.

Full suite: 30 failures before, 25 after, and a set-difference of the
failing test ids shows nothing newly broken -- the delta is exactly the
five new tests. test_labeling's batching test is flaky independently of
this change (it fails intermittently on unpatched HEAD too).

Scoped deliberately to field position. The call-site half of Graphify-Labs#2911
(`services.AddScoped<IThing, Thing>()`) lives in the call-resolution
path and overlaps Graphify-Labs#2676, so it is better as its own change.

Refs Graphify-Labs#2911.

@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 C# field references in _extract_generic to walk the full type expression via _csharp_collect_type_refs instead of reading only the outer type name, so generic arguments like the SomeContext in IDbContextFactory<SomeContext> now get linked (with generic_arg context) the same way properties, return types, and parameters already do. Type parameters and predefined types are excluded from node creation. Adds tests/test_csharp_field_generic_args.py covering nested generics, multiple declarators, field/property parity, and non-fabrication of builtins and type params.

No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 627 functions depend on the 216 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() — 16 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 — 627 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: 567 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.

1 participant