Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/Controls/src/SourceGen/IKnownMarkupValueProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.CodeDom.Compiler;
using Microsoft.CodeAnalysis;
using Microsoft.Maui.Controls.Xaml;

namespace Microsoft.Maui.Controls.SourceGen;

/// <summary>
/// Interface for known markup types that can provide inline value initialization.
/// Separates the "can we inline?" check from the actual inlining logic.
/// </summary>
internal interface IKnownMarkupValueProvider
{
/// <summary>
/// Determines if this element can be fully inlined without requiring
/// property assignments or service provider infrastructure.
/// </summary>
/// <param name="node">The element node to check</param>
/// <param name="context">The source generation context</param>
/// <returns>True if the element can be inlined, false otherwise</returns>
bool CanProvideValue(ElementNode node, SourceGenContext context);

/// <summary>
/// Provides the inline value initialization code.
/// </summary>
/// <param name="node">The element node</param>
/// <param name="writer">The code writer</param>
/// <param name="context">The source generation context</param>
/// <param name="getNodeValue">Delegate to get node values</param>
/// <param name="returnType">The return type of the value</param>
/// <param name="value">The generated value code</param>
/// <returns>True if value was provided, false otherwise</returns>
bool TryProvideValue(ElementNode node, IndentedTextWriter writer, SourceGenContext context, NodeSGExtensions.GetNodeValueDelegate? getNodeValue, out ITypeSymbol? returnType, out string value);
}
40 changes: 0 additions & 40 deletions src/Controls/src/SourceGen/KnownMarkups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,46 +122,6 @@ public static bool ProvideValueForTypeExtension(ElementNode node, IndentedTextWr
return false;
}

public static bool ProvideValueForSetter(ElementNode node, IndentedTextWriter writer, SourceGenContext context, NodeSGExtensions.GetNodeValueDelegate? getNodeValue, out ITypeSymbol? returnType, out string value)
{
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Setter")!;

if (!node.Properties.TryGetValue(new XmlName("", "Value"), out INode? valueNode) &&
!node.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "Value"), out valueNode) &&
node.CollectionItems.Count == 1)
valueNode = node.CollectionItems[0];

var bpNode = (ValueNode)node.Properties[new XmlName("", "Property")];
var bpRef = bpNode.GetBindableProperty(context);

string targetsetter;
if (node.Properties.TryGetValue(new XmlName("", "TargetName"), out var targetNode))
targetsetter = $"TargetName = \"{((ValueNode)targetNode).Value}\", ";
else
targetsetter = "";

if (valueNode is ValueNode vn)
{
value = $"new global::Microsoft.Maui.Controls.Setter {{{targetsetter}Property = {bpRef.ToFQDisplayString()}, Value = {vn.ConvertTo(bpRef, writer, context)}}}";
return true;
}
else if (getNodeValue != null)
{
var lvalue = getNodeValue(valueNode, bpRef.Type);
value = $"new global::Microsoft.Maui.Controls.Setter {{{targetsetter}Property = {bpRef.ToFQDisplayString()}, Value = {lvalue.ValueAccessor}}}";
return true;
}
else if (context.Variables.TryGetValue(valueNode, out var variable))
{
value = $"new global::Microsoft.Maui.Controls.Setter {{{targetsetter}Property = {bpRef.ToFQDisplayString()}, Value = {variable.ValueAccessor}}}";
return true;
}

value = string.Empty;
//FIXME context.ReportDiagnostic
return false;
}

