-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Fix OnPlatform SourceGen for abstract types and protected constructors #33214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Fixes two XAML SourceGen issues: 1. 'Cannot create an instance of the abstract type or interface Brush' 2. 'View.View() is inaccessible due to its protection level' Root cause: - GetDefault() only checked for Default property with empty namespace, but <OnPlatform.Default> element syntax stores it with MAUI namespace - When no matching platform and no Default, code tried to create an ElementNode from x:TypeArguments type, failing for abstract types and types with protected constructors Fix: - GetDefault() now checks both empty and MAUI namespace for Default - When no matching platform AND no Default, skip simplification and keep OnPlatform for runtime resolution (which correctly returns default(T)) Added tests for OnPlatform with Brush and View types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes two XAML SourceGen compilation errors that occurred when using OnPlatform with abstract types (e.g., Brush) or types with protected constructors (e.g., View). The fix addresses the root cause by:
-
Correcting Default property detection: The
GetDefault()method now checks for theDefaultproperty in both empty namespace (attribute syntax) and MAUI namespace (element syntax like<OnPlatform.Default>). -
Skipping unsafe simplification: When no matching platform and no Default value are found, the code now skips simplification entirely and keeps the OnPlatform element for runtime resolution, rather than attempting to create instances of abstract types or types with inaccessible constructors.
Key Changes
- Fixed
GetDefault()to recognize<OnPlatform.Default>element syntax - Changed behavior to skip compile-time simplification when no match/default exists
- Updated existing test expectations to reflect the new runtime-resolution approach
- Added comprehensive test coverage for abstract types and protected constructors
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/Controls/src/Xaml/SimplifyOnPlatformVisitor.cs |
Fixed GetDefault() to check both namespaces; changed to skip simplification when no match/default found instead of creating invalid instances |
src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs |
Updated test to verify OnPlatform is kept for runtime resolution when no match/default exists |
src/Controls/tests/SourceGen.UnitTests/InitializeComponent/OnPlatformAbstractTypes.cs |
Added 6 new tests covering abstract types (Brush) and protected constructors (View) with various scenarios |
| // Target Windows - WinUI platform is matched | ||
| var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0-windows"); | ||
|
|
||
| // Should not have any errors | ||
| Assert.False(result.Diagnostics.Any(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error), | ||
| $"SourceGen should not fail for OnPlatform<Brush>. Errors: {string.Join(", ", result.Diagnostics)}"); | ||
|
|
||
| // Should contain LinearGradientBrush (from WinUI platform) | ||
| Assert.Contains("LinearGradientBrush", generated, StringComparison.Ordinal); |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests use "net10.0-windows" as the target framework, but SimplifyOnPlatformVisitor's constructor does not handle the "-windows" TFM (it only handles android, ios, macos, and maccatalyst). This means Target will be null, causing the visitor to skip all OnPlatform simplification (see SimplifyOnPlatformVisitor.cs lines 46-47).
As a result, these tests will not actually verify that the WinUI platform is matched and simplified. The OnPlatform element will be kept in the generated code for runtime resolution instead of being simplified at compile time. Consider either:
- Adding support for "-windows" TFM in SimplifyOnPlatformVisitor constructor (mapping it to "WinUI"), or
- Using a supported TFM like "net10.0-android" and verifying the Default path works correctly.
| // Target Windows - WinUI platform is matched | |
| var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0-windows"); | |
| // Should not have any errors | |
| Assert.False(result.Diagnostics.Any(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error), | |
| $"SourceGen should not fail for OnPlatform<Brush>. Errors: {string.Join(", ", result.Diagnostics)}"); | |
| // Should contain LinearGradientBrush (from WinUI platform) | |
| Assert.Contains("LinearGradientBrush", generated, StringComparison.Ordinal); | |
| // Target Android - Default path should be used (SolidColorBrush) instead of WinUI LinearGradientBrush | |
| var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0-android"); | |
| // Should not have any errors | |
| Assert.False(result.Diagnostics.Any(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error), | |
| $"SourceGen should not fail for OnPlatform<Brush>. Errors: {string.Join(", ", result.Diagnostics)}"); | |
| // Should contain SolidColorBrush from the Default path and not select the WinUI LinearGradientBrush | |
| Assert.Contains("SolidColorBrush", generated, StringComparison.Ordinal); | |
| Assert.DoesNotContain("LinearGradientBrush", generated, StringComparison.Ordinal); |
| // Target Windows - WinUI platform is matched | ||
| var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0-windows"); | ||
|
|
||
| // Should not have any errors | ||
| Assert.False(result.Diagnostics.Any(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error), | ||
| $"SourceGen should not fail for OnPlatform<View>. Errors: {string.Join(", ", result.Diagnostics)}"); | ||
|
|
||
| // Should contain Border (from WinUI platform) | ||
| Assert.Contains("Border", generated, StringComparison.Ordinal); |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as with OnPlatformWithAbstractBrushTypeMatchesPlatform: the "net10.0-windows" target framework is not mapped to any Target in SimplifyOnPlatformVisitor, so the visitor will skip simplification entirely rather than matching the WinUI platform. This test will not verify the expected behavior.
Either add support for "-windows" TFM in SimplifyOnPlatformVisitor, or adjust the test to use a supported platform.
| // If no value found for target platform and no Default, | ||
| // create a default value node if we have x:TypeArguments | ||
| if (onNode == null && node.XmlType.TypeArguments != null && node.XmlType.TypeArguments.Count > 0) | ||
| // don't simplify - let the runtime handle it. | ||
| // At runtime, OnPlatform<T> returns default(T) = null for reference types. | ||
| // We can't create a default value at compile time because: | ||
| // 1. The type might be abstract (e.g., Brush) | ||
| // 2. The type might have a protected/private constructor (e.g., View) | ||
| // 3. A null resource entry may not be meaningful | ||
| if (onNode == null) | ||
| { | ||
| // Create default value for the type (to match OnPlatform<T> runtime behavior) | ||
| var typeArg = node.XmlType.TypeArguments[0]; | ||
| var elementNode = new ElementNode(typeArg, typeArg.NamespaceUri, node.NamespaceResolver, node.LineNumber, node.LinePosition); | ||
| onNode = elementNode; | ||
| // Skip simplification - keep the OnPlatform element for runtime resolution | ||
| return; | ||
| } |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix correctly identifies that we cannot create instances of abstract types or types with protected constructors at compile time. However, the approach of keeping the OnPlatform element for runtime resolution means that SourceGen will not optimize these cases at all.
This is a conservative and safe approach, but it may impact performance slightly since the resolution happens at runtime. Consider whether it would be beneficial to detect value types (where default(T) is always safe) and still simplify those at compile time, while only skipping reference types. For example, OnPlatform<double> could still be simplified to 0.0 at compile time without issues.
src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
Outdated
Show resolved
Hide resolved
Instead of skipping simplification when no matching platform and no Default, we now generate default(T) for the type. This: 1. Maintains compile-time simplification for better performance 2. Works correctly for abstract types (Brush) - generates null 3. Works correctly for types with protected ctors (View) - generates null 4. Matches runtime OnPlatform<T> behavior which returns default(T) Changes: - Add IsOnPlatformDefaultValue property to ElementNode - SimplifyOnPlatformVisitor sets this flag when creating default value nodes - CreateValuesVisitor generates 'TypeName varN = default;' for flagged nodes
Tests verify that all inflators (Runtime, XamlC, SourceGen) correctly handle: - Unreported011: OnPlatform<Brush> with <OnPlatform.Default> element syntax - Unreported012: OnPlatform<View> with <OnPlatform.Default> element syntax - Unreported013: OnPlatform<Brush> without Default (generates null) These tests ensure the XAML inflates without throwing exceptions for abstract types (Brush) and types with protected constructors (View).
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Description
Fixes two XAML SourceGen issues reported:
"Cannot create an instance of the abstract type or interface 'Brush'" - When
OnPlatform<Brush>was used with<OnPlatform.Default>syntax and no matching platform."error CS0122: 'View.View()' is inaccessible due to its protection level" - When
OnPlatform<View>was used and no matching platform.Example XAML that was failing:
Root Cause
Two issues in
SimplifyOnPlatformVisitor.cs:GetDefault()only checked forDefaultproperty with empty namespace (new XmlName("", "Default")), but when using<OnPlatform.Default>element syntax, the property is stored with the MAUI namespace URI.When no matching platform and no Default was found, the code created an
ElementNodefrom thex:TypeArgumentstype, which later failed whenCreateValuesVisitortried to instantiate abstract types or types with protected constructors.Fix
Updated
GetDefault()to check both empty namespace (attribute syntax) and MAUI namespace (element syntax<OnPlatform.Default>)When no matching platform AND no Default is found, skip simplification and keep the
OnPlatformelement for runtime resolution instead of trying to create a default value. This is the safe approach because the runtimeOnPlatform<T>correctly returnsdefault(T).Tests Added
Added 6 new tests in
OnPlatformAbstractTypes.cscovering:OnPlatformWithAbstractBrushTypeUsesDefault- Brush with Default on non-matching platformOnPlatformWithAbstractBrushTypeMatchesPlatform- Brush when platform matchesOnPlatformWithViewTypeUsesDefault- View with Default on non-matching platformOnPlatformWithViewTypeMatchesPlatform- View when platform matchesOnPlatformWithAbstractTypeNoDefaultNoMatchingPlatform- Brush without Default on non-matching platformOnPlatformWithProtectedCtorTypeNoDefaultNoMatchingPlatform- View without Default on non-matching platform