diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 35d34d83a8..02a7d936b6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -55,18 +55,21 @@ + + + + - diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs index 8f01c71b28..5f0e84c63a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs @@ -28,7 +28,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public string ResourceKey { get; set; } - public override object ProvideValue(IServiceProvider serviceProvider) + public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider); + + public IBinding ProvideTypedValue(IServiceProvider serviceProvider) { var provideTarget = serviceProvider.GetService(); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 768dbb7c36..490b94ea52 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -16,6 +16,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions private AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration) : base(configuration, true) { + void InsertAfter(params IXamlIlAstTransformer[] t) + => Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t); + + void InsertBefore(params IXamlIlAstTransformer[] t) + => Transformers.InsertRange(Transformers.FindIndex(x => x is T), t); + + // Before everything else Transformers.Insert(0, new XNameTransformer()); @@ -25,21 +32,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions // Targeted - Transformers.Insert(Transformers.FindIndex(x => x is XamlIlPropertyReferenceResolver), - new AvaloniaXamlIlTransformInstanceAttachedProperties()); + InsertBefore(new AvaloniaXamlIlTransformInstanceAttachedProperties()); + InsertAfter(new AvaloniaXamlIlAvaloniaPropertyResolver()); - Transformers.Insert(Transformers.FindIndex(x => x is XamlIlXamlPropertyValueTransformer), - new KnownPseudoMarkupExtensionsTransformer()); - - Transformers.InsertRange(Transformers.FindIndex(x => x is XamlIlNewObjectTransformer), - new IXamlIlAstTransformer[] - { - new AvaloniaXamlIlSelectorTransformer(), - new AvaloniaXamlIlSetterTransformer(), - new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), - new AvaloniaXamlIlConstructorServiceProviderTransformer() - } + InsertBefore(new AvaloniaXamlIlBindingPropertyAssignmentsTransformer()); + + + InsertBefore( + new AvaloniaXamlIlSelectorTransformer(), + new AvaloniaXamlIlSetterTransformer(), + new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), + new AvaloniaXamlIlConstructorServiceProviderTransformer() ); + // After everything else Transformers.Add(new AddNameScopeRegistration()); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs new file mode 100644 index 0000000000..ed17e59e14 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs @@ -0,0 +1,24 @@ +using System.Linq; +using XamlIl.Ast; +using XamlIl.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + public class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlIlAstTransformer + { + public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) + { + if (node is XamlIlAstClrPropertyReference prop) + { + var n = prop.Property.Name + "Property"; + var field = + (prop.Property.Getter ?? prop.Property.Setter).DeclaringType.Fields + .FirstOrDefault(f => f.Name == n); + if (field != null) + prop.Property = new XamlIlAvaloniaProperty(prop.Property, field); + } + + return node; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPropertyAssignmentsTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPropertyAssignmentsTransformer.cs new file mode 100644 index 0000000000..20a394c4e0 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPropertyAssignmentsTransformer.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using XamlIl.Ast; +using XamlIl.Transform; +using XamlIl.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + public class AvaloniaXamlIlBindingPropertyAssignmentsTransformer : IXamlIlAstTransformer + { + public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) + { + if (node is XamlIlAstXamlPropertyValueNode pv + && pv.Property.GetClrProperty() is IXamlIlAvaloniaProperty ap + && pv.Values.Count == 1) + { + var types = context.GetAvaloniaTypes(); + + var vn = pv.Values[0]; + if(vn.Type.GetClrType().Name.Contains("TemplateBinding")) + Console.WriteLine(); + // Special handling for markup extensions + if (vn.Type.IsMarkupExtension) + { + if (XamlIlTransformHelpers + .GetMarkupExtensionProvideValueAlternatives(context, vn.Type.GetClrType()) + .Any(x => types.IBinding.IsAssignableFrom(x.ReturnType))) + { + if (XamlIlTransformHelpers.TryConvertMarkupExtension(context, pv.Values[0], + new AssignBindingProperty(types, ap), out var ext)) + return ext; + } + } + else if (types.IBinding.IsAssignableFrom(vn.Type.GetClrType())) + { + pv.Property = new XamlIlAstClrPropertyReference(pv.Property, new AssignBindingProperty(types, ap)); + } + } + + return node; + } + + class AssignBindingProperty : IXamlIlAvaloniaProperty + { + public AssignBindingProperty( + AvaloniaXamlIlWellKnownTypes types, + IXamlIlAvaloniaProperty property + ) + { + PropertyType = types.IBinding; + AvaloniaProperty = property.AvaloniaProperty; + CustomAttributes = property.CustomAttributes; + Name = property.Name; + Setter = new SetterMethod(types, (property.Setter ?? property.Getter).DeclaringType, + AvaloniaProperty); + } + + public bool Equals(IXamlIlProperty other) => + other is AssignBindingProperty abp && abp.AvaloniaProperty.Equals(AvaloniaProperty); + + public string Name { get; } + public IXamlIlType PropertyType { get; } + public IXamlIlMethod Setter { get; } + public IXamlIlMethod Getter { get; } + public IReadOnlyList CustomAttributes { get; } + public IXamlIlField AvaloniaProperty { get; } + + class SetterMethod : IXamlIlCustomEmitMethod + { + private readonly AvaloniaXamlIlWellKnownTypes _types; + private readonly IXamlIlField _avaloniaProperty; + + public SetterMethod(AvaloniaXamlIlWellKnownTypes types, + IXamlIlType declaringType, + IXamlIlField avaloniaProperty) + { + _types = types; + _avaloniaProperty = avaloniaProperty; + Parameters = new[] {types.AvaloniaObject, types.IBinding}; + ReturnType = types.XamlIlTypes.Void; + DeclaringType = declaringType; + Name = "Bind_" + avaloniaProperty.Name; + } + + public bool Equals(IXamlIlMethod other) => + other is SetterMethod sm && sm._avaloniaProperty.Equals(_avaloniaProperty); + + public string Name { get; } + public bool IsPublic => true; + public bool IsStatic => true; + public IXamlIlType ReturnType { get; } + public IReadOnlyList Parameters { get; } + public IXamlIlType DeclaringType { get; } + public void EmitCall(IXamlIlEmitter emitter) + { + using (var bloc = emitter.LocalsPool.GetLocal(_types.IBinding)) + emitter + .Stloc(bloc.Local) + .Ldsfld(_avaloniaProperty) + .Ldloc(bloc.Local) + // TODO: provide anchor? + .Ldnull(); + emitter.EmitCall(_types.AvaloniaObjectBindMethod, true); + } + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs index aed38dbcdf..1b3b1d7047 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs @@ -71,7 +71,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return node; } - class AvaloniaAttachedInstanceProperty : IXamlIlProperty + class AvaloniaAttachedInstanceProperty : IXamlIlAvaloniaProperty { private readonly XamlIlTransformerConfiguration _config; private readonly IXamlIlType _declaringType; @@ -103,6 +103,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers Getter = new GetterMethod(this); } + public IXamlIlField AvaloniaProperty => _field; public bool Equals(IXamlIlProperty other) => other is AvaloniaAttachedInstanceProperty ap && ap._field.Equals(_field); diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs new file mode 100644 index 0000000000..388bb94b35 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -0,0 +1,42 @@ +using XamlIl.Transform; +using XamlIl.TypeSystem; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + class AvaloniaXamlIlWellKnownTypes + { + public IXamlIlType AvaloniaObject { get; } + public IXamlIlType IAvaloniaObject { get; } + public IXamlIlType AvaloniaObjectExtensions { get; } + public IXamlIlType AvaloniaProperty { get; } + public IXamlIlType IBinding { get; } + public IXamlIlMethod AvaloniaObjectBindMethod { get; } + public IXamlIlType IDisposable { get; } + public XamlIlTypeWellKnownTypes XamlIlTypes { get; } + + public AvaloniaXamlIlWellKnownTypes(XamlIlAstTransformationContext ctx) + { + XamlIlTypes = ctx.Configuration.WellKnownTypes; + AvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObject"); + IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject"); + AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions"); + AvaloniaProperty = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"); + IBinding = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.IBinding"); + IDisposable = ctx.Configuration.TypeSystem.GetType("System.IDisposable"); + AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject, + AvaloniaProperty, + IBinding, ctx.Configuration.WellKnownTypes.Object); + } + } + + static class AvaloniaXamlIlWellKnownTypesExtensions + { + public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlIlAstTransformationContext ctx) + { + if (ctx.TryGetItem(out var rv)) + return rv; + ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx)); + return rv; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs deleted file mode 100644 index de3c9ddc13..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using XamlIl.Ast; -using XamlIl.Transform; - -namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers -{ - class KnownPseudoMarkupExtensionsTransformer : IXamlIlAstTransformer - { - private static readonly List s_knownPseudoExtensions = new List - { - "Avalonia.Data.TemplateBinding", - "Avalonia.Data.MultiBinding", - "Avalonia.Data.Binding", - }; - - public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) - { - if (node is XamlIlAstXamlPropertyValueNode pn - && pn.Values.Count == 1 - && s_knownPseudoExtensions.Contains(pn.Values[0].Type.GetClrType().FullName)) - return new XamlIlMarkupExtensionNode(node, pn.Property.GetClrProperty(), - null, pn.Values[0], null); - else - return node; - } - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 27c39aa8b9..e090b42d17 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -15,6 +15,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXamlIlProperty property) { + if (property is IXamlIlAvaloniaProperty ap) + { + emitter.Ldsfld(ap.AvaloniaProperty); + return true; + } var type = (property.Getter ?? property.Setter).DeclaringType; var name = property.Name + "Property"; var found = type.Fields.FirstOrDefault(f => f.IsStatic && f.Name == name); @@ -75,4 +80,30 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return XamlIlNodeEmitResult.Type(0, Type.GetClrType()); } } + + interface IXamlIlAvaloniaProperty : IXamlIlProperty + { + IXamlIlField AvaloniaProperty { get; } + } + + class XamlIlAvaloniaProperty : IXamlIlAvaloniaProperty + { + private readonly IXamlIlProperty _original; + + public IXamlIlField AvaloniaProperty { get; } + public XamlIlAvaloniaProperty(IXamlIlProperty original, IXamlIlField field) + { + _original = original; + AvaloniaProperty = field; + } + + public bool Equals(IXamlIlProperty other) => + other is XamlIlAvaloniaProperty p && p.AvaloniaProperty.Equals(AvaloniaProperty); + + public string Name => _original.Name; + public IXamlIlType PropertyType => _original.PropertyType; + public IXamlIlMethod Setter => _original.Setter; + public IXamlIlMethod Getter => _original.Getter; + public IReadOnlyList CustomAttributes => _original.CustomAttributes; + } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index e1ae067813..cf70f5da92 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit e1ae06781318a65810adb83b87ba56ffeafaf50b +Subproject commit cf70f5da92e04e1d2d36da5451813bea2b8f54ba diff --git a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs index fab9bdbf55..7fb8742f26 100644 --- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs @@ -176,5 +176,7 @@ namespace Avalonia.Data PublishValue(); } } + + public IBinding ProvideValue() => this; } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/XamlIlBugTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/XamlIlBugTests.cs new file mode 100644 index 0000000000..73312749c6 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/XamlIlBugTests.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Avalonia.Controls; +using Avalonia.Media; +using JetBrains.Annotations; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests +{ + public class XamlIlBugTests + { + [Fact] + public void Binding_Button_IsPressed_ShouldWork() + { + var parsed = (Button)AvaloniaXamlLoader.Parse(@" +