public static bool ProvideValueForDynamicResourceExtension(ElementNode markupNode, IndentedTextWriter writer, SourceGenContext context, NodeSGExtensions.GetNodeValueDelegate? getNodeValue, out ITypeSymbol? returnType, out string value)
{
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Internals.DynamicResource")!;
Expand Down
8 changes: 4 additions & 4 deletions src/Controls/src/SourceGen/NodeSGExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ static string ConvertEnum(string value, BaseNode node, ITypeSymbol toType, Inden
//{ context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.StrokeShapeTypeConverter")!, (CreateRegistryConverter("Microsoft.Maui.Controls.Shapes.Shape"), context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Shapes.Shape")!) },
};

public static Dictionary<ITypeSymbol, ProvideValueDelegate> GetKnownValueProviders(SourceGenContext context)
public static Dictionary<ITypeSymbol, IKnownMarkupValueProvider> GetKnownValueProviders(SourceGenContext context)
=> context.knownSGValueProviders ??= new(SymbolEqualityComparer.Default)
{
{context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Setter")!, KnownMarkups.ProvideValueForSetter},
{context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Setter")!, new SetterValueProvider()},
};


Expand Down Expand Up @@ -541,8 +541,8 @@ public static bool TryProvideValue(this ElementNode node, IndentedTextWriter wri
return true;
}

if (GetKnownValueProviders(context).TryGetValue(variable.Type, out provideValue)
&& provideValue.Invoke(node, writer, context, getNodeValue, out returnType0, out value))
if (GetKnownValueProviders(context).TryGetValue(variable.Type, out var valueProvider)
&& valueProvider.TryProvideValue(node, writer, context, getNodeValue, out returnType0, out value))
{
var variableName = NamingHelpers.CreateUniqueVariableName(context, returnType0 ?? context.Compilation.ObjectType);
context.Writer.WriteLine($"var {variableName} = {value};");
Expand Down
90 changes: 90 additions & 0 deletions src/Controls/src/SourceGen/SetterValueProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.CodeDom.Compiler;
using System.Linq;
using System.Xml;
using Microsoft.CodeAnalysis;
using Microsoft.Maui.Controls.Xaml;

namespace Microsoft.Maui.Controls.SourceGen;

internal class SetterValueProvider : IKnownMarkupValueProvider
{
public bool CanProvideValue(ElementNode node, SourceGenContext context)
{
// Can only inline if all properties are simple ValueNodes (no markup extensions)
// We need to check both the properties and any collection items

// Get the value node (shared logic with TryProvideValue)
var valueNode = GetValueNode(node);

// Value must be a simple ValueNode (not a MarkupNode or ElementNode)
if (valueNode is MarkupNode or ElementNode)
return false;

// All properties must be simple ValueNodes (no ElementNode or MarkupNode)
foreach (var prop in node.Properties.Values)
{
if (prop is MarkupNode or ElementNode)
return false;
}

return true;
}

public bool TryProvideValue(ElementNode node, IndentedTextWriter writer, SourceGenContext context, NodeSGExtensions.GetNodeValueDelegate? getNodeValue, out ITypeSymbol? returnType, out string value)
{
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.Setter")!;

// Get the value node (shared logic with CanProvideValue)
var valueNode = GetValueNode(node);
if (valueNode == null)
{
value = string.Empty;
return false;
}

var bpNode = (ValueNode)node.Properties[new XmlName("", "Property")];
var bpRef = bpNode.GetBindableProperty(context);

string targetsetter;
if (node.Properties.TryGetValue(new XmlName("", "TargetName"), out var targetNode))
targetsetter = $"TargetName = \"{((ValueNode)targetNode).Value}\", ";
else
targetsetter = "";

if (valueNode is ValueNode vn)
{
value = $"new global::Microsoft.Maui.Controls.Setter {{{targetsetter}Property = {bpRef.ToFQDisplayString()}, Value = {vn.ConvertTo(bpRef, writer, context)}}}";
return true;
}
else if (getNodeValue != null)
{
var lvalue = getNodeValue(valueNode, bpRef.Type);
value = $"new global::Microsoft.Maui.Controls.Setter {{{targetsetter}Property = {bpRef.ToFQDisplayString()}, Value = {lvalue.ValueAccessor}}}";
return true;
}
else if (context.Variables.TryGetValue(valueNode, out var variable))
{
value = $"new global::Microsoft.Maui.Controls.Setter {{{targetsetter}Property = {bpRef.ToFQDisplayString()}, Value = {variable.ValueAccessor}}}";
return true;
}

value = string.Empty;
//FIXME context.ReportDiagnostic
return false;
}

/// <summary>
/// Shared helper to get the value node from a Setter element.
/// Checks properties first, then collection items.
/// </summary>
private static INode? GetValueNode(ElementNode node)
{
INode? valueNode = null;
if (!node.Properties.TryGetValue(new XmlName("", "Value"), out valueNode) &&
!node.Properties.TryGetValue(new XmlName(XamlParser.MauiUri, "Value"), out valueNode) &&
node.CollectionItems.Count == 1)
valueNode = node.CollectionItems[0];

return valueNode;
}
}
2 changes: 1 addition & 1 deletion src/Controls/src/SourceGen/SourceGenContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void AddLocalMethod(string code)
}

internal Dictionary<ITypeSymbol, (ConverterDelegate, ITypeSymbol)>? knownSGTypeConverters;
internal Dictionary<ITypeSymbol, ProvideValueDelegate>? knownSGValueProviders;
internal Dictionary<ITypeSymbol, IKnownMarkupValueProvider>? knownSGValueProviders;
internal Dictionary<ITypeSymbol, ProvideValueDelegate>? knownSGEarlyMarkupExtensions;
internal Dictionary<ITypeSymbol, ProvideValueDelegate>? knownSGLateMarkupExtensions;
}
31 changes: 31 additions & 0 deletions src/Controls/src/SourceGen/Visitors/CreateValuesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,36 @@ public static void CreateValue(ElementNode node, IndentedTextWriter writer, IDic
}
else if (ctor != null)
{
// Check if this type is a known value provider that can be inlined.
// Use CanProvideValue to verify that ALL properties can be inlined (no markup extensions).
if (NodeSGExtensions.GetKnownValueProviders(Context).TryGetValue(type, out var valueProvider) &&
valueProvider.CanProvideValue(node, Context))
{
// This element can be fully inlined without property assignments or variable creation.
// Skip setting all simple value properties since they'll be handled
// by inline initialization in TryProvideValue.
//
// This eliminates the dead code that creates:
// 1. Empty variable instantiation (var setter = new Setter();)
// 2. Service providers (XamlServiceProvider, SimpleValueTargetProvider,
// XmlNamespaceResolver, XamlTypeResolver) for property assignments
//
// These are not AOT-compatible and were completely dead code.
//
// Register a placeholder variable entry so TryProvideValue can replace it
// with the actual inline instantiation later.
foreach (var prop in node.Properties)
{
if (prop.Value is ValueNode && !node.SkipProperties.Contains(prop.Key))
node.SkipProperties.Add(prop.Key);
}

// Register placeholder - TryProvideValue will create the actual variable
// Use empty string as placeholder name since it will be replaced
variables[node] = new LocalVariable(type, string.Empty);
return;
}

var variableName = NamingHelpers.CreateUniqueVariableName(Context, type);

if (requiredPropAndFields.Any())
Expand All @@ -284,6 +314,7 @@ public static void CreateValue(ElementNode node, IndentedTextWriter writer, IDic
writer.WriteLine($"var {variableName} = new {type.ToFQDisplayString()}({string.Join(", ", parameters?.ToMethodParameters(writer, Context) ?? [])});");
variables[node] = new LocalVariable(type, variableName);
node.RegisterSourceInfo(Context, writer);

return;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
using System;
using System.IO;
using System.Linq;
using Xunit;

namespace Microsoft.Maui.Controls.SourceGen.UnitTests;

public class SetterCompiledConverters : SourceGenXamlInitializeComponentTestBase
{
[Fact]
public void SetterWithCompiledConverters_DoesNotGenerateDeadCode()
{
var xaml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.TestPage">
<ContentPage.Resources>
<ResourceDictionary>
<Style x:Key="testStyle" TargetType="Label">
<Setter Property="FontSize" Value="16" />
<Setter Property="TextColor" Value="Red" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
</ContentPage>
""";

var code =
"""
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Test;

[XamlProcessing(XamlInflator.SourceGen)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
""";

var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
var expected =
$$"""
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a .NET MAUI source generator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
#nullable enable

namespace Test;

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null", "10.0.0.0")]
public partial class TestPage
{
private partial void InitializeComponent()
{
// Fallback to Runtime inflation if the page was updated by HotReload
static string? getPathForType(global::System.Type type)
{
var assembly = type.Assembly;
foreach (var xria in global::System.Reflection.CustomAttributeExtensions.GetCustomAttributes<global::Microsoft.Maui.Controls.Xaml.XamlResourceIdAttribute>(assembly))
{
if (xria.Type == type)
return xria.Path;
}
return null;
}

var rlr = global::Microsoft.Maui.Controls.Internals.ResourceLoader.ResourceProvider2?.Invoke(new global::Microsoft.Maui.Controls.Internals.ResourceLoader.ResourceLoadingQuery
{
AssemblyName = typeof(global::Test.TestPage).Assembly.GetName(),
ResourcePath = getPathForType(typeof(global::Test.TestPage)),
Instance = this,
});

if (rlr?.ResourceContent != null)
{
this.InitializeComponentRuntime();
return;
}

var style1 = new global::Microsoft.Maui.Controls.Style(typeof(global::Microsoft.Maui.Controls.Label));
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(style1!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 5);
var resourceDictionary = new global::Microsoft.Maui.Controls.ResourceDictionary();
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(resourceDictionary!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 7, 4);
var __root = this;
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(__root!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 2, 2);
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
global::Microsoft.Maui.Controls.Internals.INameScope iNameScope = global::Microsoft.Maui.Controls.Internals.NameScope.GetNameScope(__root) ?? new global::Microsoft.Maui.Controls.Internals.NameScope();
#endif
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
global::Microsoft.Maui.Controls.Internals.NameScope.SetNameScope(__root, iNameScope);
#endif
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
global::Microsoft.Maui.Controls.Internals.INameScope iNameScope1 = new global::Microsoft.Maui.Controls.Internals.NameScope();
#endif
#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
global::Microsoft.Maui.Controls.Internals.INameScope iNameScope2 = new global::Microsoft.Maui.Controls.Internals.NameScope();
#endif
#line 7 "{{testXamlFilePath}}"
__root.Resources = (global::Microsoft.Maui.Controls.ResourceDictionary)resourceDictionary;
#line default
var setter = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.FontSizeProperty, Value = 16D};
if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter!) == null)
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 9, 6);
#line 9 "{{testXamlFilePath}}"
((global::System.Collections.Generic.ICollection<global::Microsoft.Maui.Controls.Setter>)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter);
#line default
var setter1 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.TextColorProperty, Value = global::Microsoft.Maui.Graphics.Colors.Red};
if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(setter1!) == null)
global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter1!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 10, 6);
#line 10 "{{testXamlFilePath}}"
((global::System.Collections.Generic.ICollection<global::Microsoft.Maui.Controls.Setter>)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter1);
#line default
resourceDictionary["testStyle"] = style1;
#line 7 "{{testXamlFilePath}}"
__root.Resources = (global::Microsoft.Maui.Controls.ResourceDictionary)resourceDictionary;
#line default
}
}

""";

var (result, generated) = RunGenerator(xaml, code);
Assert.False(result.Diagnostics.Any());
Assert.Equal(expected, generated, ignoreLineEndingDifferences: true);

// Explicitly verify that XamlTypeResolver is not used anywhere in the generated code
// This is critical because XamlTypeResolver is not AOT-compatible
Assert.DoesNotContain("XamlTypeResolver", generated, StringComparison.Ordinal);
}
}
Loading
Loading