From 7719216e509da5d9cb3498378ff106fdd6b82d1e Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 28 Aug 2024 09:28:44 +0300 Subject: [PATCH] fix: Properly resolve interface members in x:Bind --- .../Helpers/SymbolExtensions.cs | 36 +++- .../XamlCodeGeneratorTests/Given_Binding.cs | 85 ++++++++ ...XamlCodeGenerator_GlobalStaticResources.cs | 55 +++++ ...XamlCodeGenerator_LocalizationResources.cs | 2 + ...inPage_d6cd66944958ced0c513e0a04797b51d.cs | 193 ++++++++++++++++++ .../XamlGenerator/XamlFileGenerator.cs | 3 +- 6 files changed, 363 insertions(+), 11 deletions(-) create mode 100644 src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_GlobalStaticResources.cs create mode 100644 src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_LocalizationResources.cs create mode 100644 src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs diff --git a/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs b/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs index f69acdb9998b..fcd38071e58c 100644 --- a/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs +++ b/src/SourceGenerators/SourceGeneratorHelpers/Helpers/SymbolExtensions.cs @@ -130,24 +130,42 @@ public static string GetDeclarationHeaderFromNamedTypeSymbol(this INamedTypeSymb public static IEnumerable GetAllMembersWithName(this ITypeSymbol? symbol, string name) { - do + if (symbol != null) { - if (symbol != null) + foreach (var member in symbol.GetMembers(name)) { - foreach (var member in symbol.GetMembers(name)) + yield return member; + } + } + + if (symbol?.TypeKind == TypeKind.Interface) + { + foreach (var @interface in symbol.AllInterfaces) + { + foreach (var member in @interface.GetMembers(name)) { yield return member; } } + } + else + { + do + { + symbol = symbol?.BaseType; - symbol = symbol?.BaseType; + if (symbol == null) + { + break; + } - if (symbol == null) - { - break; - } + foreach (var member in symbol.GetMembers(name)) + { + yield return member; + } - } while (symbol.SpecialType != SpecialType.System_Object); + } while (symbol.SpecialType != SpecialType.System_Object); + } } /// diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Given_Binding.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Given_Binding.cs index 717742a9aa31..ec65c369dc7c 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Given_Binding.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Given_Binding.cs @@ -587,4 +587,89 @@ public MainPage() await test.RunAsync(); } + + [TestMethod] + public async Task TestInterfaceDerivesFromAnother() + { + var xamlFile = new XamlFile("MainPage.xaml", """ + + + + + + + """); + + var test = new Verify.Test(xamlFile) + { + TestState = + { + Sources = + { + """ + using System.ComponentModel; + using Microsoft.UI.Xaml.Controls; + + namespace TestRepro + { + public sealed partial class MainPage : Page + { + public MainPage() + { + MyFooInterface = new MyBarClass("John Doe"); + this.InitializeComponent(); + } + + private IMyFooInterface MyFooInterface { get; set; } + } + + public partial class MyBarClass : INotifyPropertyChanged, IMyFooInterface + { + private string _name; + + public MyBarClass(string name) + { + Name = name; + } + + public string Name + { + get => _name; + set + { + if (_name != value) + { + _name = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); + } + } + } + + public event PropertyChangedEventHandler PropertyChanged; + } + + public interface IMyFooInterface : INameProvider + { + } + + public interface INameProvider + { + string Name { get; set; } + } + } + + """ + } + } + }.AddGeneratedSources(); + + await test.RunAsync(); + } } diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_GlobalStaticResources.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_GlobalStaticResources.cs new file mode 100644 index 000000000000..ad59ff1058b5 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_GlobalStaticResources.cs @@ -0,0 +1,55 @@ +// +namespace MyProject +{ + /// + /// Contains all the static resources defined for the application + /// + public sealed partial class GlobalStaticResources + { + static bool _initialized; + private static bool _stylesRegistered; + private static bool _dictionariesRegistered; + internal static global::Uno.UI.Xaml.XamlParseContext __ParseContext_ { get; } = new global::Uno.UI.Xaml.XamlParseContext() + { + AssemblyName = "TestProject", + } + ; + + static GlobalStaticResources() + { + Initialize(); + } + public static void Initialize() + { + if (!_initialized) + { + _initialized = true; + global::Uno.UI.GlobalStaticResources.Initialize(); + global::Uno.UI.GlobalStaticResources.RegisterDefaultStyles(); + global::Uno.UI.GlobalStaticResources.RegisterResourceDictionariesBySource(); + } + } + public static void RegisterDefaultStyles() + { + if(!_stylesRegistered) + { + _stylesRegistered = true; + RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d(); + } + } + // Register ResourceDictionaries using ms-appx:/// syntax, this is called for external resources + public static void RegisterResourceDictionariesBySource() + { + if(!_dictionariesRegistered) + { + _dictionariesRegistered = true; + } + } + // Register ResourceDictionaries using ms-resource:/// syntax, this is called for local resources + internal static void RegisterResourceDictionariesBySourceLocal() + { + } + static partial void RegisterDefaultStyles_MainPage_d6cd66944958ced0c513e0a04797b51d(); + + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_LocalizationResources.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_LocalizationResources.cs new file mode 100644 index 000000000000..115ce87c0105 --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_LocalizationResources.cs @@ -0,0 +1,2 @@ +// +[assembly: global::System.Reflection.AssemblyMetadata("UnoHasLocalizationResources", "False")] \ No newline at end of file diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs new file mode 100644 index 000000000000..51629ab748af --- /dev/null +++ b/src/SourceGenerators/Uno.UI.SourceGenerators.Tests/XamlCodeGeneratorTests/Out/TIDFA/XamlCodeGenerator_MainPage_d6cd66944958ced0c513e0a04797b51d.cs @@ -0,0 +1,193 @@ +// +#pragma warning disable CS0114 +#pragma warning disable CS0108 +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Uno.UI; +using Uno.UI.Xaml; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Shapes; +using Windows.UI.Text; +using Uno.Extensions; +using Uno; +using Uno.UI.Helpers; +using Uno.UI.Helpers.Xaml; +using MyProject; + +#if __ANDROID__ +using _View = Android.Views.View; +#elif __IOS__ +using _View = UIKit.UIView; +#elif __MACOS__ +using _View = AppKit.NSView; +#else +using _View = Microsoft.UI.Xaml.UIElement; +#endif + +namespace TestRepro +{ + partial class MainPage : global::Microsoft.UI.Xaml.Controls.Page + { + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + private const string __baseUri_prefix_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/"; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + private const string __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d = "ms-appx:///TestProject/"; + private global::Microsoft.UI.Xaml.NameScope __nameScope = new global::Microsoft.UI.Xaml.NameScope(); + private void InitializeComponent() + { + NameScope.SetNameScope(this, __nameScope); + var __that = this; + base.IsParsing = true; + // Source 0\MainPage.xaml (Line 1:2) + base.Content = + new global::Microsoft.UI.Xaml.Controls.StackPanel + { + IsParsing = true, + // Source 0\MainPage.xaml (Line 10:3) + Children = + { + new global::Microsoft.UI.Xaml.Controls.TextBox + { + IsParsing = true, + // Source 0\MainPage.xaml (Line 11:4) + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler0)(c0 => + { + /* _isTopLevelDictionary:False */ + __that._component_0 = c0; + c0.SetBinding( + global::Microsoft.UI.Xaml.Controls.TextBox.TextProperty, + new Microsoft.UI.Xaml.Data.Binding() + { + Mode = global::Microsoft.UI.Xaml.Data.BindingMode.TwoWay, + } + .BindingApply(___b => /*defaultBindModeOneTime MyFooInterface.Name*/ global::Uno.UI.Xaml.BindingHelper.SetBindingXBindProvider(___b, __that, ___ctx => ___ctx is global::TestRepro.MainPage ___tctx ? ((true, ___tctx.MyFooInterface.Name)) : (false, default), (___ctx, __value) => { if(___ctx is global::TestRepro.MainPage ___tctx) ___tctx.MyFooInterface.Name = (string)global::Microsoft.UI.Xaml.Markup.XamlBindingHelper.ConvertValue(typeof(string), __value); } , new [] {"MyFooInterface.Name"})) + ); + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c0, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c0.CreationComplete(); + } + )) + , + } + } + .MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply((MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions.XamlApplyHandler1)(c1 => + { + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c1, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c1.CreationComplete(); + } + )) + ; + + this + .GenericApply(((c2) => + { + // Source 0\MainPage.xaml (Line 1:2) + + // WARNING Property c2.base does not exist on {http://schemas.microsoft.com/winfx/2006/xaml/presentation}Page, the namespace is http://www.w3.org/XML/1998/namespace. This error was considered irrelevant by the XamlFileGenerator + } + )) + .GenericApply(((c3) => + { + // Class TestRepro.MainPage + global::Uno.UI.FrameworkElementHelper.SetBaseUri(c3, __baseUri_MainPage_d6cd66944958ced0c513e0a04797b51d); + c3.CreationComplete(); + } + )) + ; + OnInitializeCompleted(); + + Bindings = new MainPage_Bindings(this); + Loading += (s, e) => + { + __that.Bindings.Update(); + __that.Bindings.UpdateResources(); + } + ; + } + partial void OnInitializeCompleted(); + private global::Microsoft.UI.Xaml.Markup.ComponentHolder _component_0_Holder = new global::Microsoft.UI.Xaml.Markup.ComponentHolder(isWeak: true); + private global::Microsoft.UI.Xaml.Controls.TextBox _component_0 + { + get + { + return (global::Microsoft.UI.Xaml.Controls.TextBox)_component_0_Holder.Instance; + } + set + { + _component_0_Holder.Instance = value; + } + } + private interface IMainPage_Bindings + { + void Initialize(); + void Update(); + void UpdateResources(); + void StopTracking(); + void NotifyXLoad(string name); + } + #pragma warning disable 0169 // Suppress unused field warning in case Bindings is not used. + private IMainPage_Bindings Bindings; + #pragma warning restore 0169 + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + private class MainPage_Bindings : IMainPage_Bindings + { + #if UNO_HAS_UIELEMENT_IMPLICIT_PINNING + private global::System.WeakReference _ownerReference; + private global::TestRepro.MainPage Owner { get => (global::TestRepro.MainPage)_ownerReference?.Target; set => _ownerReference = new global::System.WeakReference(value); } + #else + private global::TestRepro.MainPage Owner { get; set; } + #endif + public MainPage_Bindings(global::TestRepro.MainPage owner) + { + Owner = owner; + } + void IMainPage_Bindings.NotifyXLoad(string name) + { + } + void IMainPage_Bindings.Initialize() + { + } + void IMainPage_Bindings.Update() + { + var owner = Owner; + owner._component_0.ApplyXBind(); + } + void IMainPage_Bindings.UpdateResources() + { + var owner = Owner; + owner._component_0.UpdateResourceBindings(resourceContextProvider: null); + } + void IMainPage_Bindings.StopTracking() + { + } + } + } +} +namespace MyProject +{ + static class MainPage_d6cd66944958ced0c513e0a04797b51dXamlApplyExtensions + { + public delegate void XamlApplyHandler0(global::Microsoft.UI.Xaml.Controls.TextBox instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.Controls.TextBox MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.TextBox instance, XamlApplyHandler0 handler) + { + handler(instance); + return instance; + } + public delegate void XamlApplyHandler1(global::Microsoft.UI.Xaml.Controls.StackPanel instance); + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static global::Microsoft.UI.Xaml.Controls.StackPanel MainPage_d6cd66944958ced0c513e0a04797b51d_XamlApply(this global::Microsoft.UI.Xaml.Controls.StackPanel instance, XamlApplyHandler1 handler) + { + handler(instance); + return instance; + } + } +} diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs index 0043f8db2109..333dca9df08a 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs @@ -4430,8 +4430,7 @@ private ITypeSymbol GetXBindPropertyPathType(string propertyPath, INamedTypeSymb } } - if (currentType is null) - throw new InvalidOperationException($"Unable to find member [{part}] on type [{currentType}]"); + throw new InvalidOperationException($"Unable to find member [{part}] on type [{currentType}]"); } if (isIndexer)