From 8ad9a0f675a54f1a14bf00d5517d751abe65762b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 14 Jun 2019 14:26:34 +0300 Subject: [PATCH] Compiled property paths --- src/Avalonia.Base/Data/Core/PropertyPath.cs | 91 +++++++ .../Utilities/CharacterReader.cs | 2 + src/Avalonia.Base/Utilities/KeywordParser.cs | 12 +- src/Avalonia.Styling/Styling/Setter.cs | 6 + .../Avalonia.Markup.Xaml.csproj | 1 + .../AvaloniaXamlIlCompiler.cs | 1 + .../AvaloniaXamlIlPropertyPathTransformer.cs | 250 ++++++++++++++++++ .../AvaloniaXamlIlSetterTransformer.cs | 35 ++- .../AvaloniaXamlIlWellKnownTypes.cs | 4 + .../XamlIlAvaloniaPropertyHelper.cs | 38 +-- .../XamlIlClrPropertyInfoHelper.cs | 39 ++- .../Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- .../Markup/Parsers/PropertyPathGrammar.cs | 24 +- .../Xaml/XamlIlTests.cs | 48 ++++ 14 files changed, 496 insertions(+), 57 deletions(-) create mode 100644 src/Avalonia.Base/Data/Core/PropertyPath.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs diff --git a/src/Avalonia.Base/Data/Core/PropertyPath.cs b/src/Avalonia.Base/Data/Core/PropertyPath.cs new file mode 100644 index 0000000000..665953c4a1 --- /dev/null +++ b/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 Elements { get; } + + public PropertyPath(IEnumerable elements) + { + Elements = elements.ToList(); + } + } + + public class PropertyPathBuilder + { + readonly List _elements = new List(); + + 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; } + } +} diff --git a/src/Avalonia.Base/Utilities/CharacterReader.cs b/src/Avalonia.Base/Utilities/CharacterReader.cs index 67a77f1c5c..3ba79c6866 100644 --- a/src/Avalonia.Base/Utilities/CharacterReader.cs +++ b/src/Avalonia.Base/Utilities/CharacterReader.cs @@ -85,6 +85,8 @@ namespace Avalonia.Utilities public ReadOnlySpan TryPeek(int count) { + if (_s.Length < count) + return ReadOnlySpan.Empty; return _s.Slice(0, count); } diff --git a/src/Avalonia.Base/Utilities/KeywordParser.cs b/src/Avalonia.Base/Utilities/KeywordParser.cs index 5c7d415820..34e1ea6f8e 100644 --- a/src/Avalonia.Base/Utilities/KeywordParser.cs +++ b/src/Avalonia.Base/Utilities/KeywordParser.cs @@ -17,11 +17,21 @@ namespace Avalonia.Utilities var ws = r.PeekWhitespace(); 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 -1; } + static bool SpanEquals(ReadOnlySpan left, ReadOnlySpan right) + { + if (left.Length != right.Length) + return false; + for(var c=0; c + /// Gets or sets the property path + /// + public PropertyPath PropertyPath { get; set; } /// /// Gets or sets the property value. diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 91d0feedfb..040ed57383 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -45,6 +45,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 0d0b0e6967..eadad60cde 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -44,6 +44,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertBefore( new AvaloniaXamlIlSelectorTransformer(), + new AvaloniaXamlIlPropertyPathTransformer(), new AvaloniaXamlIlSetterTransformer(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlConstructorServiceProviderTransformer(), diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlPropertyPathTransformer.cs new file mode 100644 index 0000000000..99ba264214 --- /dev/null +++ b/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() + .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 parsed; + try + { + parsed = PropertyPathGrammar.Parse(text.Text); + } + catch (Exception e) + { + throw new XamlIlParseException("Unable to parse PropertyPath: " + e.Message, text); + } + + var elements = new List(); + 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() + .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 _elements; + private readonly AvaloniaXamlIlWellKnownTypes _types; + + public XamlIlPropertyPathNode(IXamlIlLineInfo lineInfo, + List 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; } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index 629e2562d3..468717f281 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/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 XamlIl; using XamlIl.Ast; using XamlIl.Transform; @@ -33,29 +34,39 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers throw new XamlIlParseException( "Can not resolve parent Style Selector type", node); - + IXamlIlType propType = null; var property = @on.Children.OfType() .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property"); - if (property == null) - throw new XamlIlParseException("Setter without a property is not valid", node); + if (property != null) + { - var propertyName = property.Values.OfType().FirstOrDefault()?.Text; - if (propertyName == null) - throw new XamlIlParseException("Setter.Property must be a string", node); + var propertyName = property.Values.OfType().FirstOrDefault()?.Text; + if (propertyName == null) + throw new XamlIlParseException("Setter.Property must be a string", node); - var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, - new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]); - property.Values = new List + var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, + new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]); + property.Values = new List {avaloniaPropertyNode}; + propType = avaloniaPropertyNode.AvaloniaPropertyType; + } + else { - avaloniaPropertyNode - }; + var propertyPath = on.Children.OfType() + .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 .OfType().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value"); if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode) { - var propType = avaloniaPropertyNode.AvaloniaPropertyType; if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0], propType, out var converted)) throw new XamlIlParseException( diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 4c118899dc..f9f59dfb87 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/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 IPropertyInfo { get; } public IXamlIlType ClrPropertyInfo { get; } + public IXamlIlType PropertyPath { get; } + public IXamlIlType PropertyPathBuilder { get; } public AvaloniaXamlIlWellKnownTypes(XamlIlTransformerConfiguration cfg) { @@ -43,6 +45,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers 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"); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index e0783364db..bc7c58bbb4 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/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"), 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, make sure to use typed properties", + lineInfo); + + } } interface IXamlIlAvaloniaPropertyNode : IXamlIlAstValueNode @@ -132,22 +152,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions IXamlIlLineInfo lineInfo, IXamlIlField 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 XamlIlParseException( - $"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty, make sure to use typed properties", - lineInfo); - + AvaloniaPropertyType = XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(field, + types, lineInfo); } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs index 5b681e94a9..95ec8536fc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlClrPropertyInfoHelper.cs @@ -8,21 +8,6 @@ using XamlIl.TypeSystem; 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 { private readonly IXamlIlTypeBuilder _builder; @@ -62,16 +47,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions 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 ? null : _builder.DefineMethod(types.XamlIlTypes.Object, new[] {types.XamlIlTypes.Object}, name + "!Getter", false, true, false); if (getter != null) { - getter.Generator - .Ldarg_0() - .Castclass(property.Getter.DeclaringType) - .EmitCall(property.Getter); + Load(property.Getter, getter.Generator); + + getter.Generator.EmitCall(property.Getter); if (property.Getter.ReturnType.IsValueType) getter.Generator.Box(property.Getter.ReturnType); getter.Generator.Ret(); @@ -84,10 +78,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions name + "!Setter", false, true, false); if (setter != null) { - setter.Generator - .Ldarg_0() - .Castclass(property.Setter.DeclaringType) - .Ldarg(1); + Load(property.Setter, setter.Generator); + + setter.Generator.Ldarg(1); if (property.Setter.Parameters[0].IsValueType) setter.Generator.Unbox_Any(property.Setter.Parameters[0]); else diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 3ea474b672..9ae023a988 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 3ea474b672ff63629300c36a9644caac2b74a1f2 +Subproject commit 9ae023a988d1b419e19386cfd3448d8e604bbc7e diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index 89490db2aa..1f0f421233 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -64,6 +64,8 @@ namespace Avalonia.Markup.Parsers private static (State, ISyntax) ParseStart(ref CharacterReader r) { + if (TryParseCasts(ref r, out var rv)) + return rv; r.SkipWhitespace(); if (r.TakeIf('(')) @@ -130,19 +132,33 @@ namespace Avalonia.Markup.Parsers 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); - 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"); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 913dd754f1..6bc7f33039 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -11,6 +11,7 @@ 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; @@ -246,6 +247,53 @@ namespace Avalonia.Markup.Xaml.UnitTests />", typeof(XamlIlClassWithClrPropertyWithValue).Assembly); Assert.Equal(6, parsed.Count); } + + [Fact] + public void Should_Provide_PropertyPath_For_Setters() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var parsed = AvaloniaXamlLoader.Parse"); + 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(s1e[1]); + Assert.Equal("Bounds", ((AvaloniaProperty)((PropertyPropertyPathElement)s1e[2]).Property).Name); + Assert.IsType(s1e[3]); + var bottomRight = ((PropertyPropertyPathElement)s1e[4]).Property; + Assert.IsType(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(s2e[1]); + Assert.Equal("RenderTransform", ((AvaloniaProperty)((PropertyPropertyPathElement)s2e[2]).Property).Name); + Assert.Equal(typeof(ScaleTransform), ((EnsureTypePropertyPathElement)s2e[3]).Type); + Assert.IsType(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(s3e[2]); + Assert.Equal("ScaleX", ((AvaloniaProperty)((PropertyPropertyPathElement)s3e[3]).Property).Name); + + + } + } } public class XamlIlBugTestsEventHandlerCodeBehind : Window