Browse Source

Merge pull request #2734 from jkoritzinsky/compiled-bindings

Compiled bindings
danwalmsley 5 years ago
parent
commit
6c02a6e44b
60 changed files with 5111 additions and 786 deletions
  1. 3 2
      samples/BindingDemo/MainWindow.xaml
  2. 2 2
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  3. 7 1
      src/Avalonia.Base/AvaloniaProperty.cs
  4. 73 0
      src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
  5. 23 9
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  6. 14 0
      src/Avalonia.Base/Data/Core/IPropertyInfo.cs
  7. 1 1
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  8. 21 11
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  9. 91 0
      src/Avalonia.Base/Data/Core/PropertyPath.cs
  10. 28 9
      src/Avalonia.Base/Data/Core/StreamNode.cs
  11. 20 0
      src/Avalonia.Base/Utilities/CharacterReader.cs
  12. 46 0
      src/Avalonia.Base/Utilities/KeywordParser.cs
  13. 13 2
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  14. 12 3
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  15. 1 0
      src/Avalonia.Styling/Styling/Setter.cs
  16. 4 24
      src/Avalonia.Themes.Fluent/ProgressBar.xaml
  17. 22 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  18. 79 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  19. 71 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs
  20. 233 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  21. 52 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs
  22. 30 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs
  23. 225 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
  24. 33 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs
  25. 53 0
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs
  26. 3 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
  27. 9 3
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  28. 24 7
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  29. 26 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  30. 1 1
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  31. 48 75
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  32. 0 20
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs
  33. 75 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
  34. 332 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  35. 33 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  36. 24 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
  37. 192 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  38. 2 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
  39. 253 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
  40. 72 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
  41. 23 12
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  42. 58 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs
  43. 54 25
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  44. 36 16
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  45. 660 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs
  46. 158 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
  47. 118 0
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs
  48. 6 0
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  49. 16 254
      src/Markup/Avalonia.Markup/Data/Binding.cs
  50. 289 0
      src/Markup/Avalonia.Markup/Data/BindingBase.cs
  51. 4 1
      src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs
  52. 387 0
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  53. 45 298
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  54. 1 1
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs
  55. 1 1
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs
  56. 1 1
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/SelfNode.cs
  57. 233 0
      src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
  58. 103 0
      tests/Avalonia.Base.UnitTests/Data/Core/PropertyPathGrammarTests.cs
  59. 621 0
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  60. 46 0
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

+ 3 - 2
samples/BindingDemo/MainWindow.xaml

@@ -5,7 +5,8 @@
         xmlns:local="clr-namespace:BindingDemo"
         Title="AvaloniaUI Bindings Test"
         Width="800"
-        Height="600">
+        Height="600"
+        x:DataType="vm:MainWindowViewModel">
   <Window.Styles>
     <Style Selector="TextBlock.h1">
       <Setter Property="FontSize" Value="18"/>
@@ -60,7 +61,7 @@
             <TextBlock FontSize="16" Text="Scheduler"/>
             <TextBox Watermark="Background Thread" Text="{Binding CurrentTime, Mode=OneWay}"/>
             <TextBlock FontSize="16" Text="Stream Operator"/>
-            <TextBox Watermark="StreamOperator" Text="{Binding CurrentTimeObservable^, Mode=OneWay}"/>
+            <TextBox Watermark="StreamOperator" Text="{CompiledBinding CurrentTimeObservable^, Mode=OneWay}"/>
           </StackPanel>
         </StackPanel>
       </StackPanel>

+ 2 - 2
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@@ -53,7 +53,7 @@ namespace BindingDemo.ViewModels
             });
 
             CurrentTimeObservable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1))
-                .Select(x => DateTimeOffset.Now.ToString());
+                .Select(x => DateTimeOffset.Now);
         }
 
         public ObservableCollection<TestItem> Items { get; }
@@ -90,7 +90,7 @@ namespace BindingDemo.ViewModels
             private set { this.RaiseAndSetIfChanged(ref _currentTime, value); }
         }
 
-        public IObservable<string> CurrentTimeObservable { get; }
+        public IObservable<DateTimeOffset> CurrentTimeObservable { get; }
         public ReactiveCommand<object, Unit> StringValueCommand { get; }
 
         public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();

+ 7 - 1
src/Avalonia.Base/AvaloniaProperty.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Reactive.Subjects;
 using Avalonia.Data;
+using Avalonia.Data.Core;
 using Avalonia.Utilities;
 
 namespace Avalonia
@@ -9,7 +10,7 @@ namespace Avalonia
     /// <summary>
     /// Base class for avalonia properties.
     /// </summary>
-    public abstract class AvaloniaProperty : IEquatable<AvaloniaProperty>
+    public abstract class AvaloniaProperty : IEquatable<AvaloniaProperty>, IPropertyInfo
     {
         /// <summary>
         /// Represents an unset property value.
@@ -582,6 +583,11 @@ namespace Avalonia
 
             return _defaultMetadata;
         }
+
+        bool IPropertyInfo.CanGet => true;
+        bool IPropertyInfo.CanSet => true;
+        object IPropertyInfo.Get(object target) => ((AvaloniaObject)target).GetValue(this);
+        void IPropertyInfo.Set(object target, object value) => ((AvaloniaObject)target).SetValue(this, value);
     }
 
     /// <summary>

+ 73 - 0
src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Avalonia.Data.Core
+{
+    public class ClrPropertyInfo : IPropertyInfo
+    {
+        private readonly Func<object, object> _getter;
+        private readonly Action<object, object> _setter;
+
+        public ClrPropertyInfo(string name, Func<object, object> getter, Action<object, object> setter, Type propertyType)
+        {
+            _getter = getter;
+            _setter = setter;
+            PropertyType = propertyType;
+            Name = name;
+        }
+
+        public string Name { get; }
+        public Type PropertyType { get; }
+
+        public object Get(object target)
+        {
+            if (_getter == null)
+                throw new NotSupportedException("Property " + Name + " doesn't have a getter");
+            return _getter(target);
+        }
+
+        public void Set(object target, object value)
+        {
+            if (_setter == null)
+                throw new NotSupportedException("Property " + Name + " doesn't have a setter");
+            _setter(target, value);
+        }
+
+        public bool CanSet => _setter != null;
+        public bool CanGet => _getter != null;
+    }
+
+    public class ReflectionClrPropertyInfo : ClrPropertyInfo
+    {
+        static Action<object, object> CreateSetter(PropertyInfo info)
+        {
+            if (info.SetMethod == null)
+                return null;
+            var target = Expression.Parameter(typeof(object), "target");
+            var value = Expression.Parameter(typeof(object), "value");
+            return Expression.Lambda<Action<object, object>>(
+                    Expression.Call(Expression.Convert(target, info.DeclaringType), info.SetMethod,
+                        Expression.Convert(value, info.SetMethod.GetParameters()[0].ParameterType)),
+                    target, value)
+                .Compile();
+        }
+        
+        static Func<object, object> CreateGetter(PropertyInfo info)
+        {
+            if (info.GetMethod == null)
+                return null;
+            var target = Expression.Parameter(typeof(object), "target");
+            return Expression.Lambda<Func<object, object>>(
+                    Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType), info.GetMethod),
+                        typeof(object)))
+                .Compile();
+        }
+
+        public ReflectionClrPropertyInfo(PropertyInfo info) : base(info.Name,
+            CreateGetter(info), CreateSetter(info), info.PropertyType)
+        {
+            
+        }
+    }
+}

+ 23 - 9
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@@ -54,6 +54,7 @@ namespace Avalonia.Data.Core
         private object _root;
         private IDisposable _rootSubscription;
         private WeakReference<object> _value;
+        private IReadOnlyList<ITransformNode> _transformNodes;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@@ -188,6 +189,24 @@ namespace Avalonia.Data.Core
                 description ?? expression.ToString());
         }
 
+        private IReadOnlyList<ITransformNode> GetTransformNodesFromChain()
+        {
+            LinkedList<ITransformNode> transforms = new LinkedList<ITransformNode>();
+            var node = _node;
+            while (node != null)
+            {
+                if (node is ITransformNode transform)
+                {
+                    transforms.AddFirst(transform);
+                }
+                node = node.Next;
+            }
+
+            return new List<ITransformNode>(transforms);
+        }
+
+        private IReadOnlyList<ITransformNode> TransformNodes => (_transformNodes ?? (_transformNodes = GetTransformNodesFromChain()));
+
         /// <summary>
         /// Attempts to set the value of a property expression.
         /// </summary>
@@ -203,18 +222,13 @@ namespace Avalonia.Data.Core
         {
             if (Leaf is SettableNode settable)
             {
-                var node = _node;
-                while (node != null)
+                foreach (var transform in TransformNodes)
                 {
-                    if (node is ITransformNode transform)
+                    value = transform.Transform(value);
+                    if (value is BindingNotification)
                     {
-                        value = transform.Transform(value);
-                        if (value is BindingNotification)
-                        {
-                            return false;
-                        }
+                        return false;
                     }
-                    node = node.Next;
                 }
                 return settable.SetTargetValue(value, priority);
             }

+ 14 - 0
src/Avalonia.Base/Data/Core/IPropertyInfo.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Avalonia.Data.Core
+{
+    public interface IPropertyInfo
+    {
+        string Name { get; }
+        object Get(object target);
+        void Set(object target, object value);
+        bool CanSet { get; }
+        bool CanGet { get; }
+        Type PropertyType { get; }
+    }
+}

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

@@ -59,7 +59,7 @@ namespace Avalonia.Data.Core.Plugins
             return Observable.Empty<object>();
         }
 
-        protected IObservable<object> HandleCompleted(Task task)
+        private IObservable<object> HandleCompleted(Task task)
         {
             var resultProperty = task.GetType().GetRuntimeProperty("Result");
             

+ 21 - 11
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@@ -7,6 +7,7 @@ namespace Avalonia.Data.Core
     public class PropertyAccessorNode : SettableNode
     {
         private readonly bool _enableValidation;
+        private IPropertyAccessorPlugin _customPlugin;
         private IPropertyAccessor _accessor;
 
         public PropertyAccessorNode(string propertyName, bool enableValidation)
@@ -15,6 +16,13 @@ namespace Avalonia.Data.Core
             _enableValidation = enableValidation;
         }
 
+        public PropertyAccessorNode(string propertyName, bool enableValidation, IPropertyAccessorPlugin customPlugin)
+        {
+            PropertyName = propertyName;
+            _enableValidation = enableValidation;
+            _customPlugin = customPlugin;
+        }
+
         public override string Description => PropertyName;
         public string PropertyName { get; }
         public override Type PropertyType => _accessor?.PropertyType;
@@ -37,17 +45,7 @@ namespace Avalonia.Data.Core
         {
             reference.TryGetTarget(out object target);
 
-            IPropertyAccessorPlugin plugin = null;
-
-            foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
-            {
-                if (x.Match(target, PropertyName))
-                {
-                    plugin = x;
-                    break;
-                }
-            }
-
+            var plugin = _customPlugin ?? GetPropertyAccessorPluginForObject(target);
             var accessor = plugin?.Start(reference, PropertyName);
 
             // We need to handle accessor fallback before handling validation. Validators do not support null accessors.
@@ -82,6 +80,18 @@ namespace Avalonia.Data.Core
             accessor.Subscribe(ValueChanged);
         }
 
+        private IPropertyAccessorPlugin GetPropertyAccessorPluginForObject(object target)
+        {
+            foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
+            {
+                if (x.Match(target, PropertyName))
+                {
+                    return x;
+                }
+            }
+            return null;
+        }
+
         protected override void StopListeningCore()
         {
             _accessor.Dispose();

+ 91 - 0
src/Avalonia.Base/Data/Core/PropertyPath.cs

@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.Data.Core
+{
+    public class PropertyPath
+    {
+        public IReadOnlyList<IPropertyPathElement> Elements { get; }
+
+        public PropertyPath(IEnumerable<IPropertyPathElement> elements)
+        {
+            Elements = elements.ToList();
+        }
+    }
+
+    public class PropertyPathBuilder
+    {
+        readonly List<IPropertyPathElement> _elements = new List<IPropertyPathElement>();
+        
+        public PropertyPathBuilder Property(IPropertyInfo property)
+        {
+            _elements.Add(new PropertyPropertyPathElement(property));
+            return this;
+        }
+        
+
+        public PropertyPathBuilder ChildTraversal()
+        {
+            _elements.Add(new ChildTraversalPropertyPathElement());
+            return this;
+        }
+
+        public PropertyPathBuilder EnsureType(Type type)
+        {
+            _elements.Add(new EnsureTypePropertyPathElement(type));
+            return this;
+        }
+
+        public PropertyPathBuilder Cast(Type type)
+        {
+            _elements.Add(new CastTypePropertyPathElement(type));
+            return this;
+        }
+
+        public PropertyPath Build()
+        {
+            return new PropertyPath(_elements);
+        }
+    }
+
+    public interface IPropertyPathElement
+    {
+        
+    }
+
+    public class PropertyPropertyPathElement : IPropertyPathElement
+    {
+        public IPropertyInfo Property { get; }
+
+        public PropertyPropertyPathElement(IPropertyInfo property)
+        {
+            Property = property;
+        }
+    }
+
+    public class ChildTraversalPropertyPathElement : IPropertyPathElement
+    {
+        
+    }
+
+    public class EnsureTypePropertyPathElement : IPropertyPathElement
+    {
+        public Type Type { get; }
+
+        public EnsureTypePropertyPathElement(Type type)
+        {
+            Type = type;
+        }
+    }
+
+    public class CastTypePropertyPathElement : IPropertyPathElement
+    {
+        public CastTypePropertyPathElement(Type type)
+        {
+            Type = type;
+        }
+
+        public Type Type { get; }
+    }
+}

+ 28 - 9
src/Avalonia.Base/Data/Core/StreamNode.cs

@@ -1,35 +1,54 @@
 using System;
 using System.Reactive.Linq;
+using Avalonia.Data.Core.Plugins;
 
 namespace Avalonia.Data.Core
 {
     public class StreamNode : ExpressionNode
     {
+        private IStreamPlugin _customPlugin = null;
         private IDisposable _subscription;
 
         public override string Description => "^";
 
+        public StreamNode() { }
+
+        public StreamNode(IStreamPlugin customPlugin)
+        {
+            _customPlugin = customPlugin;
+        }
+
         protected override void StartListeningCore(WeakReference<object> reference)
         {
+            GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged);
+        }
+
+        protected override void StopListeningCore()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+        }
+
+        private IStreamPlugin GetPlugin(WeakReference<object> reference)
+        {
+            if (_customPlugin != null)
+            {
+                return _customPlugin;
+            }
+
             foreach (var plugin in ExpressionObserver.StreamHandlers)
             {
                 if (plugin.Match(reference))
                 {
-                    _subscription = plugin.Start(reference).Subscribe(ValueChanged);
-                    return;
+                    return plugin;
                 }
             }
 
-            // TODO: Improve error.
+            // TODO: Improve error
             ValueChanged(new BindingNotification(
                 new MarkupBindingChainException("Stream operator applied to unsupported type", Description),
                 BindingErrorType.Error));
-        }
-
-        protected override void StopListeningCore()
-        {
-            _subscription?.Dispose();
-            _subscription = null;
+            return null;
         }
     }
 }

+ 20 - 0
src/Avalonia.Base/Utilities/CharacterReader.cs

@@ -79,5 +79,25 @@ namespace Avalonia.Utilities
             Position += len;
             return span;
         }
+
+        public ReadOnlySpan<char> TryPeek(int count)
+        {
+            if (_s.Length < count)
+                return ReadOnlySpan<char>.Empty;
+            return _s.Slice(0, count);
+        }
+
+        public ReadOnlySpan<char> PeekWhitespace()
+        {
+            var trimmed = _s.TrimStart();
+            return _s.Slice(0, _s.Length - trimmed.Length);
+        }
+
+        public void Skip(int count)
+        {
+            if (_s.Length < count)
+                throw new IndexOutOfRangeException();
+            _s = _s.Slice(count);
+        }
     }
 }

+ 46 - 0
src/Avalonia.Base/Utilities/KeywordParser.cs

@@ -0,0 +1,46 @@
+using System;
+
+namespace Avalonia.Utilities
+{
+#if !BUILDTASK
+    public
+#endif
+    static class KeywordParser
+    {
+        public static bool CheckKeyword(this ref CharacterReader r, string keyword)
+        {
+            return (CheckKeywordInternal(ref r, keyword) >= 0);
+        }
+        
+        static int CheckKeywordInternal(this ref CharacterReader r, string keyword)
+        {
+            var ws = r.PeekWhitespace();
+
+            var chars = r.TryPeek(ws.Length + keyword.Length);
+            if (chars.IsEmpty)
+                return -1;
+            if (SpanEquals(chars.Slice(ws.Length), keyword.AsSpan()))
+                return chars.Length;
+            return -1;
+        }
+
+        static bool SpanEquals(ReadOnlySpan<char> left, ReadOnlySpan<char> right)
+        {
+            if (left.Length != right.Length)
+                return false;
+            for(var c=0; c<left.Length;c++)
+                if (left[c] != right[c])
+                    return false;
+            return true;
+        }
+
+        public static bool TakeIfKeyword(this ref CharacterReader r, string keyword)
+        {
+            var l = CheckKeywordInternal(ref r, keyword);
+            if (l < 0)
+                return false;
+            r.Skip(l);
+            return true;
+        }
+    }
+}

+ 13 - 2
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -30,6 +30,9 @@
       <Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
+      <Compile Include="../Markup/Avalonia.Markup\Markup\Parsers\PropertyPathGrammar.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
       <Compile Include="../Markup/Avalonia.Markup.Xaml/Parsers/PropertyParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
@@ -38,14 +41,22 @@
       </Compile>
       <Compile Include="../Avalonia.Base/Utilities/CharacterReader.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
-      </Compile>      
+      </Compile>
       <Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
+      <Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
+      <Compile Include="../Avalonia.Base/Utilities/KeywordParser.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
+      <Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
       <Compile Include="../Avalonia.Base/Utilities/StyleClassParser.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
-
       <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
       <PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />

+ 12 - 3
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@@ -51,13 +51,22 @@ namespace Avalonia.Build.Tasks
             if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0)
                 // Nothing to do
                 return new CompileResult(true);
-            
+
+            var clrPropertiesDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlHelpers",
+                TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+            asm.MainModule.Types.Add(clrPropertiesDef);
+            var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
+                TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+            asm.MainModule.Types.Add(indexerAccessorClosure);
+
             var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
-            var compilerConfig = new TransformerConfiguration(typeSystem,
+            var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
                 typeSystem.TargetAssembly,
                 xamlLanguage,
                 XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
-                AvaloniaXamlIlLanguage.CustomValueConverter);
+                AvaloniaXamlIlLanguage.CustomValueConverter,
+                new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
+                new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)));
 
 
             var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext", 

+ 1 - 0
src/Avalonia.Styling/Styling/Setter.cs

@@ -1,6 +1,7 @@
 using System;
 using Avalonia.Animation;
 using Avalonia.Data;
+using Avalonia.Data.Core;
 using Avalonia.Metadata;
 using Avalonia.Utilities;
 

+ 4 - 24
src/Avalonia.Themes.Fluent/ProgressBar.xaml

@@ -142,35 +142,15 @@
     </Style.Animations>
   </Style>
   <Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator">
-    <Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
-    <Setter Property="RenderTransform">
-      <Setter.Value>
-        <TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
-      </Setter.Value>
-    </Setter>
+    <Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />    
   </Style>
   <Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator2">
-    <Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
-    <Setter Property="RenderTransform">
-      <Setter.Value>
-        <TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
-      </Setter.Value>
-    </Setter>
+    <Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />    
   </Style>
   <Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator">
-    <Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
-    <Setter Property="RenderTransform">
-      <Setter.Value>
-        <TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
-      </Setter.Value>
-    </Setter>
+    <Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />    
   </Style>
   <Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator2">
-    <Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
-    <Setter Property="RenderTransform">
-      <Setter.Value>
-        <TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
-      </Setter.Value>
-    </Setter>
+    <Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />    
   </Style>
 </Styles>

+ 22 - 2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -16,6 +16,14 @@
         <Compile Include="Converters\TimeSpanTypeConverter.cs" />
         <Compile Include="Extensions.cs" />
         <Compile Include="MarkupExtension.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindingExtension.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindings\ArrayElementPlugin.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindings\CompiledBindingPath.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindings\FindVisualAncestorNode.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindings\ObservableStreamPlugin.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorFactory.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorPlugin.cs" />
+        <Compile Include="MarkupExtensions\CompiledBindings\TaskStreamPlugin.cs" />
         <Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
         <Compile Include="MarkupExtensions\ResourceInclude.cs" />
         <Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
@@ -25,7 +33,7 @@
         <Compile Include="Converters\IconTypeConverter.cs" />
         <Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" />
         <Compile Include="Converters\PointsListTypeConverter.cs" />
-        <Compile Include="MarkupExtensions\BindingExtension.cs" />
+        <Compile Include="MarkupExtensions\ReflectionBindingExtension.cs" />
         <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
         <Compile Include="Properties\AssemblyInfo.cs" />
         <Compile Include="Styling\StyleInclude.cs" />
@@ -37,13 +45,21 @@
         <Compile Include="Templates\TemplateContent.cs" />
         <Compile Include="Templates\TreeDataTemplate.cs" />
         <Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionHackTransformer.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompilerConfiguration.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlAvaloniaPropertyResolver.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathParser.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlConstructorServiceProviderTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDataContextTypeTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDesignPropertiesTransformer.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlCompiledBindingsMetadataRemover.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlMetadataRemover.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlPropertyPathTransformer.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlRootObjectScopeTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformInstanceAttachedProperties.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlWellKnownTypes.cs" />
         <Compile Include="XamlIl\CompilerExtensions\XamlIlAvaloniaPropertyHelper.cs" />
@@ -54,6 +70,9 @@
         <Compile Include="XamlIl\CompilerExtensions\Transformers\IgnoredDirectivesTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlSelectorTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\XNameTransformer.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\XamlIlBindingPathHelper.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\XamlIlClrPropertyInfoHelper.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\XamlIlPropertyInfoAccessorFactoryEmitter.cs" />
         <Compile Include="XamlIl\Runtime\IAvaloniaXamlIlParentStackProvider.cs" />
         <Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
         <Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
@@ -62,6 +81,7 @@
         <Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlX.Il.Cecil\**\*.cs" />
         <Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" />
         <Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
+        <Compile Include="..\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs" />
         <Compile Include="XamlTypes.cs" />
     </ItemGroup>
   <ItemGroup>

+ 79 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@@ -0,0 +1,79 @@
+using System;
+using Avalonia.Data;
+using Avalonia.Controls;
+using Avalonia.Styling;
+using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
+using Avalonia.Data.Core;
+using Avalonia.Markup.Parsers;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions
+{
+    public class CompiledBindingExtension : BindingBase
+    {
+        public CompiledBindingExtension()
+        {
+            Path = new CompiledBindingPath();
+        }
+
+        public CompiledBindingExtension(CompiledBindingPath path)
+        {
+            Path = path;
+        }
+
+        public CompiledBindingExtension ProvideValue(IServiceProvider provider)
+        {
+            return new CompiledBindingExtension
+            {
+                Path = Path,
+                Converter = Converter,
+                FallbackValue = FallbackValue,
+                Mode = Mode,
+                Priority = Priority,
+                StringFormat = StringFormat,
+                DefaultAnchor = new WeakReference(GetDefaultAnchor(provider))
+            };
+        }
+
+        private static object GetDefaultAnchor(IServiceProvider provider)
+        {
+            // If the target is not a control, so we need to find an anchor that will let us look
+            // up named controls and style resources. First look for the closest IControl in
+            // the context.
+            object anchor = provider.GetFirstParent<IControl>();
+
+            // If a control was not found, then try to find the highest-level style as the XAML
+            // file could be a XAML file containing only styles.
+            return anchor ??
+                    provider.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
+                    provider.GetLastParent<IStyle>();
+        }
+
+        protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
+        {
+            if (Path.RawSource != null)
+            {
+                return CreateSourceObserver(
+                    Path.RawSource,
+                    Path.BuildExpression(enableDataValidation));
+            }
+
+            if (Path.SourceMode == SourceMode.Data)
+            {
+                return CreateDataContextObserver(
+                    target,
+                    Path.BuildExpression(enableDataValidation),
+                    targetProperty == StyledElement.DataContextProperty,
+                    anchor);
+            }
+            else
+            {
+                return CreateSourceObserver(
+                    (target as IStyledElement) ?? (anchor as IStyledElement),
+                    Path.BuildExpression(enableDataValidation));
+            }
+        }
+
+        [ConstructorArgument("path")]
+        public CompiledBindingPath Path { get; set; }
+    }
+}

