Skip to content

C#: calls through interfaces / virtual members are not attributed to implementers (0 inbound callers); overloads bound by name only #2893

Open

Description

@eXist06

Context. We index a large legacy .NET codebase (≈45k C# files, 64 solutions, mixed .NET Framework / ASP.NET Web Forms / EF6 / EF Core, ~1.9M nodes) with codebase-memory-mcp 0.11.0 (production) and 0.12.1 (trial). To know how far to trust the graph we built a Roslyn 5.9 reference (MSBuildWorkspace per solution, SemanticModel.GetSymbolInfo on every invocation / object creation) and compared edge by edge (key = caller file, call line ±1, target name; the engine's properties.line/strategy/confidence were used). Numbers below are from one sub-repo (8,458 files, 904,672 call sites, 310,119 with the target defined in the repo); the two larger sub-repos (≈1.4M and ≈2.6M call sites) show the same picture within a few points. Related: #2874 (INHERITS loss with constructors) and #2876 (maintainer C# gap list).

1. Interface / virtual dispatch is invisible. Calls through an interface receiver (DI-injected IFooService.GetByKey(...)) produce an edge to the interface method; implementers show 0 inbound callers, so "who calls FooService.GetByKey" is answered "nobody" although the class is called through its interface from many places. The data to derive this exists: Roslyn finds implementers for 100 % of the 106 source interfaces in this sub-repo and the engine's own IMPLEMENTS recall is 98.2 % in 0.12.1 (88.8 % in 0.11.0). Example shape: IUnitBusiness.GetByReferenceKey → 2 implementing classes in different projects, 4 Roslyn call sites through the interface, 0 graph callers on either implementation.

2. Overload resolution by name only. 16,927 call sites (5.5 % of the resolved ones) have several same-name overloads in the target type; the engine picks one by name, Roslyn by arity/argument types, so inbound lists mix overloads and sometimes point to the wrong one.

Suggestions. (a) A query-time option (trace_path … expand_dispatch=true, same for inbound callers) or a derived CALLS_VIA_INTERFACE edge from the interface/virtual member to each implementer/override, so inbound traces on an implementation include calls made through its interfaces and base virtuals (IMPLEMENTS/OVERRIDES edges already carry what is needed); (b) at least arity-aware overload binding — the argument count is available syntactically — with confidence lowered when several overloads still match.

Reproduction / offer. Our comparison script (reads the engine's SQLite graph and a Roslyn-produced SQLite call map, reports recall/precision per strategy with the error classes above) and the Roslyn extractor are generic (.NET 8, Microsoft.CodeAnalysis 5.9). We can share both, and can run any candidate build against the same corpus and report the same tables.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions