diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 9d1f439bab..5b0fbb744b 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -86,6 +86,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) }, new AvaloniaXamlIlDeferredResourceTransformer()); + InsertBefore(new AvaloniaXamlIlTransformRoutedEvent()); + Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer()); Transformers.Add(new AvaloniaXamlIlMetadataRemover()); Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformRoutedEvent.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformRoutedEvent.cs new file mode 100644 index 0000000000..bccb2d5a14 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlTransformRoutedEvent.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Linq; +using XamlX.Ast; +using XamlX.Emit; +using XamlX.IL; +using XamlX.Transform; +using XamlX.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; + +internal class AvaloniaXamlIlTransformRoutedEvent : IXamlAstTransformer +{ + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (node is XamlAstNamePropertyReference prop + && prop.TargetType is XamlAstClrTypeReference targetRef + && prop.DeclaringType is XamlAstClrTypeReference declaringRef) + { + var xkt = context.GetAvaloniaTypes(); + var interactiveType = xkt.Interactivity.Interactive; + var routedEventType = xkt.Interactivity.RoutedEvent; + var AddHandlerT = xkt.Interactivity.AddHandlerT; + + if (interactiveType.IsAssignableFrom(targetRef.Type)) + { + var eventName = $"{prop.Name}Event"; + if (declaringRef.Type.GetAllFields().FirstOrDefault(f => f.IsStatic && f.Name == eventName) is { } eventField) + { + if (routedEventType.IsAssignableFrom(eventField.FieldType)) + { + var instance = new XamlAstClrProperty(prop + , prop.Name + , targetRef.Type + , null + ); + instance.Setters.Add(new XamlDirectCallAddHandler(eventField, + targetRef.Type, + xkt.Interactivity.AddHandler, + xkt.Interactivity.RoutedEventHandler + ) + ); + if (eventField.FieldType.GenericArguments?.Count == 1) + { + var agrument = eventField.FieldType.GenericArguments[0]; + if (!agrument.Equals(xkt.Interactivity.RoutedEventArgs)) + { + instance.Setters.Add(new XamlDirectCallAddHandler(eventField, + targetRef.Type, + xkt.Interactivity.AddHandlerT.MakeGenericMethod([agrument]), + xkt.EventHandlerT.MakeGenericType(agrument) + ) + ); + } + } + return instance; + } + else + { + context.ReportDiagnostic(new XamlX.XamlDiagnostic( + AvaloniaXamlDiagnosticCodes.TransformError, + XamlX.XamlDiagnosticSeverity.Error, + $"Event definition {prop.Name} found, but its type {eventField.FieldType.GetFqn()} is not compatible with RoutedEvent.", + node)); + } + } + } + } + return node; + } + + private sealed class XamlDirectCallAddHandler : IXamlILOptimizedEmitablePropertySetter + { + private readonly IXamlField _eventField; + private readonly IXamlType _declaringType; + private readonly IXamlMethod _addMethod; + + public XamlDirectCallAddHandler(IXamlField eventField, + IXamlType declaringType, + IXamlMethod addMethod, + IXamlType routedEventHandler + ) + { + Parameters = [routedEventHandler]; + _eventField = eventField; + _declaringType = declaringType; + _addMethod = addMethod; + } + + public IXamlType TargetType => _declaringType; + public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters(); + public IReadOnlyList Parameters { get; } + + public IReadOnlyList CustomAttributes => []; + + public void Emit(IXamlILEmitter emitter) + => emitter.EmitCall(_addMethod, true); + + public void EmitWithArguments(XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + + using (var loc = emitter.LocalsPool.GetLocal(_declaringType)) + emitter + .Ldloc(loc.Local); + + emitter.Ldfld(_eventField); + + for (var i = 0; i < arguments.Count; ++i) + context.Emit(arguments[i], emitter, Parameters[i]); + + emitter.Ldc_I4(5); + emitter.Ldc_I4(0); + + emitter.EmitCall(_addMethod, true); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 3250c7b836..7d50e80c45 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -9,7 +9,8 @@ using XamlX.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { - class AvaloniaXamlIlWellKnownTypes + + sealed class AvaloniaXamlIlWellKnownTypes { public IXamlType RuntimeHelpers { get; } public IXamlType AvaloniaObject { get; } @@ -124,6 +125,49 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType WindowTransparencyLevel { get; } public IXamlType IReadOnlyListOfT { get; } public IXamlType ControlTemplate { get; } + public IXamlType EventHandlerT { get; } + + sealed internal class InteractivityWellKnownTypes + { + public IXamlType Interactive { get; } + public IXamlType RoutedEvent { get; } + public IXamlType RoutedEventArgs { get; } + public IXamlType RoutedEventHandler { get; } + public IXamlMethod AddHandler { get; } + public IXamlMethod AddHandlerT { get; } + + internal InteractivityWellKnownTypes(TransformerConfiguration cfg) + { + var ts = cfg.TypeSystem; + Interactive = ts.FindType("Avalonia.Interactivity.Interactive"); + RoutedEvent = ts.FindType("Avalonia.Interactivity.RoutedEvent"); + RoutedEventArgs = ts.FindType("Avalonia.Interactivity.RoutedEventArgs"); + var eventHanlderT = ts.FindType("System.EventHandler`1"); + RoutedEventHandler = eventHanlderT.MakeGenericType(RoutedEventArgs); + AddHandler = Interactive.FindMethod(m => m.IsPublic + && !m.IsStatic + && m.Name == "AddHandler" + && m.Parameters.Count == 4 + && m.Parameters[0].Equals(RoutedEvent) + && m.Parameters[1].Equals(cfg.WellKnownTypes.Delegate) + && m.Parameters[2].IsEnum + && m.Parameters[3].Equals(cfg.WellKnownTypes.Boolean) + ); + AddHandlerT = Interactive.FindMethod(m => m.IsPublic + && !m.IsStatic + && m.Name == "AddHandler" + && m.Parameters.Count == 4 + && RoutedEvent.IsAssignableFrom(m.Parameters[0]) + && m.Parameters[0].GenericArguments?.Count == 1 // This is specific this case workaround to check is generic method + && (cfg.WellKnownTypes.Delegate).IsAssignableFrom(m.Parameters[1]) + && m.Parameters[2].IsEnum + && m.Parameters[3].Equals(cfg.WellKnownTypes.Boolean) == true + ); + + } + } + + public InteractivityWellKnownTypes Interactivity { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -161,7 +205,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IBinding, cfg.WellKnownTypes.Object); UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType"); StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement"); - StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement"); INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope"); INameScopeRegister = INameScope.GetMethod( new FindMethodMethodSignature("Register", XamlIlTypes.Void, @@ -242,7 +285,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers StyledElementClassesProperty = StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes)); ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions") - .FindMethod( "BindClass", IDisposable, false, StyledElement, + .FindMethod("BindClass", IDisposable, false, StyledElement, cfg.WellKnownTypes.String, IBinding, cfg.WellKnownTypes.Object); @@ -271,6 +314,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme"); ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate"); IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1"); + EventHandlerT = cfg.TypeSystem.GetType("System.EventHandler`1"); + Interactivity = new InteractivityWellKnownTypes(cfg); } } @@ -291,7 +336,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration)); return rv; } - + public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstGroupTransformationContext ctx) { if (ctx.TryGetItem(out var rv)) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index 6c488ec94f..4c0217bcc7 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -36,5 +36,15 @@ PlatformFactAttribute.cs + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs index c9e0a3d5f0..304da097c0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs @@ -39,6 +39,23 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(target.WasTapped); } + [Fact] + public void Attached_Event_Is_Assigned_Generic() + { + var xaml = @"