diff --git a/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs b/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs
index 881d418251e8..58febacb15a1 100644
--- a/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs
+++ b/src/Controls/src/Build.Tasks/CompiledConverters/BindablePropertyConverter.cs
@@ -38,7 +38,7 @@ public FieldReference GetBindablePropertyFieldReference(string value, ILContext
{
if (parent.XmlType.IsOfAnyType(nameof(Trigger), nameof(DataTrigger), nameof(MultiTrigger), nameof(Style)))
{
- typeName = GetTargetTypeName(parent);
+ typeName = GetTargetTypeFromElement(parent, node.NamespaceResolver, (IXmlLineInfo)node);
}
else if (parent.XmlType.IsOfAnyType(nameof(VisualState)))
{
@@ -47,7 +47,7 @@ public FieldReference GetBindablePropertyFieldReference(string value, ILContext
}
else if (node.Parent is ElementNode { XmlType: XmlType xt1 } && xt1.IsOfAnyType(nameof(Trigger)))
{
- typeName = GetTargetTypeName(node.Parent);
+ typeName = GetTargetTypeFromElement(node.Parent as ElementNode, node.NamespaceResolver, (IXmlLineInfo)node);
}
propertyName = parts[0];
}
@@ -71,38 +71,44 @@ public FieldReference GetBindablePropertyFieldReference(string value, ILContext
if (bpRef == null)
throw new BuildException(PropertyResolution, node, null, propertyName, typeRef.Name);
return bpRef;
-
- static XmlType GetTargetTypeName(INode node)
- {
- var targetType = ((node as ElementNode).Properties[new XmlName("", "TargetType")] as ValueNode)?.Value as string;
- return TypeArgumentsParser.ParseSingle(targetType, node.NamespaceResolver, (IXmlLineInfo)node);
- }
}
static XmlType FindTypeNameForVisualState(ElementNode parent, IXmlLineInfo lineInfo, ILContext context)
{
- //1. parent is VisualState, don't check that
+ // 1. parent is VisualState, don't check that
- //2. check that the VS is in a VSG
- // if (!(parent.Parent is IElementNode target) || target.XmlType.NamespaceUri != XamlParser.MauiUri || target.XmlType.Name != nameof(VisualStateGroup))
+ // 2. check that the VS is in a VSG
if (parent.Parent is not ElementNode target || !target.XmlType.IsOfAnyType(nameof(VisualStateGroup)))
throw new XamlParseException($"Expected {nameof(VisualStateGroup)} but found {parent.Parent}", lineInfo);
- //3. if the VSG is in a VSGL, skip that as it could be implicit
- if ( target.Parent is ListNode
+ // 3. if the VSG is in a VSGL, skip that as it could be implicit
+ if (target.Parent is ListNode
|| target.Parent is ElementNode { XmlType: XmlType xt } && xt.IsOfAnyType(nameof(VisualStateGroupList)))
target = target.Parent.Parent as ElementNode;
else
target = target.Parent as ElementNode;
- //4. target is now a Setter in a Style, or a VE
+ // 4. target is now a Setter in a Style, or a VE
if (target.XmlType.IsOfAnyType(nameof(Setter)))
- {
- var targetType = ((target?.Parent as ElementNode)?.Properties[new XmlName("", "TargetType")] as ValueNode)?.Value as string;
- return TypeArgumentsParser.ParseSingle(targetType, parent.NamespaceResolver, lineInfo);
- }
- else
- return target.XmlType;
+ return GetTargetTypeFromElement(target?.Parent as ElementNode, parent.NamespaceResolver, lineInfo);
+
+ return target.XmlType;
+ }
+
+ ///
+ /// Extracts the TargetType attribute from an element node and parses it as an XmlType.
+ /// Returns null if the element is null, has no TargetType, or TargetType is empty.
+ ///
+ static XmlType GetTargetTypeFromElement(ElementNode element, IXmlNamespaceResolver namespaceResolver, IXmlLineInfo lineInfo)
+ {
+ if (element?.Properties.TryGetValue(new XmlName("", "TargetType"), out var targetTypeNode) != true)
+ return null;
+
+ var targetType = (targetTypeNode as ValueNode)?.Value as string;
+ if (string.IsNullOrEmpty(targetType))
+ return null;
+
+ return TypeArgumentsParser.ParseSingle(targetType, namespaceResolver, lineInfo);
}
public static FieldReference GetBindablePropertyFieldReference(XamlCache cache, TypeReference typeRef, string propertyName, ModuleDefinition module)
diff --git a/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs b/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs
index fe92b6dfb78f..1b3efef54201 100644
--- a/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs
+++ b/src/Controls/src/Build.Tasks/CreateObjectVisitor.cs
@@ -20,6 +20,8 @@ class CreateObjectVisitor(ILContext context) : IXamlNodeVisitor
public bool StopOnDataTemplate => true;
public bool StopOnResourceDictionary => false;
public bool VisitNodeOnDataTemplate => false;
+ public bool StopOnStyle => false;
+ public bool VisitNodeOnStyle => true;
public bool SkipChildren(INode node, INode parentNode) => false;
public bool IsResourceDictionary(ElementNode node)
@@ -29,6 +31,8 @@ public bool IsResourceDictionary(ElementNode node)
|| parentVar.VariableType.Resolve().BaseType?.FullName == "Microsoft.Maui.Controls.ResourceDictionary";
}
+ public bool IsStyle(ElementNode node) => false;
+
public void Visit(ValueNode node, INode parentNode)
{
Context.Values[node] = node.Value;
diff --git a/src/Controls/src/Build.Tasks/ExpandMarkupsVisitor.cs b/src/Controls/src/Build.Tasks/ExpandMarkupsVisitor.cs
index d1688e47d29b..9e8c43da74cf 100644
--- a/src/Controls/src/Build.Tasks/ExpandMarkupsVisitor.cs
+++ b/src/Controls/src/Build.Tasks/ExpandMarkupsVisitor.cs
@@ -22,6 +22,8 @@ class ExpandMarkupsVisitor(ILContext context) : IXamlNodeVisitor
public bool StopOnDataTemplate => false;
public bool StopOnResourceDictionary => false;
public bool VisitNodeOnDataTemplate => true;
+ public bool StopOnStyle => false;
+ public bool VisitNodeOnStyle => true;
public bool SkipChildren(INode node, INode parentNode) => false;
public bool IsResourceDictionary(ElementNode node)
@@ -31,6 +33,8 @@ public bool IsResourceDictionary(ElementNode node)
|| parentVar.VariableType.Resolve().BaseType?.FullName == "Microsoft.Maui.Controls.ResourceDictionary";
}
+ public bool IsStyle(ElementNode node) => false;
+
public void Visit(ValueNode node, INode parentNode)
{
}
diff --git a/src/Controls/src/Build.Tasks/SetFieldVisitor.cs b/src/Controls/src/Build.Tasks/SetFieldVisitor.cs
index 114c1dae927c..3544f1848d0e 100644
--- a/src/Controls/src/Build.Tasks/SetFieldVisitor.cs
+++ b/src/Controls/src/Build.Tasks/SetFieldVisitor.cs
@@ -12,6 +12,8 @@ class SetFieldVisitor(ILContext context) : IXamlNodeVisitor
public bool StopOnDataTemplate => true;
public bool StopOnResourceDictionary => false;
public bool VisitNodeOnDataTemplate => false;
+ public bool StopOnStyle => false;
+ public bool VisitNodeOnStyle => true;
public bool SkipChildren(INode node, INode parentNode) => false;
public bool IsResourceDictionary(ElementNode node)
@@ -21,6 +23,8 @@ public bool IsResourceDictionary(ElementNode node)
|| parentVar.VariableType.Resolve().BaseType?.FullName == "Microsoft.Maui.Controls.ResourceDictionary";
}
+ public bool IsStyle(ElementNode node) => false;
+
public void Visit(ValueNode node, INode parentNode)
{
if (!IsXNameProperty(node, parentNode))
diff --git a/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs b/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs
index 6ced1a68d6d3..ac9513bbea1f 100644
--- a/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs
+++ b/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs
@@ -14,6 +14,8 @@ class SetNamescopesAndRegisterNamesVisitor(ILContext context) : IXamlNodeVisitor
public bool StopOnDataTemplate => true;
public bool StopOnResourceDictionary => false;
public bool VisitNodeOnDataTemplate => false;
+ public bool StopOnStyle => false;
+ public bool VisitNodeOnStyle => true;
public bool SkipChildren(INode node, INode parentNode) => false;
public bool IsResourceDictionary(ElementNode node)
@@ -23,6 +25,8 @@ public bool IsResourceDictionary(ElementNode node)
|| parentVar.VariableType.Resolve().BaseType?.FullName == "Microsoft.Maui.Controls.ResourceDictionary";
}
+ public bool IsStyle(ElementNode node) => false;
+
public void Visit(ValueNode node, INode parentNode)
{
Context.Scopes[node] = Context.Scopes[parentNode];
diff --git a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
index 7b49f0485d0d..bc69bc35083e 100644
--- a/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
+++ b/src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
@@ -32,6 +32,8 @@ class SetPropertiesVisitor(ILContext context, bool stopOnResourceDictionary = fa
public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
public bool StopOnDataTemplate => true;
public bool VisitNodeOnDataTemplate => true;
+ public bool StopOnStyle => false;
+ public bool VisitNodeOnStyle => true;
public bool SkipChildren(INode node, INode parentNode) => false;
public bool IsResourceDictionary(ElementNode node)
@@ -41,6 +43,8 @@ public bool IsResourceDictionary(ElementNode node)
|| parentVar.VariableType.Resolve().BaseType?.FullName == "Microsoft.Maui.Controls.ResourceDictionary";
}
+ public bool IsStyle(ElementNode node) => false;
+
ModuleDefinition Module { get; } = context.Body.Method.Module;
// Track properties that have been set to detect duplicates
diff --git a/src/Controls/src/Build.Tasks/SetResourcesVisitor.cs b/src/Controls/src/Build.Tasks/SetResourcesVisitor.cs
index 0d9758c83f11..f9a256764747 100644
--- a/src/Controls/src/Build.Tasks/SetResourcesVisitor.cs
+++ b/src/Controls/src/Build.Tasks/SetResourcesVisitor.cs
@@ -12,6 +12,10 @@ class SetResourcesVisitor(ILContext context) : IXamlNodeVisitor
public bool StopOnDataTemplate => true;
public bool StopOnResourceDictionary => false;
public bool VisitNodeOnDataTemplate => false;
+ public bool StopOnStyle => false;
+ public bool VisitNodeOnStyle => true;
+
+ public bool IsStyle(ElementNode node) => false;
public void Visit(ValueNode node, INode parentNode)
{
diff --git a/src/Controls/src/Core/IStyle.cs b/src/Controls/src/Core/IStyle.cs
index 05d45756545b..7c2c8b1a3d66 100644
--- a/src/Controls/src/Core/IStyle.cs
+++ b/src/Controls/src/Core/IStyle.cs
@@ -1,11 +1,13 @@
-#nullable disable
using System;
namespace Microsoft.Maui.Controls
{
interface IStyle
{
- Type TargetType { get; }
+ ///
+ /// Gets the target type for this style. May return null for lazy styles if the type was trimmed.
+ ///
+ Type? TargetType { get; }
void Apply(BindableObject bindable, SetterSpecificity specificity);
void UnApply(BindableObject bindable);
diff --git a/src/Controls/src/Core/MergedStyle.cs b/src/Controls/src/Core/MergedStyle.cs
index 179315528749..c6449bbd6c64 100644
--- a/src/Controls/src/Core/MergedStyle.cs
+++ b/src/Controls/src/Core/MergedStyle.cs
@@ -30,10 +30,12 @@ sealed class MergedStyle : IStyle
IList _styleClass;
+ readonly Type _targetType;
+
public MergedStyle(Type targetType, BindableObject target)
{
Target = target;
- TargetType = targetType;
+ _targetType = targetType;
// RegisterImplicitStyles handles the initial apply via OnImplicitStyleChanged -> SetStyle.
// An explicit Apply(Target) call here would double-attach event handlers when
// Application.Current.Resources already contains the implicit style (#24152).
@@ -47,8 +49,10 @@ public IStyle Style
{
if (_style == value)
return;
- if (value != null && !value.TargetType.IsAssignableFrom(TargetType))
- MauiLogger
-
-
+xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
+xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+x:Class="Test.TestPage">
+
+
+
+
+
""";
- var code =
+var code =
"""
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;
@@ -38,113 +38,31 @@ namespace Test;
[XamlProcessing(XamlInflator.SourceGen)]
public partial class TestPage : ContentPage
{
- public TestPage()
- {
- InitializeComponent();
- }
+public TestPage()
+{
+InitializeComponent();
+}
}
""";
- var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
- var expected =
-$$"""
-//------------------------------------------------------------------------------
-//
-// 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.
-//
-//------------------------------------------------------------------------------
-#nullable enable
-#pragma warning disable CS0219 // Variable is assigned but its value is never used
+var (result, generated) = RunGenerator(xaml, code);
+Assert.False(result.Diagnostics.Any());
-namespace Test;
+// Verify trimmable style constructor is used (not typeof)
+Assert.Contains("new global::Microsoft.Maui.Controls.Style(\"Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls\")", generated, StringComparison.Ordinal);
-[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=11.0.0.0, Culture=neutral, PublicKeyToken=null", "11.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(assembly))
- {
- if (xria.Type == type)
- return xria.Path;
- }
- return null;
- }
+// Verify setters are generated with compiled property references
+Assert.Contains("Label.FontSizeProperty", generated, StringComparison.Ordinal);
+Assert.Contains("Label.TextColorProperty", generated, StringComparison.Ordinal);
+Assert.Contains("16D", generated, StringComparison.Ordinal);
+Assert.Contains("Colors.Red", generated, StringComparison.Ordinal);
- 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,
- });
+// Verify lazy initialization pattern for trimmable styles
+Assert.Contains("LazyInitialization = (__style, __target) =>", generated, StringComparison.Ordinal);
+Assert.Contains("__style.Setters", generated, StringComparison.Ordinal);
- if (rlr?.ResourceContent != null)
- {
- this.InitializeComponentRuntime();
- return;
- }
-
- 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
-#line 7 "{{testXamlFilePath}}"
- __root.Resources = (global::Microsoft.Maui.Controls.ResourceDictionary)resourceDictionary;
-#line default
- resourceDictionary.AddFactory("testStyle", () =>
- {
- 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);
-#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
-#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
- global::Microsoft.Maui.Controls.Internals.INameScope iNameScope3 = new global::Microsoft.Maui.Controls.Internals.NameScope();
-#endif
- 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)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)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter1);
-#line default
- return style1;
- }, shared: true);
-#line 7 "{{testXamlFilePath}}"
- __root.Resources = (global::Microsoft.Maui.Controls.ResourceDictionary)resourceDictionary;
-#line default
- }
+// 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);
}
-
-""";
-
- 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);
- }
}
diff --git a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
index b0de84267be5..e4c39143a335 100644
--- a/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/InitializeComponent/SimplifyOnPlatform.cs
@@ -1,5 +1,4 @@
using System;
-using System.IO;
using System.Linq;
using Xunit;
@@ -42,97 +41,21 @@ public TestPage()
InitializeComponent();
}
}
-""";
-
- var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
- var expected = $$"""
-//------------------------------------------------------------------------------
-//
-// 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.
-//
-//------------------------------------------------------------------------------
-#nullable enable
-#pragma warning disable CS0219 // Variable is assigned but its value is never used
-
-namespace Test;
-
-[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=11.0.0.0, Culture=neutral, PublicKeyToken=null", "11.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(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 __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
- __root.Resources.AddFactory("style", () =>
- {
- 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), 7, 10);
-#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
-#if !_MAUIXAML_SG_NAMESCOPE_DISABLE
- global::Microsoft.Maui.Controls.Internals.INameScope iNameScope3 = new global::Microsoft.Maui.Controls.Internals.NameScope();
-#endif
- var setter = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.Label.TextColorProperty, Value = global::Microsoft.Maui.Graphics.Colors.Pink};
- 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), 8, 14);
-#line 8 "{{testXamlFilePath}}"
- ((global::System.Collections.Generic.ICollection)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter);
-#line default
- var setter1 = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.VisualElement.IsVisibleProperty, Value = (bool)new global::Microsoft.Maui.Controls.VisualElement.VisibilityConverter().ConvertFromInvariantString("True")!};
- 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), 9, 14);
-#line 9 "{{testXamlFilePath}}"
- ((global::System.Collections.Generic.ICollection)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter1);
-#line default
- return style1;
- }, shared: true);
- }
-}
-
""";
var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0-android");
Assert.False(result.Diagnostics.Any());
- Assert.Equal(expected, generated, ignoreLineEndingDifferences: true);
+ // Verify trimmable style constructor is used
+ Assert.Contains("new global::Microsoft.Maui.Controls.Style(\"Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls\")", generated, StringComparison.Ordinal);
+
+ // Verify setters with platform-simplified values
+ Assert.Contains("Label.TextColorProperty", generated, StringComparison.Ordinal);
+ Assert.Contains("Colors.Pink", generated, StringComparison.Ordinal);
+ Assert.Contains("VisualElement.IsVisibleProperty", generated, StringComparison.Ordinal);
+
+ // Verify lazy initialization pattern
+ Assert.Contains("LazyInitialization = (__style, __target) =>", generated, StringComparison.Ordinal);
}
[Fact]
diff --git a/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs b/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs
index dc3669328cd0..634bce0bbe33 100644
--- a/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs
+++ b/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs
@@ -1,5 +1,4 @@
using System;
-using System.IO;
using System.Linq;
using Xunit;
@@ -48,93 +47,19 @@ public TestPage()
}
""";
- var testXamlFilePath = Path.Combine(Environment.CurrentDirectory, "Test.xaml");
- var expected =
-$$"""
-//------------------------------------------------------------------------------
-//
-// 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.
-//
-//------------------------------------------------------------------------------
-#nullable enable
-#pragma warning disable CS0219 // Variable is assigned but its value is never used
-
-namespace Test;
-
-[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=11.0.0.0, Culture=neutral, PublicKeyToken=null", "11.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(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 (result, generated) = RunGenerator(xaml, code);
+ Assert.False(result.Diagnostics.Any());
- 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
-#line 7 "{{testXamlFilePath}}"
- __root.Resources = (global::Microsoft.Maui.Controls.ResourceDictionary)resourceDictionary;
-#line default
- resourceDictionary.AddFactory("NetworkIndicator", () =>
- {
- var style1 = new global::Microsoft.Maui.Controls.Style(typeof(global::Microsoft.Maui.Controls.Image));
- global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(style1!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 5);
-#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
- var setter = new global::Microsoft.Maui.Controls.Setter {Property = global::Microsoft.Maui.Controls.AbsoluteLayout.LayoutBoundsProperty, Value = (global::Microsoft.Maui.Graphics.Rect)new global::Microsoft.Maui.Controls.BoundsTypeConverter().ConvertFromInvariantString("10,10,20,20")!};
- 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)style1.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter);
-#line default
- return style1;
- }, shared: true);
-#line 7 "{{testXamlFilePath}}"
- __root.Resources = (global::Microsoft.Maui.Controls.ResourceDictionary)resourceDictionary;
-#line default
- }
-}
+ // Verify trimmable style constructor is used for Image TargetType
+ Assert.Contains("new global::Microsoft.Maui.Controls.Style(\"Microsoft.Maui.Controls.Image, Microsoft.Maui.Controls\")", generated, StringComparison.Ordinal);
-""";
+ // Verify setter with attached property LayoutBounds and content value syntax
+ Assert.Contains("AbsoluteLayout.LayoutBoundsProperty", generated, StringComparison.Ordinal);
+ Assert.Contains("10,10,20,20", generated, StringComparison.Ordinal);
+ Assert.Contains("BoundsTypeConverter", generated, StringComparison.Ordinal);
- var (result, generated) = RunGenerator(xaml, code);
- Assert.False(result.Diagnostics.Any());
- Assert.Equal(expected, generated, ignoreLineEndingDifferences: true);
+ // Verify lazy initialization pattern
+ Assert.Contains("LazyInitialization = (__style, __target) =>", generated, StringComparison.Ordinal);
+ Assert.Contains("__style.Setters", generated, StringComparison.Ordinal);
}
}
diff --git a/src/Controls/tests/SourceGen.UnitTests/StyleSourceGenTests.cs b/src/Controls/tests/SourceGen.UnitTests/StyleSourceGenTests.cs
new file mode 100644
index 000000000000..0ca3af2174bd
--- /dev/null
+++ b/src/Controls/tests/SourceGen.UnitTests/StyleSourceGenTests.cs
@@ -0,0 +1,351 @@
+using System;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.Maui.Controls.SourceGen;
+using Xunit;
+
+using static Microsoft.Maui.Controls.Xaml.UnitTests.SourceGen.SourceGeneratorDriver;
+
+namespace Microsoft.Maui.Controls.Xaml.UnitTests.SourceGen;
+
+public class StyleSourceGenTests : SourceGenTestsBase
+{
+ private record AdditionalXamlFile(string Path, string Content, string? RelativePath = null, string? TargetPath = null, string? ManifestResourceName = null, string? TargetFramework = null, string? NoWarn = null)
+ : AdditionalFile(Text: SourceGeneratorDriver.ToAdditionalText(Path, Content), Kind: "Xaml", RelativePath: RelativePath ?? Path, TargetPath: TargetPath, ManifestResourceName: ManifestResourceName, TargetFramework: TargetFramework, NoWarn: NoWarn);
+
+static string Normalize(string text)
+{
+ var normalized = text.Replace("\r\n", "\n", System.StringComparison.Ordinal);
+ var lines = normalized.Split('\n');
+ var nonEmptyLines = lines.Where(line => line.Trim().Length > 0);
+ return string.Join("\n", nonEmptyLines).Trim('\n');
+}
+
+static string GetGeneratedCode(GeneratorDriverRunResult result)
+{
+ var tree = result.GeneratedTrees
+ .FirstOrDefault(t => t.FilePath.EndsWith(".xsg.cs", System.StringComparison.Ordinal));
+ Assert.NotNull(tree);
+ return Normalize(tree.GetText().ToString());
+}
+
+static void AssertSnapshot(string expected, string actual)
+{
+ if (!string.Equals(expected, actual, System.StringComparison.Ordinal))
+ {
+ var index = 0;
+ for (; index < expected.Length && index < actual.Length; index++)
+ {
+ if (expected[index] != actual[index])
+ break;
+ }
+ System.Console.WriteLine($"Snapshot diff at {index}");
+ System.Console.WriteLine($"Expected snippet: {EscapeSnippet(expected, index)}");
+ System.Console.WriteLine($"Actual snippet: {EscapeSnippet(actual, index)}");
+ System.Console.WriteLine($"Expected length: {expected.Length}");
+ System.Console.WriteLine($"Actual length: {actual.Length}");
+ }
+ Assert.Equal(expected, actual);
+}
+
+static string EscapeSnippet(string text, int index)
+{
+ var length = System.Math.Min(120, text.Length - index);
+ var snippet = text.Substring(index, length);
+ return snippet.Replace("\n", "\\n", System.StringComparison.Ordinal);
+}
+
+ [Fact]
+ public void SimpleStyleWithSetter()
+ {
+ var xaml =
+"""
+
+
+
+
+
+
+""";
+
+ var compilation = CreateMauiCompilation();
+ var result = RunGenerator(compilation, new AdditionalXamlFile("Test.xaml", xaml));
+
+ // Check for errors after output
+ var errors = result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();
+ Assert.Empty(errors);
+
+ // Find the actual generated code (the .xsg.cs file)
+ var generatedCode = GetGeneratedCode(result);
+ Assert.Contains("new global::Microsoft.Maui.Controls.Style(\"Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls\")", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("Label.TextColorProperty", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("style.LazyInitialization = (__style, __target) =>", generatedCode, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void StyleWithMultipleSetters()
+ {
+ var xaml =
+"""
+
+
+
+
+
+
+""";
+
+ var compilation = CreateMauiCompilation();
+ var result = RunGenerator(compilation, new AdditionalXamlFile("Test.xaml", xaml));
+
+ var errors = result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();
+ Assert.Empty(errors);
+
+ var generatedCode = GetGeneratedCode(result);
+ Assert.Contains("new global::Microsoft.Maui.Controls.Style(\"Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls\")", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("Label.TextColorProperty", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("Label.FontSizeProperty", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("Label.FontAttributesProperty", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("style.LazyInitialization = (__style, __target) =>", generatedCode, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void StyleWithoutSetters()
+ {
+ var xaml =
+"""
+
+
+
+
+
+
+""";
+
+ var compilation = CreateMauiCompilation();
+ var result = RunGenerator(compilation, new AdditionalXamlFile("Test.xaml", xaml));
+
+ var errors = result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();
+ Assert.Empty(errors);
+
+ // Find the actual generated code (the .xsg.cs file)
+ var generatedCode = GetGeneratedCode(result);
+ var expected = Normalize("""
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+#nullable enable
+#pragma warning disable CS0219 // Variable is assigned but its value is never used
+namespace Test;
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=11.0.0.0, Culture=neutral, PublicKeyToken=null", "11.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(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 __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
+ var style = new global::Microsoft.Maui.Controls.Style("Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls");
+ global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(style!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 7, 4);
+ __root.Resources["EmptyStyle"] = style;
+ }
+}
+""");
+ AssertSnapshot(expected, generatedCode);
+ }
+
+ [Fact]
+ public void StyleInitializerIsSetBeforeStyleIsAppliedToElement()
+ {
+ // This test verifies that when a Style with Setters is applied to an element,
+ // the Initializer is assigned BEFORE the SetValue(StyleProperty, style) call.
+ // This is critical because IStyle.Apply runs the lazy initializer which needs
+ // to be set, otherwise Setters won't be populated.
+ var xaml =
+"""
+
+
+
+
+""";
+
+ var compilation = CreateMauiCompilation();
+ var result = RunGenerator(compilation, new AdditionalXamlFile("Test.xaml", xaml));
+
+ var errors = result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();
+ Assert.Empty(errors);
+
+ var generatedCode = GetGeneratedCode(result);
+
+ // Verify key elements exist
+ Assert.Contains("new global::Microsoft.Maui.Controls.Style(\"Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls\")", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("style.LazyInitialization = (__style, __target) =>", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("label.SetValue(global::Microsoft.Maui.Controls.VisualElement.StyleProperty, style)", generatedCode, StringComparison.Ordinal);
+ Assert.Contains("Label.TextColorProperty", generatedCode, StringComparison.Ordinal);
+
+ // CRITICAL: Verify the ORDER - Initializer must be set BEFORE SetValue(StyleProperty)
+ var initializerSetIndex = generatedCode.IndexOf("style.LazyInitialization = (__style, __target) =>", StringComparison.Ordinal);
+ var setValueIndex = generatedCode.IndexOf("label.SetValue(global::Microsoft.Maui.Controls.VisualElement.StyleProperty, style)", StringComparison.Ordinal);
+
+ Assert.True(initializerSetIndex >= 0, "style.LazyInitialization assignment not found in generated code");
+ Assert.True(setValueIndex >= 0, "label.SetValue(StyleProperty) not found in generated code");
+ Assert.True(initializerSetIndex < setValueIndex,
+ $"style.LazyInitialization must be set BEFORE label.SetValue(StyleProperty, style).\n" +
+ $"Initializer set at index {initializerSetIndex}, SetValue at index {setValueIndex}.\n" +
+ $"Generated code:\n{generatedCode}");
+ }
+
+ [Fact]
+ public void SimpleStyleWithSetterFullSnapshot()
+ {
+ // Full snapshot test to verify the complete lazy style pattern
+ var xaml =
+"""
+
+
+
+
+
+
+""";
+
+ var compilation = CreateMauiCompilation();
+ var result = RunGenerator(compilation, new AdditionalXamlFile("Test.xaml", xaml));
+
+ var errors = result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToList();
+ Assert.Empty(errors);
+
+ var generatedCode = GetGeneratedCode(result);
+ var expected = Normalize("""
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+#nullable enable
+#pragma warning disable CS0219 // Variable is assigned but its value is never used
+namespace Test;
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=11.0.0.0, Culture=neutral, PublicKeyToken=null", "11.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(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 __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
+ var style = new global::Microsoft.Maui.Controls.Style("Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls");
+ global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(style!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 7, 4);
+ style.LazyInitialization = (__style, __target) =>
+ {
+ if (__target is not global::Microsoft.Maui.Controls.Label) return;
+#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
+ var setter = 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(setter!) == null)
+ global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(setter!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 5);
+#line 8 "Test.xaml"
+ ((global::System.Collections.Generic.ICollection)__style.Setters).Add((global::Microsoft.Maui.Controls.Setter)setter);
+#line default
+ };
+ __root.Resources["TestStyle"] = style;
+ }
+}
+""");
+ AssertSnapshot(expected, generatedCode);
+ }
+}
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Bz51567.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Bz51567.xaml.cs
index 1cae55d9634a..36d93995bc10 100644
--- a/src/Controls/tests/Xaml.UnitTests/Issues/Bz51567.xaml.cs
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Bz51567.xaml.cs
@@ -18,6 +18,11 @@ internal void SetterWithElementValue(XamlInflator inflator)
{
var page = new Bz51567(inflator);
var style = page.Resources["ListText"] as Style;
+
+ // For SourceGen, styles are lazy - force initialization before inspecting Setters
+ if (inflator == XamlInflator.SourceGen)
+ style.ForceInitialize(new Label());
+
var setter = style.Setters[1];
Assert.NotNull(setter);
}
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui21757.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui21757.xaml.cs
index 0a50fd9e45cc..a0d05f0a9639 100644
--- a/src/Controls/tests/Xaml.UnitTests/Issues/Maui21757.xaml.cs
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui21757.xaml.cs
@@ -36,12 +36,22 @@ internal void TypeLiteralAndXTypeCanBeUsedInterchangeably(XamlInflator inflator)
var styleA = resourceDictionary["A"] as Style;
Assert.NotNull(styleA);
+
+ // For SourceGen, styles are lazy - force initialization before inspecting Setters
+ if (inflator == XamlInflator.SourceGen)
+ styleA.ForceInitialize(new BoxView());
+
Assert.Equal(typeof(BoxView), styleA.TargetType);
Assert.Equal(BoxView.ColorProperty, styleA.Setters[0].Property);
Assert.Equal(Color.FromArgb("#C8C8C8"), styleA.Setters[0].Value);
var styleB = resourceDictionary["B"] as Style;
Assert.NotNull(styleB);
+
+ // For SourceGen, styles are lazy - force initialization before inspecting Setters
+ if (inflator == XamlInflator.SourceGen)
+ styleB.ForceInitialize(new BoxView());
+
Assert.Equal(typeof(BoxView), styleB.TargetType);
Assert.Equal(BoxView.ColorProperty, styleB.Setters[0].Property);
Assert.Equal(Color.FromArgb("#C8C8C8"), styleB.Setters[0].Value);
diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported009.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported009.xaml.cs
index 15be4a93661d..1946bfa91b5a 100644
--- a/src/Controls/tests/Xaml.UnitTests/Issues/Unreported009.xaml.cs
+++ b/src/Controls/tests/Xaml.UnitTests/Issues/Unreported009.xaml.cs
@@ -15,6 +15,11 @@ internal void AllowSetterValueAsElementProperties(XamlInflator inflator)
{
var p = new Unreported009(inflator);
var s = p.Resources["Default"] as Style;
+
+ // For SourceGen, styles are lazy - force initialization before inspecting Setters
+ if (inflator == XamlInflator.SourceGen)
+ s.ForceInitialize(new ContentView());
+
Assert.Equal("Bananas!", (s.Setters[0].Value as Label).Text);
}
}
diff --git a/src/Controls/tests/Xaml.UnitTests/LazyStyleTestHelper.cs b/src/Controls/tests/Xaml.UnitTests/LazyStyleTestHelper.cs
new file mode 100644
index 000000000000..ac8faca20e64
--- /dev/null
+++ b/src/Controls/tests/Xaml.UnitTests/LazyStyleTestHelper.cs
@@ -0,0 +1,23 @@
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+///
+/// Helper for lazy style tests. Forces initialization of a source-gen lazy style
+/// by running its LazyInitialization callback without applying the style.
+///
+static class LazyStyleTestHelper
+{
+ ///
+ /// Forces a lazy style to initialize by invoking its LazyInitialization callback.
+ /// Use this when tests need to inspect Setters before the style is naturally applied.
+ /// Unlike IStyle.Apply, this does not apply setters to the target — it only populates
+ /// the style's Setters collection.
+ ///
+ internal static void ForceInitialize(this Style style, BindableObject target)
+ {
+ if (style.LazyInitialization is not null)
+ {
+ style.LazyInitialization(style, target);
+ style.LazyInitialization = null;
+ }
+ }
+}
diff --git a/src/Controls/tests/Xaml.UnitTests/StyleTests.xaml.cs b/src/Controls/tests/Xaml.UnitTests/StyleTests.xaml.cs
index 0230af0f9aa9..70b882bf58e4 100644
--- a/src/Controls/tests/Xaml.UnitTests/StyleTests.xaml.cs
+++ b/src/Controls/tests/Xaml.UnitTests/StyleTests.xaml.cs
@@ -33,6 +33,11 @@ internal void TestConversionOnSetters(XamlInflator inflator)
{
var layout = new StyleTests(inflator);
Style style = layout.style1;
+
+ // For SourceGen, styles are lazy - force initialization before inspecting Setters
+ if (inflator == XamlInflator.SourceGen)
+ style.ForceInitialize(new Label());
+
Setter setter;
//Test built-in conversions
@@ -65,6 +70,11 @@ internal void PropertyDoesNotNeedTypes(XamlInflator inflator)
{
var layout = new StyleTests(inflator);
Style style2 = layout.style2;
+
+ // For SourceGen, styles are lazy - force initialization before inspecting Setters
+ if (inflator == XamlInflator.SourceGen)
+ style2.ForceInitialize(new Label());
+
var s0 = style2.Setters[0];
var s1 = style2.Setters[1];
Assert.Equal(Label.TextProperty, s0.Property);
@@ -120,7 +130,8 @@ public partial class StyleTests : ContentPage
.RunMauiSourceGenerator(typeof(StyleTests));
Assert.False(result.Diagnostics.Any());
var initComp = result.GeneratedInitializeComponent();
- Assert.Contains("new global::Microsoft.Maui.Controls.Style(typeof(global::Microsoft.Maui.Controls.Label))", initComp, StringComparison.InvariantCulture);
+ // Trimmable styles use string-based constructor for AOT compatibility
+ Assert.Contains("new global::Microsoft.Maui.Controls.Style(\"Microsoft.Maui.Controls.Label, Microsoft.Maui.Controls\")", initComp, StringComparison.InvariantCulture);
}
}
}
\ No newline at end of file