diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index c0ebdea7b5..610d489d34 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -59,6 +59,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index 8bb2494706..4ecaf82faf 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -24,6 +24,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Targeted
+
+ Transformers.Insert(Transformers.FindIndex(x => x is XamlIlPropertyReferenceResolver),
+ new AvaloniaXamlIlTransformInstanceAttachedProperties());
Transformers.Insert(Transformers.FindIndex(x => x is XamlIlXamlPropertyValueTransformer),
new KnownPseudoMarkupExtensionsTransformer());
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
new file mode 100644
index 0000000000..241b0f12a3
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
@@ -0,0 +1,201 @@
+using System.Collections.Generic;
+using System.Linq;
+using XamlIl;
+using XamlIl.Ast;
+using XamlIl.Transform;
+using XamlIl.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+ public class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlIlAstTransformer
+ {
+
+ public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+ {
+ if (node is XamlIlAstNamePropertyReference prop
+ && prop.TargetType is XamlIlAstClrTypeReference targetRef
+ && prop.DeclaringType is XamlIlAstClrTypeReference declaringRef)
+ {
+ // Target and declared type aren't assignable but both inherit from AvaloniaObject
+ var avaloniaObject = context.Configuration.TypeSystem.FindType("Avalonia.AvaloniaObject");
+ if (avaloniaObject.IsAssignableFrom(targetRef.Type)
+ && avaloniaObject.IsAssignableFrom(declaringRef.Type)
+ && !targetRef.Type.IsAssignableFrom(declaringRef.Type))
+ {
+ // Instance property
+ var clrProp = declaringRef.Type.GetAllProperties().FirstOrDefault(p => p.Name == prop.Name);
+ if (clrProp != null
+ && (clrProp.Getter?.IsStatic == false || clrProp.Setter?.IsStatic == false))
+ {
+ var declaringType = (clrProp.Getter ?? clrProp.Setter)?.DeclaringType;
+ var avaloniaPropertyFieldName = prop.Name + "Property";
+ var avaloniaPropertyField = declaringType.Fields.FirstOrDefault(f => f.IsStatic && f.Name == avaloniaPropertyFieldName);
+ if (avaloniaPropertyField != null)
+ {
+ var avaloniaPropertyType = avaloniaPropertyField.FieldType;
+ while (avaloniaPropertyType != null
+ && !(avaloniaPropertyType.Namespace == "Avalonia"
+ && (avaloniaPropertyType.Name == "AvaloniaProperty"
+ || avaloniaPropertyType.Name == "AvaloniaProperty`1"
+ )))
+ {
+ // Attached properties are handled by vanilla XamlIl
+ if (avaloniaPropertyType.Name.StartsWith("AttachedProperty"))
+ return node;
+
+ avaloniaPropertyType = avaloniaPropertyType.BaseType;
+ }
+
+ if (avaloniaPropertyType == null)
+ return node;
+
+ if (avaloniaPropertyType.GenericArguments?.Count > 1)
+ return node;
+
+ var propertyType = avaloniaPropertyType.GenericArguments?.Count == 1 ?
+ avaloniaPropertyType.GenericArguments[0] :
+ context.Configuration.WellKnownTypes.Object;
+
+ return new XamlIlAstClrPropertyReference(prop,
+ new AvaloniaAttachedInstanceProperty(prop.Name, context.Configuration,
+ declaringType, propertyType, avaloniaPropertyType, avaloniaObject,
+ avaloniaPropertyField));
+ }
+
+ }
+
+
+ }
+ }
+
+ return node;
+ }
+
+ class AvaloniaAttachedInstanceProperty : IXamlIlProperty
+ {
+ private readonly XamlIlTransformerConfiguration _config;
+ private readonly IXamlIlType _declaringType;
+ private readonly IXamlIlType _avaloniaPropertyType;
+ private readonly IXamlIlType _avaloniaObject;
+ private readonly IXamlIlField _field;
+
+ public AvaloniaAttachedInstanceProperty(string name,
+ XamlIlTransformerConfiguration config,
+ IXamlIlType declaringType,
+ IXamlIlType type,
+ IXamlIlType avaloniaPropertyType,
+ IXamlIlType avaloniaObject,
+ IXamlIlField field)
+ {
+ _config = config;
+ _declaringType = declaringType;
+ _avaloniaPropertyType = avaloniaPropertyType;
+
+ // XamlIl doesn't support generic methods yet
+ if (_avaloniaPropertyType.GenericArguments?.Count > 0)
+ _avaloniaPropertyType = _avaloniaPropertyType.BaseType;
+
+ _avaloniaObject = avaloniaObject;
+ _field = field;
+ Name = name;
+ PropertyType = type;
+ Setter = new SetterMethod(this);
+ Getter = new GetterMethod(this);
+ }
+
+ public bool Equals(IXamlIlProperty other) =>
+ other is AvaloniaAttachedInstanceProperty ap && ap._field.Equals(_field);
+
+ public string Name { get; }
+ public IXamlIlType PropertyType { get; }
+ public IXamlIlMethod Setter { get; }
+ public IXamlIlMethod Getter { get; }
+ public IReadOnlyList CustomAttributes { get; } = new List();
+
+ class Method
+ {
+ public AvaloniaAttachedInstanceProperty Parent { get; }
+ public bool IsPublic => true;
+ public bool IsStatic => true;
+ public string Name { get; protected set; }
+ public IXamlIlType DeclaringType { get; }
+ public Method(AvaloniaAttachedInstanceProperty parent)
+ {
+ Parent = parent;
+ DeclaringType = parent._declaringType;
+ }
+
+ public bool Equals(IXamlIlMethod other) =>
+ other is Method m && m.Name == Name && m.DeclaringType.Equals(DeclaringType);
+ }
+
+ class SetterMethod : Method, IXamlIlCustomEmitMethod
+ {
+ public SetterMethod(AvaloniaAttachedInstanceProperty parent) : base(parent)
+ {
+ Name = "AvaloniaObject:SetValue_" + Parent.Name;
+ Parameters = new[] {Parent._avaloniaObject, Parent.PropertyType};
+ }
+
+ public IXamlIlType ReturnType => Parent._config.WellKnownTypes.Void;
+ public IReadOnlyList Parameters { get; }
+
+ public void EmitCall(IXamlIlEmitter emitter)
+ {
+ var so = Parent._config.WellKnownTypes.Object;
+ var method = Parent._avaloniaObject
+ .FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "SetValue"
+ &&
+ m.Parameters.Count == 3
+ && m.Parameters[0].Equals(Parent._avaloniaPropertyType)
+ && m.Parameters[1].Equals(so)
+ && m.Parameters[2].IsEnum
+ );
+ if (method == null)
+ throw new XamlIlTypeSystemException(
+ "Unable to find SetValue(AvaloniaProperty, object, BindingPriority) on AvaloniaObject");
+ var loc = emitter.DefineLocal(Parent.PropertyType);
+ emitter
+ .Stloc(loc)
+ .Ldsfld(Parent._field)
+ .Ldloc(loc);
+ if(Parent.PropertyType.IsValueType)
+ emitter.Box(Parent.PropertyType);
+ emitter
+ .Ldc_I4(0)
+ .EmitCall(method);
+
+ }
+ }
+
+ class GetterMethod : Method, IXamlIlCustomEmitMethod
+ {
+ public GetterMethod(AvaloniaAttachedInstanceProperty parent) : base(parent)
+ {
+ Name = "AvaloniaObject:GetValue_" + Parent.Name;
+ Parameters = new[] {parent._avaloniaObject};
+ }
+
+ public IXamlIlType ReturnType => Parent.PropertyType;
+ public IReadOnlyList Parameters { get; }
+ public void EmitCall(IXamlIlEmitter emitter)
+ {
+ var method = Parent._avaloniaObject
+ .FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "GetValue"
+ &&
+ m.Parameters.Count == 1
+ && m.Parameters[0].Equals(Parent._avaloniaPropertyType));
+ if (method == null)
+ throw new XamlIlTypeSystemException(
+ "Unable to find T GetValue(AvaloniaProperty) on AvaloniaObject");
+ emitter
+ .Ldsfld(Parent._field)
+ .EmitCall(method);
+ if (Parent.PropertyType.IsValueType)
+ emitter.Unbox_Any(Parent.PropertyType);
+
+ }
+ }
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs
index 55ce1462ce..175175977f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs
@@ -10,6 +10,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
"Avalonia.Data.TemplateBinding",
"Avalonia.Data.MultiBinding",
+ "Avalonia.Data.Binding",
};
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
index 1b3fda73e4..5070101d67 160000
--- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
+++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
@@ -1 +1 @@
-Subproject commit 1b3fda73e4cece31a4fec22ce146640ed5f2fd4c
+Subproject commit 5070101d67673da49f955055b7890e7a53a77397
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
index 91da8e18a1..f4c3302d52 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
{
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{
- var xaml = "";
+ var xaml = "";
var loader = new AvaloniaXamlLoader();
var style = (Style)loader.Load(xaml);
var setter = (Setter)(style.Setters.First());
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
index d2247e3d02..359d2521e0 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
@@ -162,6 +162,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact]
public void Non_Attached_Property_With_Attached_Property_Syntax_Throws()
{
+ // 1) It has been allowed in AvaloniaObject.SetValue for ages
+ // 2) There is no way to know if AddOwner was called in compile-time
+ if (!AvaloniaXamlLoader.UseLegacyXamlLoader)
+ return;
var xaml =
@"";