+ 71 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ArrayElementPlugin.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Data;
+using Avalonia.Data.Core.Plugins;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
+{
+    class ArrayElementPlugin : IPropertyAccessorPlugin
+    {
+        private readonly int[] _indices;
+        private readonly Type _elementType;
+
+        public ArrayElementPlugin(int[] indices, Type elementType)
+        {
+            _indices = indices;
+            _elementType = elementType;
+        }
+
+        public bool Match(object obj, string propertyName)
+        {
+            throw new InvalidOperationException("The ArrayElementPlugin does not support dynamic matching");
+        }
+
+        public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
+        {
+            if (reference.TryGetTarget(out var target) && target is Array arr)
+            {
+                return new Accessor(new WeakReference<Array>(arr), _indices, _elementType);
+            }
+            return null;
+        }
+
+        class Accessor : PropertyAccessorBase
+        {
+            private readonly int[] _indices;
+            private readonly WeakReference<Array> _reference;
+
+            public Accessor(WeakReference<Array> reference, int[] indices, Type elementType)
+            {
+                _reference = reference;
+                _indices = indices;
+                PropertyType = elementType;
+            }
+
+            public override Type PropertyType { get; }
+
+            public override object Value => _reference.TryGetTarget(out var arr) ? arr.GetValue(_indices) : null;
+
+            public override bool SetValue(object value, BindingPriority priority)
+            {
+                if (_reference.TryGetTarget(out var arr))
+                {
+                    arr.SetValue(value, _indices);
+                    return true;
+                }
+                return false;
+            }
+
+            protected override void SubscribeCore()
+            {
+                PublishValue(Value);
+            }
+
+            protected override void UnsubscribeCore()
+            {
+            }
+        }
+    }
+
+}

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

@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Data.Core;
+using Avalonia.Data.Core.Plugins;
+using Avalonia.Markup.Parsers;
+using Avalonia.Markup.Parsers.Nodes;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
+{
+    public class CompiledBindingPath
+    {
+        private readonly List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
+
+        public CompiledBindingPath() { }
+
+        internal CompiledBindingPath(IEnumerable<ICompiledBindingPathElement> bindingPath, object rawSource)
+        {
+            _elements = new List<ICompiledBindingPathElement>(bindingPath);
+            RawSource = rawSource;
+        }
+
+        public ExpressionNode BuildExpression(bool enableValidation)
+        {
+            ExpressionNode pathRoot = null;
+            ExpressionNode path = null;
+            foreach (var element in _elements)
+            {
+                ExpressionNode node = null;
+                switch (element)
+                {
+                    case NotExpressionPathElement _:
+                        node = new LogicalNotNode();
+                        break;
+                    case PropertyElement prop:
+                        node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory));
+                        break;
+                    case ArrayElementPathElement arr:
+                        node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType));
+                        break;
+                    case VisualAncestorPathElement visualAncestor:
+                        node = new FindVisualAncestorNode(visualAncestor.AncestorType, visualAncestor.Level);
+                        break;
+                    case AncestorPathElement ancestor:
+                        node = new FindAncestorNode(ancestor.AncestorType, ancestor.Level);
+                        break;
+                    case SelfPathElement _:
+                        node = new SelfNode();
+                        break;
+                    case ElementNameElement name:
+                        node = new ElementNameNode(name.NameScope, name.Name);
+                        break;
+                    case IStronglyTypedStreamElement stream:
+                        node = new StreamNode(stream.CreatePlugin());
+                        break;
+                    default:
+                        throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}");
+                }
+
+                path = pathRoot is null ? (pathRoot = node) : path.Next = node;
+            }
+
+            return pathRoot ?? new EmptyExpressionNode();
+        }
+
+        internal SourceMode SourceMode => _elements.Count > 0 && _elements[0] is IControlSourceBindingPathElement ? SourceMode.Control : SourceMode.Data;
+
+        internal object RawSource { get; }
+    }
+
+    public class CompiledBindingPathBuilder
+    {
+        private object _rawSource;
+        private List<ICompiledBindingPathElement> _elements = new List<ICompiledBindingPathElement>();
+
+        public CompiledBindingPathBuilder Not()
+        {
+            _elements.Add(new NotExpressionPathElement());
+            return this;
+        }
+
+        public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
+        {
+            _elements.Add(new PropertyElement(info, accessorFactory));
+            return this;
+        }
+
+        public CompiledBindingPathBuilder StreamTask<T>()
+        {
+            _elements.Add(new TaskStreamPathElement<T>());
+            return this;
+        }
+
+        public CompiledBindingPathBuilder StreamObservable<T>()
+        {
+            _elements.Add(new ObservableStreamPathElement<T>());
+            return this;
+        }
+
+        public CompiledBindingPathBuilder Self()
+        {
+            _elements.Add(new SelfPathElement());
+            return this;
+        }
+
+        public CompiledBindingPathBuilder Ancestor(Type ancestorType, int level)
+        {
+            _elements.Add(new AncestorPathElement(ancestorType, level));
+            return this;
+        }
+        public CompiledBindingPathBuilder VisualAncestor(Type ancestorType, int level)
+        {
+            _elements.Add(new VisualAncestorPathElement(ancestorType, level));
+            return this;
+        }
+
+        public CompiledBindingPathBuilder ElementName(INameScope nameScope, string name)
+        {
+            _elements.Add(new ElementNameElement(nameScope, name));
+            return this;
+        }
+
+        public CompiledBindingPathBuilder ArrayElement(int[] indices, Type elementType)
+        {
+            _elements.Add(new ArrayElementPathElement(indices, elementType));
+            return this;
+        }
+
+        public CompiledBindingPathBuilder SetRawSource(object rawSource)
+        {
+            _rawSource = rawSource;
+            return this;
+        }
+
+        public CompiledBindingPath Build() => new CompiledBindingPath(_elements, _rawSource);
+    }
+
+    public interface ICompiledBindingPathElement
+    {
+    }
+
+    internal interface IControlSourceBindingPathElement { }
+
+    internal class NotExpressionPathElement : ICompiledBindingPathElement
+    {
+        public static readonly NotExpressionPathElement Instance = new NotExpressionPathElement();
+    }
+
+    internal class PropertyElement : ICompiledBindingPathElement
+    {
+        public PropertyElement(IPropertyInfo property, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
+        {
+            Property = property;
+            AccessorFactory = accessorFactory;
+        }
+
+        public IPropertyInfo Property { get; }
+
+        public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
+    }
+
+    internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement
+    {
+        IStreamPlugin CreatePlugin();
+    }
+
+    internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement
+    {
+        public static readonly TaskStreamPathElement<T> Instance = new TaskStreamPathElement<T>();
+
+        public IStreamPlugin CreatePlugin() => new TaskStreamPlugin<T>();
+    }
+
+    internal class ObservableStreamPathElement<T> : IStronglyTypedStreamElement
+    {
+        public static readonly ObservableStreamPathElement<T> Instance = new ObservableStreamPathElement<T>();
+
+        public IStreamPlugin CreatePlugin() => new ObservableStreamPlugin<T>();
+    }
+
+    internal class SelfPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
+    {
+        public static readonly SelfPathElement Instance = new SelfPathElement();
+    }
+
+    internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
+    {
+        public AncestorPathElement(Type ancestorType, int level)
+        {
+            AncestorType = ancestorType;
+            Level = level;
+        }
+
+        public Type AncestorType { get; }
+        public int Level { get; }
+    }
+
+    internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
+    {
+        public VisualAncestorPathElement(Type ancestorType, int level)
+        {
+            AncestorType = ancestorType;
+            Level = level;
+        }
+
+        public Type AncestorType { get; }
+        public int Level { get; }
+    }
+
+    internal class ElementNameElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
+    {
+        public ElementNameElement(INameScope nameScope, string name)
+        {
+            NameScope = nameScope;
+            Name = name;
+        }
+
+        public INameScope NameScope { get; }
+        public string Name { get; }
+    }
+
+    internal class ArrayElementPathElement : ICompiledBindingPathElement
+    {
+        public ArrayElementPathElement(int[] indices, Type elementType)
+        {
+            Indices = indices;
+            ElementType = elementType;
+        }
+
+        public int[] Indices { get; }
+        public Type ElementType { get; }
+    }
+}

+ 52 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs

@@ -0,0 +1,52 @@
+using System;
+using Avalonia.Data.Core;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
+{
+    class FindVisualAncestorNode : ExpressionNode
+    {
+        private readonly int _level;
+        private readonly Type _ancestorType;
+        private IDisposable _subscription;
+
+        public FindVisualAncestorNode(Type ancestorType, int level)
+        {
+            _level = level;
+            _ancestorType = ancestorType;
+        }
+
+        public override string Description
+        {
+            get
+            {
+                if (_ancestorType == null)
+                {
+                    return $"$visualparent[{_level}]";
+                }
+                else
+                {
+                    return $"$visualparent[{_ancestorType.Name}, {_level}]";
+                }
+            }
+        }
+
+        protected override void StartListeningCore(WeakReference<object> reference)
+        {
+            if (reference.TryGetTarget(out object target) && target is IVisual visual)
+            {
+                _subscription = VisualLocator.Track(visual, _level, _ancestorType).Subscribe(ValueChanged);
+            }
+            else
+            {
+                _subscription = null;
+            }
+        }
+
+        protected override void StopListeningCore()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+        }
+    }
+}

+ 30 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using System.Text;
+using Avalonia.Data.Core.Plugins;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
+{
+    class ObservableStreamPlugin<T> : IStreamPlugin
+    {
+        public bool Match(WeakReference<object> reference)
+        {
+            return reference.TryGetTarget(out var target) && target is IObservable<T>;
+        }
+
+        public IObservable<object> Start(WeakReference<object> reference)
+        {
+            if (!(reference.TryGetTarget(out var target) && target is IObservable<T> obs))
+            {
+                return Observable.Empty<object>();
+            }
+            else if (target is IObservable<object> obj)
+            {
+                return obj;
+            }
+
+            return obs.Select(x => (object)x);
+        }
+    }
+}

+ 225 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs

@@ -0,0 +1,225 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Text;
+using Avalonia.Data;
+using Avalonia.Data.Core;
+using Avalonia.Data.Core.Plugins;
+using Avalonia.Utilities;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
+{
+    public static class PropertyInfoAccessorFactory
+    {
+        public static IPropertyAccessor CreateInpcPropertyAccessor(WeakReference<object> target, IPropertyInfo property)
+            => new InpcPropertyAccessor(target, property);
+
+        public static IPropertyAccessor CreateAvaloniaPropertyAccessor(WeakReference<object> target, IPropertyInfo property)
+            => new AvaloniaPropertyAccessor(new WeakReference<AvaloniaObject>((AvaloniaObject)(target.TryGetTarget(out var o) ? o : null)), (AvaloniaProperty)property);
+
+        public static IPropertyAccessor CreateIndexerPropertyAccessor(WeakReference<object> target, IPropertyInfo property, int argument)
+            => new IndexerAccessor(target, property, argument);
+    }
+
+    internal class AvaloniaPropertyAccessor : PropertyAccessorBase
+    {
+        private readonly WeakReference<AvaloniaObject> _reference;
+        private readonly AvaloniaProperty _property;
+        private IDisposable _subscription;
+
+        public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject> reference, AvaloniaProperty property)
+        {
+            Contract.Requires<ArgumentNullException>(reference != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            _reference = reference;
+            _property = property;
+        }
+
+        public AvaloniaObject Instance
+        {
+            get
+            {
+                _reference.TryGetTarget(out var result);
+                return result;
+            }
+        }
+
+        public override Type PropertyType => _property.PropertyType;
+        public override object Value => Instance?.GetValue(_property);
+
+        public override bool SetValue(object value, BindingPriority priority)
+        {
+            if (!_property.IsReadOnly)
+            {
+                Instance.SetValue(_property, value, priority);
+                return true;
+            }
+
+            return false;
+        }
+
+        protected override void SubscribeCore()
+        {
+            _subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
+        }
+
+        protected override void UnsubscribeCore()
+        {
+            _subscription?.Dispose();
+            _subscription = null;
+        }
+    }
+
+    internal class InpcPropertyAccessor : PropertyAccessorBase
+    {
+        protected readonly WeakReference<object> _reference;
+        private readonly IPropertyInfo _property;
+
+        public InpcPropertyAccessor(WeakReference<object> reference, IPropertyInfo property)
+        {
+            Contract.Requires<ArgumentNullException>(reference != null);
+            Contract.Requires<ArgumentNullException>(property != null);
+
+            _reference = reference;
+            _property = property;
+        }
+
+        public override Type PropertyType => _property.PropertyType;
+
+        public override object Value
+        {
+            get
+            {
+                return _reference.TryGetTarget(out var o) ? _property.Get(o) : null;
+            }
+        }
+
+        public override bool SetValue(object value, BindingPriority priority)
+        {
+            if (_property.CanSet && _reference.TryGetTarget(out var o))
+            {
+                _property.Set(o, value);
+
+                SendCurrentValue();
+
+                return true;
+            }
+
+            return false;
+        }
+
+        void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
+            {
+                SendCurrentValue();
+            }
+        }
+
+        protected override void SubscribeCore()
+        {
+            SendCurrentValue();
+            SubscribeToChanges();
+        }
+
+        protected override void UnsubscribeCore()
+        {
+            if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
+            {
+                WeakEventHandlerManager.Unsubscribe<PropertyChangedEventArgs, InpcPropertyAccessor>(
+                    inpc,
+                    nameof(INotifyPropertyChanged.PropertyChanged),
+                    OnNotifyPropertyChanged);
+            }
+        }
+
+        protected void SendCurrentValue()
+        {
+            try
+            {
+                var value = Value;
+                PublishValue(value);
+            }
+            catch { }
+        }
+
+        private void SubscribeToChanges()
+        {
+            if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
+            {
+                WeakEventHandlerManager.Subscribe<INotifyPropertyChanged, PropertyChangedEventArgs, InpcPropertyAccessor>(
+                    inpc,
+                    nameof(INotifyPropertyChanged.PropertyChanged),
+                    OnNotifyPropertyChanged);
+            }
+        }
+    }
+
+    internal class IndexerAccessor : InpcPropertyAccessor
+    {
+        private int _index;
+
+        public IndexerAccessor(WeakReference<object> target, IPropertyInfo basePropertyInfo, int argument)
+            :base(target, basePropertyInfo)
+        {
+            _index = argument;
+        }
+
+
+        protected override void SubscribeCore()
+        {
+            base.SubscribeCore();
+            if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
+            {
+                WeakEventHandlerManager.Subscribe<INotifyCollectionChanged, NotifyCollectionChangedEventArgs, IndexerAccessor>(
+                  incc,
+                  nameof(INotifyCollectionChanged.CollectionChanged),
+                  OnNotifyCollectionChanged);
+            }
+        }
+
+        protected override void UnsubscribeCore()
+        {
+            base.UnsubscribeCore();
+            if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
+            {
+                WeakEventHandlerManager.Unsubscribe<NotifyCollectionChangedEventArgs, IndexerAccessor>(
+                  incc,
+                  nameof(INotifyCollectionChanged.CollectionChanged),
+                  OnNotifyCollectionChanged);
+            }
+        }
+
+        void OnNotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+        {
+            if (ShouldNotifyListeners(args))
+            {
+                SendCurrentValue();
+            }
+        }
+
+        bool ShouldNotifyListeners(NotifyCollectionChangedEventArgs e)
+        {
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    return _index >= e.NewStartingIndex;
+                case NotifyCollectionChangedAction.Remove:
+                    return _index >= e.OldStartingIndex;
+                case NotifyCollectionChangedAction.Replace:
+                    return _index >= e.NewStartingIndex &&
+                           _index < e.NewStartingIndex + e.NewItems.Count;
+                case NotifyCollectionChangedAction.Move:
+                    return (_index >= e.NewStartingIndex &&
+                            _index < e.NewStartingIndex + e.NewItems.Count) ||
+                           (_index >= e.OldStartingIndex &&
+                            _index < e.OldStartingIndex + e.OldItems.Count);
+                case NotifyCollectionChangedAction.Reset:
+                    return true;
+            }
+            return false;
+        }
+    }
+}

+ 33 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorPlugin.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using Avalonia.Data;
+using Avalonia.Data.Core;
+using Avalonia.Data.Core.Plugins;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
+{
+    class PropertyInfoAccessorPlugin : IPropertyAccessorPlugin
+    {
+        private readonly IPropertyInfo _propertyInfo;
+        private readonly Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> _accessorFactory;
+
+        public PropertyInfoAccessorPlugin(IPropertyInfo propertyInfo, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
+        {
+            _propertyInfo = propertyInfo;
+            _accessorFactory = accessorFactory;
+        }
+
+        public bool Match(object obj, string propertyName)
+        {
+            throw new InvalidOperationException("The PropertyInfoAccessorPlugin does not support dynamic matching");
+        }
+
+        public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
+        {
+            Debug.Assert(_propertyInfo.Name == propertyName);
+            return _accessorFactory(reference, _propertyInfo);
+        }
+    }
+}

+ 53 - 0
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Threading.Tasks;
+using Avalonia.Data;
+using Avalonia.Data.Core.Plugins;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
+{
+    class TaskStreamPlugin<T> : IStreamPlugin
+    {
+        public bool Match(WeakReference<object> reference)
+        {
+            return reference.TryGetTarget(out var target) && target is Task<T>;
+        }
+
+        public IObservable<object> Start(WeakReference<object> reference)
+        {
+            if(!(reference.TryGetTarget(out var target) && target is Task<T> task))
+            {
+                return Observable.Empty<object>();
+            }
+
+            switch (task.Status)
+            {
+                case TaskStatus.RanToCompletion:
+                case TaskStatus.Faulted:
+                    return HandleCompleted(task);
+                default:
+                    var subject = new Subject<object>();
+                    task.ContinueWith(
+                            x => HandleCompleted(task).Subscribe(subject),
+                            TaskScheduler.FromCurrentSynchronizationContext())
+                        .ConfigureAwait(false);
+                    return subject;
+            }
+        }
+        
+        
+        private static IObservable<object> HandleCompleted(Task<T> task)
+        {
+            switch (task.Status)
+            {
+                case TaskStatus.RanToCompletion:
+                    return Observable.Return((object)task.Result);
+                case TaskStatus.Faulted:
+                    return Observable.Return(new BindingNotification(task.Exception, BindingErrorType.Error));
+                default:
+                    throw new AvaloniaInternalException("HandleCompleted called for non-completed Task.");
+            }
+        }
+    }
+}

+ 3 - 3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs → src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs

@@ -9,13 +9,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
     using Avalonia.Styling;
     using System.ComponentModel;
 
-    public class BindingExtension
+    public class ReflectionBindingExtension
     {
-        public BindingExtension()
+        public ReflectionBindingExtension()
         {
         }
 
-        public BindingExtension(string path)
+        public ReflectionBindingExtension(string path)
         {
             Path = path;
         }

+ 9 - 3
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@@ -119,12 +119,17 @@ namespace Avalonia.Markup.Xaml.XamlIl
 
             InitializeSre();
             var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly);
+            var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
+            var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));
+            var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
 
-            var compiler = new AvaloniaXamlIlCompiler(new TransformerConfiguration(_sreTypeSystem, asm,
-                    _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter),
+            var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
+                _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
+                new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
+                new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))), 
                 _sreEmitMappings,
                 _sreContextType) { EnableIlVerification = true };
-            var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
+            
 
             IXamlType overrideType = null;
             if (rootInstance != null)
@@ -135,6 +140,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
             compiler.IsDesignMode = isDesignMode;
             compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
             var created = tb.CreateTypeInfo();
+            clrPropertyBuilder.CreateTypeInfo();
 
             return LoadOrPopulate(created, rootInstance);
         }

+ 24 - 7
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@@ -17,6 +17,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
         private readonly TransformerConfiguration _configuration;
         private readonly IXamlType _contextType;
         private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
+        private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
 
         private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
             : base(configuration, emitMappings, true)
@@ -35,32 +36,42 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             Transformers.Insert(0, new XNameTransformer());
             Transformers.Insert(1, new IgnoredDirectivesTransformer());
             Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
-            Transformers.Insert(3, new AvaloniaBindingExtensionHackTransformer());
+            Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
             
             
             // Targeted
 
-            InsertBefore<PropertyReferenceResolver>(new AvaloniaXamlIlTransformInstanceAttachedProperties());
+            InsertBefore<PropertyReferenceResolver>(
+                new AvaloniaXamlIlTransformInstanceAttachedProperties(),
+                new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
             InsertAfter<PropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver());
             
 
 
             InsertBefore<ContentConvertTransformer>(
+                new AvaloniaXamlIlBindingPathParser(),
                 new AvaloniaXamlIlSelectorTransformer(),
-                new AvaloniaXamlIlSetterTransformer(),
                 new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
+                new AvaloniaXamlIlPropertyPathTransformer(),
+                new AvaloniaXamlIlSetterTransformer(),
                 new AvaloniaXamlIlConstructorServiceProviderTransformer(),
                 new AvaloniaXamlIlTransitionsTypeMetadataTransformer()
             );
-            
+
             // After everything else
-            
-            Transformers.Add(new AddNameScopeRegistration());
+            InsertBefore<NewObjectTransformer>(
+                new AddNameScopeRegistration(),
+                new AvaloniaXamlIlDataContextTypeTransformer(),
+                new AvaloniaXamlIlBindingPathTransformer(),
+                new AvaloniaXamlIlCompiledBindingsMetadataRemover()
+                );
+
             Transformers.Add(new AvaloniaXamlIlMetadataRemover());
+            Transformers.Add(new AvaloniaXamlIlRootObjectScope());
 
             Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
+            Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
         }
-
         public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
             XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
             IXamlTypeBuilder<IXamlILEmitter> contextTypeBuilder)
@@ -86,6 +97,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             set => _designTransformer.IsDesignMode = value;
         }
 
+        public bool DefaultCompileBindings
+        {
+            get => _bindingTransformer.CompileBindingsByDefault;
+            set => _bindingTransformer.CompileBindingsByDefault = value;
+        }
+
         public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder<IXamlILEmitter> tb, IXamlType overrideRootType)
         {
             var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>

+ 26 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs

@@ -0,0 +1,26 @@
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
+{
+    class AvaloniaXamlIlCompilerConfiguration : TransformerConfiguration
+    {
+        public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; }
+        public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; }
+
+        public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem, 
+            IXamlAssembly defaultAssembly, 
+            XamlLanguageTypeMappings typeMappings,
+            XamlXmlnsMappings xmlnsMappings,
+            XamlValueConverter customValueConverter,
+            XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
+            XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter)
+            : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter)
+        {
+            ClrPropertyEmitter = clrPropertyEmitter;
+            AccessorFactoryEmitter = accessorFactoryEmitter;
+            AddExtra(ClrPropertyEmitter);
+            AddExtra(AccessorFactoryEmitter);
+        }
+    }
+}

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@@ -58,7 +58,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 
             var emit = new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>
             {
-                ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
+                ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.EmitProvideValueTarget,
                 ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c)
             };
             return (rv, emit);

+ 48 - 75
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs

@@ -1,10 +1,11 @@
 using System;
 using System.Linq;
+using XamlX;
 using XamlX.Ast;
-using XamlX.Emit;
-using XamlX.IL;
 using XamlX.Transform;
 using XamlX.TypeSystem;
+using XamlX.Emit;
+using XamlX.IL;
 
 namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
 {
@@ -12,99 +13,71 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         {
-            if (node is XamlPropertyAssignmentNode pa
-                && pa.Property.Name == "Name"
-                && pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
+            if (node is XamlPropertyAssignmentNode pa)
             {
-                if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
-                    && mg.Children.OfType<AvaloniaNameScopeRegistrationXamlIlNode>().Any())
-                    return node;
-                
-                IXamlAstValueNode value = null;
-                for (var c = 0; c < pa.Values.Count; c++)
-                    if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
-                    {
-                        value = pa.Values[c];
-                        if (!(value is XamlAstTextNode))
+                if (pa.Property.Name == "Name"
+                    && pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
+                {
+                    if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
+                        && mg.Children.OfType<AvaloniaNameScopeRegistrationXamlIlNode>().Any())
+                        return node;
+
+                    IXamlAstValueNode value = null;
+                    for (var c = 0; c < pa.Values.Count; c++)
+                        if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
                         {
-                            var local = new XamlAstCompilerLocalNode(value);
-                            // Wrap original in local initialization
-                            pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
-                            // Use local
-                            value = local;
+                            value = pa.Values[c];
+                            if (!(value is XamlAstTextNode))
+                            {
+                                var local = new XamlAstCompilerLocalNode(value);
+                                // Wrap original in local initialization
+                                pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
+                                // Use local
+                                value = local;
+                            }
+
+                            break;
                         }
 
-                        break;
-                    }
-
-                if (value != null)
-                    return new XamlManipulationGroupNode(pa)
+                    if (value != null)
                     {
-                        Children =
+                        var objectType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault()?.Type.GetClrType();
+                        return new XamlManipulationGroupNode(pa)
                         {
-                            pa,
-                            new AvaloniaNameScopeRegistrationXamlIlNode(value)
-                        }
-                    };
+                            Children =
+                            {
+                                pa,
+                                new AvaloniaNameScopeRegistrationXamlIlNode(value, objectType)
+                            }
+                        };
+                    }
+                }
+                else if (pa.Property.CustomAttributes.Select(attr => attr.Type).Intersect(context.Configuration.TypeMappings.DeferredContentPropertyAttributes).Any())
+                {
+                    pa.Values[pa.Values.Count - 1] =
+                        new NestedScopeMetadataNode(pa.Values[pa.Values.Count - 1]);
+                }
             }
 
-            if (!context.ParentNodes().Any()
-                && node is XamlValueWithManipulationNode mnode)
-            {
-                mnode.Manipulation = new XamlManipulationGroupNode(mnode,
-                    new[]
-                    {
-                        mnode.Manipulation,
-                        new HandleRootObjectScopeNode(mnode, context.GetAvaloniaTypes())
-                    });
-            }
             return node;
         }
+    }
 
-        class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
+    class NestedScopeMetadataNode : XamlValueWithSideEffectNodeBase
+    {
+        public NestedScopeMetadataNode(IXamlAstValueNode value) : base(value, value)
         {
-            private readonly AvaloniaXamlIlWellKnownTypes _types;
-
-            public HandleRootObjectScopeNode(IXamlLineInfo lineInfo,
-                AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
-            {
-                _types = types;
-            }
-
-            public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
-            {
-                var next = codeGen.DefineLabel();
-                var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
-                    f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
-                using (var local = codeGen.LocalsPool.GetLocal(_types.StyledElement))
-                {
-                    codeGen
-                        .Isinst(_types.StyledElement)
-                        .Dup()
-                        .Stloc(local.Local)
-                        .Brfalse(next)
-                        .Ldloc(local.Local)
-                        .Ldloc(context.ContextLocal)
-                        .Ldfld(scopeField)
-                        .EmitCall(_types.NameScopeSetNameScope, true)
-                        .MarkLabel(next)
-                        .Ldloc(context.ContextLocal)
-                        .Ldfld(scopeField)
-                        .EmitCall(_types.INameScopeComplete, true);
-                }
-
-                return XamlILNodeEmitResult.Void(1);
-
-            }
         }
     }
 
     class AvaloniaNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode
     {
         public IXamlAstValueNode Name { get; set; }
+        public IXamlType TargetType { get; }
 
-        public AvaloniaNameScopeRegistrationXamlIlNode(IXamlAstValueNode name) : base(name)
+        public AvaloniaNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType targetType) : base(name)
         {
+            TargetType = targetType;
             Name = name;
         }
 

+ 0 - 20
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs

@@ -1,20 +0,0 @@
-using XamlX.Ast;
-using XamlX.Transform;
-
-namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
-{
-    class AvaloniaBindingExtensionHackTransformer : IXamlAstTransformer
-    {
-        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
-        {
-            // Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `<Foo>` syntax
-            // This is the legacy of Portable.Xaml, so we emulate that behavior here
-
-            if (node is XamlAstXmlTypeReference tref
-                && tref.Name == "Binding"
-                && tref.XmlNamespace == "https://github.com/avaloniaui")
-                tref.IsMarkupExtension = true;
-            return node;
-        }
-    }
-}

+ 75 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs

@@ -0,0 +1,75 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+
+using XamlParseException = XamlX.XamlParseException;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaBindingExtensionTransformer : IXamlAstTransformer
+    {
+        public bool CompileBindingsByDefault { get; set; }
+
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlCompileBindingsNode)
+            {
+                return node;
+            }
+
+            if (node is XamlAstObjectNode obj)
+            {
+                foreach (var item in obj.Children)
+                {
+                    if (item is XamlAstXmlDirective directive)
+                    {
+                        if (directive.Namespace == XamlNamespaces.Xaml2006
+                            && directive.Name == "CompileBindings"
+                            && directive.Values.Count == 1)
+                        {
+                            if (!(directive.Values[0] is XamlAstTextNode text
+                                && bool.TryParse(text.Text, out var compileBindings)))
+                            {
+                                throw new XamlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
+                            }
+
+                            obj.Children.Remove(directive);
+
+                            return new AvaloniaXamlIlCompileBindingsNode(obj, compileBindings);
+                        }
+                    }
+                }
+            }
+
+            // Convert the <Binding> tag to either a CompiledBinding or ReflectionBinding tag.
+
+            if (node is XamlAstXmlTypeReference tref
+                && tref.Name == "Binding"
+                && tref.XmlNamespace == "https://github.com/avaloniaui")
+            {
+                tref.IsMarkupExtension = true;
+
+                var compileBindings = context.ParentNodes()
+                    .OfType<AvaloniaXamlIlCompileBindingsNode>()
+                    .FirstOrDefault()
+                    ?.CompileBindings ?? CompileBindingsByDefault;
+
+                tref.Name = compileBindings ? "CompiledBinding" : "ReflectionBinding";
+            }
+            return node;
+        }
+    }
+
+    internal class AvaloniaXamlIlCompileBindingsNode : XamlValueWithSideEffectNodeBase
+    {
+        public AvaloniaXamlIlCompileBindingsNode(IXamlAstValueNode value, bool compileBindings)
+            : base(value, value)
+        {
+            CompileBindings = compileBindings;
+        }
+
+        public bool CompileBindings { get; }
+    }
+}

+ 332 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@@ -0,0 +1,332 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Avalonia.Markup.Parsers;
+using Avalonia.Utilities;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+using XamlParseException = XamlX.XamlParseException;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaXamlIlBindingPathParser : IXamlAstTransformer
+    {
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (node is XamlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+            {
+                var convertedNode = ConvertLongFormPropertiesToBindingExpressionNode(context, binding);
+
+                if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlAstTextNode bindingPathText)
+                {
+                    var reader = new CharacterReader(bindingPathText.Text.AsSpan());
+                    var (nodes, _) = BindingExpressionGrammar.Parse(ref reader);
+
+                    if (convertedNode != null)
+                    {
+                        nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode);
+                    }
+
+                    binding.Arguments[0] = new ParsedBindingPathNode(bindingPathText, context.GetAvaloniaTypes().CompiledBindingPath, nodes);
+                }
+                else
+                {
+                    var bindingPathAssignment = binding.Children.OfType<XamlAstXamlPropertyValueNode>()
+                        .FirstOrDefault(v => v.Property.GetClrProperty().Name == "Path");
+
+                    if (bindingPathAssignment != null && bindingPathAssignment.Values[0] is XamlAstTextNode pathValue)
+                    {
+                        var reader = new CharacterReader(pathValue.Text.AsSpan());
+                        var (nodes, _) = BindingExpressionGrammar.Parse(ref reader);
+
+                        if (convertedNode != null)
+                        {
+                            nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode);
+                        }
+
+                        bindingPathAssignment.Values[0] = new ParsedBindingPathNode(pathValue, context.GetAvaloniaTypes().CompiledBindingPath, nodes);
+                    }
+                }
+            }
+
+            return node;
+        }
+
+        private static BindingExpressionGrammar.INode ConvertLongFormPropertiesToBindingExpressionNode(
+            AstTransformationContext context,
+            XamlAstObjectNode binding)
+        {
+            BindingExpressionGrammar.INode convertedNode = null;
+
+            var syntheticCompiledBindingProperties = binding.Children.OfType<XamlAstXamlPropertyValueNode>()
+                .Where(v => v.Property is AvaloniaSyntheticCompiledBindingProperty)
+                .ToList();
+
+            var elementNameProperty = syntheticCompiledBindingProperties
+                .FirstOrDefault(v =>
+                    v.Property is AvaloniaSyntheticCompiledBindingProperty prop
+                    && prop.Name == SyntheticCompiledBindingPropertyName.ElementName);
+
+            var sourceProperty = syntheticCompiledBindingProperties
+                .FirstOrDefault(v =>
+                    v.Property is AvaloniaSyntheticCompiledBindingProperty prop
+                    && prop.Name == SyntheticCompiledBindingPropertyName.Source);
+
+            var relativeSourceProperty = syntheticCompiledBindingProperties
+                .FirstOrDefault(v =>
+                    v.Property is AvaloniaSyntheticCompiledBindingProperty prop
+                    && prop.Name == SyntheticCompiledBindingPropertyName.RelativeSource);
+
+            if (elementNameProperty?.Values[0] is XamlAstTextNode elementName)
+            {
+                convertedNode = new BindingExpressionGrammar.NameNode { Name = elementName.Text };
+            }
+            else if (elementNameProperty != null)
+            {
+                throw new XamlParseException($"Invalid ElementName '{elementNameProperty.Values[0]}'.", elementNameProperty.Values[0]);
+            }
+
+            if (sourceProperty?.Values[0] != null)
+            {
+                if (convertedNode != null)
+                {
+                    throw new XamlParseException("Only one of ElementName, Source, or RelativeSource specified as a binding source. Only one property is allowed.", binding);
+                }
+
+                convertedNode = new RawSourceBindingExpressionNode(sourceProperty?.Values[0]);
+            }
+
+            if (GetRelativeSourceObjectFromAssignment(
+                context,
+                relativeSourceProperty,
+                out var relativeSourceObject))
+            {
+                if (convertedNode != null)
+                {
+                    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
+                    .OfType<XamlAstXamlPropertyValueNode>()
+                    .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Mode")
+                    ?.Values[0] is XamlAstTextNode modeAssignedValue ? modeAssignedValue.Text : null;
+                if (relativeSourceObject.Arguments.Count == 0 && mode == null)
+                {
+                    mode = "FindAncestor";
+                }
+
+                if (mode == "FindAncestor")
+                {
+                    var ancestorLevel = relativeSourceObject.Children
+                        .OfType<XamlAstXamlPropertyValueNode>()
+                        .FirstOrDefault(x => x.Property.GetClrProperty().Name == "FindAncestor")
+                        ?.Values[0] is XamlAstTextNode ancestorLevelText ? int.Parse(ancestorLevelText.Text) - 1 : 0;
+
+                    var treeType = relativeSourceObject.Children
+                        .OfType<XamlAstXamlPropertyValueNode>()
+                        .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Tree")
+                        ?.Values[0] is XamlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual";
+
+                    var ancestorTypeName = relativeSourceObject.Children
+                        .OfType<XamlAstXamlPropertyValueNode>()
+                        .FirstOrDefault(x => x.Property.GetClrProperty().Name == "AncestorType")
+                        ?.Values[0] as XamlAstTextNode;
+
+                    IXamlType ancestorType = null;
+                    if (ancestorTypeName is null)
+                    {
+                        if (treeType == "Visual")
+                        {
+                            throw new XamlParseException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.", relativeSourceObject);
+                        }
+                        else if (treeType == "Logical")
+                        {
+                            var styledElementType = context.GetAvaloniaTypes().StyledElement;
+                            ancestorType = context
+                                .ParentNodes()
+                                .OfType<XamlAstObjectNode>()
+                                .Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
+                                .ElementAtOrDefault(ancestorLevel)
+                                ?.Type.GetClrType();
+
+                            if (ancestorType is null)
+                            {
+                                throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", relativeSourceObject);
+                            }
+                        }
+                    }
+                    else
+                    {
+                        ancestorType = TypeReferenceResolver.ResolveType(
+                                            context,
+                                            ancestorTypeName.Text,
+                                            false,
+                                            ancestorTypeName,
+                                            true).GetClrType();
+                    }
+
+                    if (treeType == "Visual")
+                    {
+                        convertedNode = new VisualAncestorBindingExpressionNode
+                        {
+                            Type = ancestorType,
+                            Level = ancestorLevel
+                        };
+                    }
+                    else if (treeType == "Logical")
+                    {
+                        convertedNode = new LogicalAncestorBindingExpressionNode
+                        {
+                            Type = ancestorType,
+                            Level = ancestorLevel
+                        };
+                    }
+                    else
+                    {
+                        throw new XamlParseException($"Unknown tree type '{treeType}'.", binding);
+                    }
+                }
+                else if (mode == "DataContext")
+                {
+                    convertedNode = null;
+                }
+                else if (mode == "Self")
+                {
+                    convertedNode = new BindingExpressionGrammar.SelfNode();
+                }
+                else if (mode == "TemplatedParent")
+                {
+                    var parentType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
+                        .FirstOrDefault(x =>
+                            x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate)
+                        ?.TargetType.GetClrType();
+
+                    if (parentType is null)
+                    {
+                        throw new XamlParseException("A binding with a TemplatedParent RelativeSource has to be in a ControlTemplate.", binding);
+                    }
+
+                    convertedNode = new TemplatedParentBindingExpressionNode { Type = parentType };
+                }
+                else
+                {
+                    throw new XamlParseException($"Unknown RelativeSource mode '{mode}'.", binding);
+                }
+            }
+
+            if (elementNameProperty != null)
+            {
+                binding.Children.Remove(elementNameProperty);
+            }
+            if (sourceProperty != null)
+            {
+                binding.Children.Remove(sourceProperty);
+            }
+            if (relativeSourceProperty != null)
+            {
+                binding.Children.Remove(relativeSourceProperty);
+            }
+
+            return convertedNode;
+        }
+
+        private static bool GetRelativeSourceObjectFromAssignment(
+            AstTransformationContext context,
+            XamlAstXamlPropertyValueNode relativeSourceProperty,
+            out XamlAstObjectNode relativeSourceObject)
+        {
+            relativeSourceObject = null;
+            if (relativeSourceProperty is null)
+            {
+                return false;
+            }
+
+            if (relativeSourceProperty.Values[0] is XamlMarkupExtensionNode me)
+            {
+                if (me.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
+                {
+                    throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{me.Type.GetClrType().GetFqn()}'", me);
+                }
+
+                relativeSourceObject = (XamlAstObjectNode)me.Value;
+                return true;
+            }
+
+            if (relativeSourceProperty.Values[0] is XamlAstObjectNode on)
+            {
+                if (on.Type.GetClrType() != context.GetAvaloniaTypes().RelativeSource)
+                {
+                    throw new XamlParseException($"Expected an object of type 'Avalonia.Data.RelativeSource'. Found a object of type '{on.Type.GetClrType().GetFqn()}'", on);
+                }
+
+                relativeSourceObject = on;
+                return true;
+            }
+
+            return false;
+        }
+    }
+
+    class ParsedBindingPathNode : XamlAstNode, IXamlAstValueNode
+    {
+        public ParsedBindingPathNode(IXamlLineInfo lineInfo, IXamlType compiledBindingType, IList<BindingExpressionGrammar.INode> path)
+            : base(lineInfo)
+        {
+            Type = new XamlAstClrTypeReference(lineInfo, compiledBindingType, false);
+            Path = path;
+        }
+
+        public IXamlAstTypeReference Type { get; }
+
+        public IList<BindingExpressionGrammar.INode> Path { get; }
+
+        public override void VisitChildren(IXamlAstVisitor visitor)
+        {
+            for (int i = 0; i < Path.Count; i++)
+            {
+                if (Path[i] is IXamlAstNode ast)
+                {
+                    Path[i] = (BindingExpressionGrammar.INode)ast.Visit(visitor);
+                }
+            }
+        }
+    }
+
+    class VisualAncestorBindingExpressionNode : BindingExpressionGrammar.INode
+    {
+        public IXamlType Type { get; set; }
+        public int Level { get; set; }
+    }
+
+    class LogicalAncestorBindingExpressionNode : BindingExpressionGrammar.INode
+    {
+        public IXamlType Type { get; set; }
+        public int Level { get; set; }
+    }
+
+    class TemplatedParentBindingExpressionNode : BindingExpressionGrammar.INode
+    {
+        public IXamlType Type { get; set; }
+    }
+
+    class RawSourceBindingExpressionNode : XamlAstNode, BindingExpressionGrammar.INode
+    {
+        public RawSourceBindingExpressionNode(IXamlAstValueNode rawSource)
+            : base(rawSource)
+        {
+            RawSource = rawSource;
+        }
+
+        public IXamlAstValueNode RawSource { get; private set; }
+
+        public override void VisitChildren(IXamlAstVisitor visitor)
+        {
+            RawSource = (IXamlAstValueNode)RawSource.Visit(visitor);
+        }
+    }
+}

+ 33 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaXamlIlBindingPathTransformer : IXamlAstTransformer
+    {
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (node is XamlAstConstructableObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+            {
+                IXamlType startType;
+                var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
+                if (parentDataContextNode is null)
+                {
+                    throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding);
+                }
+
+                startType = parentDataContextNode.DataContextType;
+
+                XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startType);
+            }
+
+            return node;
+        }
+    }
+}

+ 24 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs

@@ -0,0 +1,24 @@
+using System.Linq;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaXamlIlCompiledBindingsMetadataRemover : IXamlAstTransformer
+    {
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            while (true)
+            {
+                if (node is NestedScopeMetadataNode nestedScope)
+                    node = nestedScope.Value;
+                else if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType)
+                    node = dataContextType.Value;
+                else if (node is AvaloniaXamlIlCompileBindingsNode compileBindings)
+                    node = compileBindings.Value;
+                else
+                    return node;
+            }
+        }
+    }
+}

+ 192 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using Avalonia.Markup.Parsers;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using Avalonia.Utilities;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer
+    {
+        private const string AvaloniaNs = "https://github.com/avaloniaui";
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlDataContextTypeMetadataNode)
+            {
+                // We've already resolved the data context type for this node.
+                return node;
+            }
+
+            if (node is XamlAstConstructableObjectNode on)
+            {
+                AvaloniaXamlIlDataContextTypeMetadataNode inferredDataContextTypeNode = null;
+                AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null;
+                bool isDataTemplate = on.Type.GetClrType().Equals(context.GetAvaloniaTypes().DataTemplate);
+
+                for (int i = 0; i < on.Children.Count; ++i)
+                {
+                    var child = on.Children[i];
+                    if (child is XamlAstXmlDirective directive)
+                    {
+                        if (directive.Namespace == XamlNamespaces.Xaml2006
+                            && directive.Name == "DataType"
+                            && directive.Values.Count == 1)
+                        {
+                            on.Children.RemoveAt(i);
+                            i--;
+                            if (directive.Values[0] is XamlAstTextNode text)
+                            {
+                                directiveDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on,
+                                    TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type);
+                            }
+                            else
+                            {
+                                throw new XamlX.XamlParseException("x:DataType should be set to a type name.", directive.Values[0]);
+                            }
+                        }
+                    }
+                    else if (child is XamlPropertyAssignmentNode pa)
+                    {
+                        if (pa.Property.Name == "DataContext"
+                            && pa.Property.DeclaringType.Equals(context.GetAvaloniaTypes().StyledElement)
+                            && pa.Values[0] is XamlMarkupExtensionNode ext
+                            && ext.Value is XamlAstConstructableObjectNode obj)
+                        {
+                            inferredDataContextTypeNode = ParseDataContext(context, on, obj);
+                        }
+                        else if(isDataTemplate
+                            && pa.Property.Name == "DataType"
+                            && pa.Values[0] is XamlTypeExtensionNode dataTypeNode)
+                        {
+                            inferredDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on, dataTypeNode.Value.GetClrType());
+                        }
+                    }
+                }
+
+                // If there is no x:DataType directive,
+                // do more specialized inference
+                if (directiveDataContextTypeNode is null)
+                {
+                    if (isDataTemplate && inferredDataContextTypeNode is null)
+                    {
+                        // Infer data type from collection binding on a control that displays items.
+                        var parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();
+                        if (parentObject != null && context.GetAvaloniaTypes().IItemsPresenterHost.IsDirectlyAssignableFrom(parentObject.Type.GetClrType()))
+                        {
+                            inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject);
+                        }
+                        else
+                        {
+                            inferredDataContextTypeNode = new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+                        }
+                    }
+                }
+
+                return directiveDataContextTypeNode ?? inferredDataContextTypeNode ?? node;
+            }
+
+            return node;
+        }
+
+        private static AvaloniaXamlIlDataContextTypeMetadataNode InferDataContextOfPresentedItem(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode parentObject)
+        {
+            var parentItemsValue = parentObject
+                                            .Children.OfType<XamlPropertyAssignmentNode>()
+                                            .FirstOrDefault(pa => pa.Property.Name == "Items")
+                                            ?.Values[0];
+            if (parentItemsValue is null)
+            {
+                // We can't infer the collection type and the currently calculated type is definitely wrong.
+                // Notify the user that we were unable to infer the data context type if they use a compiled binding.
+                return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+            }
+
+            IXamlType itemsCollectionType = null;
+            if (context.GetAvaloniaTypes().IBinding.IsAssignableFrom(parentItemsValue.Type.GetClrType()))
+            {
+                if (parentItemsValue.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)
+                    && parentItemsValue is XamlMarkupExtensionNode ext && ext.Value is XamlAstConstructableObjectNode parentItemsBinding)
+                {
+                    var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
+                    if (parentItemsDataContext != null)
+                    {
+                        itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, parentItemsDataContext.DataContextType);
+                    }
+                }
+            }
+            else
+            {
+                itemsCollectionType = parentItemsValue.Type.GetClrType();
+            }
+
+            if (itemsCollectionType != null)
+            {
+                var elementType = itemsCollectionType
+                    .GetAllInterfaces()
+                    .FirstOrDefault(i =>
+                        i.GenericTypeDefinition?.Equals(context.Configuration.WellKnownTypes.IEnumerableT) == true)
+                    .GenericArguments[0];
+                return new AvaloniaXamlIlDataContextTypeMetadataNode(on, elementType);
+            }
+            // We can't infer the collection type and the currently calculated type is definitely wrong.
+            // Notify the user that we were unable to infer the data context type if they use a compiled binding.
+            return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+        }
+
+        private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(AstTransformationContext context, XamlAstConstructableObjectNode on, XamlAstConstructableObjectNode obj)
+        {
+            var bindingType = context.GetAvaloniaTypes().IBinding;
+            if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension))
+            {
+                return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType());
+            }
+            else if (obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+            {
+                IXamlType startType;
+                var parentDataContextNode = context.ParentNodes().OfType<AvaloniaXamlIlDataContextTypeMetadataNode>().FirstOrDefault();
+                if (parentDataContextNode is null)
+                {
+                    throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj);
+                }
+
+                startType = parentDataContextNode.DataContextType;
+
+                var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startType);
+                return new AvaloniaXamlIlDataContextTypeMetadataNode(on, bindingResultType);
+            }
+
+            return new AvaloniaXamlIlUninferrableDataContextMetadataNode(on);
+        }
+    }
+
+    [DebuggerDisplay("DataType = {DataContextType}")]
+    class AvaloniaXamlIlDataContextTypeMetadataNode : XamlValueWithSideEffectNodeBase
+    {
+        public virtual IXamlType DataContextType { get; }
+
+        public AvaloniaXamlIlDataContextTypeMetadataNode(IXamlAstValueNode value, IXamlType targetType)
+            : base(value, value)
+        {
+            DataContextType = targetType;
+        }
+    }
+
+    [DebuggerDisplay("DataType = Unknown")]
+    class AvaloniaXamlIlUninferrableDataContextMetadataNode : AvaloniaXamlIlDataContextTypeMetadataNode
+    {
+        public AvaloniaXamlIlUninferrableDataContextMetadataNode(IXamlAstValueNode value)
+            : base(value, null)
+        {
+        }
+
+        public override IXamlType DataContextType => throw new XamlTransformException("Unable to infer DataContext type for compiled bindings nested within this element.", Value);
+    }
+}

+ 2 - 2
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs

@@ -8,8 +8,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
     {
         public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
         {
-            if (node is AvaloniaXamlIlTargetTypeMetadataNode md)
-                return md.Value;
+            if (node is AvaloniaXamlIlTargetTypeMetadataNode targetType)
+                return targetType.Value;
 
             return node;
         }

+ 253 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs

@@ -0,0 +1,253 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Markup.Parsers;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+using XamlX.Emit;
+using XamlX.IL;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaXamlIlPropertyPathTransformer : IXamlAstTransformer
+    {
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (node is XamlAstXamlPropertyValueNode pv
+                && pv.Values.Count == 1
+                && pv.Values[0] is XamlAstTextNode text
+                && pv.Property.GetClrProperty().Getter?.ReturnType
+                    .Equals(context.GetAvaloniaTypes().PropertyPath) == true
+            )
+            {
+                var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
+                    .FirstOrDefault();
+                if(parentScope == null)
+                    throw new XamlX.XamlParseException("No target type scope found for property path", text);
+                if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
+                    throw new XamlX.XamlParseException("PropertyPath is currently only valid for styles", pv);
+
+
+                IEnumerable<PropertyPathGrammar.ISyntax> parsed;
+                try
+                {
+                    parsed = PropertyPathGrammar.Parse(text.Text);
+                }
+                catch (Exception e)
+                {
+                    throw new XamlX.XamlParseException("Unable to parse PropertyPath: " + e.Message, text);
+                }
+
+                var elements = new List<IXamlIlPropertyPathElementNode>();
+                IXamlType currentType = parentScope.TargetType.GetClrType();
+                
+                
+                var expectProperty = true;
+                var expectCast = true;
+                var expectTraversal = false;
+                var types = context.GetAvaloniaTypes();
+                
+                IXamlType GetType(string ns, string name)
+                {
+                    return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
+                        text, true).GetClrType();
+                }
+
+                void HandleProperty(string name, string typeNamespace, string typeName)
+                {
+                    if(!expectProperty || currentType == null)
+                        throw new XamlX.XamlParseException("Unexpected property node", text);
+
+                    var propertySearchType =
+                        typeName != null ? GetType(typeNamespace, typeName) : currentType;
+
+                    IXamlIlPropertyPathElementNode prop = null;
+                    var avaloniaPropertyFieldName = name + "Property";
+                    var avaloniaPropertyField = propertySearchType.GetAllFields().FirstOrDefault(f =>
+                        f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldName);
+                    if (avaloniaPropertyField != null)
+                    {
+                        prop = new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyField,
+                            XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, types, text));
+                    }
+                    else
+                    {
+                        var clrProperty = propertySearchType.GetAllProperties().FirstOrDefault(p => p.Name == name);
+                        prop = new XamlIClrPropertyPathElementNode(clrProperty);
+                    }
+
+                    if (prop == null)
+                        throw new XamlX.XamlParseException(
+                            $"Unable to resolve property {name} on type {propertySearchType.GetFqn()}",
+                            text);
+                    
+                    currentType = prop.Type;
+                    elements.Add(prop);
+                    expectProperty = false;
+                    expectTraversal = expectCast = true;
+                }
+                
+                foreach (var ge in parsed)
+                {
+                    if (ge is PropertyPathGrammar.ChildTraversalSyntax)
+                    {
+                        if (!expectTraversal)
+                            throw new XamlX.XamlParseException("Unexpected child traversal .", text);
+                        elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
+                        expectTraversal = expectCast = false;
+                        expectProperty = true;
+                    }
+                    else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
+                    {
+                        if(!expectCast)
+                            throw new XamlX.XamlParseException("Unexpected cast node", text);
+                        currentType = GetType(ets.TypeNamespace, ets.TypeName);
+                        elements.Add(new XamlIlCastPropertyPathElementNode(currentType, true));
+                        expectProperty = false;
+                        expectCast = expectTraversal = true;
+                    }
+                    else if (ge is PropertyPathGrammar.CastTypeSyntax cts)
+                    {
+                        if(!expectCast)
+                            throw new XamlX.XamlParseException("Unexpected cast node", text);
+                        //TODO: Check if cast can be done
+                        currentType = GetType(cts.TypeNamespace, cts.TypeName);
+                        elements.Add(new XamlIlCastPropertyPathElementNode(currentType, false));
+                        expectProperty = false;
+                        expectCast = expectTraversal = true;
+                    }
+                    else if (ge is PropertyPathGrammar.PropertySyntax ps)
+                    {
+                        HandleProperty(ps.Name, null, null);
+                    }
+                    else if (ge is PropertyPathGrammar.TypeQualifiedPropertySyntax tqps)
+                    {
+                        HandleProperty(tqps.Name, tqps.TypeNamespace, tqps.TypeName);
+                    }
+                    else
+                        throw new XamlX.XamlParseException("Unexpected node " + ge, text);
+                    
+                }
+                var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
+                if (propertyPathNode.Type == null)
+                    throw new XamlX.XamlParseException("Unexpected end of the property path", text);
+                pv.Values[0] = propertyPathNode;
+            }
+
+            return node;
+        }
+
+        interface IXamlIlPropertyPathElementNode
+        {
+            void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen);
+            IXamlType Type { get; }
+        }
+
+        class XamlIlChildTraversalPropertyPathElementNode : IXamlIlPropertyPathElementNode
+        {
+            public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
+                => codeGen.EmitCall(
+                    context.GetAvaloniaTypes()
+                        .PropertyPathBuilder.FindMethod(m => m.Name == "ChildTraversal"));
+
+            public IXamlType Type => null;
+        }
+        
+        class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlPropertyPathElementNode
+        {
+            private readonly IXamlField _field;
+
+            public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlField field, IXamlType propertyType)
+            {
+                _field = field;
+                Type = propertyType;
+            }
+
+            public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
+                => codeGen
+                    .Ldsfld(_field)
+                    .EmitCall(context.GetAvaloniaTypes()
+                        .PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
+
+            public IXamlType Type { get; }
+        }
+        
+        class XamlIClrPropertyPathElementNode : IXamlIlPropertyPathElementNode
+        {
+            private readonly IXamlProperty _property;
+
+            public XamlIClrPropertyPathElementNode(IXamlProperty property)
+            {
+                _property = property;
+            }
+
+            public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
+            {
+                context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
+                    .Emit(context, codeGen, _property);
+
+                codeGen.EmitCall(context.GetAvaloniaTypes()
+                    .PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
+            }
+
+            public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
+        }
+
+        class XamlIlCastPropertyPathElementNode : IXamlIlPropertyPathElementNode
+        {
+            private readonly IXamlType _type;
+            private readonly bool _ensureType;
+
+            public XamlIlCastPropertyPathElementNode(IXamlType type, bool ensureType)
+            {
+                _type = type;
+                _ensureType = ensureType;
+            }
+            
+            public void Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
+            {
+                codeGen
+                    .Ldtype(_type)
+                    .EmitCall(context.GetAvaloniaTypes()
+                        .PropertyPathBuilder.FindMethod(m => m.Name == (_ensureType ? "EnsureType" : "Cast")));
+            }
+
+            public IXamlType Type => _type;
+        }
+
+        class XamlIlPropertyPathNode : XamlAstNode, IXamlIlPropertyPathNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
+        {
+            private readonly List<IXamlIlPropertyPathElementNode> _elements;
+            private readonly AvaloniaXamlIlWellKnownTypes _types;
+
+            public XamlIlPropertyPathNode(IXamlLineInfo lineInfo,
+                List<IXamlIlPropertyPathElementNode> elements,
+                AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
+            {
+                _elements = elements;
+                _types = types;
+                Type = new XamlAstClrTypeReference(this, types.PropertyPath, false);
+            }
+
+            public IXamlAstTypeReference Type { get; }
+            public IXamlType PropertyType => _elements.LastOrDefault()?.Type;
+            public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
+            {
+                codeGen
+                    .Newobj(_types.PropertyPathBuilder.FindConstructor());
+                foreach(var e in _elements)
+                    e.Emit(context, codeGen);
+                codeGen.EmitCall(_types.PropertyPathBuilder.FindMethod(m => m.Name == "Build"));
+                return XamlILNodeEmitResult.Type(0, _types.PropertyPath);
+            }
+        }
+    }
+
+    interface IXamlIlPropertyPathNode : IXamlAstValueNode
+    {
+        IXamlType PropertyType { get; }
+    }
+}

+ 72 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+using XamlX.IL;
+using XamlX.Emit;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaXamlIlRootObjectScope : IXamlAstTransformer
+    {
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (!context.ParentNodes().Any()
+                && node is XamlValueWithManipulationNode mnode)
+            {
+                mnode.Manipulation = new XamlManipulationGroupNode(mnode,
+                    new[]
+                    {
+                        mnode.Manipulation,
+                        new HandleRootObjectScopeNode(mnode, context.GetAvaloniaTypes())
+                    });
+            }
+            return node;
+        }
+        class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode
+        {
+            private readonly AvaloniaXamlIlWellKnownTypes _types;
+
+            public HandleRootObjectScopeNode(IXamlLineInfo lineInfo,
+                AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
+            {
+                _types = types;
+            }
+        }
+        internal class Emitter : IXamlILAstNodeEmitter
+        {
+            public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
+            {
+                if (!(node is HandleRootObjectScopeNode))
+                {
+                    return null;
+                }
+                var types = context.GetAvaloniaTypes();
+                
+                var next = codeGen.DefineLabel();
+                var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
+                    f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
+                using (var local = codeGen.LocalsPool.GetLocal(types.StyledElement))
+                {
+                    codeGen
+                        .Isinst(types.StyledElement)
+                        .Dup()
+                        .Stloc(local.Local)
+                        .Brfalse(next)
+                        .Ldloc(local.Local)
+                        .Ldloc(context.ContextLocal)
+                        .Ldfld(scopeField)
+                        .EmitCall(types.NameScopeSetNameScope, true)
+                        .MarkLabel(next)
+                        .Ldloc(context.ContextLocal)
+                        .Ldfld(scopeField)
+                        .EmitCall(types.INameScopeComplete, true);
+                }
+
+                return XamlILNodeEmitResult.Void(1);
+            }
+        }
+    }
+}

+ 23 - 12
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using Avalonia.Data.Core;
 using XamlX;
 using XamlX.Ast;
 using XamlX.Emit;
@@ -37,29 +38,39 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
                 throw new XamlParseException(
                     "Can not resolve parent Style Selector type", node);
 
-
+            IXamlType propType = null;
             var property = @on.Children.OfType<XamlAstXamlPropertyValueNode>()
                 .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
-            if (property == null)
-                throw new XamlParseException("Setter without a property is not valid", node);
+            if (property != null)
+            {
 
-            var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
-            if (propertyName == null)
-                throw new XamlParseException("Setter.Property must be a string", node);
+                var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
+                if (propertyName == null)
+                    throw new XamlParseException("Setter.Property must be a string", node);
 
 
-            var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
-                new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
-            property.Values = new List<IXamlAstValueNode>
+                var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
+                    new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
+                property.Values = new List<IXamlAstValueNode> {avaloniaPropertyNode};
+                propType = avaloniaPropertyNode.AvaloniaPropertyType;
+            }
+            else
             {
-                avaloniaPropertyNode
-            };
+                var propertyPath = on.Children.OfType<XamlAstXamlPropertyValueNode>()
+                    .FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
+                if (propertyPath == null)
+                    throw new XamlX.XamlParseException("Setter without a property or property path is not valid", node);
+                if (propertyPath.Values[0] is IXamlIlPropertyPathNode ppn
+                    && ppn.PropertyType != null)
+                    propType = ppn.PropertyType;
+                else
+                    throw new XamlX.XamlParseException("Unable to get the property path property type", node);
+            }
 
             var valueProperty = on.Children
                 .OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
             if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlAstTextNode)
             {
-                var propType = avaloniaPropertyNode.AvaloniaPropertyType;
                 if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
                         propType, out var converted))
                     throw new XamlParseException(

+ 58 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformSyntheticCompiledBindingMembers.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaXamlIlTransformSyntheticCompiledBindingMembers : IXamlAstTransformer
+    {
+        public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+        {
+            if (node is XamlAstNamePropertyReference prop
+               && prop.TargetType is XamlAstClrTypeReference targetRef
+               && targetRef.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
+            {
+                if (prop.Name == "ElementName")
+                {
+                    return new AvaloniaSyntheticCompiledBindingProperty(node,
+                        SyntheticCompiledBindingPropertyName.ElementName);
+                }
+                else if (prop.Name == "RelativeSource")
+                {
+                    return new AvaloniaSyntheticCompiledBindingProperty(node,
+                        SyntheticCompiledBindingPropertyName.RelativeSource);
+                }
+                else if (prop.Name == "Source")
+                {
+                    return new AvaloniaSyntheticCompiledBindingProperty(node,
+                        SyntheticCompiledBindingPropertyName.Source);
+                }
+            }
+
+            return node;
+        }
+    }
+
+    enum SyntheticCompiledBindingPropertyName
+    {
+        ElementName,
+        RelativeSource,
+        Source
+    }
+
+    class AvaloniaSyntheticCompiledBindingProperty : XamlAstNode, IXamlAstPropertyReference
+    {
+        public SyntheticCompiledBindingPropertyName Name { get; }
+
+        public AvaloniaSyntheticCompiledBindingProperty(
+            IXamlLineInfo lineInfo,
+            SyntheticCompiledBindingPropertyName name)
+            : base(lineInfo)
+        {
+            Name = name;
+        }
+    }
+}

+ 54 - 25
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@@ -28,45 +28,74 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         public IXamlType INameScope { get; }
         public IXamlMethod INameScopeRegister { get; }
         public IXamlMethod INameScopeComplete { get; }
-        
-        public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration config)
+        public IXamlType IPropertyInfo { get; }
+        public IXamlType ClrPropertyInfo { get; }
+        public IXamlType PropertyPath { get; }
+        public IXamlType PropertyPathBuilder { get; }
+        public IXamlType IPropertyAccessor { get; }
+        public IXamlType PropertyInfoAccessorFactory { get; }
+        public IXamlType CompiledBindingPathBuilder { get; }
+        public IXamlType CompiledBindingPath { get; }
+        public IXamlType CompiledBindingExtension { get; }
+        public IXamlType DataTemplate { get; }
+        public IXamlType IItemsPresenterHost { get; }
+        public IXamlType ReflectionBindingExtension { get; }
+
+        public IXamlType RelativeSource { get; }
+
+        public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
         {
-            XamlIlTypes = config.WellKnownTypes;
-            XamlIlMappings = config.TypeMappings;
-            AvaloniaObject = config.TypeSystem.GetType("Avalonia.AvaloniaObject");
-            IAvaloniaObject = config.TypeSystem.GetType("Avalonia.IAvaloniaObject");
-            AvaloniaObjectExtensions = config.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
-            AvaloniaProperty = config.TypeSystem.GetType("Avalonia.AvaloniaProperty");
-            AvaloniaPropertyT = config.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
-            BindingPriority = config.TypeSystem.GetType("Avalonia.Data.BindingPriority");
-            IBinding = config.TypeSystem.GetType("Avalonia.Data.IBinding");
-            IDisposable = config.TypeSystem.GetType("System.IDisposable");
-            Transitions = config.TypeSystem.GetType("Avalonia.Animation.Transitions");
-            AssignBindingAttribute = config.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
+            XamlIlTypes = cfg.WellKnownTypes;
+            AvaloniaObject = cfg.TypeSystem.GetType("Avalonia.AvaloniaObject");
+            IAvaloniaObject = cfg.TypeSystem.GetType("Avalonia.IAvaloniaObject");
+            AvaloniaObjectExtensions = cfg.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
+            AvaloniaProperty = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty");
+            AvaloniaPropertyT = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
+            BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority");
+            IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding");
+            IDisposable = cfg.TypeSystem.GetType("System.IDisposable");
+            Transitions = cfg.TypeSystem.GetType("Avalonia.Animation.Transitions");
+            AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
             AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
                 AvaloniaProperty,
-                IBinding, config.WellKnownTypes.Object);
-            UnsetValueType = config.TypeSystem.GetType("Avalonia.UnsetValueType");
-            StyledElement = config.TypeSystem.GetType("Avalonia.StyledElement");
-            INameScope = config.TypeSystem.GetType("Avalonia.Controls.INameScope");
+                IBinding, cfg.WellKnownTypes.Object);
+            UnsetValueType = cfg.TypeSystem.GetType("Avalonia.UnsetValueType");
+            StyledElement = cfg.TypeSystem.GetType("Avalonia.StyledElement");
+            INameScope = cfg.TypeSystem.GetType("Avalonia.Controls.INameScope");
             INameScopeRegister = INameScope.GetMethod(
                 new FindMethodMethodSignature("Register", XamlIlTypes.Void,
                      XamlIlTypes.String, XamlIlTypes.Object)
                 {
-                    IsStatic = false, DeclaringOnly = true, IsExactMatch = true
+                    IsStatic = false,
+                    DeclaringOnly = true,
+                    IsExactMatch = true
                 });
             INameScopeComplete = INameScope.GetMethod(
                 new FindMethodMethodSignature("Complete", XamlIlTypes.Void)
                 {
-                    IsStatic = false, DeclaringOnly = true, IsExactMatch = true
+                    IsStatic = false,
+                    DeclaringOnly = true,
+                    IsExactMatch = true
                 });
-            NameScope = config.TypeSystem.GetType("Avalonia.Controls.NameScope");
+            NameScope = cfg.TypeSystem.GetType("Avalonia.Controls.NameScope");
             NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope",
-                XamlIlTypes.Void, StyledElement, INameScope) {IsStatic = true});
-
+                XamlIlTypes.Void, StyledElement, INameScope)
+            { IsStatic = true });
             AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void,
                 false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
-            
+            IPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.IPropertyInfo");
+            ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo");
+            PropertyPath = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPath");
+            PropertyPathBuilder = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPathBuilder");
+            IPropertyAccessor = cfg.TypeSystem.GetType("Avalonia.Data.Core.Plugins.IPropertyAccessor");
+            PropertyInfoAccessorFactory = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.PropertyInfoAccessorFactory");
+            CompiledBindingPathBuilder = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPathBuilder");
+            CompiledBindingPath = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings.CompiledBindingPath");
+            CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
+            DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
+            IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
+            ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");
+            RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource");
         }
     }
 
@@ -79,7 +108,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
             return rv;
         }
-
+        
         public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> ctx)
         {
             if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))

+ 36 - 16
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@@ -20,6 +20,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
 {
     class XamlIlAvaloniaPropertyHelper
     {
+        public static bool EmitProvideValueTarget(XamlIlEmitContext context, IXamlILEmitter emitter,
+            XamlAstClrProperty property)
+        {
+            if (Emit(context, emitter, property))
+                return true;
+            var foundClr = property.DeclaringType.Properties.FirstOrDefault(p => p.Name == property.Name);
+            if (foundClr == null)
+                return false;
+            context
+                .Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
+                .Emit(context, emitter, foundClr);
+            return true;
+        }
+        
         public static bool Emit(XamlIlEmitContext context, IXamlILEmitter emitter, XamlAstClrProperty property)
         {
             if (property is IXamlIlAvaloniaProperty ap)
@@ -85,6 +99,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
                 context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"),
                 clrProperty);
         }
+
+        public static IXamlType GetAvaloniaPropertyType(IXamlField field,
+            AvaloniaXamlIlWellKnownTypes types, IXamlLineInfo lineInfo)
+        {
+            var avaloniaPropertyType = field.FieldType;
+            while (avaloniaPropertyType != null)
+            {
+                if (avaloniaPropertyType.GenericTypeDefinition?.Equals(types.AvaloniaPropertyT) == true)
+                {
+                    return avaloniaPropertyType.GenericArguments[0];
+                }
+
+                avaloniaPropertyType = avaloniaPropertyType.BaseType;
+            }
+
+            throw new XamlX.XamlParseException(
+                $"{field.Name}'s type {field.FieldType} doesn't inherit from  AvaloniaProperty<T>, make sure to use typed properties",
+                lineInfo);
+
+        }
     }
 
     interface IXamlIlAvaloniaPropertyNode : IXamlAstValueNode
@@ -123,22 +157,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             IXamlLineInfo lineInfo, IXamlField field) : base(lineInfo)
         {
             _field = field;
-            var avaloniaPropertyType = field.FieldType;
-            while (avaloniaPropertyType != null)
-            {
-                if (avaloniaPropertyType.GenericTypeDefinition?.Equals(types.AvaloniaPropertyT) == true)
-                {
-                    AvaloniaPropertyType = avaloniaPropertyType.GenericArguments[0];
-                    return;
-                }
-
-                avaloniaPropertyType = avaloniaPropertyType.BaseType;
-            }
-
-            throw new XamlX.XamlParseException(
-                $"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty<T>, make sure to use typed properties",
-                lineInfo);
-
+            AvaloniaPropertyType = XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(field,
+                types, lineInfo);
         }
         
         

+ 660 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlBindingPathHelper.cs

