Skip to content

Commit 8fe90ca

Browse files
Fix OnPlatform SourceGen for abstract types and protected constructors (dotnet#33214)
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) 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: ```xml <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
1 parent 80b6844 commit 8fe90ca

File tree

11 files changed

+574
-10
lines changed

11 files changed

+574
-10
lines changed

src/Controls/src/SourceGen/Visitors/CreateValuesVisitor.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ public static void CreateValue(ElementNode node, IndentedTextWriter writer, IDic
3838
if (!node.XmlType.TryResolveTypeSymbol(null, compilation, xmlnsCache, Context.TypeCache, out var type) || type is null)
3939
return;
4040

41+
// Handle OnPlatform default value nodes - generate default(T) instead of instantiation
42+
// This is used when OnPlatform has no matching platform and no Default specified
43+
if (node.IsOnPlatformDefaultValue)
44+
{
45+
var variableName = NamingHelpers.CreateUniqueVariableName(Context, type);
46+
writer.WriteLine($"{type.ToFQDisplayString()} {variableName} = default;");
47+
variables[node] = new LocalVariable(type, variableName);
48+
node.RegisterSourceInfo(Context, writer);
49+
return;
50+
}
51+
4152
//x:Array
4253
if (type.Equals(compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Xaml.ArrayExtension"), SymbolEqualityComparer.Default))
4354
{

src/Controls/src/Xaml/SimplifyOnPlatformVisitor.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,17 @@ public void Visit(ElementNode node, INode parentNode)
8787
bool hasKey = node.Properties.TryGetValue(XmlName.xKey, out keyNode);
8888

8989
// If no value found for target platform and no Default,
90-
// create a default value node if we have x:TypeArguments
90+
// create a default value node if we have x:TypeArguments.
91+
// Mark it as IsOnPlatformDefaultValue so SourceGen can generate default(T)
92+
// instead of trying to instantiate the type (which fails for abstract types
93+
// or types with protected constructors).
9194
if (onNode == null && node.XmlType.TypeArguments != null && node.XmlType.TypeArguments.Count > 0)
9295
{
93-
// Create default value for the type (to match OnPlatform<T> runtime behavior)
9496
var typeArg = node.XmlType.TypeArguments[0];
95-
var elementNode = new ElementNode(typeArg, typeArg.NamespaceUri, node.NamespaceResolver, node.LineNumber, node.LinePosition);
97+
var elementNode = new ElementNode(typeArg, typeArg.NamespaceUri, node.NamespaceResolver, node.LineNumber, node.LinePosition)
98+
{
99+
IsOnPlatformDefaultValue = true
100+
};
96101
onNode = elementNode;
97102
}
98103

@@ -190,8 +195,11 @@ INode GetOnNode(ElementNode onPlatform, string target)
190195

191196
INode GetDefault(ElementNode onPlatform)
192197
{
198+
// Check both empty namespace (attribute syntax) and MAUI namespace (element syntax)
193199
if (onPlatform.Properties.TryGetValue(new XmlName("", "Default"), out INode defaultNode))
194200
return defaultNode;
201+
if (onPlatform.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "Default"), out defaultNode))
202+
return defaultNode;
195203
return null;
196204
}
197205

src/Controls/src/Xaml/XamlNode.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ class ElementNode(XmlType type, string namespaceURI, IXmlNamespaceResolver names
110110
public XmlType XmlType { get; } = type;
111111
public string NamespaceURI { get; } = namespaceURI;
112112
public NameScopeRef NameScopeRef { get; set; }
113+
114+
/// <summary>
115+
/// When true, this node represents a default value for an OnPlatform element
116+
/// where no matching platform was found. SourceGen should generate default(T)
117+
/// instead of trying to instantiate the type.
118+
/// </summary>
119+
public bool IsOnPlatformDefaultValue { get; set; }
113120

114121
public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
115122
{
@@ -150,7 +157,8 @@ public override INode Clone()
150157
{
151158
var clone = new ElementNode(XmlType, NamespaceURI, NamespaceResolver, LineNumber, LinePosition)
152159
{
153-
IgnorablePrefixes = IgnorablePrefixes
160+
IgnorablePrefixes = IgnorablePrefixes,
161+
IsOnPlatformDefaultValue = IsOnPlatformDefaultValue
154162
};
155163
foreach (var kvp in Properties)
156164
clone.Properties.Add(kvp.Key, kvp.Value.Clone());

0 commit comments

Comments
 (0)