From bf6375fe266f3825e59eaf5f020c9bfbe955f45a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 29 May 2018 16:49:08 -0500 Subject: [PATCH] Fix indexer and casting expressions. --- .../Parsers/ExpressionVisitorNodeBuilder.cs | 49 ++++++++++-- .../ExpressionObserverTests_ExpressionTree.cs | 80 +++++++++++++++++++ 2 files changed, 122 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs index 8564bf5111..5affe227e1 100644 --- a/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs +++ b/src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -28,24 +29,43 @@ namespace Avalonia.Data.Core.Parsers protected override Expression VisitUnary(UnaryExpression node) { - if (node.NodeType != ExpressionType.Not || node.Type != typeof(bool)) + if (node.NodeType == ExpressionType.Not && node.Type == typeof(bool)) { - throw new ExpressionParseException(0, $"Invalid unary operation {node.NodeType} in binding expression"); + Nodes.Add(new LogicalNotNode()); + } + else if (node.NodeType == ExpressionType.Convert) + { + if (node.Operand.Type.IsAssignableFrom(node.Type)) + { + // Ignore inheritance casts + } + else + { + throw new ExpressionParseException(0, $"Cannot parse non-inheritance casts in a binding expression."); + } + } + else if (node.NodeType == ExpressionType.TypeAs) + { + // Ignore as operator. + } + else + { + throw new ExpressionParseException(0, $"Unable to parse unary operator {node.NodeType} in a binding expression"); } - - Nodes.Add(new LogicalNotNode()); return base.VisitUnary(node); } protected override Expression VisitMember(MemberExpression node) { + var visited = base.VisitMember(node); Nodes.Add(new PropertyAccessorNode(node.Member.Name, enableDataValidation)); - return base.VisitMember(node); + return visited; } protected override Expression VisitIndex(IndexExpression node) { + var visited = base.VisitIndex(node); if (node.Indexer == AvaloniaObjectIndexer) { var property = GetArgumentExpressionValue(node.Arguments[0]); @@ -56,7 +76,7 @@ namespace Avalonia.Data.Core.Parsers Nodes.Add(new IndexerExpressionNode(node)); } - return node; + return visited; } private T GetArgumentExpressionValue(Expression expr) @@ -75,7 +95,8 @@ namespace Avalonia.Data.Core.Parsers { if (node.NodeType == ExpressionType.ArrayIndex) { - return base.VisitBinary(node); + base.VisitBinary(node); + return Visit(Expression.MakeIndex(node.Left, null, new[] { node.Right })); } throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); } @@ -137,9 +158,23 @@ namespace Avalonia.Data.Core.Parsers protected override Expression VisitMethodCall(MethodCallExpression node) { + base.VisitMethodCall(node); + var property = TryGetPropertyFromMethod(node.Method); + + if (property != null) + { + return Visit(Expression.MakeIndex(node.Object, property, node.Arguments)); + } + throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); } + private PropertyInfo TryGetPropertyFromMethod(MethodInfo method) + { + var type = method.DeclaringType; + return type.GetRuntimeProperties().FirstOrDefault(prop => prop.GetMethod == method); + } + protected override Expression VisitSwitch(SwitchExpression node) { throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs index 1ec4bdb4f5..ebf3ca2a49 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs @@ -19,6 +19,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var observer = ExpressionObserver.CreateFromExpression(target, o => o); Assert.Equal(target, await observer.Take(1)); + GC.KeepAlive(target); } [Fact] @@ -62,6 +63,18 @@ namespace Avalonia.Base.UnitTests.Data.Core var target = ExpressionObserver.CreateFromExpression(data, o => o[0]); Assert.Equal(data[0], await target.Take(1)); + GC.KeepAlive(data); + } + + [Fact] + public async Task Indexer_List_Accessor_Can_Read_Value() + { + var data = new List { 1, 2, 3, 4 }; + + var target = ExpressionObserver.CreateFromExpression(data, o => o[0]); + + Assert.Equal(data[0], await target.Take(1)); + GC.KeepAlive(data); } [Fact] @@ -76,6 +89,8 @@ namespace Avalonia.Base.UnitTests.Data.Core var target = ExpressionObserver.CreateFromExpression(data, o => o[key]); Assert.Equal(data[key], await target.Take(1)); + + GC.KeepAlive(data); } [Fact] @@ -93,6 +108,62 @@ namespace Avalonia.Base.UnitTests.Data.Core GC.KeepAlive(data); } + [Fact] + public async Task Inheritance_Casts_Should_Be_Ignored() + { + NotifyingBase test = new Class1 { Foo = "Test" }; + + var target = ExpressionObserver.CreateFromExpression(test, o => ((Class1)o).Foo); + + Assert.Equal("Test", await target.Take(1)); + + GC.KeepAlive(test); + } + + [Fact] + public void Convert_Casts_Should_Error() + { + var test = 1; + + Assert.Throws(() => ExpressionObserver.CreateFromExpression(test, o => (double)o)); + } + + [Fact] + public async Task As_Operator_Should_Be_Ignored() + { + NotifyingBase test = new Class1 { Foo = "Test" }; + + var target = ExpressionObserver.CreateFromExpression(test, o => (o as Class1).Foo); + + Assert.Equal("Test", await target.Take(1)); + + GC.KeepAlive(test); + } + + [Fact] + public async Task Avalonia_Property_Indexer_Reads_Avalonia_Property_Value() + { + var test = new Class2(); + + var target = ExpressionObserver.CreateFromExpression(test, o => o[Class2.FooProperty]); + + Assert.Equal("foo", await target.Take(1)); + + GC.KeepAlive(test); + } + + [Fact] + public async Task Complex_Expression_Correctly_Parsed() + { + var test = new Class1 { Foo = "Test" }; + + var target = ExpressionObserver.CreateFromExpression(test, o => o.Foo.Length); + + Assert.Equal(test.Foo.Length, await target.Take(1)); + + GC.KeepAlive(test); + } + private class Class1 : NotifyingBase { private string _foo; @@ -107,5 +178,14 @@ namespace Avalonia.Base.UnitTests.Data.Core } } } + + + private class Class2 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register("Foo", defaultValue: "foo"); + + public string ClrProperty { get; } = "clr-property"; + } } }