@@ -0,0 +1,660 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Reflection.Emit;
+using Avalonia.Markup.Parsers;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+using XamlX.TypeSystem;
+using XamlX;
+using XamlX.Emit;
+using XamlX.IL;
+using Avalonia.Utilities;
+
+using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
+{
+    static class XamlIlBindingPathHelper
+    {
+        public static IXamlType UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, IXamlType startType)
+        {
+            IXamlType bindingResultType = null;
+            if (binding.Arguments.Count > 0 && binding.Arguments[0] is ParsedBindingPathNode bindingPath)
+            {
+                var transformed = TransformBindingPath(
+                    context,
+                    bindingPath,
+                    startType,
+                    bindingPath.Path);
+
+                bindingResultType = transformed.BindingResultType;
+                binding.Arguments[0] = transformed;
+            }
+            else
+            {
+                var bindingPathAssignment = binding.Children.OfType<XamlPropertyAssignmentNode>()
+                    .FirstOrDefault(v => v.Property.Name == "Path");
+
+                if (bindingPathAssignment is null)
+                {
+                    return startType;
+                }
+
+                if (bindingPathAssignment.Values[0] is ParsedBindingPathNode bindingPathNode)
+                {
+                    var transformed = TransformBindingPath(
+                        context,
+                        bindingPathNode,
+                        startType,
+                        bindingPathNode.Path);
+
+                    bindingResultType = transformed.BindingResultType;
+                    bindingPathAssignment.Values[0] = transformed;
+                }
+                else
+                {
+                    throw new InvalidOperationException();
+                }
+            }
+
+            return bindingResultType;
+        }
+
+        private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, IXamlType startType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
+        {
+            List<IXamlIlBindingPathElementNode> transformNodes = new List<IXamlIlBindingPathElementNode>();
+            List<IXamlIlBindingPathElementNode> nodes = new List<IXamlIlBindingPathElementNode>();
+            foreach (var astNode in bindingExpression)
+            {
+                var targetType = nodes.Count == 0 ? startType : nodes[nodes.Count - 1].Type;
+                switch (astNode)
+                {
+                    case BindingExpressionGrammar.EmptyExpressionNode _:
+                        break;
+                    case BindingExpressionGrammar.NotNode _:
+                        transformNodes.Add(new XamlIlNotPathElementNode(context.Configuration.WellKnownTypes.Boolean));
+                        break;
+                    case BindingExpressionGrammar.StreamNode _:
+                        IXamlType observableType;
+                        if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true)
+                        {
+                            observableType = targetType;
+                        }
+                        else
+                        {
+                            observableType = targetType.GetAllInterfaces().FirstOrDefault(i => i.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) ?? false);
+                        }
+
+                        if (observableType != null)
+                        {
+                            nodes.Add(new XamlIlStreamObservablePathElementNode(observableType.GenericArguments[0]));
+                            break;
+                        }
+                        bool foundTask = false;
+                        for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
+                        {
+                            if (currentType.GenericTypeDefinition.Equals(context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1")))
+                            {
+                                foundTask = true;
+                                nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0]));
+                                break;
+                            }
+                        }
+                        if (foundTask)
+                        {
+                            break;
+                        }
+                        throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo);
+                    case BindingExpressionGrammar.PropertyNameNode propName:
+                        var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property";
+                        var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f =>
+                            f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe);
+
+                        if (avaloniaPropertyFieldMaybe != null)
+                        {
+                            nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe,
+                                XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo)));
+                        }
+                        else
+                        {
+                            var clrProperty = targetType.GetAllProperties().FirstOrDefault(p => p.Name == propName.PropertyName);
+
+                            if (clrProperty is null)
+                            {
+                                throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
+                            }
+                            nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty));
+                        }
+                        break;
+                    case BindingExpressionGrammar.IndexerNode indexer:
+                        {
+                            if (targetType.IsArray)
+                            {
+                                nodes.Add(new XamlIlArrayIndexerPathElementNode(targetType, indexer.Arguments, lineInfo));
+                                break;
+                            }
+
+                            IXamlProperty property = null;
+                            for (var currentType = targetType; currentType != null; currentType = currentType.BaseType)
+                            {
+                                var defaultMemberAttribute = currentType.CustomAttributes.FirstOrDefault(x => x.Type.Namespace == "System.Reflection" && x.Type.Name == "DefaultMemberAttribute");
+                                if (defaultMemberAttribute != null)
+                                {
+                                    property = targetType.GetAllProperties().FirstOrDefault(x => x.Name == (string)defaultMemberAttribute.Parameters[0]);
+                                    break;
+                                }
+                            };
+                            if (property is null)
+                            {
+                                throw new XamlX.XamlParseException($"The type '${targetType}' does not have an indexer.", lineInfo);
+                            }
+
+                            IEnumerable<IXamlType> parameters = property.IndexerParameters;
+
+                            List<IXamlAstValueNode> values = new List<IXamlAstValueNode>();
+                            int currentParamIndex = 0;
+                            foreach (var param in parameters)
+                            {
+                                var textNode = new XamlAstTextNode(lineInfo, indexer.Arguments[currentParamIndex], type: context.Configuration.WellKnownTypes.String);
+                                if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, textNode,
+                                        param, out var converted))
+                                    throw new XamlX.XamlParseException(
+                                        $"Unable to convert indexer parameter value of '{indexer.Arguments[currentParamIndex]}' to {param.GetFqn()}",
+                                        textNode);
+
+                                values.Add(converted);
+                                currentParamIndex++;
+                            }
+
+                            bool isNotifyingCollection = targetType.GetAllInterfaces().Any(i => i.FullName == "System.Collections.Specialized.INotifyCollectionChanged");
+
+                            nodes.Add(new XamlIlClrIndexerPathElementNode(property, values, string.Join(",", indexer.Arguments), isNotifyingCollection));
+                            break;
+                        }
+                    case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
+                        var avaloniaPropertyFieldName = attachedProp.PropertyName + "Property";
+                        var avaloniaPropertyField = GetType(attachedProp.Namespace, attachedProp.TypeName).GetAllFields().FirstOrDefault(f =>
+                            f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldName);
+                        nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyField,
+                            XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, context.GetAvaloniaTypes(), lineInfo)));
+                        break;
+                    case BindingExpressionGrammar.SelfNode _:
+                        nodes.Add(new SelfPathElementNode(targetType));
+                        break;
+                    case VisualAncestorBindingExpressionNode visualAncestor:
+                        nodes.Add(new FindVisualAncestorPathElementNode(visualAncestor.Type, visualAncestor.Level));
+                        break;
+                    case TemplatedParentBindingExpressionNode templatedParent:
+                        var templatedParentField = context.GetAvaloniaTypes().StyledElement.GetAllFields()
+                            .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == "TemplatedParentProperty");
+                        nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(
+                            templatedParentField,
+                            templatedParent.Type));
+                        break;
+                    case BindingExpressionGrammar.AncestorNode ancestor:
+                        if (ancestor.Namespace is null && ancestor.TypeName is null)
+                        {
+                            var styledElementType = context.GetAvaloniaTypes().StyledElement;
+                            var ancestorType = context
+                                .ParentNodes()
+                                .OfType<XamlAstConstructableObjectNode>()
+                                .Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
+                                .ElementAtOrDefault(ancestor.Level)
+                                ?.Type.GetClrType();
+
+                            if (ancestorType is null)
+                            {
+                                throw new XamlX.XamlParseException("Unable to resolve implicit ancestor type based on XAML tree.", lineInfo);
+                            }
+
+                            nodes.Add(new FindAncestorPathElementNode(ancestorType, ancestor.Level));
+                        }
+                        else
+                        {
+                            nodes.Add(new FindAncestorPathElementNode(GetType(ancestor.Namespace, ancestor.TypeName), ancestor.Level));
+                        }
+                        break;
+                    case BindingExpressionGrammar.NameNode elementName:
+                        IXamlType elementType = null;
+                        foreach (var deferredContent in context.ParentNodes().OfType<NestedScopeMetadataNode>())
+                        {
+                            elementType = ScopeRegistrationFinder.GetTargetType(deferredContent, elementName.Name);
+                            if (!(elementType is null))
+                            {
+                                break;
+                            }
+                        }
+                        if (elementType is null)
+                        {
+                            elementType = ScopeRegistrationFinder.GetTargetType(context.ParentNodes().Last(), elementName.Name);
+                        }
+
+                        if (elementType is null)
+                        {
+                            throw new XamlX.XamlParseException($"Unable to find element '{elementName.Name}' in the current namescope. Unable to use a compiled binding with a name binding if the name cannot be found at compile time.", lineInfo);
+                        }
+                        nodes.Add(new ElementNamePathElementNode(elementName.Name, elementType));
+                        break;
+                    case RawSourceBindingExpressionNode rawSource:
+                        nodes.Add(new RawSourcePathElementNode(rawSource.RawSource));
+                        break;
+                }
+            }
+
+            return new XamlIlBindingPathNode(lineInfo, context.GetAvaloniaTypes().CompiledBindingPath, transformNodes, nodes);
+
+            IXamlType GetType(string ns, string name)
+            {
+                return TypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
+                    lineInfo, true).GetClrType();
+            }
+        }
+
+        class ScopeRegistrationFinder : IXamlAstVisitor
+        {
+            private Stack<IXamlAstNode> _stack = new Stack<IXamlAstNode>();
+            private Stack<IXamlAstNode> _childScopesStack = new Stack<IXamlAstNode>();
+
+            private ScopeRegistrationFinder(string name)
+            {
+                Name = name;
+            }
+
+            string Name { get; }
+
+            IXamlType TargetType { get; set; }
+
+            public static IXamlType GetTargetType(IXamlAstNode namescopeRoot, string name)
+            {
+                var finder = new ScopeRegistrationFinder(name);
+                namescopeRoot.Visit(finder);
+                return finder.TargetType;
+            }
+
+            void IXamlAstVisitor.Pop()
+            {
+                var node = _stack.Pop();
+                if (_childScopesStack.Count > 0 && node == _childScopesStack.Peek())
+                {
+                    _childScopesStack.Pop();
+                }
+            }
+
+            void IXamlAstVisitor.Push(IXamlAstNode node)
+            {
+                _stack.Push(node);
+                if (node is NestedScopeMetadataNode)
+                {
+                    _childScopesStack.Push(node);
+                }
+            }
+
+            IXamlAstNode IXamlAstVisitor.Visit(IXamlAstNode node)
+            {
+                if (_childScopesStack.Count == 0 && node is AvaloniaNameScopeRegistrationXamlIlNode registration)
+                {
+                    if (registration.Name is XamlAstTextNode text && text.Text == Name)
+                    {
+                        TargetType = registration.TargetType;
+                    }
+                }
+                return node;
+            }
+        }
+
+        interface IXamlIlBindingPathElementNode
+        {
+            IXamlType Type { get; }
+
+            void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen);
+        }
+
+        class XamlIlNotPathElementNode : IXamlIlBindingPathElementNode
+        {
+            public XamlIlNotPathElementNode(IXamlType boolType)
+            {
+                Type = boolType;
+            }
+
+            public IXamlType Type { get; }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Not"));
+            }
+        }
+
+        class XamlIlStreamObservablePathElementNode : IXamlIlBindingPathElementNode
+        {
+            public XamlIlStreamObservablePathElementNode(IXamlType type)
+            {
+                Type = type;
+            }
+
+            public IXamlType Type { get; }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamObservable").MakeGenericMethod(new[] { Type }));
+            }
+        }
+
+        class XamlIlStreamTaskPathElementNode : IXamlIlBindingPathElementNode
+        {
+            public XamlIlStreamTaskPathElementNode(IXamlType type)
+            {
+                Type = type;
+            }
+
+            public IXamlType Type { get; }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "StreamTask").MakeGenericMethod(new[] { Type }));
+            }
+        }
+
+        class SelfPathElementNode : IXamlIlBindingPathElementNode
+        {
+            public SelfPathElementNode(IXamlType type)
+            {
+                Type = type;
+            }
+
+            public IXamlType Type { get; }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Self"));
+            }
+        }
+
+        class FindAncestorPathElementNode : IXamlIlBindingPathElementNode
+        {
+            private readonly int _level;
+
+            public FindAncestorPathElementNode(IXamlType ancestorType, int level)
+            {
+                Type = ancestorType;
+                _level = level;
+            }
+
+            public IXamlType Type { get; }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                codeGen.Ldtype(Type)
+                    .Ldc_I4(_level)
+                    .EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "FindAncestor"));
+            }
+        }
+
+        class FindVisualAncestorPathElementNode : IXamlIlBindingPathElementNode
+        {
+            private readonly int _level;
+
+            public FindVisualAncestorPathElementNode(IXamlType ancestorType, int level)
+            {
+                Type = ancestorType;
+                _level = level;
+            }
+
+            public IXamlType Type { get; }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                codeGen.Ldtype(Type)
+                    .Ldc_I4(_level)
+                    .EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "VisualAncestor"));
+            }
+        }
+
+        class ElementNamePathElementNode : IXamlIlBindingPathElementNode
+        {
+            private readonly string _name;
+
+            public ElementNamePathElementNode(string name, IXamlType elementType)
+            {
+                _name = name;
+                Type = elementType;
+            }
+
+            public IXamlType Type { get; }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
+                    f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
+
+                codeGen
+                    .Ldloc(context.ContextLocal)
+                    .Ldfld(scopeField)
+                    .Ldstr(_name)
+                    .EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "ElementName"));
+            }
+        }
+
+        class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlBindingPathElementNode
+        {
+            private readonly IXamlField _field;
+
+            public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlField field, IXamlType propertyType)
+            {
+                _field = field;
+                Type = propertyType;
+            }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                codeGen.Ldsfld(_field);
+                context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
+                    .EmitLoadAvaloniaPropertyAccessorFactory(context, codeGen);
+                codeGen.EmitCall(context.GetAvaloniaTypes()
+                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
+            }
+
+            public IXamlType Type { get; }
+        }
+
+        class XamlIlClrPropertyPathElementNode : IXamlIlBindingPathElementNode
+        {
+            private readonly IXamlProperty _property;
+
+            public XamlIlClrPropertyPathElementNode(IXamlProperty property)
+            {
+                _property = property;
+            }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
+                    .Emit(context, codeGen, _property);
+
+                context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
+                    .EmitLoadInpcPropertyAccessorFactory(context, codeGen);
+
+                codeGen
+                    .EmitCall(context.GetAvaloniaTypes()
+                        .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
+            }
+
+            public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
+        }
+
+        class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode
+        {
+            private readonly IXamlProperty _property;
+            private readonly List<IXamlAstValueNode> _values;
+            private readonly string _indexerKey;
+            private readonly bool _isNotifyingCollection;
+
+            public XamlIlClrIndexerPathElementNode(IXamlProperty property, List<IXamlAstValueNode> values, string indexerKey, bool isNotifyingCollection)
+            {
+                _property = property;
+                _values = values;
+                _indexerKey = indexerKey;
+                _isNotifyingCollection = isNotifyingCollection;
+            }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                var intType = context.Configuration.TypeSystem.GetType("System.Int32");
+                context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
+                    .Emit(context, codeGen, _property, _values, _indexerKey);
+
+                if (_isNotifyingCollection
+                    &&
+                    _values.Count == 1
+                    && _values[0].Type.GetClrType().Equals(intType))
+                {
+                    context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
+                        .EmitLoadIndexerAccessorFactory(context, codeGen, _values[0]);
+                }
+                else
+                {
+                    context.Configuration.GetExtra<XamlIlPropertyInfoAccessorFactoryEmitter>()
+                        .EmitLoadInpcPropertyAccessorFactory(context, codeGen);
+                }
+
+                codeGen.EmitCall(context.GetAvaloniaTypes()
+                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Property"));
+            }
+
+            public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
+        }
+
+        class XamlIlArrayIndexerPathElementNode : IXamlIlBindingPathElementNode
+        {
+            private readonly IXamlType _arrayType;
+            private readonly List<int> _values;
+
+            public XamlIlArrayIndexerPathElementNode(IXamlType arrayType, IList<string> values, IXamlLineInfo lineInfo)
+            {
+                _arrayType = arrayType;
+                _values = new List<int>(values.Count);
+                foreach (var item in values)
+                {
+                    if (!int.TryParse(item, out var index))
+                    {
+                        throw new XamlX.XamlParseException($"Unable to convert '{item}' to an integer.", lineInfo.Line, lineInfo.Position);
+                    }
+                    _values.Add(index);
+                }
+            }
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                var intType = context.Configuration.TypeSystem.GetType("System.Int32");
+                var indices = codeGen.DefineLocal(intType.MakeArrayType(1));
+                codeGen.Ldc_I4(_values.Count)
+                    .Newarr(intType)
+                    .Stloc(indices);
+                for (int i = 0; i < _values.Count; i++)
+                {
+                    codeGen.Ldloc(indices)
+                        .Ldc_I4(i)
+                        .Ldc_I4(_values[i])
+                        .Emit(OpCodes.Stelem_I4);
+                }
+
+                codeGen.Ldloc(indices)
+                    .Ldtype(Type)
+                    .EmitCall(context.GetAvaloniaTypes()
+                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "ArrayElement"));
+            }
+
+            public IXamlType Type => _arrayType.ArrayElementType;
+        }
+
+        class RawSourcePathElementNode : XamlAstNode, IXamlIlBindingPathElementNode
+        {
+            private readonly IXamlAstValueNode _rawSource;
+
+            public RawSourcePathElementNode(IXamlAstValueNode rawSource)
+                :base(rawSource)
+            {
+                _rawSource = rawSource;
+                
+            }
+
+            public IXamlType Type => _rawSource.Type.GetClrType();
+
+            public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                context.Emit(_rawSource, codeGen, Type);
+                codeGen
+                    .EmitCall(context.GetAvaloniaTypes()
+                    .CompiledBindingPathBuilder.FindMethod(m => m.Name == "SetRawSource"));
+            }
+        }
+
+        class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
+        {
+            private readonly List<IXamlIlBindingPathElementNode> _transformElements;
+            private readonly List<IXamlIlBindingPathElementNode> _elements;
+
+            public XamlIlBindingPathNode(IXamlLineInfo lineInfo,
+                IXamlType bindingPathType,
+                List<IXamlIlBindingPathElementNode> transformElements,
+                List<IXamlIlBindingPathElementNode> elements) : base(lineInfo)
+            {
+                Type = new XamlAstClrTypeReference(lineInfo, bindingPathType, false);
+                _transformElements = transformElements;
+                _elements = elements;
+            }
+
+            public IXamlType BindingResultType
+                => _transformElements.Count > 0
+                    ? _transformElements[0].Type
+                    : _elements[_elements.Count - 1].Type;
+
+            public IXamlAstTypeReference Type { get; }
+
+            public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
+            {
+                var types = context.GetAvaloniaTypes();
+                codeGen.Newobj(types.CompiledBindingPathBuilder.FindConstructor());
+
+                foreach (var transform in _transformElements)
+                {
+                    transform.Emit(context, codeGen);
+                }
+
+                foreach (var element in _elements)
+                {
+                    element.Emit(context, codeGen);
+                }
+
+                codeGen.EmitCall(types.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Build"));
+                return XamlILNodeEmitResult.Type(0, types.CompiledBindingPath);
+            }
+
+            public override void VisitChildren(IXamlAstVisitor visitor)
+            {
+                for (int i = 0; i < _transformElements.Count; i++)
+                {
+                    if (_transformElements[i] is IXamlAstNode ast)
+                    {
+                        _transformElements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
+                    }
+                }
+                for (int i = 0; i < _elements.Count; i++)
+                {
+                    if (_elements[i] is IXamlAstNode ast)
+                    {
+                        _elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
+                    }
+                }
+            }
+        }
+    }
+
+    interface IXamlIlBindingPathNode : IXamlAstValueNode
+    {
+        IXamlType BindingResultType { get; }
+    }
+}

+ 158 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs

@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+using XamlX.IL;
+using XamlX.Emit;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
+{
+    class XamlIlClrPropertyInfoEmitter
+    {
+        private readonly IXamlTypeBuilder<IXamlILEmitter> _builder;
+
+        private Dictionary<string, List<(IXamlProperty prop, IXamlMethod get)>> _fields
+            = new Dictionary<string, List<(IXamlProperty prop, IXamlMethod get)>>();
+        
+        public XamlIlClrPropertyInfoEmitter(IXamlTypeBuilder<IXamlILEmitter> builder)
+        {
+            _builder = builder;
+        }
+
+        static string GetKey(IXamlProperty property, string indexerArgumentsKey)
+        {
+            var baseKey = property.Getter.DeclaringType.GetFullName() + "." + property.Name;
+
+            if (indexerArgumentsKey is null)
+            {
+                return baseKey;
+            }
+
+            return baseKey + $"[{indexerArgumentsKey}]";
+        }
+
+        public IXamlType Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen, IXamlProperty property, IEnumerable<IXamlAstValueNode> indexerArguments = null, string indexerArgumentsKey = null)
+        {
+            indexerArguments = indexerArguments ?? Enumerable.Empty<IXamlAstValueNode>();
+            var types = context.GetAvaloniaTypes();
+            IXamlMethod Get()
+            {
+                var key = GetKey(property, indexerArgumentsKey);
+                if (!_fields.TryGetValue(key, out var lst))
+                    _fields[key] = lst = new List<(IXamlProperty prop, IXamlMethod get)>();
+
+                foreach (var cached in lst)
+                {
+                    if (
+                        ((cached.prop.Getter == null && property.Getter == null) ||
+                         cached.prop.Getter?.Equals(property.Getter) == true) &&
+                        ((cached.prop.Setter == null && property.Setter == null) ||
+                         cached.prop.Setter?.Equals(property.Setter) == true)
+                    )
+                        return cached.get;
+                }
+
+                var name = lst.Count == 0 ? key : key + "_" + Guid.NewGuid().ToString("N");
+                
+                var field = _builder.DefineField(types.IPropertyInfo, name + "!Field", false, true);
+
+                void Load(IXamlMethod m, IXamlILEmitter cg)
+                {
+                    cg
+                        .Ldarg_0();
+                    if (m.DeclaringType.IsValueType)
+                        cg.Unbox(m.DeclaringType);
+                    else
+                        cg.Castclass(m.DeclaringType);
+
+                    foreach (var indexerArg in indexerArguments)
+                    {
+                        context.Emit(indexerArg, cg, indexerArg.Type.GetClrType());
+                    }
+                }
+
+                var getter = property.Getter == null ?
+                    null :
+                    _builder.DefineMethod(types.XamlIlTypes.Object,
+                        new[] {types.XamlIlTypes.Object}, name + "!Getter", false, true, false);
+                if (getter != null)
+                {
+                    Load(property.Getter, getter.Generator);
+                    
+                    getter.Generator.EmitCall(property.Getter);
+                    if (property.Getter.ReturnType.IsValueType)
+                        getter.Generator.Box(property.Getter.ReturnType);
+                    getter.Generator.Ret();
+                }
+
+                var setter = property.Setter == null ?
+                    null :
+                    _builder.DefineMethod(types.XamlIlTypes.Void,
+                        new[] {types.XamlIlTypes.Object, types.XamlIlTypes.Object},
+                        name + "!Setter", false, true, false);
+                if (setter != null)
+                {
+                    Load(property.Setter, setter.Generator);
+                    
+                    setter.Generator.Ldarg(1);
+                    if (property.Setter.Parameters[0].IsValueType)
+                        setter.Generator.Unbox_Any(property.Setter.Parameters[0]);
+                    else
+                        setter.Generator.Castclass(property.Setter.Parameters[0]);
+                    setter.Generator
+                        .EmitCall(property.Setter, true)
+                        .Ret();
+                }
+
+                var get = _builder.DefineMethod(types.IPropertyInfo, Array.Empty<IXamlType>(),
+                    name + "!Property", true, true, false);
+
+
+                var ctor = types.ClrPropertyInfo.Constructors.First(c =>
+                    c.Parameters.Count == 4 && c.IsStatic == false);
+                
+                var cacheMiss = get.Generator.DefineLabel();
+                get.Generator
+                    .Ldsfld(field)
+                    .Brfalse(cacheMiss)
+                    .Ldsfld(field)
+                    .Ret()
+                    .MarkLabel(cacheMiss)
+                    .Ldstr(property.Name);
+
+                void EmitFunc(IXamlILEmitter emitter, IXamlMethod method, IXamlType del)
+                {
+                    if (method == null)
+                        emitter.Ldnull();
+                    else
+                    {
+                        emitter
+                            .Ldnull()
+                            .Ldftn(method)
+                            .Newobj(del.Constructors.First(c =>
+                                c.Parameters.Count == 2 &&
+                                c.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object)));
+                    }
+                }
+
+                EmitFunc(get.Generator, getter, ctor.Parameters[1]);
+                EmitFunc(get.Generator, setter, ctor.Parameters[2]);
+                get.Generator
+                    .Ldtype(property.PropertyType)
+                    .Newobj(ctor)
+                    .Stsfld(field)
+                    .Ldsfld(field)
+                    .Ret();
+
+                lst.Add((property, get));
+                return get;
+            }
+
+            codeGen.EmitCall(Get());
+            return types.IPropertyInfo;
+        }
+    }
+}

+ 118 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlPropertyInfoAccessorFactoryEmitter.cs

@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+using XamlX.Emit;
+using XamlX.IL;
+
+using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
+{
+    class XamlIlPropertyInfoAccessorFactoryEmitter
+    {
+        private const string IndexerClosureFactoryMethodName = "CreateAccessor";
+        private readonly IXamlTypeBuilder<IXamlILEmitter> _indexerClosureTypeBuilder;
+        private IXamlType _indexerClosureType;
+        public XamlIlPropertyInfoAccessorFactoryEmitter(IXamlTypeBuilder<IXamlILEmitter> indexerClosureType)
+        {
+            _indexerClosureTypeBuilder = indexerClosureType;
+        }
+
+        public IXamlType EmitLoadInpcPropertyAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen)
+        {
+            codeGen.Ldnull();
+            EmitLoadPropertyAccessorFactory(context, codeGen, context.GetAvaloniaTypes().PropertyInfoAccessorFactory, "CreateInpcPropertyAccessor");
+            return EmitCreateAccessorFactoryDelegate(context, codeGen);
+        }
+
+        public IXamlType EmitLoadAvaloniaPropertyAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen)
+        {
+            codeGen.Ldnull();
+            EmitLoadPropertyAccessorFactory(context, codeGen, context.GetAvaloniaTypes().PropertyInfoAccessorFactory, "CreateAvaloniaPropertyAccessor");
+            return EmitCreateAccessorFactoryDelegate(context, codeGen);
+        }
+
+        private void EmitLoadPropertyAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen, IXamlType type, string accessorFactoryName, bool isStatic = true)
+        {
+            var types = context.GetAvaloniaTypes();
+            var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference`1").MakeGenericType(context.Configuration.WellKnownTypes.Object);
+            FindMethodMethodSignature accessorFactorySignature = new FindMethodMethodSignature(accessorFactoryName, types.IPropertyAccessor, weakReferenceType, types.IPropertyInfo)
+            {
+                IsStatic = isStatic
+            };
+            codeGen.Ldftn(type.GetMethod(accessorFactorySignature));
+        }
+
+        public IXamlType EmitLoadIndexerAccessorFactory(XamlIlEmitContext context, IXamlILEmitter codeGen, IXamlAstValueNode value)
+        {
+            var intType = context.Configuration.TypeSystem.GetType("System.Int32");
+            if (_indexerClosureType is null)
+            {
+                _indexerClosureType = InitializeClosureType(context);
+            }
+
+            context.Emit(value, codeGen, intType);
+            codeGen.Newobj(_indexerClosureType.FindConstructor(new List<IXamlType> { intType }));
+            EmitLoadPropertyAccessorFactory(context, codeGen, _indexerClosureType, IndexerClosureFactoryMethodName, isStatic: false);
+            return EmitCreateAccessorFactoryDelegate(context, codeGen);
+        }
+
+        private IXamlType InitializeClosureType(XamlIlEmitContext context)
+        {
+            var types = context.GetAvaloniaTypes();
+            var intType = context.Configuration.TypeSystem.GetType("System.Int32");
+            var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference`1").MakeGenericType(context.Configuration.WellKnownTypes.Object);
+            var indexAccessorFactoryMethod = context.GetAvaloniaTypes().PropertyInfoAccessorFactory.GetMethod(
+                    new FindMethodMethodSignature(
+                        "CreateIndexerPropertyAccessor",
+                        types.IPropertyAccessor,
+                        weakReferenceType,
+                        types.IPropertyInfo,
+                        intType)
+                    {
+                        IsStatic = true
+                    });
+            var indexField = _indexerClosureTypeBuilder.DefineField(intType, "_index", false, false);
+            var ctor = _indexerClosureTypeBuilder.DefineConstructor(false, intType);
+            ctor.Generator
+                .Ldarg_0()
+                .Ldarg(1)
+                .Stfld(indexField)
+                .Ret();
+            _indexerClosureTypeBuilder.DefineMethod(
+                types.IPropertyAccessor,
+                new[] { weakReferenceType, types.IPropertyInfo },
+                IndexerClosureFactoryMethodName,
+                isPublic: true,
+                isStatic: false,
+                isInterfaceImpl: false)
+                .Generator
+                .Ldarg(1)
+                .Ldarg(2)
+                .LdThisFld(indexField)
+                .EmitCall(indexAccessorFactoryMethod)
+                .Ret();
+
+            return _indexerClosureTypeBuilder.CreateType();
+        }
+
+        private IXamlType EmitCreateAccessorFactoryDelegate(XamlIlEmitContext context, IXamlILEmitter codeGen)
+        {
+            var types = context.GetAvaloniaTypes();
+            var weakReferenceType = context.Configuration.TypeSystem.GetType("System.WeakReference`1").MakeGenericType(context.Configuration.WellKnownTypes.Object);
+            var funcType = context.Configuration.TypeSystem.GetType("System.Func`3").MakeGenericType(
+                            weakReferenceType,
+                            types.IPropertyInfo,
+                            types.IPropertyAccessor);
+            codeGen.Newobj(funcType.Constructors.First(c =>
+                                c.Parameters.Count == 2 &&
+                                c.Parameters[0].Equals(context.Configuration.WellKnownTypes.Object)));
+            return funcType;
+        }
+    }
+}

+ 6 - 0
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@@ -3,6 +3,12 @@
     <TargetFramework>netstandard2.0</TargetFramework>
     <RootNamespace>Avalonia</RootNamespace>
   </PropertyGroup>
+  <ItemGroup>
+    <None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="Markup\Parsers\BindingExpressionGrammar.cs" />
+  </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
     <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />

+ 16 - 254
src/Markup/Avalonia.Markup/Data/Binding.cs

@@ -15,15 +15,14 @@ namespace Avalonia.Data
     /// <summary>
     /// A XAML binding.
     /// </summary>
-    public class Binding : IBinding
+    public class Binding : BindingBase
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="Binding"/> class.
         /// </summary>
         public Binding()
+            :base()
         {
-            FallbackValue = AvaloniaProperty.UnsetValue;
-            TargetNullValue = AvaloniaProperty.UnsetValue;
         }
 
         /// <summary>
@@ -32,52 +31,16 @@ namespace Avalonia.Data
         /// <param name="path">The binding path.</param>
         /// <param name="mode">The binding mode.</param>
         public Binding(string path, BindingMode mode = BindingMode.Default)
-            : this()
+            : base(mode)
         {
             Path = path;
-            Mode = mode;
         }
 
-        /// <summary>
-        /// Gets or sets the <see cref="IValueConverter"/> to use.
-        /// </summary>
-        public IValueConverter Converter { get; set; }
-
-        /// <summary>
-        /// Gets or sets a parameter to pass to <see cref="Converter"/>.
-        /// </summary>
-        public object ConverterParameter { get; set; }
-
         /// <summary>
         /// Gets or sets the name of the element to use as the binding source.
         /// </summary>
         public string ElementName { get; set; }
 
-        /// <summary>
-        /// Gets or sets the value to use when the binding is unable to produce a value.
-        /// </summary>
-        public object FallbackValue { get; set; }
-
-        /// <summary>
-        /// Gets or sets the value to use when the binding result is null.
-        /// </summary>
-        public object TargetNullValue { get; set; }
-
-        /// <summary>
-        /// Gets or sets the binding mode.
-        /// </summary>
-        public BindingMode Mode { get; set; }
-
-        /// <summary>
-        /// Gets or sets the binding path.
-        /// </summary>
-        public string Path { get; set; } = "";
-
-        /// <summary>
-        /// Gets or sets the binding priority.
-        /// </summary>
-        public BindingPriority Priority { get; set; }
-
         /// <summary>
         /// Gets or sets the relative source for the binding.
         /// </summary>
@@ -89,68 +52,58 @@ namespace Avalonia.Data
         public object Source { get; set; }
 
         /// <summary>
-        /// Gets or sets the string format.
+        /// Gets or sets the binding path.
         /// </summary>
-        public string StringFormat { get; set; }
-
-        public WeakReference DefaultAnchor { get; set; }
-        
-        public WeakReference<INameScope> NameScope { get; set; }
+        public string Path { get; set; } = "";
 
         /// <summary>
         /// Gets or sets a function used to resolve types from names in the binding path.
         /// </summary>
         public Func<string, string, Type> TypeResolver { get; set; }
 
-        /// <inheritdoc/>
-        public InstancedBinding Initiate(
-            IAvaloniaObject target,
-            AvaloniaProperty targetProperty,
-            object anchor = null,
-            bool enableDataValidation = false)
+        protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(target != null);
             anchor = anchor ?? DefaultAnchor?.Target;
             
             enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
-            
-            ExpressionObserver observer;
 
             INameScope nameScope = null;
             NameScope?.TryGetTarget(out nameScope);
