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); + + 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