fix(csharp): apply Blazor _Imports.razor usings in the razor type resolver (#3187) - #3264
fix(csharp): apply Blazor _Imports.razor usings in the razor type resolver (#3187)#3264abhay-codes07 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR completes the Blazor _Imports.razor portion of the Razor → C# cross-file type resolution work by making _Imports.razor @using and alias directives apply to .razor/.cshtml files according to Razor’s directory scoping rules, eliminating per-file sourceless stubs for @inject when the only usings are inherited.
Changes:
- Indexes
_Imports.razornamespace usings and aliases by governing directory and merges applicable ancestors for Razor-family files during resolution. - Ensures
.csfiles do not inherit_Imports.razordirectives and that sibling directories do not leak. - Adds focused regression tests covering positive resolution cases, alias support, and scoping/ambiguity controls.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| graphify/extractors/csharp.py | Extends CsharpNameResolver to inherit _Imports.razor usings/aliases for .razor/.cshtml based on directory ancestry. |
| tests/test_razor_imports_usings.py | Adds regression tests validating _Imports.razor scoping rules and resolver behavior for Razor @inject resolution. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
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
Extends the C# name resolver so Blazor _Imports.razor @using and alias directives apply to every Razor/.cshtml file in the same directory and below, folding those inherited usings into scope resolution (_scopes_for) and alias lookups (_aliases_for) so a bare @inject WidgetService resolves to its canonical definition instead of dangling on a sourceless stub. Directory governance is computed by _razor_imports_dir and _razor_dir_applies, with the root _Imports.razor covering the whole tree; inheritance is restricted to Razor-family files, so sibling directories and same-directory .cs files never pick up these usings, and ambiguous inherited names still refuse to guess a winner.
No blocking issues surfaced. 4 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1148 functions depend on the 55 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 522 callers, 43 callees - new:
_rebuild_code()— 113 callers, 50 callees - new:
dispatch_command()— 2 callers, 123 callees - new:
run_pipeline()— 8 callers, 13 callees - new:
watch()— 5 callers, 7 callees - new:
_build()— 7 callers, 3 callees - new:
_resolve_csharp_type_references()— 2 callers, 10 callees - new:
main()— 1 callers, 6 callees - …and 6 more — each is listed as a finding
Verification — 1148 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: 498 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify \_\_init\_\_.
The verifier did not have enough to check \_\_init\_\_, 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: not verifiable: all 27 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly __init__ raised on the synthesized receiver args — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_resolve\_alias.
The verifier did not have enough to check \_resolve\_alias, 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: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly __init__ raised on the synthesized receiver args — names the real obstacle, not a sampling gap)
Could not verify: Could not verify resolve\_qualified.
The verifier did not have enough to check resolve\_qualified, 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 `qualifier` is annotated `object` — outside the synthesizable primitive/collection set
Could not verify: Could not verify resolve\_type\_name.
The verifier did not have enough to check resolve\_type\_name, 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: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly __init__ raised on the synthesized receiver args — names the real obstacle, not a sampling gap)
Could not verify: Could not verify \_scopes\_for.
The verifier did not have enough to check \_scopes\_for, 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: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly __init__ raised on the synthesized receiver args — names the real obstacle, not a sampling gap)
· 14 more finding(s) on lines outside this diff (see the check run).
Addresses the remaining
_Imports.razorhalf of #3187 (follow-up to #3188).The problem
#3188 routed a Razor
@injectthrough the C# cross-file type resolver, but only directives written in the page itself count. The Razor compiler applies_Imports.razor@using/alias directives to every Razor file in the same directory and below — and the standard Blazor template keeps the app's namespaces there — so the issue's repro still dangles on 0.9.53: withPages/_Imports.razorcarrying@using Demo.Services,@inject WidgetService _widgetsinPages/AlphaPage.razorproduces a sourceless per-file stub while the canonicalDemo.Services.WidgetServicedefinition sits in the graph.(The other still-open sub-case named in the #3188 comment — a bare
@injectwith no using in scope anywhere — dangles on purpose: that Razor file would not compile either, and the resolver treats it exactly like a bare C# cross-namespace reference. A control test documents that.)The change
CsharpNameResolvernow indexes each_Imports.razor's using/alias entries by the directory they govern, and lookups for razor-family files only (.razor/.cshtml) fold in every governing ancestor's entries — same-directory or above, root_Imports.razorgoverning the whole tree, mirroring the Razor compiler's rule. Namespace usings extend_scopes_for; aliases merge into the alias view used byresolve_type_name/resolve_qualified..csfiles never inherit them, sibling directories never leak, and the existing single-candidate soundness gate is unchanged — two same-named types both brought in scope by inherited usings still dangle rather than picking a winner.Tests
tests/test_razor_imports_usings.py— 7 tests: same-directory and root_Imports.razorresolve the issue's@injectrepro to the canonical definition (stub gone); an alias directive in_Imports.razorresolves; and four scoping controls — sibling directories don't leak,.csfiles don't inherit, ambiguous inherited usings still dangle, and no-using-anywhere still dangles. With the fix reverted, the 3 positive tests fail and the 4 controls keep passing. The razor/csharp suites are unchanged (188 passed); the full suite matches a fresh same-versionv8baseline.