diff --git a/src/Controls/src/SourceGen/KnownMarkups.cs b/src/Controls/src/SourceGen/KnownMarkups.cs index a706bcb34d60..6974f988ae2b 100644 --- a/src/Controls/src/SourceGen/KnownMarkups.cs +++ b/src/Controls/src/SourceGen/KnownMarkups.cs @@ -772,6 +772,13 @@ internal static bool ProvideValueForStaticResourceExtension(ElementNode node, In return false; } + if (variable is ILocalValue localVar && !localVar.Type.Equals(context.Compilation.GetTypeByMetadataName("System.String")!, SymbolEqualityComparer.Default)) + { + returnType = localVar.Type; + value = localVar.ValueAccessor; + return true; + } + //if the resource is a string, try to convert it if (resource.CollectionItems.Count == 1 && resource.CollectionItems[0] is ValueNode vn && vn.Value is string) { diff --git a/src/Controls/tests/SourceGen.UnitTests/StaticResourceInMarkup.cs b/src/Controls/tests/SourceGen.UnitTests/StaticResourceInMarkup.cs new file mode 100644 index 000000000000..294ffa9101fb --- /dev/null +++ b/src/Controls/tests/SourceGen.UnitTests/StaticResourceInMarkup.cs @@ -0,0 +1,190 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Maui.Controls.SourceGen; +using Xunit; + +using static Microsoft.Maui.Controls.Xaml.UnitTests.SourceGen.SourceGeneratorDriver; + +namespace Microsoft.Maui.Controls.SourceGen.UnitTests; + +public class StaticResourceInMarkup : SourceGenXamlInitializeComponentTestBase +{ + + [Fact] + public void ColorResourceAsStaticResourceInMarkupExtension_ShouldNotProduceCS0030Error() + { + // Test reproducing issue #32836: Color resource defined in XAML should be treated as Color, not string + // when used with StaticResource inside a markup extension + // + // This test validates that nested StaticResource in markup extensions generates correct code + + var xaml = +""" + + + + #00FF00 + + + +"""; + + var code = +""" +using System; +using Microsoft.Maui.Controls; +using Microsoft.Maui.Controls.Xaml; +using Microsoft.Maui.Graphics; + +namespace TestApp +{ + public partial class TestPage : ContentPage + { + public TestPage() + { + InitializeComponent(); + } + } + + public class MyExtension : IMarkupExtension + { + public Color Source { get; set; } + + public Color ProvideValue(IServiceProvider serviceProvider) + { + return Source; + } + + object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) + { + return (this as IMarkupExtension).ProvideValue(serviceProvider); + } + } +} +"""; + + 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 + +namespace TestApp; + +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Maui.Controls.SourceGen, Version=10.0.0.0, Culture=neutral, PublicKeyToken=null", "10.0.0.0")] +public partial class TestPage +{ + private partial void InitializeComponent() + { + // Fallback to Runtime inflation if the page was updated by HotReload + static string? getPathForType(global::System.Type type) + { + var assembly = type.Assembly; + foreach (var xria in global::System.Reflection.CustomAttributeExtensions.GetCustomAttributes(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::TestApp.TestPage).Assembly.GetName(), + ResourcePath = getPathForType(typeof(global::TestApp.TestPage)), + Instance = this, + }); + + if (rlr?.ResourceContent != null) + { + this.InitializeComponentRuntime(); + return; + } + + var color = new global::Microsoft.Maui.Graphics.Color(0f, 1f, 0f, 1f) /* #00FF00 */; + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(color!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 8, 4); + var staticResourceExtension = new global::Microsoft.Maui.Controls.Xaml.StaticResourceExtension(); + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(staticResourceExtension!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 11, 9); + var myExtension = new global::TestApp.MyExtension(); + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(myExtension!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 11, 9); + var label = new global::Microsoft.Maui.Controls.Label(); + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(label!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 11, 3); + var __root = this; + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(__root!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 2, 2); +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + global::Microsoft.Maui.Controls.Internals.INameScope iNameScope = global::Microsoft.Maui.Controls.Internals.NameScope.GetNameScope(__root) ?? new global::Microsoft.Maui.Controls.Internals.NameScope(); +#endif +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + global::Microsoft.Maui.Controls.Internals.NameScope.SetNameScope(__root, iNameScope); +#endif +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + label.transientNamescope = iNameScope; +#endif + __root.Resources["MyColor"] = color; +#line 11 "{{testXamlFilePath}}" + staticResourceExtension.Key = "MyColor"; +#line default + var color1 = color; + if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(color1!) == null) + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(color1!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 11, 9); +#line 11 "{{testXamlFilePath}}" + myExtension.Source = (global::Microsoft.Maui.Graphics.Color)color1; +#line default + var xamlServiceProvider = new global::Microsoft.Maui.Controls.Xaml.Internals.XamlServiceProvider(__root); + var iProvideValueTarget = new global::Microsoft.Maui.Controls.Xaml.Internals.SimpleValueTargetProvider( + new object?[] {label, __root}, + global::Microsoft.Maui.Controls.Label.TextColorProperty, +#if !_MAUIXAML_SG_NAMESCOPE_DISABLE + new [] { iNameScope }, +#else + null, +#endif + __root); + xamlServiceProvider.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IReferenceProvider), iProvideValueTarget); + xamlServiceProvider.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IProvideValueTarget), iProvideValueTarget); + var xmlNamespaceResolver = new global::Microsoft.Maui.Controls.Xaml.Internals.XmlNamespaceResolver(); + xmlNamespaceResolver.Add("__f__", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver.Add("__g__", "http://schemas.microsoft.com/dotnet/maui/global"); + xmlNamespaceResolver.Add("", "http://schemas.microsoft.com/dotnet/2021/maui"); + xmlNamespaceResolver.Add("x", "http://schemas.microsoft.com/winfx/2009/xaml"); + xmlNamespaceResolver.Add("local", "clr-namespace:TestApp"); + xamlServiceProvider.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXamlTypeResolver), new global::Microsoft.Maui.Controls.Xaml.Internals.XamlTypeResolver(xmlNamespaceResolver, typeof(global::TestApp.TestPage).Assembly)); + xamlServiceProvider.Add(typeof(global::Microsoft.Maui.Controls.Xaml.IXmlLineInfoProvider), new global::Microsoft.Maui.Controls.Xaml.Internals.XmlLineInfoProvider(new global::Microsoft.Maui.Controls.Xaml.XmlLineInfo(11, 9))); + var color2 = (global::Microsoft.Maui.Graphics.Color)((global::Microsoft.Maui.Controls.Xaml.IMarkupExtension)myExtension).ProvideValue(xamlServiceProvider); + if (global::Microsoft.Maui.VisualDiagnostics.GetSourceInfo(color2!) == null) + global::Microsoft.Maui.VisualDiagnostics.RegisterSourceInfo(color2!, new global::System.Uri(@"Test.xaml;assembly=SourceGeneratorDriver.Generated", global::System.UriKind.Relative), 11, 9); +#line 11 "{{testXamlFilePath}}" + label.SetValue(global::Microsoft.Maui.Controls.Label.TextColorProperty, color2); +#line default +#line 11 "{{testXamlFilePath}}" + __root.SetValue(global::Microsoft.Maui.Controls.ContentPage.ContentProperty, label); +#line default + } +} + +"""; + + var (result, generated) = RunGenerator(xaml, code); + Assert.False(result.Diagnostics.Any()); + Assert.Equal(expected, generated, ignoreLineEndingDifferences: true); + + } + + +} diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui32837.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui32837.xaml new file mode 100644 index 000000000000..fef318e6859f --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui32837.xaml @@ -0,0 +1,22 @@ + + + + 16 + + + + + + + + + + + + diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui32837.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui32837.xaml.cs new file mode 100644 index 000000000000..9a278e188061 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui32837.xaml.cs @@ -0,0 +1,82 @@ +using System; +using System.Globalization; +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Controls.Core.UnitTests; +using Microsoft.Maui.Controls.Shapes; +using Microsoft.Maui.Dispatching; +using Microsoft.Maui.UnitTests; +using NUnit.Framework; + +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public partial class Maui32837 : Application +{ + public Maui32837() => InitializeComponent(); + + class Test + { + [SetUp] + public void Setup() + { + Application.SetCurrentApplication(new MockApplication()); + DispatcherProvider.SetCurrent(new DispatcherProviderStub()); + } + + [TearDown] + public void TearDown() + { + AppInfo.SetCurrent(null); + Application.SetCurrentApplication(null); + } + + [Test] + public void ConverterReceivesCorrectValueFromStaticResource([Values] XamlInflator inflator) + { + var app = new Maui32837(inflator); + + // Get the converter from resources + var converter = app.Resources["IntToCornerRadiusConverter"] as Maui32837IntToCornerRadiusConverter; + Assert.IsNotNull(converter, "Converter should not be null"); + + // Get the RoundRectangle from resources + var roundRect = app.Resources["MyRoundRectangle"] as RoundRectangle; + Assert.IsNotNull(roundRect, "RoundRectangle should not be null"); + + // The binding should have been evaluated and converter should have been called + // Check that the converter was actually invoked by looking at the result + var cornerRadius = roundRect.CornerRadius; + + // The converter should have converted the int value 16 to CornerRadius(16) + Assert.That(cornerRadius.TopLeft, Is.EqualTo(16.0), + $"TopLeft corner radius should be 16.0 for {inflator}, but was {cornerRadius.TopLeft}"); + Assert.That(cornerRadius.TopRight, Is.EqualTo(16.0), + $"TopRight corner radius should be 16.0 for {inflator}, but was {cornerRadius.TopRight}"); + Assert.That(cornerRadius.BottomLeft, Is.EqualTo(16.0), + $"BottomLeft corner radius should be 16.0 for {inflator}, but was {cornerRadius.BottomLeft}"); + Assert.That(cornerRadius.BottomRight, Is.EqualTo(16.0), + $"BottomRight corner radius should be 16.0 for {inflator}, but was {cornerRadius.BottomRight}"); + } + } +} + +#nullable enable +public class Maui32837IntToCornerRadiusConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is int radius) + { + return new CornerRadius(radius); + } + return new CornerRadius(0); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is CornerRadius cornerRadius) + { + return (int)cornerRadius.TopLeft; + } + return 0; + } +}