+
             var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope);
 
             if (ElementName != null)
             {
-                observer = CreateElementObserver(
+                return CreateElementObserver(
                     (target as IStyledElement) ?? (anchor as IStyledElement),
                     ElementName,
                     node);
             }
             else if (Source != null)
             {
-                observer = CreateSourceObserver(Source, node);
+                return CreateSourceObserver(Source, node);
             }
             else if (RelativeSource == null)
             {
                 if (mode == SourceMode.Data)
                 {
-                    observer = CreateDataContextObserver(
+                    return CreateDataContextObserver(
                         target,
                         node,
                         targetProperty == StyledElement.DataContextProperty,
-                        anchor); 
+                        anchor);
                 }
                 else
                 {
-                    observer = new ExpressionObserver(
+                    return CreateSourceObserver(
                         (target as IStyledElement) ?? (anchor as IStyledElement),
                         node);
                 }
             }
             else if (RelativeSource.Mode == RelativeSourceMode.DataContext)
             {
-                observer = CreateDataContextObserver(
+                return CreateDataContextObserver(
                     target,
                     node,
                     targetProperty == StyledElement.DataContextProperty,
@@ -158,13 +111,13 @@ namespace Avalonia.Data
             }
             else if (RelativeSource.Mode == RelativeSourceMode.Self)
             {
-                observer = CreateSourceObserver(
+                return CreateSourceObserver(
                     (target as IStyledElement) ?? (anchor as IStyledElement),
                     node);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
             {
-                observer = CreateTemplatedParentObserver(
+                return CreateTemplatedParentObserver(
                     (target as IStyledElement) ?? (anchor as IStyledElement),
                     node);
             }
@@ -175,7 +128,7 @@ namespace Avalonia.Data
                     throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
                 }
 
-                observer = CreateFindAncestorObserver(
+                return CreateFindAncestorObserver(
                     (target as IStyledElement) ?? (anchor as IStyledElement),
                     RelativeSource,
                     node);
@@ -184,197 +137,6 @@ namespace Avalonia.Data
             {
                 throw new NotSupportedException();
             }
-
-            var fallback = FallbackValue;
-
-            // If we're binding to DataContext and our fallback is UnsetValue then override
-            // the fallback value to null, as broken bindings to DataContext must reset the
-            // DataContext in order to not propagate incorrect DataContexts to child controls.
-            // See Avalonia.Markup.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
-            if (targetProperty == StyledElement.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
-            {
-                fallback = null;
-            }
-
-            var converter = Converter;
-            var targetType = targetProperty?.PropertyType ?? typeof(object);
-
-            // We only respect `StringFormat` if the type of the property we're assigning to will
-            // accept a string. Note that this is slightly different to WPF in that WPF only applies
-            // `StringFormat` for target type `string` (not `object`).
-            if (!string.IsNullOrWhiteSpace(StringFormat) && 
-                (targetType == typeof(string) || targetType == typeof(object)))
-            {
-                converter = new StringFormatValueConverter(StringFormat, converter);
-            }
-
-            var subject = new BindingExpression(
-                observer,
-                targetType,
-                fallback,
-                TargetNullValue,
-                converter ?? DefaultValueConverter.Instance,
-                ConverterParameter,
-                Priority);
-
-            return new InstancedBinding(subject, Mode, Priority);
-        }
-
-        private ExpressionObserver CreateDataContextObserver(
-            IAvaloniaObject target,
-            ExpressionNode node,
-            bool targetIsDataContext,
-            object anchor)
-        {
-            Contract.Requires<ArgumentNullException>(target != null);
-
-            if (!(target is IDataContextProvider))
-            {
-                target = anchor as IDataContextProvider;
-
-                if (target == null)
-                {
-                    throw new InvalidOperationException("Cannot find a DataContext to bind to.");
-                }
-            }
-
-            if (!targetIsDataContext)
-            {
-                var result = new ExpressionObserver(
-                    () => target.GetValue(StyledElement.DataContextProperty),
-                    node,
-                    new UpdateSignal(target, StyledElement.DataContextProperty),
-                    null);
-
-                return result;
-            }
-            else
-            {
-                return new ExpressionObserver(
-                    GetParentDataContext(target),
-                    node,
-                    null);
-            }
-        }
-
-        private ExpressionObserver CreateElementObserver(
-            IStyledElement target,
-            string elementName,
-            ExpressionNode node)
-        {
-            Contract.Requires<ArgumentNullException>(target != null);
-
-            NameScope.TryGetTarget(out var scope);
-            if (scope == null)
-                throw new InvalidOperationException("Name scope is null or was already collected");
-            var result = new ExpressionObserver(
-                NameScopeLocator.Track(scope, elementName),
-                node,
-                null);
-            return result;
-        }
-
-        private ExpressionObserver CreateFindAncestorObserver(
-            IStyledElement target,
-            RelativeSource relativeSource,
-            ExpressionNode node)
-        {
-            Contract.Requires<ArgumentNullException>(target != null);
-
-            IObservable<object> controlLocator;
-
-            switch (relativeSource.Tree)
-            {
-                case TreeType.Logical:
-                    controlLocator = ControlLocator.Track(
-                        (ILogical)target,
-                        relativeSource.AncestorLevel - 1,
-                        relativeSource.AncestorType);
-                    break;
-                case TreeType.Visual:
-                    controlLocator = VisualLocator.Track(
-                        (IVisual)target,
-                        relativeSource.AncestorLevel - 1,
-                        relativeSource.AncestorType);
-                    break;
-                default:
-                    throw new InvalidOperationException("Invalid tree to traverse.");
-            }
-
-            return new ExpressionObserver(
-                controlLocator,
-                node,
-                null);
-        }
-
-        private ExpressionObserver CreateSourceObserver(
-            object source,
-            ExpressionNode node)
-        {
-            Contract.Requires<ArgumentNullException>(source != null);
-
-            return new ExpressionObserver(source, node);
-        }
-
-        private ExpressionObserver CreateTemplatedParentObserver(
-            IAvaloniaObject target,
-            ExpressionNode node)
-        {
-            Contract.Requires<ArgumentNullException>(target != null);
-            
-            var result = new ExpressionObserver(
-                () => target.GetValue(StyledElement.TemplatedParentProperty),
-                node,
-                new UpdateSignal(target, StyledElement.TemplatedParentProperty),
-                null);
-
-            return result;
-        }
-
-        private IObservable<object> GetParentDataContext(IAvaloniaObject target)
-        {
-            // The DataContext is based on the visual parent and not the logical parent: this may
-            // seem counter intuitive considering the fact that property inheritance works on the logical
-            // tree, but consider a ContentControl with a ContentPresenter. The ContentControl's
-            // Content property is bound to a value which becomes the ContentPresenter's 
-            // DataContext - it is from this that the child hosted by the ContentPresenter needs to
-            // inherit its DataContext.
-            return target.GetObservable(Visual.VisualParentProperty)
-                .Select(x =>
-                {
-                    return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ?? 
-                           Observable.Return((object)null);
-                }).Switch();
-        }
-
-        private class UpdateSignal : SingleSubscriberObservableBase<Unit>
-        {
-            private readonly IAvaloniaObject _target;
-            private readonly AvaloniaProperty _property;
-
-            public UpdateSignal(IAvaloniaObject target, AvaloniaProperty property)
-            {
-                _target = target;
-                _property = property;
-            }
-
-            protected override void Subscribed()
-            {
-                _target.PropertyChanged += PropertyChanged;
-            }
-
-            protected override void Unsubscribed()
-            {
-                _target.PropertyChanged -= PropertyChanged;
-            }
-
-            private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
-            {
-                if (e.Property == _property)
-                {
-                    PublishNext(Unit.Default);
-                }
-            }
         }
     }
 }

+ 289 - 0
src/Markup/Avalonia.Markup/Data/BindingBase.cs

@@ -0,0 +1,289 @@
+
+using System;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Linq;
+using Avalonia.Controls;
+using Avalonia.Data.Converters;
+using Avalonia.Data.Core;
+using Avalonia.LogicalTree;
+using Avalonia.Markup.Parsers;
+using Avalonia.Reactive;
+using Avalonia.VisualTree;
+
+
+namespace Avalonia.Data
+{
+    public abstract class BindingBase : IBinding
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Binding"/> class.
+        /// </summary>
+        public BindingBase()
+        {
+            FallbackValue = AvaloniaProperty.UnsetValue;
+            TargetNullValue = AvaloniaProperty.UnsetValue;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Binding"/> class.
+        /// </summary>
+        /// <param name="mode">The binding mode.</param>
+        public BindingBase(BindingMode mode = BindingMode.Default)
+            :this()
+        {
+            Mode = mode;
+        }
+
+        /// <summary>
+        /// Gets or sets the <see cref="IValueConverter"/> to use.
+        /// </summary>
+        public IValueConverter Converter { get; set; }
+
+        /// <summary>
+        /// Gets or sets a parameter to pass to <see cref="Converter"/>.
+        /// </summary>
+        public object ConverterParameter { get; set; }
+
+        /// <summary>
+        /// Gets or sets the value to use when the binding is unable to produce a value.
+        /// </summary>
+        public object FallbackValue { get; set; }
+
+        /// <summary>
+        /// Gets or sets the value to use when the binding result is null.
+        /// </summary>
+        public object TargetNullValue { get; set; }
+
+        /// <summary>
+        /// Gets or sets the binding mode.
+        /// </summary>
+        public BindingMode Mode { get; set; }
+
+        /// <summary>
+        /// Gets or sets the binding priority.
+        /// </summary>
+        public BindingPriority Priority { get; set; }
+
+        /// <summary>
+        /// Gets or sets the string format.
+        /// </summary>
+        public string StringFormat { get; set; }
+
+        public WeakReference DefaultAnchor { get; set; }
+
+        public WeakReference<INameScope> NameScope { get; set; }
+
+        protected abstract ExpressionObserver CreateExpressionObserver(
+            IAvaloniaObject target,
+            AvaloniaProperty targetProperty,
+            object anchor,
+            bool enableDataValidation);
+
+        /// <inheritdoc/>
+        public InstancedBinding Initiate(
+            IAvaloniaObject target,
+            AvaloniaProperty targetProperty,
+            object anchor = null,
+            bool enableDataValidation = false)
+        {
+            Contract.Requires<ArgumentNullException>(target != null);
+            anchor = anchor ?? DefaultAnchor?.Target;
+
+            enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
+
+            var observer = CreateExpressionObserver(target, targetProperty, anchor, enableDataValidation);
+
+            var fallback = FallbackValue;
+
+            // If we're binding to DataContext and our fallback is UnsetValue then override
+            // the fallback value to null, as broken bindings to DataContext must reset the
+            // DataContext in order to not propagate incorrect DataContexts to child controls.
+            // See Avalonia.Markup.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
+            if (targetProperty == StyledElement.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
+            {
+                fallback = null;
+            }
+
+            var converter = Converter;
+            var targetType = targetProperty?.PropertyType ?? typeof(object);
+
+            // We only respect `StringFormat` if the type of the property we're assigning to will
+            // accept a string. Note that this is slightly different to WPF in that WPF only applies
+            // `StringFormat` for target type `string` (not `object`).
+            if (!string.IsNullOrWhiteSpace(StringFormat) &&
+                (targetType == typeof(string) || targetType == typeof(object)))
+            {
+                converter = new StringFormatValueConverter(StringFormat, converter);
+            }
+
+            var subject = new BindingExpression(
+                observer,
+                targetType,
+                fallback,
+                TargetNullValue,
+                converter ?? DefaultValueConverter.Instance,
+                ConverterParameter,
+                Priority);
+
+            return new InstancedBinding(subject, Mode, Priority);
+        }
+
+        protected ExpressionObserver CreateDataContextObserver(
+            IAvaloniaObject target,
+            ExpressionNode node,
+            bool targetIsDataContext,
+            object anchor)
+        {
+            Contract.Requires<ArgumentNullException>(target != null);
+
+            if (!(target is IStyledElement))
+            {
+                target = anchor as IStyledElement;
+
+                if (target == null)
+                {
+                    throw new InvalidOperationException("Cannot find a DataContext to bind to.");
+                }
+            }
+
+            if (!targetIsDataContext)
+            {
+                var result = new ExpressionObserver(
+                    () => target.GetValue(StyledElement.DataContextProperty),
+                    node,
+                    new UpdateSignal(target, StyledElement.DataContextProperty),
+                    null);
+
+                return result;
+            }
+            else
+            {
+                return new ExpressionObserver(
+                    GetParentDataContext(target),
+                    node,
+                    null);
+            }
+        }
+
+        protected ExpressionObserver CreateElementObserver(
+            IStyledElement target,
+            string elementName,
+            ExpressionNode node)
+        {
+            Contract.Requires<ArgumentNullException>(target != null);
+
+            NameScope.TryGetTarget(out var scope);
+            if (scope == null)
+                throw new InvalidOperationException("Name scope is null or was already collected");
+            var result = new ExpressionObserver(
+                NameScopeLocator.Track(scope, elementName),
+                node,
+                null);
+            return result;
+        }
+
+        protected ExpressionObserver CreateFindAncestorObserver(
+            IStyledElement target,
+            RelativeSource relativeSource,
+            ExpressionNode node)
+        {
+            Contract.Requires<ArgumentNullException>(target != null);
+
+            IObservable<object> controlLocator;
+
+            switch (relativeSource.Tree)
+            {
+                case TreeType.Logical:
+                    controlLocator = ControlLocator.Track(
+                        (ILogical)target,
+                        relativeSource.AncestorLevel - 1,
+                        relativeSource.AncestorType);
+                    break;
+                case TreeType.Visual:
+                    controlLocator = VisualLocator.Track(
+                        (IVisual)target,
+                        relativeSource.AncestorLevel - 1,
+                        relativeSource.AncestorType);
+                    break;
+                default:
+                    throw new InvalidOperationException("Invalid tree to traverse.");
+            }
+
+            return new ExpressionObserver(
+                controlLocator,
+                node,
+                null);
+        }
+
+        protected ExpressionObserver CreateSourceObserver(
+            object source,
+            ExpressionNode node)
+        {
+            Contract.Requires<ArgumentNullException>(source != null);
+
+            return new ExpressionObserver(source, node);
+        }
+
+        protected ExpressionObserver CreateTemplatedParentObserver(
+            IAvaloniaObject target,
+            ExpressionNode node)
+        {
+            Contract.Requires<ArgumentNullException>(target != null);
+
+            var result = new ExpressionObserver(
+                () => target.GetValue(StyledElement.TemplatedParentProperty),
+                node,
+                new UpdateSignal(target, StyledElement.TemplatedParentProperty),
+                null);
+
+            return result;
+        }
+
+        protected IObservable<object> GetParentDataContext(IAvaloniaObject target)
+        {
+            // The DataContext is based on the visual parent and not the logical parent: this may
+            // seem counter intuitive considering the fact that property inheritance works on the logical
+            // tree, but consider a ContentControl with a ContentPresenter. The ContentControl's
+            // Content property is bound to a value which becomes the ContentPresenter's 
+            // DataContext - it is from this that the child hosted by the ContentPresenter needs to
+            // inherit its DataContext.
+            return target.GetObservable(Visual.VisualParentProperty)
+                .Select(x =>
+                {
+                    return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ??
+                           Observable.Return((object)null);
+                }).Switch();
+        }
+
+        private class UpdateSignal : SingleSubscriberObservableBase<Unit>
+        {
+            private readonly IAvaloniaObject _target;
+            private readonly AvaloniaProperty _property;
+
+            public UpdateSignal(IAvaloniaObject target, AvaloniaProperty property)
+            {
+                _target = target;
+                _property = property;
+            }
+
+            protected override void Subscribed()
+            {
+                _target.PropertyChanged += PropertyChanged;
+            }
+
+            protected override void Unsubscribed()
+            {
+                _target.PropertyChanged -= PropertyChanged;
+            }
+
+            private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+            {
+                if (e.Property == _property)
+                {
+                    PublishNext(Unit.Default);
+                }
+            }
+        }
+    }
+}

+ 4 - 1
src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs

@@ -4,7 +4,10 @@ using Avalonia.Utilities;
 
 namespace Avalonia.Markup.Parsers
 {
-    internal static class ArgumentListParser
+#if !BUILDTASK
+    public
+#endif
+    static class ArgumentListParser
     {
         public static IList<string> ParseArguments(this ref CharacterReader r, char open, char close, char delimiter = ',')
         {

+ 387 - 0
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@@ -0,0 +1,387 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Data.Core;
+using Avalonia.Utilities;
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.Markup.Parsers
+{
+    internal enum SourceMode
+    {
+        Data,
+        Control
+    }
+
+    internal static class BindingExpressionGrammar
+    {
+        public static (IList<INode> Nodes, SourceMode Mode) Parse(ref CharacterReader r)
+        {
+            var nodes = new List<INode>();
+            var state = State.Start;
+            var mode = SourceMode.Data;
+
+            while (!r.End && state != State.End)
+            {
+                switch (state)
+                {
+                    case State.Start:
+                        state = ParseStart(ref r, nodes);
+                        break;
+
+                    case State.AfterMember:
+                        state = ParseAfterMember(ref r, nodes);
+                        break;
+
+                    case State.BeforeMember:
+                        state = ParseBeforeMember(ref r, nodes);
+                        break;
+
+                    case State.AttachedProperty:
+                        state = ParseAttachedProperty(ref r, nodes);
+                        break;
+
+                    case State.Indexer:
+                        state = ParseIndexer(ref r, nodes);
+                        break;
+
+                    case State.ElementName:
+                        state = ParseElementName(ref r, nodes);
+                        mode = SourceMode.Control;
+                        break;
+
+                    case State.RelativeSource:
+                        state = ParseRelativeSource(ref r, nodes);
+                        mode = SourceMode.Control;
+                        break;
+                }
+            }
+
+            if (state == State.BeforeMember)
+            {
+                throw new ExpressionParseException(r.Position, "Unexpected end of expression.");
+            }
+
+            return (nodes, mode);
+        }
+
+        private static State ParseStart(ref CharacterReader r, IList<INode> nodes)
+        {
+            if (ParseNot(ref r))
+            {
+                nodes.Add(new NotNode());
+                return State.Start;
+            }
+
+            else if (ParseSharp(ref r))
+            {
+                return State.ElementName;
+            }
+            else if (ParseDollarSign(ref r))
+            {
+                return State.RelativeSource;
+            }
+            else if (ParseOpenBrace(ref r))
+            {
+                return State.AttachedProperty;
+            }
+            else if (PeekOpenBracket(ref r))
+            {
+                return State.Indexer;
+            }
+            else if (ParseDot(ref r))
+            {
+                nodes.Add(new EmptyExpressionNode());
+                return State.End;
+            }
+            else
+            {
+                var identifier = r.ParseIdentifier();
+
+                if (!identifier.IsEmpty)
+                {
+                    nodes.Add(new PropertyNameNode { PropertyName = identifier.ToString() });
+                    return State.AfterMember;
+                }
+            }
+
+            return State.End;
+        }
+
+        private static State ParseAfterMember(ref CharacterReader r, IList<INode> nodes)
+        {
+            if (ParseMemberAccessor(ref r))
+            {
+                return State.BeforeMember;
+            }
+            else if (ParseStreamOperator(ref r))
+            {
+                nodes.Add(new StreamNode());
+                return State.AfterMember;
+            }
+            else if (PeekOpenBracket(ref r))
+            {
+                return State.Indexer;
+            }
+
+            return State.End;
+        }
+
+        private static State ParseBeforeMember(ref CharacterReader r, IList<INode> nodes)
+        {
+            if (ParseOpenBrace(ref r))
+            {
+                return State.AttachedProperty;
+            }
+            else
+            {
+                var identifier = r.ParseIdentifier();
+
+                if (!identifier.IsEmpty)
+                {
+                    nodes.Add(new PropertyNameNode { PropertyName = identifier.ToString() });
+                    return State.AfterMember;
+                }
+
+                return State.End;
+            }
+        }
+
+        private static State ParseAttachedProperty(ref CharacterReader r, List<INode> nodes)
+        {
+            var (ns, owner) = ParseTypeName(ref r);
+
+            if (r.End || !r.TakeIf('.'))
+            {
+                throw new ExpressionParseException(r.Position, "Invalid attached property name.");
+            }
+
+            var name = r.ParseIdentifier();
+
+            if (r.End || !r.TakeIf(')'))
+            {
+                throw new ExpressionParseException(r.Position, "Expected ')'.");
+            }
+
+            nodes.Add(new AttachedPropertyNameNode
+            {
+                Namespace = ns.ToString(),
+                TypeName = owner.ToString(),
+                PropertyName = name.ToString()
+            });
+            return State.AfterMember;
+        }
+
+        private static State ParseIndexer(ref CharacterReader r, List<INode> nodes)
+        {
+            var args = r.ParseArguments('[', ']');
+
+            if (args.Count == 0)
+            {
+                throw new ExpressionParseException(r.Position, "Indexer may not be empty.");
+            }
+
+            nodes.Add(new IndexerNode { Arguments = args });
+            return State.AfterMember;
+        }
+
+        private static State ParseElementName(ref CharacterReader r, List<INode> nodes)
+        {
+            var name = r.ParseIdentifier();
+
+            if (name.IsEmpty)
+            {
+                throw new ExpressionParseException(r.Position, "Element name expected after '#'.");
+            }
+
+            nodes.Add(new NameNode { Name = name.ToString() });
+            return State.AfterMember;
+        }
+
+        private static State ParseRelativeSource(ref CharacterReader r, List<INode> nodes)
+        {
+            var mode = r.ParseIdentifier();
+
+            if (mode.SequenceEqual("self".AsSpan()))
+            {
+                nodes.Add(new SelfNode());
+            }
+            else if (mode.SequenceEqual("parent".AsSpan()))
+            {
+                string ancestorNamespace = null;
+                string ancestorType = null;
+                var ancestorLevel = 0;
+                if (PeekOpenBracket(ref r))
+                {
+                    var args = r.ParseArguments('[', ']', ';');
+                    if (args.Count > 2 || args.Count == 0)
+                    {
+                        throw new ExpressionParseException(r.Position, "Too many arguments in RelativeSource syntax sugar");
+                    }
+                    else if (args.Count == 1)
+                    {
+                        if (int.TryParse(args[0], out int level))
+                        {
+                            ancestorType = null;
+                            ancestorLevel = level;
+                        }
+                        else
+                        {
+                            var reader = new CharacterReader(args[0].AsSpan());
+                            (ancestorNamespace, ancestorType) = ParseTypeName(ref reader);
+                        }
+                    }
+                    else
+                    {
+                        var reader = new CharacterReader(args[0].AsSpan());
+                        (ancestorNamespace, ancestorType) = ParseTypeName(ref reader);
+                        ancestorLevel = int.Parse(args[1]);
+                    }
+                }
+                nodes.Add(new AncestorNode
+                {
+                    Namespace = ancestorNamespace,
+                    TypeName = ancestorType,
+                    Level = ancestorLevel
+                });
+            }
+            else
+            {
+                throw new ExpressionParseException(r.Position, "Unknown RelativeSource mode.");
+            }
+
+            return State.AfterMember;
+        }
+
+        private static TypeName ParseTypeName(ref CharacterReader r)
+        {
+            ReadOnlySpan<char> ns, typeName;
+            ns = ReadOnlySpan<char>.Empty;
+            var typeNameOrNamespace = r.ParseIdentifier();
+
+            if (!r.End && r.TakeIf(':'))
+            {
+                ns = typeNameOrNamespace;
+                typeName = r.ParseIdentifier();
+            }
+            else
+            {
+                typeName = typeNameOrNamespace;
+            }
+
+            return new TypeName(ns, typeName);
+        }
+
+        private static bool ParseNot(ref CharacterReader r)
+        {
+            return !r.End && r.TakeIf('!');
+        }
+
+        private static bool ParseMemberAccessor(ref CharacterReader r)
+        {
+            return !r.End && r.TakeIf('.');
+        }
+
+        private static bool ParseOpenBrace(ref CharacterReader r)
+        {
+            return !r.End && r.TakeIf('(');
+        }
+
+        private static bool PeekOpenBracket(ref CharacterReader r)
+        {
+            return !r.End && r.Peek == '[';
+        }
+
+        private static bool ParseStreamOperator(ref CharacterReader r)
+        {
+            return !r.End && r.TakeIf('^');
+        }
+
+        private static bool ParseDollarSign(ref CharacterReader r)
+        {
+            return !r.End && r.TakeIf('$');
+        }
+
+        private static bool ParseSharp(ref CharacterReader r)
+        {
+            return !r.End && r.TakeIf('#');
+        }
+
+        private static bool ParseDot(ref CharacterReader r)
+        {
+            return !r.End && r.TakeIf('.');
+        }
+
+        private enum State
+        {
+            Start,
+            RelativeSource,
+            ElementName,
+            AfterMember,
+            BeforeMember,
+            AttachedProperty,
+            Indexer,
+            End,
+        }
+
+        private readonly ref struct TypeName
+        {
+            public TypeName(ReadOnlySpan<char> ns, ReadOnlySpan<char> typeName)
+            {
+                Namespace = ns;
+                Type = typeName;
+            }
+
+            public readonly ReadOnlySpan<char> Namespace;
+            public readonly ReadOnlySpan<char> Type;
+
+            public void Deconstruct(out string ns, out string typeName)
+            {
+                ns = Namespace.ToString();
+                typeName = Type.ToString();
+            }
+        }
+
+        public interface INode {}
+
+        public interface ITransformNode {}
+
+        public class EmptyExpressionNode : INode { }
+
+        public class PropertyNameNode : INode
+        {
+            public string PropertyName { get; set; }
+        }
+
+        public class AttachedPropertyNameNode : INode
+        {
+            public string Namespace { get; set; }
+            public string TypeName { get; set; }
+            public string PropertyName { get; set; }
+        }
+
+        public class IndexerNode : INode
+        {
+            public IList<string> Arguments { get; set; }
+        }
+
+        public class NotNode : INode, ITransformNode {}
+
+        public class StreamNode : INode {}
+
+        public class SelfNode : INode {}
+
+        public class NameNode : INode
+        {
+            public string Name { get; set; }
+        }
+
+        public class AncestorNode : INode
+        {
+            public string Namespace { get; set; }
+            public string TypeName { get; set; }
+            public int Level { get; set; }
+        }
+    }
+}

+ 45 - 298
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@@ -8,12 +8,6 @@ using Avalonia.Controls;
 
 namespace Avalonia.Markup.Parsers
 {
-    internal enum SourceMode
-    {
-        Data,
-        Control
-    }
-
     internal class ExpressionParser
     {
         private readonly bool _enableValidation;
@@ -29,332 +23,85 @@ namespace Avalonia.Markup.Parsers
 
         public (ExpressionNode Node, SourceMode Mode) Parse(ref CharacterReader r)
         {
-            var nodes = new List<ExpressionNode>();
-            var state = State.Start;
-            var mode = SourceMode.Data;
+            ExpressionNode rootNode = null;
+            ExpressionNode node = null;
+            var (astNodes, mode) = BindingExpressionGrammar.Parse(ref r);
 
-            while (!r.End && state != State.End)
+            foreach (var astNode in astNodes)
             {
-                switch (state)
+                ExpressionNode nextNode = null;
+                switch (astNode)
                 {
-                    case State.Start:
-                        state = ParseStart(ref r, nodes);
+                    case BindingExpressionGrammar.EmptyExpressionNode _:
+                        nextNode = new EmptyExpressionNode();
                         break;
-
-                    case State.AfterMember:
-                        state = ParseAfterMember(ref r, nodes);
+                    case BindingExpressionGrammar.NotNode _:
+                        nextNode = new LogicalNotNode();
                         break;
-
-                    case State.BeforeMember:
-                        state = ParseBeforeMember(ref r, nodes);
+                    case BindingExpressionGrammar.StreamNode _:
+                        nextNode = new StreamNode();
                         break;
-
-                    case State.AttachedProperty:
-                        state = ParseAttachedProperty(ref r, nodes);
+                    case BindingExpressionGrammar.PropertyNameNode propName:
+                        nextNode = new PropertyAccessorNode(propName.PropertyName, _enableValidation);
                         break;
-
-                    case State.Indexer:
-                        state = ParseIndexer(ref r, nodes);
+                    case BindingExpressionGrammar.IndexerNode indexer:
+                        nextNode = new StringIndexerNode(indexer.Arguments);
                         break;
-
-                    case State.ElementName:
-                        state = ParseElementName(ref r, nodes);
-                        mode = SourceMode.Control;
+                    case BindingExpressionGrammar.AttachedPropertyNameNode attachedProp:
+                        nextNode = ParseAttachedProperty(attachedProp);
                         break;
-
-                    case State.RelativeSource:
-                        state = ParseRelativeSource(ref r, nodes);
-                        mode = SourceMode.Control;
+                    case BindingExpressionGrammar.SelfNode _:
+                        nextNode = new SelfNode();
+                        break;
+                    case BindingExpressionGrammar.AncestorNode ancestor:
+                        nextNode = ParseFindAncestor(ancestor);
+                        break;
+                    case BindingExpressionGrammar.NameNode elementName:
+                        nextNode = new ElementNameNode(_nameScope, elementName.Name);
                         break;
                 }
-            }
-
-            if (state == State.BeforeMember)
-            {
-                throw new ExpressionParseException(r.Position, "Unexpected end of expression.");
-            }
-
-            for (int n = 0; n < nodes.Count - 1; ++n)
-            {
-                nodes[n].Next = nodes[n + 1];
-            }
-
-            return (nodes.FirstOrDefault(), mode);
-        }
-
-        private State ParseStart(ref CharacterReader r, IList<ExpressionNode> nodes)
-        {
-            if (ParseNot(ref r))
-            {
-                nodes.Add(new LogicalNotNode());
-                return State.Start;
-            }
-
-            else if (ParseSharp(ref r))
-            {
-                return State.ElementName;
-            }
-            else if (ParseDollarSign(ref r))
-            {
-                return State.RelativeSource;
-            }
-            else if (ParseOpenBrace(ref r))
-            {
-                return State.AttachedProperty;
-            }
-            else if (PeekOpenBracket(ref r))
-            {
-                return State.Indexer;
-            }
-            else if (ParseDot(ref r))
-            {
-                nodes.Add(new EmptyExpressionNode());
-                return State.End;
-            }
-            else
-            {
-                var identifier = r.ParseIdentifier();
-
-                if (!identifier.IsEmpty)
+                if (rootNode is null)
                 {
-                    nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation));
-                    return State.AfterMember;
+                    rootNode = node = nextNode;
                 }
-            }
-
-            return State.End;
-        }
-
-        private static State ParseAfterMember(ref CharacterReader r, IList<ExpressionNode> nodes)
-        {
-            if (ParseMemberAccessor(ref r))
-            {
-                return State.BeforeMember;
-            }
-            else if (ParseStreamOperator(ref r))
-            {
-                nodes.Add(new StreamNode());
-                return State.AfterMember;
-            }
-            else if (PeekOpenBracket(ref r))
-            {
-                return State.Indexer;
-            }
-
-            return State.End;
-        }
-
-        private State ParseBeforeMember(ref CharacterReader r, IList<ExpressionNode> nodes)
-        {
-            if (ParseOpenBrace(ref r))
-            {
-                return State.AttachedProperty;
-            }
-            else
-            {
-                var identifier = r.ParseIdentifier();
-
-                if (!identifier.IsEmpty)
+                else
                 {
-                    nodes.Add(new PropertyAccessorNode(identifier.ToString(), _enableValidation));
-                    return State.AfterMember;
+                    node.Next = nextNode;
+                    node = nextNode;
                 }
-
-                return State.End;
-            }
-        }
-
-        private State ParseAttachedProperty(ref CharacterReader r, List<ExpressionNode> nodes)
-        {
-            var (ns, owner) = ParseTypeName(ref r);
-
-            if (r.End || !r.TakeIf('.'))
-            {
-                throw new ExpressionParseException(r.Position, "Invalid attached property name.");
-            }
-
-            var name = r.ParseIdentifier();
-
-            if (r.End || !r.TakeIf(')'))
-            {
-                throw new ExpressionParseException(r.Position, "Expected ')'.");
-            }
-
-            if (_typeResolver == null)
-            {
-                throw new InvalidOperationException("Cannot parse a binding path with an attached property without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
-            }
-
-            var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(ns.ToString(), owner.ToString()), name.ToString());
-
-            nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableValidation));
-            return State.AfterMember;
-        }
-
-        private State ParseIndexer(ref CharacterReader r, List<ExpressionNode> nodes)
-        {
-            var args = r.ParseArguments('[', ']');
-
-            if (args.Count == 0)
-            {
-                throw new ExpressionParseException(r.Position, "Indexer may not be empty.");
             }
 
-            nodes.Add(new StringIndexerNode(args));
-            return State.AfterMember;
+            return (rootNode, mode);
         }
 
-        private State ParseElementName(ref CharacterReader r, List<ExpressionNode> nodes)
+        private FindAncestorNode ParseFindAncestor(BindingExpressionGrammar.AncestorNode node)
         {
-            var name = r.ParseIdentifier();
+            Type ancestorType = null;
+            var ancestorLevel = node.Level;
 
-            if (name == null)
+            if (!(node.Namespace is null) && !(node.TypeName is null))
             {
-                throw new ExpressionParseException(r.Position, "Element name expected after '#'.");
-            }
-
-            nodes.Add(new ElementNameNode(_nameScope, name.ToString()));
-            return State.AfterMember;
-        }
-
-        private State ParseRelativeSource(ref CharacterReader r, List<ExpressionNode> nodes)
-        {
-            var mode = r.ParseIdentifier();
-
-            if (mode.Equals("self".AsSpan(), StringComparison.InvariantCulture))
-            {
-                nodes.Add(new SelfNode());
-            }
-            else if (mode.Equals("parent".AsSpan(), StringComparison.InvariantCulture))
-            {
-                Type ancestorType = null;
-                var ancestorLevel = 0;
-                if (PeekOpenBracket(ref r))
+                if (_typeResolver == null)
                 {
-                    var args = r.ParseArguments('[', ']', ';');
-                    if (args.Count > 2 || args.Count == 0)
-                    {
-                        throw new ExpressionParseException(r.Position, "Too many arguments in RelativeSource syntax sugar");
-                    }
-                    else if (args.Count == 1)
-                    {
-                        if (int.TryParse(args[0], out int level))
-                        {
-                            ancestorType = null;
-                            ancestorLevel = level;
-                        }
-                        else
-                        {
-                            var reader = new CharacterReader(args[0].AsSpan());
-                            var typeName = ParseTypeName(ref reader);
-                            ancestorType = _typeResolver(typeName.Namespace.ToString(), typeName.Type.ToString());
-                        }
-                    }
-                    else
-                    {
-                        var reader = new CharacterReader(args[0].AsSpan());
-                        var typeName = ParseTypeName(ref reader);
-                        ancestorType = _typeResolver(typeName.Namespace.ToString(), typeName.Type.ToString());
-                        ancestorLevel = int.Parse(args[1]);
-                    }
+                    throw new InvalidOperationException("Cannot parse a binding path with a typed FindAncestor without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
                 }
-                nodes.Add(new FindAncestorNode(ancestorType, ancestorLevel));
-            }
-            else
-            {
-                throw new ExpressionParseException(r.Position, "Unknown RelativeSource mode.");
-            }
 
-            return State.AfterMember;
-        }
-        
-        private static TypeName ParseTypeName(ref CharacterReader r)
-        {
-            ReadOnlySpan<char> ns, typeName;
-            ns = ReadOnlySpan<char>.Empty;
-            var typeNameOrNamespace = r.ParseIdentifier();
-
-            if (!r.End && r.TakeIf(':'))
-            {
-                ns = typeNameOrNamespace;
-                typeName = r.ParseIdentifier();
+                ancestorType = _typeResolver(node.Namespace, node.TypeName);
             }
-            else
-            {
-                typeName = typeNameOrNamespace;
-            }
-
-            return new TypeName(ns, typeName);
-        }
-      
-        private static bool ParseNot(ref CharacterReader r)
-        {
-            return !r.End && r.TakeIf('!');
-        }
-
-        private static bool ParseMemberAccessor(ref CharacterReader r)
-        {
-            return !r.End && r.TakeIf('.');
-        }
-
-        private static bool ParseOpenBrace(ref CharacterReader r)
-        {
-            return !r.End && r.TakeIf('(');
-        }
-
-        private static bool PeekOpenBracket(ref CharacterReader r)
-        {
-            return !r.End && r.Peek == '[';
-        }
-
-        private static bool ParseStreamOperator(ref CharacterReader r)
-        {
-            return !r.End && r.TakeIf('^');
-        }
-
-        private static bool ParseDollarSign(ref CharacterReader r)
-        {
-            return !r.End && r.TakeIf('$');
-        }
-
-        private static bool ParseSharp(ref CharacterReader r)
-        {
-            return !r.End && r.TakeIf('#');
-        }
 
-        private static bool ParseDot(ref CharacterReader r)
-        {
-            return !r.End && r.TakeIf('.');
-        }
-
-        private enum State
-        {
-            Start,
-            RelativeSource,
-            ElementName,
-            AfterMember,
-            BeforeMember,
-            AttachedProperty,
-            Indexer,
-            End,
+            return new FindAncestorNode(ancestorType, ancestorLevel);
         }
 
-        private readonly ref struct TypeName
+        private AvaloniaPropertyAccessorNode ParseAttachedProperty(BindingExpressionGrammar.AttachedPropertyNameNode node)
         {
-            public TypeName(ReadOnlySpan<char> ns, ReadOnlySpan<char> typeName)
+            if (_typeResolver == null)
             {
-                Namespace = ns;
-                Type = typeName;
+                throw new InvalidOperationException("Cannot parse a binding path with an attached property without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
             }
 
-            public readonly ReadOnlySpan<char> Namespace;
-            public readonly ReadOnlySpan<char> Type;
+            var property = AvaloniaPropertyRegistry.Instance.FindRegistered(_typeResolver(node.Namespace, node.TypeName), node.PropertyName);
 
-            public void Deconstruct(out ReadOnlySpan<char> ns, out ReadOnlySpan<char> typeName)
-            {
-                ns = Namespace;
-                typeName = Type;
-            }
+            return new AvaloniaPropertyAccessorNode(property, _enableValidation);
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs

@@ -5,7 +5,7 @@ using Avalonia.LogicalTree;
 
 namespace Avalonia.Markup.Parsers.Nodes
 {
-    internal class ElementNameNode : ExpressionNode
+    public class ElementNameNode : ExpressionNode
     {
         private readonly WeakReference<INameScope> _nameScope;
         private readonly string _name;

+ 1 - 1
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs

@@ -4,7 +4,7 @@ using Avalonia.LogicalTree;
 
 namespace Avalonia.Markup.Parsers.Nodes
 {
-    internal class FindAncestorNode : ExpressionNode
+    public class FindAncestorNode : ExpressionNode
     {
         private readonly int _level;
         private readonly Type _ancestorType;

+ 1 - 1
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/SelfNode.cs

@@ -2,7 +2,7 @@
 
 namespace Avalonia.Markup.Parsers.Nodes
 {
-    internal class SelfNode : ExpressionNode
+    public class SelfNode : ExpressionNode
     {
         public override string Description => "$self";
     }

+ 233 - 0
src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs

@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Data.Core;
+using Avalonia.Utilities;
+
+namespace Avalonia.Markup.Parsers
+{
+#if !BUILDTASK
+    public
+#endif
+    class PropertyPathGrammar
+    {
+        private enum State
+        {
+            Start,
+            Next,
+            AfterProperty,
+            End
+        }
+
+        public static IEnumerable<ISyntax> Parse(string s)
+        {
+            var r = new CharacterReader(s.AsSpan());
+            return Parse(ref r);
+        }
+
+        private static IEnumerable<ISyntax> Parse(ref CharacterReader r)
+        {
+            var state = State.Start;
+            var parsed = new List<ISyntax>();
+            while (state != State.End)
+            {
+                ISyntax syntax = null;
+                if (state == State.Start)
+                    (state, syntax) = ParseStart(ref r);
+                else if (state == State.Next)
+                    (state, syntax) = ParseNext(ref r);
+                else if (state == State.AfterProperty)
+                    (state, syntax) = ParseAfterProperty(ref r);
+                
+                
+                if (syntax != null)
+                {
+                    parsed.Add(syntax);
+                }
+            }
+
+            if (state != State.End && r.End)
+            {
+                throw new ExpressionParseException(r.Position, "Unexpected end of property path");
+            }
+
+            return parsed;
+        }
+        
+        private static (State, ISyntax) ParseNext(ref CharacterReader r)
+        {
+            r.SkipWhitespace();
+            if (r.End)
+                return (State.End, null);
+            
+            return ParseStart(ref r);
+        }
+        
+        private static (State, ISyntax) ParseStart(ref CharacterReader r)
+        {
+            if (TryParseCasts(ref r, out var rv))
+                return rv;
+            r.SkipWhitespace();
+
+            if (r.TakeIf('('))
+                return ParseTypeQualifiedProperty(ref r);
+
+            return ParseProperty(ref r);
+        }
+
+        private static (State, ISyntax) ParseTypeQualifiedProperty(ref CharacterReader r)
+        {
+            r.SkipWhitespace();
+            const string error =
+                "Unable to parse qualified property name, expected `(ns:TypeName.PropertyName)` or `(TypeName.PropertyName)` after `(`";
+
+            var typeName = ParseXamlIdentifier(ref r);
+            
+            
+            if (!r.TakeIf('.'))
+                throw new ExpressionParseException(r.Position, error);
+
+            var propertyName = r.ParseIdentifier();
+            if (propertyName.IsEmpty)
+                throw new ExpressionParseException(r.Position, error);
+
+            r.SkipWhitespace();
+            if (!r.TakeIf(')'))
+                throw new ExpressionParseException(r.Position,
+                    "Expected ')' after qualified property name "
+                    + typeName.ns + ':' + typeName.name +
+                    "." + propertyName.ToString());
+
+            return (State.AfterProperty,
+                new TypeQualifiedPropertySyntax
+                {
+                    Name = propertyName.ToString(),
+                    TypeName = typeName.name,
+                    TypeNamespace = typeName.ns
+                });
+        }
+
+        static (string ns, string name) ParseXamlIdentifier(ref CharacterReader r)
+        {
+            var ident = r.ParseIdentifier();
+            if (ident.IsEmpty)
+                throw new ExpressionParseException(r.Position, "Expected identifier");
+            if (r.TakeIf(':'))
+            {
+                var part2 = r.ParseIdentifier();
+                if (part2.IsEmpty)
+                    throw new ExpressionParseException(r.Position,
+                        "Expected the rest of the identifier after " + ident.ToString() + ":");
+                return (ident.ToString(), part2.ToString());
+            }
+
+            return (null, ident.ToString());
+        }
+        
+        private static (State, ISyntax) ParseProperty(ref CharacterReader r)
+        {
+            r.SkipWhitespace();
+            var prop = r.ParseIdentifier();
+            if (prop.IsEmpty)
+                throw new ExpressionParseException(r.Position, "Unable to parse property name");
+            return (State.AfterProperty, new PropertySyntax {Name = prop.ToString()});
+        }
+
+        private static bool TryParseCasts(ref CharacterReader r, out (State, ISyntax) rv)
+        {
+            if (r.TakeIfKeyword(":="))
+                rv = ParseEnsureType(ref r);
+            else if (r.TakeIfKeyword(":>") || r.TakeIfKeyword("as "))
+                rv = ParseCastType(ref r);
+            else
+            {
+                rv = default;
+                return false;
+            }
+
+            return true;
+        }
+        
+        private static (State, ISyntax) ParseAfterProperty(ref CharacterReader r)
+        {
+            if (TryParseCasts(ref r, out var rv))
+                return rv;
+            
+            r.SkipWhitespace();
+            if (r.End)
+                return (State.End, null);
+            if (r.TakeIf('.'))
+                return (State.Next, ChildTraversalSyntax.Instance);
+            
+
+            
+            throw new ExpressionParseException(r.Position, "Unexpected character " + r.Peek + " after property name");
+        }
+
+        private static (State, ISyntax) ParseEnsureType(ref CharacterReader r)
+        {
+            r.SkipWhitespace();
+            var type = ParseXamlIdentifier(ref r);
+            return (State.AfterProperty, new EnsureTypeSyntax {TypeName = type.name, TypeNamespace = type.ns});
+        }
+        
+        private static (State, ISyntax) ParseCastType(ref CharacterReader r)
+        {
+            r.SkipWhitespace();
+            var type = ParseXamlIdentifier(ref r);
+            return (State.AfterProperty, new CastTypeSyntax {TypeName = type.name, TypeNamespace = type.ns});
+        }
+
+        public interface ISyntax
+        {
+            
+        }
+
+        public class PropertySyntax : ISyntax
+        {
+            public string Name { get; set; }
+
+            public override bool Equals(object obj)
+                => obj is PropertySyntax other
+                   && other.Name == Name;
+        }
+        
+        public class TypeQualifiedPropertySyntax : ISyntax
+        {
+            public string Name { get; set; }
+            public string TypeName { get; set; }
+            public string TypeNamespace { get; set; }
+
+            public override bool Equals(object obj)
+                => obj is TypeQualifiedPropertySyntax other
+                   && other.Name == Name
+                   && other.TypeName == TypeName
+                   && other.TypeNamespace == TypeNamespace;
+        }
+        
+        public class ChildTraversalSyntax : ISyntax
+        {
+            public static ChildTraversalSyntax Instance { get;  } = new ChildTraversalSyntax();
+            public override bool Equals(object obj) => obj is ChildTraversalSyntax;
+        }
+        
+        public class EnsureTypeSyntax : ISyntax
+        {
+            public string TypeName { get; set; }
+            public string TypeNamespace { get; set; }
+            public override bool Equals(object obj)
+                => obj is EnsureTypeSyntax other
+                   && other.TypeName == TypeName
+                   && other.TypeNamespace == TypeNamespace;
+        }
+        
+        public class CastTypeSyntax : ISyntax
+        {
+            public string TypeName { get; set; }
+            public string TypeNamespace { get; set; }
+            public override bool Equals(object obj)
+                => obj is CastTypeSyntax other
+                   && other.TypeName == TypeName
+                   && other.TypeNamespace == TypeNamespace;
+        }
+    }
+}

+ 103 - 0
tests/Avalonia.Base.UnitTests/Data/Core/PropertyPathGrammarTests.cs

@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Markup.Parsers;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests.Data.Core
+{
+    public class PropertyPathGrammarTests
+    {
+        static void Check(string s, params PropertyPathGrammar.ISyntax[] expected)
+        {
+            var parsed = PropertyPathGrammar.Parse(s).ToList();
+            Assert.Equal(expected.Length, parsed.Count);
+            for (var c = 0; c < parsed.Count; c++)
+                Assert.Equal(expected[c], parsed[c]);
+        }
+
+        [Fact]
+        public void PropertyPath_Should_Support_Simple_Properties()
+        {
+            Check("SomeProperty", new PropertyPathGrammar.PropertySyntax {Name = "SomeProperty"});
+        }
+
+        [Fact]
+        public void PropertyPath_Should_Ignore_Trailing_Whitespace()
+        {
+            Check("  SomeProperty   ", new PropertyPathGrammar.PropertySyntax {Name = "SomeProperty"});
+        }
+
+        [Fact]
+        public void PropertyPath_Should_Support_Qualified_Properties()
+        {
+            Check(" ( somens:SomeType.SomeProperty ) ",
+                new PropertyPathGrammar.TypeQualifiedPropertySyntax()
+                {
+                    Name = "SomeProperty", TypeName = "SomeType", TypeNamespace = "somens"
+                });
+        }
+        
+        [Fact]
+        public void PropertyPath_Should_Support_Property_Paths()
+        {
+            Check(" ( somens:SomeType.SomeProperty ).Child . SubChild ",
+                new PropertyPathGrammar.TypeQualifiedPropertySyntax()
+                {
+                    Name = "SomeProperty", TypeName = "SomeType", TypeNamespace = "somens"
+                },
+                PropertyPathGrammar.ChildTraversalSyntax.Instance,
+                new PropertyPathGrammar.PropertySyntax {Name = "Child"},
+                PropertyPathGrammar.ChildTraversalSyntax.Instance,
+                new PropertyPathGrammar.PropertySyntax {Name = "SubChild"}
+            );
+        }
+        
+        [Fact]
+        public void PropertyPath_Should_Support_Casts()
+        {
+            Check(" ( somens:SomeType.SomeProperty ) :> SomeType.Child as somens:SomeType . SubChild ",
+                new PropertyPathGrammar.TypeQualifiedPropertySyntax()
+                {
+                    Name = "SomeProperty", TypeName = "SomeType", TypeNamespace = "somens"
+                },
+                new PropertyPathGrammar.CastTypeSyntax
+                {
+                    TypeName = "SomeType"
+                },
+                PropertyPathGrammar.ChildTraversalSyntax.Instance,
+                new PropertyPathGrammar.PropertySyntax {Name = "Child"},
+                new PropertyPathGrammar.CastTypeSyntax
+                {
+                    TypeName = "SomeType",
+                    TypeNamespace = "somens"
+                },
+                PropertyPathGrammar.ChildTraversalSyntax.Instance,
+                new PropertyPathGrammar.PropertySyntax {Name = "SubChild"}
+            );
+        }
+        
+        [Fact]
+        public void PropertyPath_Should_Support_Ensure_Type()
+        {
+            Check(" ( somens:SomeType.SomeProperty ) := SomeType.Child := somens:SomeType . SubChild ",
+                new PropertyPathGrammar.TypeQualifiedPropertySyntax()
+                {
+                    Name = "SomeProperty", TypeName = "SomeType", TypeNamespace = "somens"
+                },
+                new PropertyPathGrammar.EnsureTypeSyntax
+                {
+                    TypeName = "SomeType"
+                },
+                PropertyPathGrammar.ChildTraversalSyntax.Instance,
+                new PropertyPathGrammar.PropertySyntax {Name = "Child"},
+                new PropertyPathGrammar.EnsureTypeSyntax
+                {
+                    TypeName = "SomeType",
+                    TypeNamespace = "somens"
+                },
+                PropertyPathGrammar.ChildTraversalSyntax.Instance,
+                new PropertyPathGrammar.PropertySyntax {Name = "SubChild"}
+            );
+        }
+    }
+}

+ 621 - 0
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@@ -0,0 +1,621 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Reactive.Subjects;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Controls.Presenters;
+using Avalonia.Data.Core;
+using Avalonia.Markup.Data;
+using Avalonia.UnitTests;
+using XamlX;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
+{
+    public class CompiledBindingExtensionTests
+    {
+        [Fact]
+        public void ResolvesClrPropertyBasedOnDataContextType()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.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 ResolvesPathPassedByProperty()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding Path=StringProperty}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.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()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding TaskProperty^}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    TaskProperty = Task.FromResult("foobar")
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.TaskProperty.Result, textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesStreamObservableBindingCorrectly()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding ObservableProperty^}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                DelayedBinding.ApplyBindings(textBlock);
+
+                var subject = new Subject<string>();
+                var dataContext = new TestDataContext
+                {
+                    ObservableProperty = subject
+                };
+
+                window.DataContext = dataContext;
+
+                subject.OnNext("foobar");
+
+                Assert.Equal("foobar", textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesIndexerBindingCorrectly()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding ListProperty[3]}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    ListProperty = { "A", "B", "C", "D", "E" }
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.ListProperty[3], textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesArrayIndexerBindingCorrectly()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding ArrayProperty[3]}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    ArrayProperty = new[] { "A", "B", "C", "D", "E" }
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.ArrayProperty[3], textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesObservableIndexerBindingCorrectly()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding ObservableCollectionProperty[3]}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    ObservableCollectionProperty = { "A", "B", "C", "D", "E" }
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.ObservableCollectionProperty[3], textBlock.Text);
+
+                dataContext.ObservableCollectionProperty[3] = "New Value";
+
+                Assert.Equal(dataContext.ObservableCollectionProperty[3], textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void InfersCompiledBindingDataContextFromDataContextBinding()
+        {
+            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'>
+    <TextBlock DataContext='{CompiledBinding StringProperty}' Text='{CompiledBinding}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
+
+                var dataContext = new TestDataContext
+                {
+                    StringProperty = "A"
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.StringProperty, textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesNonIntegerIndexerBindingCorrectly()
+        {
+            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'>
+    <TextBlock Text='{CompiledBinding NonIntegerIndexerProperty[Test]}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext();
+
+                dataContext.NonIntegerIndexerProperty["Test"] = "Initial Value";
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.NonIntegerIndexerProperty["Test"], textBlock.Text);
+
+                dataContext.NonIntegerIndexerProperty["Test"] = "New Value";
+
+                Assert.Equal(dataContext.NonIntegerIndexerProperty["Test"], textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void InfersDataTemplateTypeFromDataTypeProperty()
+        {
+            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'>
+    <Window.DataTemplates>
+        <DataTemplate DataType='{x:Type x:String}'>
+            <TextBlock Text='{CompiledBinding}' Name='textBlock' />
+        </DataTemplate>
+    </Window.DataTemplates>
+    <ContentControl Name='target' Content='{CompiledBinding StringProperty}' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var target = window.FindControl<ContentControl>("target");
+
+                var dataContext = new TestDataContext();
+
+                dataContext.StringProperty = "Initial Value";
+
+                window.DataContext = dataContext;
+
+                window.ApplyTemplate();
+                target.ApplyTemplate();
+                ((ContentPresenter)target.Presenter).UpdateChild();
+
+                Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text);
+            }
+        }
+
+
+        [Fact]
+        public void ThrowsOnUninferrableLooseDataTemplateNoDataTypeWithCompiledBindingPath()
+        {
+            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'>
+    <Window.DataTemplates>
+        <DataTemplate>
+            <TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
+        </DataTemplate>
+    </Window.DataTemplates>
+    <ContentControl Name='target' Content='{CompiledBinding}' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<XamlTransformException>(() => loader.Load(xaml));
+            }
+        }
+
+        [Fact]
+        public void ThrowsOnUninferrableDataTypeFromNonCompiledDataContextBindingWithCompiledBindingPath()
+        {
+            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'>
+    <ContentControl Name='target' DataContext='{Binding}'>
+        <TextBlock Text='{CompiledBinding StringProperty}' Name='textBlock' />
+    </ContentControl>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<XamlTransformException>(() => loader.Load(xaml));
+            }
+        }
+
+        [Fact]
+        public void InfersDataTemplateTypeFromParentCollectionItemsType()
+        {
+            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 Items='{CompiledBinding ListProperty}' Name='target'>
+        <ItemsControl.DataTemplates>
+            <DataTemplate>
+                <TextBlock Text='{CompiledBinding}' Name='textBlock' />
+            </DataTemplate>
+        </ItemsControl.DataTemplates>
+    </ItemsControl>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var target = window.FindControl<ItemsControl>("target");
+
+                var dataContext = new TestDataContext();
+
+                dataContext.ListProperty.Add("Test");
+
+                window.DataContext = dataContext;
+
+                window.ApplyTemplate();
+                target.ApplyTemplate();
+                target.Presenter.ApplyTemplate();
+
+                Assert.Equal(dataContext.ListProperty[0], (string)((ContentPresenter)target.Presenter.Panel.Children[0]).Content);
+            }
+        }
+
+        [Fact]
+        public void ThrowsOnUninferrableDataTemplateInItemsControlWithoutItemsBinding()
+        {
+            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='target'>
+        <ItemsControl.DataTemplates>
+            <DataTemplate>
+                <TextBlock Text='{CompiledBinding Property}' Name='textBlock' />
+            </DataTemplate>
+        </ItemsControl.DataTemplates>
+    </ItemsControl>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<XamlTransformException>(() => loader.Load(xaml));
+            }
+        }
+
+        [Fact]
+        public void ResolvesElementNameBinding()
+        {
+            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'>
+    <StackPanel>
+        <TextBlock Text='{CompiledBinding StringProperty}' x:Name='text' />
+        <TextBlock Text='{CompiledBinding #text.Text}' x:Name='text2' />
+    </StackPanel>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("text2");
+
+                var dataContext = new TestDataContext
+                {
+                    StringProperty = "foobar"
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.StringProperty, textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesElementNameBindingFromLongForm()
+        {
+            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'>
+    <StackPanel>
+        <TextBlock Text='{CompiledBinding StringProperty}' x:Name='text' />
+        <TextBlock Text='{CompiledBinding Text, ElementName=text}' x:Name='text2' />
+    </StackPanel>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("text2");
+
+                var dataContext = new TestDataContext
+                {
+                    StringProperty = "foobar"
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.StringProperty, textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesRelativeSourceBindingLongForm()
+        {
+            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'
+        Title='test'>
+    <TextBlock Text='{CompiledBinding Title, RelativeSource={RelativeSource AncestorType=Window}}' x:Name='text'/>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var target = window.FindControl<TextBlock>("text");
+
+                window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
+                target.ApplyTemplate();
+
+                Assert.Equal("test", target.Text);
+            }
+        }
+
+        [Fact]
+        public void ResolvesSourceBindingLongForm()
+        {
+            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'
+        Title='test'>
+    <TextBlock Text='{CompiledBinding Length, Source=Test}' x:Name='text'/>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var target = window.FindControl<TextBlock>("text");
+
+                window.ApplyTemplate();
+                window.Presenter.ApplyTemplate();
+                target.ApplyTemplate();
+
+                Assert.Equal("Test".Length.ToString(), target.Text);
+            }
+        }
+
+        [Fact]
+        public void CompilesBindingWhenRequested()
+        {
+            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'
+        x:CompileBindings='true'>
+    <TextBlock Text='{Binding StringProperty}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.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 ThrowsOnInvalidBindingPathOnCompiledBindingEnabledViaDirective()
+        {
+            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'
+        x:CompileBindings='true'>
+    <TextBlock Text='{Binding InvalidPath}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<XamlX.XamlParseException>(() => loader.Load(xaml));
+            }
+        }
+
+        [Fact]
+        public void ThrowsOnInvalidCompileBindingsDirective()
+        {
+            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'
+        x:CompileBindings='notabool'>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<XamlX.XamlParseException>(() => loader.Load(xaml));
+            }
+        }
+    }
+
+    public class TestDataContext
+    {
+        public string StringProperty { get; set; }
+
+        public Task<string> TaskProperty { get; set; }
+
+        public IObservable<string> ObservableProperty { get; set; }
+
+        public ObservableCollection<string> ObservableCollectionProperty { get; set; } = new ObservableCollection<string>();
+
+        public string[] ArrayProperty { get; set; }
+
+        public List<string> ListProperty { get; set; } = new List<string>();
+
+        public NonIntegerIndexer NonIntegerIndexerProperty { get; set; } = new NonIntegerIndexer();
+
+        public class NonIntegerIndexer : NotifyingBase
+        {
+            private readonly Dictionary<string, string> _storage = new Dictionary<string, string>();
+
+            public string this[string key]
+            {
+                get
+                {
+                    return _storage[key];
+                }
+                set
+                {
+                    _storage[key] = value;
+                    RaisePropertyChanged(CommonPropertyNames.IndexerName);
+                }
+            }
+        }
+    }
+}

+ 46 - 0
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@@ -6,7 +6,11 @@ using System.Linq;
 using System.Runtime.CompilerServices;
 using Avalonia.Controls;
 using Avalonia.Data.Converters;
+using Avalonia.Data.Core;
+using Avalonia.Input;
+using Avalonia.Interactivity;
 using Avalonia.Media;
+using Avalonia.Styling;
 using Avalonia.Threading;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
@@ -234,6 +238,30 @@ namespace Avalonia.Markup.Xaml.UnitTests
             }
         }
 
+        [Fact]
+        public void Provide_Value_Target_Should_Provide_Clr_Property_Info()
+        {
+            var parsed = AvaloniaXamlLoader.Parse<XamlIlClassWithClrPropertyWithValue>(@"
+<XamlIlClassWithClrPropertyWithValue 
+    xmlns='clr-namespace:Avalonia.Markup.Xaml.UnitTests'
+    Count='{XamlIlCheckClrPropertyInfo ExpectedPropertyName=Count}'
+/>", typeof(XamlIlClassWithClrPropertyWithValue).Assembly);
+            Assert.Equal(6, parsed.Count);
+        }
+
+        [Fact]
+        public void DataContextType_Resolution()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var parsed = AvaloniaXamlLoader.Parse<UserControl>(@"
+<UserControl 
+    xmlns='https://github.com/avaloniaui'
+    xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests;assembly=Avalonia.Markup.Xaml.UnitTests'
+    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' x:DataType='local:XamlIlBugTestsDataContext' />");
+            }
+        }
+
         [Fact]
         public void DataTemplates_Should_Resolve_Named_Controls_From_Parent_Scope()
         {
@@ -345,4 +373,22 @@ namespace Avalonia.Markup.Xaml.UnitTests
             return (int)control.GetValue(TestIntProperty);
         }
     }
+
+    public class XamlIlCheckClrPropertyInfoExtension
+    {
+        public string ExpectedPropertyName { get; set; }
+
+        public object ProvideValue(IServiceProvider prov)
+        {
+            var pvt = prov.GetService<IProvideValueTarget>();
+            var info = (ClrPropertyInfo)pvt.TargetProperty;
+            var v = (int)info.Get(pvt.TargetObject);
+            return v + 1;
+        }
+    }
+
+    public class XamlIlClassWithClrPropertyWithValue
+    {
+        public int Count { get; set; }= 5;
+    }
 }