Razor: split component referencing a fallback component's nested delegate/enum loses metadata (decl/impl split)
- Dominant language
- C#
- Stars
- 20.7k
- Forks
- 4.3k
- PR merge metrics
- PR metrics pending
Description
### Summary
With the Razor decl/impl split (Sonic S2 pre-compilation source output), a **split** (fast-path) component that has a `[Parameter]` whose type is a **delegate or enum nested inside a *fallback* component** gets a degraded tag-helper descriptor, producing a **hard compile error** in a consumer.
A fallback component emits a bodiless "type shell" declaration to the pre-compilation compilation so its type resolves. The shell declares only the outer class, so a nested delegate/enum binds as an *error type* during fast discovery. `ComponentTagHelperProducer` classifies delegates/enums by `TypeKind` (`IsDelegate` -> `TypeKind.Delegate`, `IsEnum` -> `TypeKind.Enum`), so the nested reference is misclassified (falls through to `PropertyKind.Default`). Slow discovery (over the augmented compilation, where the nested type resolves) computes the correct descriptor but keeps only fallback-component types, so the referencing split component's descriptor is never corrected.
### Repro
```razor
@* Widget.razor -- a FALLBACK component (an @implements/@inherits/@typeparam directive forces the fallback path) *@
@implements System.IDisposable
@code {
public delegate void MyHandler(int x);
public void Dispose() { }
}
```
```razor
@* Card.razor -- SPLIT (fast path); its ValueChanged parameter type is Widget's nested delegate *@
@code {
[Parameter] public int Value { get; set; }
[Parameter] public Widget.MyHandler? ValueChanged { get; set; }
}
```
```razor
@* Consumer.razor *@
@code { int v; }
```
Generated code for the consumer fails to compile:
```
error CS1503: Argument 1: cannot convert from 'Microsoft.AspNetCore.Components.EventCallback' to 'Widget.MyHandler?'
```
Because `ValueChanged` was not recognized as a delegate at discovery time, `@bind-Value` lowers it as an `EventCallback` instead of a delegate. An identical setup where `Widget` is *splittable* (no fallback-forcing directive) compiles cleanly, confirming the bodiless shell is the cause. Nested **enums** break analogously; nested **classes/structs** are unaffected (they need only the type *name*, which the shell already resolves via the outer type).
### Scope
- Only the pre-compilation-consuming path (Sonic S2 and later). Pre-Sonic and S1 use a single declaration compilation containing all decls, so the nested type always resolves and the descriptor is correct.
- Narrow: requires a nested delegate/enum in a fallback component, referenced as a split component's `[Parameter]`, where the delegate/enum classification matters (`@bind`, enum-typed attribute handling).
- Fails loudly (compile error), not silently.
### Fix options
- **A -- Partition nested types in the split phase.** Parse the `@code` C#, move nested type declarations into the shell and remove them from the impl (length-preserving blank-out to keep source mappings). Keeps split components on the fast path; costs C# parsing in the split phase plus baseline churn on fallback-with-nested-type components.
- **B -- Route affected components to slow discovery.** Flag a fast descriptor when a `[Parameter]` binds to an error type; have slow discovery (which already runs over the fully-resolved augmented compilation) own those descriptors. Reuses the existing slow path; no shell/impl/source-mapping churn; minimal perf impact.
Likely resolved holistically when the separate declaration-engine run is eliminated (end-state), provided that design handles fallback nested types.
Contributor guide
Research direction
Start with ComponentTagHelperProducer and the fast and slow discovery paths described in the issue. Reproduce the fallback Widget, split Card, and Consumer binding case, then trace how the nested delegate or enum is classified and whether the descriptor is corrected. Done means the repro compiles and nested enum handling remains correct without regressing the split path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100