Browse Source

Compiled property paths

pull/2659/head
Nikita Tsukanov 7 years ago
parent
commit
8ad9a0f675
  1. 91
      src/Avalonia.Base/Data/Core/PropertyPath.cs
  2. 2
      src/Avalonia.Base/Utilities/CharacterReader.cs
  3. 12
      src/Avalonia.Base/Utilities/KeywordParser.cs
  4. 6
      src/Avalonia.Styling/Styling/Setter.cs
  5. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  6. 1
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  7. 250
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs
  8. 35
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  9. 4
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  10. 38
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  11. 39
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs
  12. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  13. 24
      src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs
  14. 48
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

91
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; }
}
}

2
src/Avalonia.Base/Utilities/CharacterReader.cs

@ -85,6 +85,8 @@ namespace Avalonia.Utilities
public ReadOnlySpan<char> TryPeek(int count) public ReadOnlySpan<char> TryPeek(int count)
{ {
if (_s.Length < count)
return ReadOnlySpan<char>.Empty;
return _s.Slice(0, count); return _s.Slice(0, count);
} }

12
src/Avalonia.Base/Utilities/KeywordParser.cs

@ -17,11 +17,21 @@ namespace Avalonia.Utilities
var ws = r.PeekWhitespace(); var ws = r.PeekWhitespace();
var chars = r.TryPeek(ws.Length + keyword.Length); var chars = r.TryPeek(ws.Length + keyword.Length);
if (chars.Slice(ws.Length).Equals(keyword.AsSpan(), StringComparison.Ordinal)) if (SpanEquals(chars.Slice(ws.Length), keyword.AsSpan()))
return chars.Length; return chars.Length;
return -1; 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) public static bool TakeIfKeyword(this ref CharacterReader r, string keyword)
{ {
var l = CheckKeywordInternal(ref r, keyword); var l = CheckKeywordInternal(ref r, keyword);

6
src/Avalonia.Styling/Styling/Setter.cs

@ -7,6 +7,7 @@ using System.Reflection;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Reactive; using Avalonia.Reactive;
@ -49,6 +50,11 @@ namespace Avalonia.Styling
get; get;
set; set;
} }
/// <summary>
/// Gets or sets the property path
/// </summary>
public PropertyPath PropertyPath { get; set; }
/// <summary> /// <summary>
/// Gets or sets the property value. /// Gets or sets the property value.

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

@ -45,6 +45,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\AvaloniaXamlIlPropertyPathTransformer.cs"/>
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformInstanceAttachedProperties.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransformInstanceAttachedProperties.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlWellKnownTypes.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlWellKnownTypes.cs" />

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

