Skip to content

C# graph cannot resolve fully-qualified type references, and namespace matching silently compensates #683

Description

@grinidx

Found while working on #682 (Razor/Blazor view support). Filing separately because it is independent of that PR and larger than it.

Two behaviours that cancel out

1. The C# dependency graph is built only from using directives. build_dep_graph collects namespace and using per file (deps_support_metadata.parse_file_metadata) and links them via expand_namespace_matches. A type referenced by its fully-qualified name, with no using, produces no edge at all.

This is common in real code. From a ten-project solution I tested against:

// src/ScentVerdict.Cli/Program.cs - no `using ScentVerdict.Cli.Commands.NoteModel;`
rootCommand.Subcommands.Add(ScentVerdict.Cli.Commands.NoteModel.VocabCommand.Create());
rootCommand.Subcommands.Add(ScentVerdict.Cli.Commands.Comparison.ComparisonCommands.CreateComparisonCommand());

14 of 95 command files in that project declare a namespace that no using in Program.cs covers. They are reached fully-qualified and are entirely live.

2. expand_namespace_matches has a clause that is not C# scoping:

or using_ns.startswith(namespace + ".")

A using of a child namespace does not bring the parent into scope. using App.Services does not import App. But combined with the root-namespace fallback in build_dep_graph, which adds every file in a project to that project's RootNamespace, this clause makes using App.Services link every file in the project.

So (2) is masking (1). Almost nothing is ever orphaned, which hides both the missing-edge bug and any genuinely dead code.

Why they cannot be fixed in either order alone

I removed clause (2) experimentally and measured on the same solution:

orphaned .cs
as-is 461
clause removed 538

All 77 newly-surfaced files are live CLI commands, orphaned purely because they are referenced fully-qualified. Tightening the namespace rule on its own converts a silent over-link into 77 false positives, which is worse: the tool would tell you to delete working code.

So the ordering is: resolve fully-qualified references first, then tighten the namespace matching. Doing the second without the first is a regression.

Suggested direction

build_dep_graph already builds enough to do this. A type-name index over declared types (I added one in #682 for the view edges, deps_support_razor.build_type_index) plus a scan for dotted references matching Namespace.Type would cover the common case without new machinery. The existing roslyn_cmd escape hatch remains the exact answer for projects that want it.

Caveats on this report

  • The 461/538 figures are from one solution. I have not measured the distribution across other C# projects.
  • I verified two of the 77 by reading Program.cs directly, and checked the namespace-vs-using coverage for all 95 command files in that project mechanically. I did not individually confirm all 77 are live.
  • I attempted to confirm with a Roslyn-backed indexer but that solution has projects failing to restore, so the index was incomplete and I did not rely on it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions