Browse Source

Merge branch 'master' into features/Core/StringBuilder

workgroupengineering 3 years ago
parent
commit
f80e8e3ffd

+ 51 - 2
src/Avalonia.Base/Controls/Classes.cs

@@ -14,6 +14,8 @@ namespace Avalonia.Controls
     /// </remarks>
     public class Classes : AvaloniaList<string>, IPseudoClasses
     {
+        private List<IClassesChangedListener>? _listeners;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="Classes"/> class.
         /// </summary>
@@ -39,6 +41,11 @@ namespace Avalonia.Controls
         {            
         }
 
+        /// <summary>
+        /// Gets the number of listeners subscribed to this collection for unit testing purposes.
+        /// </summary>
+        internal int ListenerCount => _listeners?.Count ?? 0;
+
         /// <summary>
         /// Parses a classes string.
         /// </summary>
@@ -62,6 +69,7 @@ namespace Avalonia.Controls
             if (!Contains(name))
             {
                 base.Add(name);
+                NotifyChanged();
             }
         }
 
@@ -89,6 +97,7 @@ namespace Avalonia.Controls
             }
 
             base.AddRange(c);
+            NotifyChanged();
         }
 
         /// <summary>
@@ -103,6 +112,8 @@ namespace Avalonia.Controls
                     RemoveAt(i);
                 }
             }
+
+            NotifyChanged();
         }
 
         /// <summary>
@@ -122,6 +133,7 @@ namespace Avalonia.Controls
             if (!Contains(name))
             {
                 base.Insert(index, name);
+                NotifyChanged();
             }
         }
 
@@ -154,6 +166,7 @@ namespace Avalonia.Controls
             if (toInsert != null)
             {
                 base.InsertRange(index, toInsert);
+                NotifyChanged();
             }
         }
 
@@ -169,7 +182,14 @@ namespace Avalonia.Controls
         public override bool Remove(string name)
         {
             ThrowIfPseudoclass(name, "removed");
-            return base.Remove(name);
+
+            if (base.Remove(name))
+            {
+                NotifyChanged();
+                return true;
+            }
+
+            return false;
         }
 
         /// <summary>
@@ -197,6 +217,7 @@ namespace Avalonia.Controls
             if (toRemove != null)
             {
                 base.RemoveAll(toRemove);
+                NotifyChanged();
             }
         }
 
@@ -214,6 +235,7 @@ namespace Avalonia.Controls
             var name = this[index];
             ThrowIfPseudoclass(name, "removed");
             base.RemoveAt(index);
+            NotifyChanged();
         }
 
         /// <summary>
@@ -224,6 +246,7 @@ namespace Avalonia.Controls
         public override void RemoveRange(int index, int count)
         {
             base.RemoveRange(index, count);
+            NotifyChanged();
         }
 
         /// <summary>
@@ -255,6 +278,7 @@ namespace Avalonia.Controls
             }
 
             base.AddRange(source);
+            NotifyChanged();
         }
 
         /// <inheritdoc/>
@@ -263,13 +287,38 @@ namespace Avalonia.Controls
             if (!Contains(name))
             {
                 base.Add(name);
+                NotifyChanged();
             }
         }
 
         /// <inheritdoc/>
         bool IPseudoClasses.Remove(string name)
         {
-            return base.Remove(name);
+            if (base.Remove(name))
+            {
+                NotifyChanged();
+                return true;
+            }
+
+            return false;
+        }
+
+        internal void AddListener(IClassesChangedListener listener)
+        {
+            (_listeners ??= new()).Add(listener);
+        }
+
+        internal void RemoveListener(IClassesChangedListener listener)
+        {
+            _listeners?.Remove(listener);
+        }
+
+        private void NotifyChanged()
+        {
+            if (_listeners is null)
+                return;
+            foreach (var listener in _listeners)
+                listener.Changed();
         }
 
         private void ThrowIfPseudoclass(string name, string operation)

+ 14 - 0
src/Avalonia.Base/Controls/IClassesChangedListener.cs

@@ -0,0 +1,14 @@
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Internal interface for listening to changes in <see cref="Classes"/> in a more
+    /// performant manner than subscribing to CollectionChanged.
+    /// </summary>
+    internal interface IClassesChangedListener
+    {
+        /// <summary>
+        /// Notifies the listener that the <see cref="Classes"/> collection has changed.
+        /// </summary>
+        void Changed();
+    }
+}

+ 1 - 1
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@@ -55,7 +55,7 @@ namespace Avalonia.Data.Core.Plugins
         
         private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName)
         {
-            if (instance is IReflectableType reflectableType)
+            if (instance is IReflectableType reflectableType && instance is not Type)
                 return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags);
 
             var type = instance.GetType();

+ 10 - 16
src/Avalonia.Base/Styling/Activators/StyleClassActivator.cs

@@ -1,6 +1,7 @@
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using Avalonia.Collections;
+using Avalonia.Controls;
 
 #nullable enable
 
@@ -10,21 +11,17 @@ namespace Avalonia.Styling.Activators
     /// An <see cref="IStyleActivator"/> which is active when a set of classes match those on a
     /// control.
     /// </summary>
-    internal sealed class StyleClassActivator : StyleActivatorBase
+    internal sealed class StyleClassActivator : StyleActivatorBase, IClassesChangedListener
     {
         private readonly IList<string> _match;
-        private readonly IAvaloniaReadOnlyList<string> _classes;
-        private NotifyCollectionChangedEventHandler? _classesChangedHandler;
+        private readonly Classes _classes;
 
-        public StyleClassActivator(IAvaloniaReadOnlyList<string> classes, IList<string> match)
+        public StyleClassActivator(Classes classes, IList<string> match)
         {
             _classes = classes;
             _match = match;
         }
 
-        private NotifyCollectionChangedEventHandler ClassesChangedHandler =>
-            _classesChangedHandler ??= ClassesChanged;
-
         public static bool AreClassesMatching(IReadOnlyList<string> classes, IList<string> toMatch)
         {
             int remainingMatches = toMatch.Count;
@@ -55,23 +52,20 @@ namespace Avalonia.Styling.Activators
             return remainingMatches == 0;
         }
 
-        protected override void Initialize()
+        void IClassesChangedListener.Changed()
         {
             PublishNext(IsMatching());
-            _classes.CollectionChanged += ClassesChangedHandler;
         }
 
-        protected override void Deinitialize()
+        protected override void Initialize()
         {
-            _classes.CollectionChanged -= ClassesChangedHandler;
+            PublishNext(IsMatching());
+            _classes.AddListener(this);
         }
 
-        private void ClassesChanged(object? sender, NotifyCollectionChangedEventArgs e)
+        protected override void Deinitialize()
         {
-            if (e.Action != NotifyCollectionChangedAction.Move)
-            {
-                PublishNext(IsMatching());
-            }
+            _classes.RemoveListener(this);
         }
 
         private bool IsMatching() => AreClassesMatching(_classes, _match);

+ 2 - 1
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
+using Avalonia.Controls;
 using Avalonia.Styling.Activators;
 
 #nullable enable
@@ -125,7 +126,7 @@ namespace Avalonia.Styling
             {
                 if (subscribe)
                 {
-                    var observable = new StyleClassActivator(control.Classes, _classes.Value);
+                    var observable = new StyleClassActivator((Classes)control.Classes, _classes.Value);
 
                     return new SelectorMatch(observable);
                 }

+ 1 - 0
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -421,6 +421,7 @@ namespace Avalonia.Controls
 
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
+            base.OnAttachedToVisualTree(e);
             InvalidateMeasure();
             _viewportManager.ResetScrollers();
         }

+ 1 - 1
src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml

@@ -19,7 +19,7 @@
                   Classes="textBoxClearButton"
                   ToolTip.Tip="Clear"
                   Cursor="Hand"
-                  Command="{ReflectionBinding $parent[TextBox].Clear}"
+                  Command="{Binding $parent[TextBox].Clear}"
                   Opacity="0.5" />
           <ToggleButton Classes="filter-text-box-toggle"
                         ToolTip.Tip="Match Case"

+ 1 - 1
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@@ -160,7 +160,7 @@
             </ContentPresenter>
             <Popup Name="PART_Popup"
                    WindowManagerAddShadowHint="False"
-                   MinWidth="{ReflectionBinding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
+                   MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
                    IsLightDismissEnabled="True"
                    IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"
                    OverlayInputPassThroughElement="{Binding $parent[Menu]}">

+ 2 - 2
src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml

@@ -21,7 +21,7 @@
     <Setter Property="MinHeight" Value="{DynamicResource ProgressBarThemeMinHeight}" />
     <Setter Property="VerticalAlignment" Value="Center" />
     <Setter Property="Template">
-      <ControlTemplate>
+      <ControlTemplate TargetType="ProgressBar">
         <Border x:Name="ProgressBarRoot" ClipToBounds="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}">          
             <Panel>
               <Panel x:Name="DeterminateRoot">
@@ -32,7 +32,7 @@
                 <Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{TemplateBinding CornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
               </Panel>
               <LayoutTransformControl x:Name="PART_LayoutTransformControl" HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{TemplateBinding ShowProgressText}">
-                <TextBlock Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" Text="{ReflectionBinding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
+                <TextBlock Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
               </LayoutTransformControl>
             </Panel>
         </Border>

+ 3 - 3
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@@ -45,10 +45,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 new AvaloniaXamlIlReorderClassesPropertiesTransformer()
             );
 
-            InsertBefore<ContentConvertTransformer>(                
-                new AvaloniaXamlIlBindingPathParser(),
+            InsertBefore<ContentConvertTransformer>(
                 new AvaloniaXamlIlSelectorTransformer(),
-                new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
+                new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),                 
+                new AvaloniaXamlIlBindingPathParser(),
                 new AvaloniaXamlIlPropertyPathTransformer(),
                 new AvaloniaXamlIlSetterTransformer(),
                 new AvaloniaXamlIlConstructorServiceProviderTransformer(),

+ 15 - 8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@@ -122,10 +122,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                     throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
                 }
 
-                var mode = relativeSourceObject.Children
+                var modeProperty = relativeSourceObject.Children
                     .OfType<XamlAstXamlPropertyValueNode>()
-                    .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Mode")
-                    ?.Values[0] is XamlAstTextNode modeAssignedValue ? modeAssignedValue.Text : null;
+                    .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Mode")?
+                    .Values.FirstOrDefault() as XamlAstTextNode
+                    ?? relativeSourceObject.Arguments.OfType<XamlAstTextNode>().FirstOrDefault();
+                
+                var mode = modeProperty?.Text;
                 if (relativeSourceObject.Arguments.Count == 0 && mode == null)
                 {
                     mode = "FindAncestor";
@@ -212,16 +215,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 }
                 else if (mode == "TemplatedParent")
                 {
-                    var parentType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
+                    var contentTemplateNode = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
                         .FirstOrDefault(x =>
-                            x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate)
-                        ?.TargetType.GetClrType();
-
-                    if (parentType is null)
+                            x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate);
+                    if (contentTemplateNode is null)
                     {
                         throw new XamlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
                     }
 
+                    var parentType = contentTemplateNode.TargetType.GetClrType();
+                    if (parentType is null)
+                    {
+                        throw new XamlParseException("TargetType has to be set on ControlTemplate or it should be defined inside of a Style.", binding);
+                    } 
+
                     convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
                 }
                 else

+ 5 - 0
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@@ -105,6 +105,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 {
                     startType = TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type;
                 }
+                
+                if (dataTypeProperty?.Values.Count is 1 && dataTypeProperty.Values[0] is XamlTypeExtensionNode typeNode)
+                {
+                    startType = typeNode.Value.GetClrType();
+                }
 
                 Func<IXamlType> startTypeResolver = startType is not null ? () => startType : () =>
                 {

+ 14 - 2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs

@@ -47,7 +47,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     return startTypeResolver();
                 }
 
-                if (bindingPathAssignment.Values[0] is ParsedBindingPathNode bindingPathNode)
+                if (bindingPathAssignment.Values[0] is XamlIlBindingPathNode pathNode)
+                {
+                    bindingResultType = pathNode.BindingResultType;
+                }
+                else if (bindingPathAssignment.Values[0] is ParsedBindingPathNode bindingPathNode)
                 {
                     var transformed = TransformBindingPath(
                         context,
@@ -63,7 +67,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 }
                 else
                 {
-                    throw new InvalidOperationException();
+                    throw new InvalidOperationException("Invalid state of Path property");
                 }
             }
 
@@ -240,6 +244,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                     case TemplatedParentBindingExpressionNode templatedParent:
                         var templatedParentField = context.GetAvaloniaTypes().StyledElement.GetAllFields()
                             .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == "TemplatedParentProperty");
+                        nodes.Add(new SelfPathElementNode(selfType));
                         nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(
                             templatedParentField,
                             templatedParent.Type));
@@ -374,6 +379,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
             public static IXamlType GetTargetType(IXamlAstNode namescopeRoot, string name)
             {
+                // If we start from the nested scope - skip it.
+                if (namescopeRoot is NestedScopeMetadataNode scope)
+                {
+                    namescopeRoot = scope.Value;
+                }
+                
                 var finder = new ScopeRegistrationFinder(name);
                 namescopeRoot.Visit(finder);
                 return finder.TargetType;
@@ -399,6 +410,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
             IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
             {
+                // Ignore name registrations, if we are inside of the nested namescope.
                 if (_childScopesStack.Count == 0 && node is AvaloniaNameScopeRegistrationXamlIlNode registration)
                 {
                     if (registration.Name is XamlAstTextNode text && text.Text == Name)

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@@ -74,6 +74,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             return pathRoot ?? new EmptyExpressionNode();
         }
 
+        internal IEnumerable<ICompiledBindingPathElement> Elements => _elements;
+        
         internal SourceMode SourceMode => _elements.Count > 0 && _elements[0] is IControlSourceBindingPathElement ? SourceMode.Control : SourceMode.Data;
 
         internal object RawSource { get; }

+ 2 - 3
tests/Avalonia.Base.UnitTests/Styling/SelectorTests_Template.cs

@@ -142,14 +142,13 @@ namespace Avalonia.Base.UnitTests.Styling
             var border = (Border)target.Object.VisualChildren.Single();
             var selector = default(Selector).OfType(templatedControl.Object.GetType()).Class("foo").Template().OfType<Border>();
             var activator = selector.Match(border).Activator;
-            var inccDebug = (INotifyCollectionChangedDebug)styleable.Object.Classes;
 
             using (activator.Subscribe(_ => { }))
             {
-                Assert.Single(inccDebug.GetCollectionChangedSubscribers());
+                Assert.Equal(1, ((Classes)styleable.Object.Classes).ListenerCount);
             }
 
-            Assert.Null(inccDebug.GetCollectionChangedSubscribers());
+            Assert.Equal(0, ((Classes)styleable.Object.Classes).ListenerCount);
         }
 
         private void BuildVisualTree<T>(Mock<T> templatedControl) where T : class, IVisual

+ 85 - 0
tests/Avalonia.Benchmarks/Styling/Style_ClassSelector.cs

@@ -0,0 +1,85 @@
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+using Avalonia.Styling;
+using BenchmarkDotNet.Attributes;
+
+#nullable enable
+
+namespace Avalonia.Benchmarks.Styling
+{
+    [MemoryDiagnoser]
+    public class Style_ClassSelector
+    {
+        private Style _style = null!;
+
+        public Style_ClassSelector()
+        {
+            RuntimeHelpers.RunClassConstructor(typeof(TestClass).TypeHandle);
+        }
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            _style = new Style(x => x.OfType<TestClass>().Class("foo"))
+            {
+                Setters = { new Setter(TestClass.StringProperty, "foo") }
+            };
+        }
+
+        [Benchmark(OperationsPerInvoke = 50)]
+        public void Apply()
+        {
+            var target = new TestClass();
+
+            target.BeginBatchUpdate();
+
+            for (var i = 0; i < 50; ++i)
+                _style.TryAttach(target, null);
+
+            target.EndBatchUpdate();
+        }
+
+        [Benchmark(OperationsPerInvoke = 50)]
+        public void Apply_Toggle()
+        {
+            var target = new TestClass();
+
+            target.BeginBatchUpdate();
+
+            for (var i = 0; i < 50; ++i)
+                _style.TryAttach(target, null);
+
+            target.EndBatchUpdate();
+
+            target.Classes.Add("foo");
+            target.Classes.Remove("foo");
+        }
+
+        [Benchmark(OperationsPerInvoke = 50)]
+        public void Apply_Detach()
+        {
+            var target = new TestClass();
+
+            target.BeginBatchUpdate();
+
+            for (var i = 0; i < 50; ++i)
+                _style.TryAttach(target, null);
+
+            target.EndBatchUpdate();
+
+            target.DetachStyles();
+        }
+
+        private class TestClass : Control
+        {
+            public static readonly StyledProperty<string?> StringProperty =
+                AvaloniaProperty.Register<TestClass, string?>("String");
+            public void DetachStyles() => InvalidateStyles();
+        }
+
+        private class TestClass2 : Control
+        {
+        }
+    }
+}

+ 1 - 1
tests/Avalonia.LeakTests/ControlTests.cs

@@ -314,7 +314,7 @@ namespace Avalonia.LeakTests
 
                 // The TextBox should have subscriptions to its Classes collection from the
                 // default theme.
-                Assert.NotEmpty(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
+                Assert.NotEqual(0, textBox.Classes.ListenerCount);
 
                 // Clear the content and ensure the TextBox is removed.
                 window.Content = null;

+ 11 - 0
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@@ -637,6 +637,17 @@ namespace Avalonia.Markup.UnitTests.Data
             Assert.Equal("baz", source["Foo"]);
         }
 
+        [Fact]
+        public void Binding_To_Types_Should_Work()
+        {
+            var type = typeof(string);
+            var textBlock = new TextBlock() { DataContext = type };
+            using (textBlock.Bind(TextBlock.TextProperty, new Binding("Name")))
+            {
+                Assert.Equal("String", textBlock.Text);
+            };
+        }
+
         private class StyledPropertyClass : AvaloniaObject
         {
             public static readonly StyledProperty<double> DoubleValueProperty =

+ 221 - 4
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@@ -3,22 +3,23 @@ using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Globalization;
+using System.Linq;
 using System.Reactive.Subjects;
-using System.Text;
 using System.Threading.Tasks;
 using Avalonia.Controls;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
+using Avalonia.Data;
 using Avalonia.Data.Converters;
 using Avalonia.Data.Core;
 using Avalonia.Input;
 using Avalonia.Markup.Data;
+using Avalonia.Markup.Xaml.MarkupExtensions;
+using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
 using Avalonia.Markup.Xaml.Templates;
 using Avalonia.Media;
 using Avalonia.Metadata;
 using Avalonia.UnitTests;
-using JetBrains.Annotations;
-using XamlX;
 using Xunit;
 
 namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
@@ -102,6 +103,91 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
                 Assert.Equal(dataContext.StringProperty, textBlock.Text);
             }
         }
+        
+        [Fact]
+        public void ResolvesPathPassedByPropertyWithInnerItemTemplate()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:TestDataContext'>
+    <ItemsControl Name='itemsControl' Items='{CompiledBinding Path=ListProperty}'>
+	    <ItemsControl.ItemTemplate>
+		    <DataTemplate>
+			    <TextBlock />
+		    </DataTemplate>
+	    </ItemsControl.ItemTemplate>
+    </ItemsControl>
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var textBlock = window.FindControl<ItemsControl>("itemsControl");
+
+                var dataContext = new TestDataContext
+                {
+                    ListProperty =
+                    {
+                        "Hello"
+                    } 
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.ListProperty, textBlock.Items);
+            }
+        }
+        
+        [Fact]
+        public void ResolvesDataTypeFromBindingProperty()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <TextBlock Text='{CompiledBinding StringProperty, DataType=local:TestDataContext}' Name='textBlock' />
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    StringProperty = "foobar"
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.StringProperty, textBlock.Text);
+            }
+        }
+        
+        [Fact]
+        public void ResolvesDataTypeFromBindingProperty_TypeExtension()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
+    <TextBlock Text='{CompiledBinding StringProperty, DataType={x:Type local:TestDataContext}}' Name='textBlock' />
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    StringProperty = "foobar"
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.StringProperty, textBlock.Text);
+            }
+        }
 
         [Fact]
         public void ResolvesStreamTaskBindingCorrectly()
@@ -641,6 +727,64 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
             }
         }
 
+        [Fact]
+        public void ResolvesRelativeSourceBindingFromTemplate()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<ContentControl xmlns='https://github.com/avaloniaui'
+                Content='Hello'>
+    <ContentControl.Styles>
+        <Style Selector='ContentControl'>
+            <Setter Property='Template'>
+                <ControlTemplate>
+                    <ContentPresenter Content='{CompiledBinding Content, RelativeSource={RelativeSource TemplatedParent}}' />
+                </ControlTemplate>
+            </Setter>
+        </Style>
+    </ContentControl.Styles>
+</ContentControl>";
+
+                var contentControl = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
+                contentControl.Measure(new Size(10, 10));
+                
+                var result = contentControl.GetTemplateChildren().OfType<ContentPresenter>().First();
+                Assert.Equal("Hello", result.Content);
+            }
+        }
+        
+        [Fact]
+        public void ResolvesElementNameInTemplate()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<ContentControl xmlns='https://github.com/avaloniaui'
+                Content='Hello'>
+    <ContentControl.Styles>
+        <Style Selector='ContentControl'>
+            <Setter Property='Template'>
+                <ControlTemplate>
+                    <Panel>
+                        <TextBox Name='InnerTextBox' Text='Hello' />
+                        <ContentPresenter Content='{CompiledBinding Text, ElementName=InnerTextBox}' />
+                    </Panel>
+                </ControlTemplate>
+            </Setter>
+        </Style>
+    </ContentControl.Styles>
+</ContentControl>";
+
+                var contentControl = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
+                contentControl.Measure(new Size(10, 10));
+                
+                var result = contentControl.GetTemplateChildren().OfType<ContentPresenter>().First();
+                
+                Assert.Equal("Hello", result.Content);
+            }
+        }
+        
         [Fact]
         public void Binds_To_Source()
         {
@@ -1145,6 +1289,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
                 Assert.Equal("bar-" + typeof(TestDataContext).FullName, textBlock.Text);
             }
         }
+        
+        [Fact]
+        public void SupportCastToTypeInExpressionWithProperty_ExplicitPropertyCast()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'>
+    <ContentControl Content='{CompiledBinding $parent.((local:IHasExplicitProperty)DataContext).ExplicitProperty}' Name='contentControl' />
+</Window>";
+                var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var contentControl = window.GetControl<ContentControl>("contentControl");
+
+                var dataContext = new TestDataContext();
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(((IHasExplicitProperty)dataContext).ExplicitProperty, contentControl.Content);
+            }
+        }
 
         [Fact]
         public void Binds_To_Self_Without_DataType()
@@ -1366,6 +1532,43 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
             }
         }
 
+        [Fact]
+        public void ResolvesDataTypeForAssignBinding()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<local:AssignBindingControl xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:TestDataContext'
+        X='{CompiledBinding StringProperty}' />";
+                var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var compiledPath = ((CompiledBindingExtension)control.X).Path;
+
+                var node = Assert.IsType<PropertyElement>(Assert.Single(compiledPath.Elements));
+                Assert.Equal(typeof(string), node.Property.PropertyType);
+            }
+        }
+        
+        [Fact]
+        public void ResolvesDataTypeForAssignBinding_FromBindingProperty()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<local:AssignBindingControl xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />";
+                var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+                var compiledPath = ((CompiledBindingExtension)control.X).Path;
+
+                var node = Assert.IsType<PropertyElement>(Assert.Single(compiledPath.Elements));
+                Assert.Equal(typeof(string), node.Property.PropertyType);
+            }
+        }
+        
         void Throws(string type, Action cb)
         {
             try
@@ -1410,6 +1613,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
     public interface IHasPropertyDerived : IHasProperty
     { }
 
+    public interface IHasExplicitProperty
+    {
+        string ExplicitProperty { get; }
+    }
+
     public class AppendConverter : IValueConverter
     {
         public static IValueConverter Instance { get; } = new AppendConverter();
@@ -1429,7 +1637,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
 
     public class TestDataContextBaseClass {}
     
-    public class TestDataContext : TestDataContextBaseClass, IHasPropertyDerived
+    public class TestDataContext : TestDataContextBaseClass, IHasPropertyDerived, IHasExplicitProperty
     {
         public string StringProperty { get; set; }
 
@@ -1449,6 +1657,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
 
         public INonIntegerIndexerDerived NonIntegerIndexerInterfaceProperty => NonIntegerIndexerProperty;
 
+        string IHasExplicitProperty.ExplicitProperty => "Hello"; 
+
+        public string ExplicitProperty => "Bye"; 
+
         public class NonIntegerIndexer : NotifyingBase, INonIntegerIndexerDerived
         {
             private readonly Dictionary<string, string> _storage = new Dictionary<string, string>();
@@ -1534,4 +1746,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
     }
     
     public class CustomDataTemplateInherit : CustomDataTemplate { }
+
+    public class AssignBindingControl : Control
+    {
+        [AssignBinding] public IBinding X { get; set; }
+    }
 }