@ -44,6 +44,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore<XamlIlContentConvertTransformer>( InsertBefore<XamlIlContentConvertTransformer>(
new AvaloniaXamlIlSelectorTransformer(), new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTransformer(), new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(),

250
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs

@ -0,0 +1,250 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Parsers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlPropertyPathTransformer : IXamlIlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{
if (node is XamlIlAstXamlPropertyValueNode pv
&& pv.Values.Count == 1
&& pv.Values[0] is XamlIlAstTextNode text
&& pv.Property.GetClrProperty().Getter.ReturnType.Equals(context.GetAvaloniaTypes().PropertyPath)
)
{
var parentScope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>()
.FirstOrDefault();
if(parentScope == null)
throw new XamlIlParseException("No target type scope found for property path", text);
if (parentScope.ScopeType != AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
throw new XamlIlParseException("PropertyPath is currently only valid for styles", pv);
IEnumerable<PropertyPathGrammar.ISyntax> parsed;
try
{
parsed = PropertyPathGrammar.Parse(text.Text);
}
catch (Exception e)
{
throw new XamlIlParseException("Unable to parse PropertyPath: " + e.Message, text);
}
var elements = new List<IXamlIlPropertyPathElementNode>();
IXamlIlType currentType = parentScope.TargetType.GetClrType();
var expectProperty = true;
var expectCast = true;
var expectTraversal = false;
var types = context.GetAvaloniaTypes();
IXamlIlType GetType(string ns, string name)
{
return XamlIlTypeReferenceResolver.ResolveType(context, $"{ns}:{name}", false,
text, true).GetClrType();
}
void HandleProperty(string name, string typeNamespace, string typeName)
{
if(!expectProperty || currentType == null)
throw new XamlIlParseException("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 XamlIlParseException(
$"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 XamlIlParseException("Unexpected child traversal .", text);
elements.Add(new XamlIlChildTraversalPropertyPathElementNode());
expectTraversal = expectCast = false;
expectProperty = true;
}
else if (ge is PropertyPathGrammar.EnsureTypeSyntax ets)
{
if(!expectCast)
throw new XamlIlParseException("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 XamlIlParseException("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 XamlIlParseException("Unexpected node " + ge, text);
}
var propertyPathNode = new XamlIlPropertyPathNode(text, elements, types);
if (propertyPathNode.Type == null)
throw new XamlIlParseException("Unexpected end of the property path", text);
pv.Values[0] = propertyPathNode;
}
return node;
}
interface IXamlIlPropertyPathElementNode
{
void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
IXamlIlType Type { get; }
}
class XamlIlChildTraversalPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
=> codeGen.EmitCall(
context.GetAvaloniaTypes()
.PropertyPathBuilder.FindMethod(m => m.Name == "ChildTraversal"));
public IXamlIlType Type => null;
}
class XamlIlAvaloniaPropertyPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
private readonly IXamlIlField _field;
public XamlIlAvaloniaPropertyPropertyPathElementNode(IXamlIlField field, IXamlIlType propertyType)
{
_field = field;
Type = propertyType;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
=> codeGen
.Ldsfld(_field)
.EmitCall(context.GetAvaloniaTypes()
.PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
public IXamlIlType Type { get; }
}
class XamlIClrPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
private readonly IXamlIlProperty _property;
public XamlIClrPropertyPathElementNode(IXamlIlProperty property)
{
_property = property;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
context.Configuration.GetExtra<XamlIlClrPropertyInfoEmitter>()
.Emit(context, codeGen, _property);
codeGen.EmitCall(context.GetAvaloniaTypes()
.PropertyPathBuilder.FindMethod(m => m.Name == "Property"));
}
public IXamlIlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
}
class XamlIlCastPropertyPathElementNode : IXamlIlPropertyPathElementNode
{
private readonly IXamlIlType _type;
private readonly bool _ensureType;
public XamlIlCastPropertyPathElementNode(IXamlIlType type, bool ensureType)
{
_type = type;
_ensureType = ensureType;
}
public void Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
codeGen
.Ldtype(_type)
.EmitCall(context.GetAvaloniaTypes()
.PropertyPathBuilder.FindMethod(m => m.Name == (_ensureType ? "EnsureType" : "Cast")));
}
public IXamlIlType Type => _type;
}
class XamlIlPropertyPathNode : XamlIlAstNode, IXamlIlPropertyPathNode, IXamlIlAstEmitableNode
{
private readonly List<IXamlIlPropertyPathElementNode> _elements;
private readonly AvaloniaXamlIlWellKnownTypes _types;
public XamlIlPropertyPathNode(IXamlIlLineInfo lineInfo,
List<IXamlIlPropertyPathElementNode> elements,
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
{
_elements = elements;
_types = types;
Type = new XamlIlAstClrTypeReference(this, types.PropertyPath, false);
}
public IXamlIlAstTypeReference Type { get; }
public IXamlIlType PropertyType => _elements.LastOrDefault()?.Type;
public XamlIlNodeEmitResult Emit(XamlIlEmitContext 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 : IXamlIlAstValueNode
{
IXamlIlType PropertyType { get; }
}
}

35
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Data.Core;
using XamlIl; using XamlIl;
using XamlIl.Ast; using XamlIl.Ast;
using XamlIl.Transform; using XamlIl.Transform;
@ -33,29 +34,39 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
throw new XamlIlParseException( throw new XamlIlParseException(
"Can not resolve parent Style Selector type", node); "Can not resolve parent Style Selector type", node);
IXamlIlType propType = null;
var property = @on.Children.OfType<XamlIlAstXamlPropertyValueNode>() var property = @on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property"); .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
if (property == null) if (property != null)
throw new XamlIlParseException("Setter without a property is not valid", node); {
var propertyName = property.Values.OfType<XamlIlAstTextNode>().FirstOrDefault()?.Text; var propertyName = property.Values.OfType<XamlIlAstTextNode>().FirstOrDefault()?.Text;
if (propertyName == null) if (propertyName == null)
throw new XamlIlParseException("Setter.Property must be a string", node); throw new XamlIlParseException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]); new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
property.Values = new List<IXamlIlAstValueNode> property.Values = new List<IXamlIlAstValueNode> {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}
else
{ {
avaloniaPropertyNode var propertyPath = on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
}; .FirstOrDefault(x => x.Property.GetClrProperty().Name == "PropertyPath");
if (propertyPath == null)
throw new XamlIlParseException("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 XamlIlParseException("Unable to get the property path property type", node);
}
var valueProperty = on.Children var valueProperty = on.Children
.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value"); .OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode) if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode)
{ {
var propType = avaloniaPropertyNode.AvaloniaPropertyType;
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0], if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted)) propType, out var converted))
throw new XamlIlParseException( throw new XamlIlParseException(

4
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -21,6 +21,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlIlType UnsetValueType { get; } public IXamlIlType UnsetValueType { get; }
public IXamlIlType IPropertyInfo { get; } public IXamlIlType IPropertyInfo { get; }
public IXamlIlType ClrPropertyInfo { get; } public IXamlIlType ClrPropertyInfo { get; }
public IXamlIlType PropertyPath { get; }
public IXamlIlType PropertyPathBuilder { get; }
public AvaloniaXamlIlWellKnownTypes(XamlIlTransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(XamlIlTransformerConfiguration cfg)
{ {
@ -43,6 +45,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority); false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
IPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.IPropertyInfo"); IPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.IPropertyInfo");
ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo"); ClrPropertyInfo = cfg.TypeSystem.GetType("Avalonia.Data.Core.ClrPropertyInfo");
PropertyPath = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPath");
PropertyPathBuilder = cfg.TypeSystem.GetType("Avalonia.Data.Core.PropertyPathBuilder");
} }
} }

38
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@ -94,6 +94,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"), context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"),
clrProperty); clrProperty);
} }
public static IXamlIlType GetAvaloniaPropertyType(IXamlIlField field,
AvaloniaXamlIlWellKnownTypes types, IXamlIlLineInfo lineInfo)
{
var avaloniaPropertyType = field.FieldType;
while (avaloniaPropertyType != null)
{
if (avaloniaPropertyType.GenericTypeDefinition?.Equals(types.AvaloniaPropertyT) == true)
{
return avaloniaPropertyType.GenericArguments[0];
}
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
throw new XamlIlParseException(
$"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty<T>, make sure to use typed properties",
lineInfo);
}
} }
interface IXamlIlAvaloniaPropertyNode : IXamlIlAstValueNode interface IXamlIlAvaloniaPropertyNode : IXamlIlAstValueNode
@ -132,22 +152,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
IXamlIlLineInfo lineInfo, IXamlIlField field) : base(lineInfo) IXamlIlLineInfo lineInfo, IXamlIlField field) : base(lineInfo)
{ {
_field = field; _field = field;
var avaloniaPropertyType = field.FieldType; AvaloniaPropertyType = XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(field,
while (avaloniaPropertyType != null) types, lineInfo);
{
if (avaloniaPropertyType.GenericTypeDefinition?.Equals(types.AvaloniaPropertyT) == true)
{
AvaloniaPropertyType = avaloniaPropertyType.GenericArguments[0];
return;
}
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
throw new XamlIlParseException(
$"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty<T>, make sure to use typed properties",
lineInfo);
} }

39
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs

@ -8,21 +8,6 @@ using XamlIl.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{ {
class XamlIlClrPropertyInfoHelper
{
class ClrPropertyInfoNode : XamlIlAstNode, IXamlIlAstValueNode
{
public ClrPropertyInfoNode(AvaloniaXamlIlWellKnownTypes types, IXamlIlLineInfo lineInfo) :
base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(this, types.XamlIlTypes.Object, false);
}
public IXamlIlAstTypeReference Type { get; }
}
}
class XamlIlClrPropertyInfoEmitter class XamlIlClrPropertyInfoEmitter
{ {
private readonly IXamlIlTypeBuilder _builder; private readonly IXamlIlTypeBuilder _builder;
@ -62,16 +47,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
var field = _builder.DefineField(types.IPropertyInfo, name + "!Field", false, true); var field = _builder.DefineField(types.IPropertyInfo, name + "!Field", false, true);
void Load(IXamlIlMethod m, IXamlIlEmitter cg)
{
cg
.Ldarg_0();
if (m.DeclaringType.IsValueType)
cg.Unbox(m.DeclaringType);
else
cg.Castclass(m.DeclaringType);
}
var getter = property.Getter == null ? var getter = property.Getter == null ?
null : null :
_builder.DefineMethod(types.XamlIlTypes.Object, _builder.DefineMethod(types.XamlIlTypes.Object,
new[] {types.XamlIlTypes.Object}, name + "!Getter", false, true, false); new[] {types.XamlIlTypes.Object}, name + "!Getter", false, true, false);
if (getter != null) if (getter != null)
{ {
getter.Generator Load(property.Getter, getter.Generator);
.Ldarg_0()
.Castclass(property.Getter.DeclaringType) getter.Generator.EmitCall(property.Getter);
.EmitCall(property.Getter);
if (property.Getter.ReturnType.IsValueType) if (property.Getter.ReturnType.IsValueType)
getter.Generator.Box(property.Getter.ReturnType); getter.Generator.Box(property.Getter.ReturnType);
getter.Generator.Ret(); getter.Generator.Ret();
@ -84,10 +78,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
name + "!Setter", false, true, false); name + "!Setter", false, true, false);
if (setter != null) if (setter != null)
{ {
setter.Generator Load(property.Setter, setter.Generator);
.Ldarg_0()
.Castclass(property.Setter.DeclaringType) setter.Generator.Ldarg(1);
.Ldarg(1);
if (property.Setter.Parameters[0].IsValueType) if (property.Setter.Parameters[0].IsValueType)
setter.Generator.Unbox_Any(property.Setter.Parameters[0]); setter.Generator.Unbox_Any(property.Setter.Parameters[0]);
else else

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

@ -1 +1 @@
Subproject commit 3ea474b672ff63629300c36a9644caac2b74a1f2 Subproject commit 9ae023a988d1b419e19386cfd3448d8e604bbc7e

24
src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs

@ -64,6 +64,8 @@ namespace Avalonia.Markup.Parsers
private static (State, ISyntax) ParseStart(ref CharacterReader r) private static (State, ISyntax) ParseStart(ref CharacterReader r)
{ {
if (TryParseCasts(ref r, out var rv))
return rv;
r.SkipWhitespace(); r.SkipWhitespace();
if (r.TakeIf('(')) if (r.TakeIf('('))
@ -130,19 +132,33 @@ namespace Avalonia.Markup.Parsers
return (State.AfterProperty, new PropertySyntax {Name = prop.ToString()}); 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) private static (State, ISyntax) ParseAfterProperty(ref CharacterReader r)
{ {
if (TryParseCasts(ref r, out var rv))
return rv;
r.SkipWhitespace(); r.SkipWhitespace();
if (r.End) if (r.End)
return (State.End, null); return (State.End, null);
if (r.TakeIf('.')) if (r.TakeIf('.'))
return (State.Next, ChildTraversalSyntax.Instance); return (State.Next, ChildTraversalSyntax.Instance);
if (r.TakeIfKeyword(":="))
return ParseEnsureType(ref r);
if (r.TakeIfKeyword(":>") || r.TakeIfKeyword("as "))
return ParseCastType(ref r);
throw new ExpressionParseException(r.Position, "Unexpected character " + r.PeekOneOrThrow + " after property name"); throw new ExpressionParseException(r.Position, "Unexpected character " + r.PeekOneOrThrow + " after property name");
} }

48
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -11,6 +11,7 @@ using Avalonia.Data.Core;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -246,6 +247,53 @@ namespace Avalonia.Markup.Xaml.UnitTests
/>", typeof(XamlIlClassWithClrPropertyWithValue).Assembly); />", typeof(XamlIlClassWithClrPropertyWithValue).Assembly);
Assert.Equal(6, parsed.Count); Assert.Equal(6, parsed.Count);
} }
[Fact]
public void Should_Provide_PropertyPath_For_Setters()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parsed = AvaloniaXamlLoader.Parse<Style>(@"
<Style Selector='Animatable' xmlns='https://github.com/avaloniaui'>
<Setter PropertyPath=':>Visual.Bounds.BottomRight.X' Value='0' />
<Setter PropertyPath=':>Visual.RenderTransform:=ScaleTransform.ScaleX' Value='0' />
<Setter PropertyPath='(Visual.RenderTransform):=ScaleTransform.ScaleX' Value='0' />
</Style>");
var s1e = ((Setter)parsed.Setters[0]).PropertyPath.Elements;
var s2e = ((Setter)parsed.Setters[1]).PropertyPath.Elements;
var s3e = ((Setter)parsed.Setters[2]).PropertyPath.Elements;
Assert.Equal(typeof(Visual), ((CastTypePropertyPathElement)s1e[0]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s1e[1]);
Assert.Equal("Bounds", ((AvaloniaProperty)((PropertyPropertyPathElement)s1e[2]).Property).Name);
Assert.IsType<ChildTraversalPropertyPathElement>(s1e[3]);
var bottomRight = ((PropertyPropertyPathElement)s1e[4]).Property;
Assert.IsType<ChildTraversalPropertyPathElement>(s1e[5]);
var pointX = ((PropertyPropertyPathElement)s1e[6]).Property;
var orect = (object)(new Rect(100, 100, 200, 200));
var point = bottomRight.Get(orect);
var x = pointX.Get(point);
Assert.Equal(300, (double)x);
Assert.Equal(typeof(Visual), ((CastTypePropertyPathElement)s2e[0]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s2e[1]);
Assert.Equal("RenderTransform", ((AvaloniaProperty)((PropertyPropertyPathElement)s2e[2]).Property).Name);
Assert.Equal(typeof(ScaleTransform), ((EnsureTypePropertyPathElement)s2e[3]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s2e[4]);
Assert.Equal("ScaleX", ((AvaloniaProperty)((PropertyPropertyPathElement)s2e[5]).Property).Name);
var s3fqp = (AvaloniaProperty)((PropertyPropertyPathElement)s3e[0]).Property;
Assert.Equal("RenderTransform", s3fqp.Name);
Assert.Equal(typeof(Visual), s3fqp.OwnerType);
Assert.Equal(typeof(ScaleTransform), ((EnsureTypePropertyPathElement)s3e[1]).Type);
Assert.IsType<ChildTraversalPropertyPathElement>(s3e[2]);
Assert.Equal("ScaleX", ((AvaloniaProperty)((PropertyPropertyPathElement)s3e[3]).Property).Name);
}
}
} }
public class XamlIlBugTestsEventHandlerCodeBehind : Window public class XamlIlBugTestsEventHandlerCodeBehind : Window

Loading…
Cancel
Save