Skip to content

Conversation

@StephaneDelcroix
Copy link
Contributor

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:

  1. "Cannot create an instance of the abstract type or interface 'Brush'" - When OnPlatform<Brush> was used with <OnPlatform.Default> syntax and no matching platform.

  2. "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:

<OnPlatform x:Key="RadEntryInvalidBorderBrush" x:TypeArguments="Brush">
    <On Platform="WinUI">
        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
            <LinearGradientBrush.GradientStops>
                <GradientStop Offset="0.00" Color="{StaticResource RadOnAppSurfaceColorAlpha6}" />
                <GradientStop Offset="0.93" Color="{StaticResource RadOnAppSurfaceColorAlpha6}" />
                <GradientStop Offset="0.93" Color="{StaticResource RadErrorColor}" />
                <GradientStop Offset="1.00" Color="{StaticResource RadErrorColor}" />
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </On>
    <OnPlatform.Default>
        <SolidColorBrush Color="{StaticResource RadErrorColor}" />
    </OnPlatform.Default>
</OnPlatform>

<OnPlatform x:TypeArguments="View">
    <On Platform="WinUI">
        <telerikMauiControls:RadBorder Grid.Row="2"
                                        BackgroundColor="{DynamicResource RadAIPromptInputViewFooterBackgroundColor}" />
    </On>
</OnPlatform>

Root Cause

Two issues in SimplifyOnPlatformVisitor.cs:

  1. GetDefault() only checked for Default property with empty namespace (new XmlName("", "Default")), but when using <OnPlatform.Default> element syntax, the property is stored with the MAUI namespace URI.

  2. When no matching platform and no Default was found, the code created an ElementNode from the x:TypeArguments type, which later failed when CreateValuesVisitor tried to instantiate abstract types or types with protected constructors.

Fix

  1. Updated GetDefault() to check both empty namespace (attribute syntax) and MAUI namespace (element syntax <OnPlatform.Default>)

  2. When no matching platform AND no Default is found, skip simplification and keep the OnPlatform element for runtime resolution instead of trying to create a default value. This is the safe approach because the runtime OnPlatform<T> correctly returns default(T).

Tests Added

Added 6 new tests in OnPlatformAbstractTypes.cs covering:

  • OnPlatformWithAbstractBrushTypeUsesDefault - Brush with Default on non-matching platform
  • OnPlatformWithAbstractBrushTypeMatchesPlatform - Brush when platform matches
  • OnPlatformWithViewTypeUsesDefault - View with Default on non-matching platform
  • OnPlatformWithViewTypeMatchesPlatform - View when platform matches
  • OnPlatformWithAbstractTypeNoDefaultNoMatchingPlatform - Brush without Default on non-matching platform
  • OnPlatformWithProtectedCtorTypeNoDefaultNoMatchingPlatform - View without Default on non-matching platform

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.
Copilot AI review requested due to automatic review settings December 18, 2025 14:11
@StephaneDelcroix StephaneDelcroix added area-xaml XAML, CSS, Triggers, Behaviors xsg Xaml sourceGen labels Dec 18, 2025
@StephaneDelcroix StephaneDelcroix added this to the .NET 10.0 SR3 milestone Dec 18, 2025
@jfversluis jfversluis added the p/0 Current heighest priority issues that we are targeting for a release. label Dec 18, 2025
@jfversluis jfversluis moved this from Todo to Ready To Review in MAUI SDK Ongoing Dec 18, 2025
@StephaneDelcroix StephaneDelcroix marked this pull request as draft December 18, 2025 14:19
Copy link
Contributor

Copilot AI left a 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:

  1. Correcting Default property detection: The GetDefault() method now checks for the Default property in both empty namespace (attribute syntax) and MAUI namespace (element syntax like <OnPlatform.Default>).

  2. 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

Comment on lines +120 to +128
// 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);
Copy link

Copilot AI Dec 18, 2025

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:

  1. Adding support for "-windows" TFM in SimplifyOnPlatformVisitor constructor (mapping it to "WinUI"), or
  2. Using a supported TFM like "net10.0-android" and verifying the Default path works correctly.
Suggested change
// 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);

Copilot uses AI. Check for mistakes.
Comment on lines +226 to +234
// 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);
Copy link

Copilot AI Dec 18, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines 89 to 100
// 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;
}
Copy link

Copilot AI Dec 18, 2025

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.

Copilot uses AI. Check for mistakes.
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).
@github-project-automation github-project-automation bot moved this from Ready To Review to Approved in MAUI SDK Ongoing Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-xaml XAML, CSS, Triggers, Behaviors p/0 Current heighest priority issues that we are targeting for a release. xsg Xaml sourceGen

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

4 participants