From 6d950d8e15fe8624acc717e5fdeec6bde1edab03 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 18 Jun 2026 23:55:34 -0700 Subject: [PATCH] Compiled Bindings: Fix unpredictable binding to method selection (#21617) * Fix unpredictable binding to method selection (compiled bindings) * Adjust tests * Add more test cases --- .../XamlIlBindingPathHelper.cs | 21 ++++++++++-- .../XamlIlTrampolineBuilder.cs | 21 ++---------- .../CompiledBindingExtensionTests.cs | 34 ++++++++++--------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index e83807ea93..84af961431 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -210,9 +210,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty, propName.AcceptsNull)); } - else if (GetAllDefinedMethods(targetType).FirstOrDefault(m => m.Name == propName.PropertyName) is IXamlMethod method) + else if (GetAllDefinedMethods(targetType) + .Where(p => p.Name == propName.PropertyName) + .OrderByDescending(m => m.Parameters.Count) + .ToArray() + is { Length: > 0 } methodCandidates) { - nodes.Add(new XamlIlClrMethodPathElementNode(method, context.Configuration.WellKnownTypes.Delegate, propName.AcceptsNull)); + var objType = context.Configuration.WellKnownTypes.Object; + var candidate = methodCandidates + .FirstOrDefault(m => m.Parameters.Count == 0 + || (m.Parameters.Count == 1 && + m.Parameters[0].Equals(objType))); + if (candidate is null) + { + throw new XamlX.XamlTransformException( + $"Unable to resolve method of name '{propName.PropertyName}' on type '{targetType}'." + + $"Expected method with no parameters or a single object overload.", + lineInfo); + } + + nodes.Add(new XamlIlClrMethodPathElementNode(candidate, context.Configuration.WellKnownTypes.Delegate, propName.AcceptsNull)); } else { diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs index ae26aec993..5245fa7d3c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs @@ -48,24 +48,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } if (executeMethod.Parameters.Count != 0) { - Debug.Assert(executeMethod.Parameters.Count == 1); - if (executeMethod.Parameters[0] != context.Configuration.WellKnownTypes.Object) - { - var convertedValue = gen.DefineLocal(context.Configuration.WellKnownTypes.Object); - gen.Ldtype(executeMethod.Parameters[0]) - .Ldarg(1) - .EmitCall(context.Configuration.WellKnownTypes.CultureInfo.GetMethod(m => m.Name == "get_CurrentCulture")) - .Ldloca(convertedValue) - .EmitCall( - context.GetAvaloniaTypes().TypeUtilities.GetMethod(m => m.Name == "TryConvert"), - swallowResult: true) - .Ldloc(convertedValue) - .Unbox_Any(executeMethod.Parameters[0]); - } - else - { - gen.Ldarg(1); - } + Debug.Assert(executeMethod.Parameters.Count == 1 + && executeMethod.Parameters[0] == context.Configuration.WellKnownTypes.Object); + gen.Ldarg(1); } gen.EmitCall(executeMethod, swallowResult: true); gen.Ret(); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 99a79ded75..d3374ce8ad 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1863,8 +1863,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions - - + @@ -1873,9 +1872,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions window.DataContext = new MethodDataContext(); Assert.IsAssignableFrom(typeof(Action), window.GetControl("action").Content); - Assert.IsAssignableFrom(typeof(Func), window.GetControl("func").Content); - Assert.IsAssignableFrom(typeof(Action), window.GetControl("action16").Content); - Assert.IsAssignableFrom(typeof(Func), window.GetControl("func16").Content); + Assert.IsAssignableFrom(typeof(Func), window.GetControl("func").Content); + Assert.IsAssignableFrom(typeof(Func), window.GetControl("func2").Content); Assert.True(typeof(Delegate).IsAssignableFrom(window.GetControl("customvoid").Content!.GetType())); Assert.True(typeof(Delegate).IsAssignableFrom(window.GetControl("customint").Content!.GetType())); } @@ -1906,17 +1904,20 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } - [Fact] - public void Binding_Method_With_Parameter_To_Command_Works() + [Theory] + [InlineData("5")] + [InlineData("hello")] + [InlineData(null)] + public void Binding_Method_With_Parameter_To_Command_Works(string? parameter) { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var xaml = @" + var xaml = $@" -