Browse Source

Support for properties "attached" by AddOwner

pull/2322/head
Nikita Tsukanov 7 years ago
parent
commit
fe251ee1aa
  1. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  2. 3
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  3. 201
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
  4. 1
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/KnownPseudoMarkupExtensionsTransformer.cs
  5. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  6. 2
      tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs
  7. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -59,6 +59,7 @@
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDesignPropertiesTransformer.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlDesignPropertiesTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlMetadataRemover.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlMetadataRemover.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformInstanceAttachedProperties.cs" />
<Compile Include="XamlIl\CompilerExtensions\XamlIlAvaloniaPropertyHelper.cs" /> <Compile Include="XamlIl\CompilerExtensions\XamlIlAvaloniaPropertyHelper.cs" />
<Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompiler.cs" /> <Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompiler.cs" />
<Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlLanguage.cs" /> <Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlLanguage.cs" />

3
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -24,6 +24,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Targeted // Targeted
Transformers.Insert(Transformers.FindIndex(x => x is XamlIlPropertyReferenceResolver),
new AvaloniaXamlIlTransformInstanceAttachedProperties());
Transformers.Insert(Transformers.FindIndex(x => x is XamlIlXamlPropertyValueTransformer), Transformers.Insert(Transformers.FindIndex(x => x is XamlIlXamlPropertyValueTransformer),
new KnownPseudoMarkupExtensionsTransformer()); new KnownPseudoMarkupExtensionsTransformer());

201
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<IXamlIlCustomAttribute> CustomAttributes { get; } = new List<IXamlIlCustomAttribute>();
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<IXamlIlType> 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<IXamlIlType> 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<T>(AvaloniaProperty<T>) on AvaloniaObject");
emitter
.Ldsfld(Parent._field)
.EmitCall(method);
if (Parent.PropertyType.IsValueType)
emitter.Unbox_Any(Parent.PropertyType);
}
}
}
}
}

1
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.TemplateBinding",
"Avalonia.Data.MultiBinding", "Avalonia.Data.MultiBinding",
"Avalonia.Data.Binding",
}; };
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node) public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 1b3fda73e4cece31a4fec22ce146640ed5f2fd4c Subproject commit 5070101d67673da49f955055b7890e7a53a77397

2
tests/Avalonia.Markup.Xaml.UnitTests/StyleTests.cs

@ -19,7 +19,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.MockPlatformWrapper)) using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
{ {
var xaml = "<Style xmlns='https://github.com/avaloniaui'><Setter Value='{Binding}'/></Style>"; var xaml = "<Style Selector='Button' xmlns='https://github.com/avaloniaui'><Setter Property='Content' Value='{Binding}'/></Style>";
var loader = new AvaloniaXamlLoader(); var loader = new AvaloniaXamlLoader();
var style = (Style)loader.Load(xaml); var style = (Style)loader.Load(xaml);
var setter = (Setter)(style.Setters.First()); var setter = (Setter)(style.Setters.First());

4
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -162,6 +162,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact] [Fact]
public void Non_Attached_Property_With_Attached_Property_Syntax_Throws() 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 = var xaml =
@"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.Text='foo'/>"; @"<ContentControl xmlns='https://github.com/avaloniaui' TextBlock.Text='foo'/>";

Loading…
Cancel
Save