You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The C# extractor walks generic type arguments in property, return and parameter position, but drops them in field and call-site position. The field/property split is the surprising one — they are semantically adjacent, and one works while the other does not.
Version: 0.9.48. Reproduced with --code-only (pure AST, no LLM).
graphify extract proj --code-only --force --out out
Result
Edges originating from Probe or its members:
position
type arg
edge emitted
field
IAlpha
none
property
IBeta
references[generic_arg]
return
IGamma
references[generic_arg]
parameter
IDelta
references[generic_arg]
call site
IEpsilon
none
DI registration
IZeta
none
Expected: all six emit a references edge to the inner type argument, as properties, returns and parameters already do.
Why it matters
Both gaps erase dependency edges that impact analysis depends on, and they erase them silently — affected returns a confident, smaller answer rather than an error or a warning.
Field position. Classic constructor injection stores its dependency in a field, so the dependency's concrete type is invisible whenever it appears as a type argument:
privatereadonlyIDbContextFactory<SomeDbContext>_factory;// SomeDbContext: no edge
The same applies to essentially every mocking-based unit test (private readonly Mock<IThing> _mock; loses IThing). In a codebase I checked this against, six of six sampled real fields of this shape produced no edge to the inner type.
Call-site position. This is the standard .NET dependency-injection registration:
Neither IThing nor Thing gets an edge, so the interface→implementation binding — the thing that makes the registration meaningful — is absent from the graph. Microsoft.Extensions.DependencyInjection is close to universal in modern .NET, so this affects most C# projects with a composition root.
Related but not the same, and I do not think #2676 closes this.
#2624 is about the calls edge to the invoked method being lost when the call site carries explicit type arguments — X.M<T>(...) normalises to a key that cannot match the stored member label. #2676 fixes that by stripping the type arguments before matching.
This issue is about the type arguments themselves never becoming references edges. Stripping them to repair the callee match discards them, so #2676 would restore the edge to AddScoped while still leaving IThing and Thing unlinked. The two fixes are complementary: one repairs the callee edge, the other emits the argument references.
Field position is outside #2624 entirely — there is no call site involved.
Suggested direction
The property/return/parameter handlers already resolve this correctly, so the missing cases look like unvisited positions rather than a new mechanism:
field: walk the declared type's type-argument list in the field_declaration handler, mirroring the property handler directly beside it.
Summary
The C# extractor walks generic type arguments in property, return and parameter position, but drops them in field and call-site position. The field/property split is the surprising one — they are semantically adjacent, and one works while the other does not.
Version:
0.9.48. Reproduced with--code-only(pure AST, no LLM).Reproduction
Types.csProbe.csResult
Edges originating from
Probeor its members:IAlphaIBetareferences[generic_arg]IGammareferences[generic_arg]IDeltareferences[generic_arg]IEpsilonIZetaExpected: all six emit a
referencesedge to the inner type argument, as properties, returns and parameters already do.Why it matters
Both gaps erase dependency edges that impact analysis depends on, and they erase them silently —
affectedreturns a confident, smaller answer rather than an error or a warning.Field position. Classic constructor injection stores its dependency in a field, so the dependency's concrete type is invisible whenever it appears as a type argument:
The same applies to essentially every mocking-based unit test (
private readonly Mock<IThing> _mock;losesIThing). In a codebase I checked this against, six of six sampled real fields of this shape produced no edge to the inner type.Call-site position. This is the standard .NET dependency-injection registration:
Neither
IThingnorThinggets an edge, so the interface→implementation binding — the thing that makes the registration meaningful — is absent from the graph.Microsoft.Extensions.DependencyInjectionis close to universal in modern .NET, so this affects most C# projects with a composition root.Relationship to #2624 / #2676
Related but not the same, and I do not think #2676 closes this.
#2624 is about the
callsedge to the invoked method being lost when the call site carries explicit type arguments —X.M<T>(...)normalises to a key that cannot match the stored member label. #2676 fixes that by stripping the type arguments before matching.This issue is about the type arguments themselves never becoming
referencesedges. Stripping them to repair the callee match discards them, so #2676 would restore the edge toAddScopedwhile still leavingIThingandThingunlinked. The two fixes are complementary: one repairs the callee edge, the other emits the argument references.Field position is outside #2624 entirely — there is no call site involved.
Suggested direction
The property/return/parameter handlers already resolve this correctly, so the missing cases look like unvisited positions rather than a new mechanism:
field_declarationhandler, mirroring the property handler directly beside it.references[generic_arg]edge per argument in the invocation's type-argument list. If fix(csharp): strip call-site type arguments from generic calls #2676 strips them for callee matching, emitting the references before the strip keeps both behaviours.Happy to put up a PR for either or both if the direction looks right — I sent #2836 for #2829 and can follow the same shape.