Browse Source

Fix indexer and casting expressions.

pull/1667/head
Jeremy Koritzinsky 8 years ago
parent
commit
bf6375fe26
  1. 49
      src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
  2. 80
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs

49
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<AvaloniaProperty>(node.Arguments[0]);
@ -56,7 +76,7 @@ namespace Avalonia.Data.Core.Parsers
Nodes.Add(new IndexerExpressionNode(node));
}
return node;
return visited;
}
private T GetArgumentExpressionValue<T>(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}.");

80
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<int> { 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<ExpressionParseException>(() => 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<string> FooProperty =
AvaloniaProperty.Register<Class2, string>("Foo", defaultValue: "foo");
public string ClrProperty { get; } = "clr-property";
}
}
}

Loading…
Cancel
Save