Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 66 additions & 4 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1996,7 +1996,7 @@ private Expression GenerateNotEqual(Expression left, Expression right)

private Expression GenerateGreaterThan(Expression left, Expression right)
{
if (left.Type == typeof(string))
if (left.Type == typeof(string) && right.Type == typeof(string))
{
return Expression.GreaterThan(
GenerateStaticMethodCall("Compare", left, right),
Expand All @@ -2008,7 +2008,7 @@ private Expression GenerateGreaterThan(Expression left, Expression right)

private Expression GenerateGreaterThanEqual(Expression left, Expression right)
{
if (left.Type == typeof(string))
if (left.Type == typeof(string) && right.Type == typeof(string))
{
return Expression.GreaterThanOrEqual(
GenerateStaticMethodCall("Compare", left, right),
Expand All @@ -2020,7 +2020,7 @@ private Expression GenerateGreaterThanEqual(Expression left, Expression right)

private Expression GenerateLessThan(Expression left, Expression right)
{
if (left.Type == typeof(string))
if (left.Type == typeof(string) && right.Type == typeof(string))
{
return Expression.LessThan(
GenerateStaticMethodCall("Compare", left, right),
Expand All @@ -2033,7 +2033,7 @@ private Expression GenerateLessThan(Expression left, Expression right)

private Expression GenerateLessThanEqual(Expression left, Expression right)
{
if (left.Type == typeof(string))
if (left.Type == typeof(string) && right.Type == typeof(string))
{
return Expression.LessThanOrEqual(
GenerateStaticMethodCall("Compare", left, right),
Expand Down Expand Up @@ -2081,6 +2081,10 @@ private Expression GenerateBinary(ExpressionType binaryType, Expression left, Ex
}

var applicableMethod = FindBinaryOperator(opName, left, right);
if (applicableMethod == null)
{
applicableMethod = FindBinaryOperatorWithImplicitConversion(opName, ref left, ref right);
}

MethodInfo operatorMethod = null;
if (applicableMethod != null)
Expand Down Expand Up @@ -2175,6 +2179,64 @@ private MethodData FindBinaryOperator(string operatorName, Expression left, Expr
return userDefinedOperator;
}

private MethodData FindBinaryOperatorWithImplicitConversion(string operatorName, ref Expression left, ref Expression right)
{
if (operatorName == null)
return null;

var leftType = left.Type;
var rightType = right.Type;
var convertedLeft = TryGenerateImplicitConversion(left, rightType);
var leftConvertedOperator = convertedLeft != null
? FindBinaryOperator(operatorName, convertedLeft, right)
: null;

var convertedRight = TryGenerateImplicitConversion(right, leftType);
var rightConvertedOperator = convertedRight != null
? FindBinaryOperator(operatorName, left, convertedRight)
: null;

if (leftConvertedOperator != null && rightConvertedOperator != null && !ReferenceEquals(leftConvertedOperator.MethodBase, rightConvertedOperator.MethodBase))
{
throw ParseException.Create(_token.pos, ErrorMessages.AmbiguousBinaryOperatorInvocation, operatorName, TypeUtils.GetTypeName(leftType), TypeUtils.GetTypeName(rightType));
}

if (leftConvertedOperator != null)
{
left = convertedLeft;
return leftConvertedOperator;
}

if (rightConvertedOperator != null)
{
right = convertedRight;
return rightConvertedOperator;
}

return null;
}

private static Expression TryGenerateImplicitConversion(Expression expression, Type type)
{
if (!HasImplicitConversion(expression.Type, type))
return null;

return Expression.Convert(expression, type);
}

private static bool HasImplicitConversion(Type sourceType, Type targetType)
{
return HasImplicitConversionOperator(sourceType, sourceType, targetType) ||
HasImplicitConversionOperator(targetType, sourceType, targetType);
}

private static bool HasImplicitConversionOperator(Type declaringType, Type sourceType, Type targetType)
{
return declaringType.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.Any(method => method.Name == "op_Implicit" && method.ReturnType == targetType &&
method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == sourceType);
}

private static Expression GenerateStringConcat(Expression left, Expression right)
{
var leftObj = GenerateStringConcatOperand(left);
Expand Down
196 changes: 196 additions & 0 deletions test/DynamicExpresso.UnitTest/OperatorsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,202 @@ public void Can_mix_overloaded_operators()
Assert.That(target.Eval<bool>("(x + z) == \"13\"", new Parameter("z", z)), Is.True);
}

[TestCase("m < \"60\"", true)]
[TestCase("m <= \"50\"", true)]
[TestCase("m > \"40\"", true)]
[TestCase("m >= \"50\"", true)]
[TestCase("\"40\" < m", true)]
[TestCase("\"50\" <= m", true)]
[TestCase("\"60\" > m", true)]
[TestCase("\"50\" >= m", true)]
public void Can_use_string_relational_operators_on_custom_type(string expression, bool expected)
{
var target = new Interpreter()
.SetVariable("m", new MoneyWithStringRelationalOperators(50));

Assert.That(target.Eval<bool>(expression), Is.EqualTo(expected));
}

[TestCase("m < \"60\"", true)]
[TestCase("m <= \"50\"", true)]
[TestCase("m > \"40\"", true)]
[TestCase("m >= \"50\"", true)]
[TestCase("\"40\" < m", true)]
[TestCase("\"50\" <= m", true)]
[TestCase("\"60\" > m", true)]
[TestCase("\"50\" >= m", true)]
public void Can_use_implicit_conversion_fallback_for_string_relational_operators_on_custom_type(string expression, bool expected)
{
var target = new Interpreter()
.SetVariable("m", new MoneyWithImplicitConversionOnlyRelationalOperators(50));

Assert.That(target.Eval<bool>(expression), Is.EqualTo(expected));
}

[TestCase("m == \"50\"", true)]
[TestCase("m != \"10\"", true)]
[TestCase("\"50\" == m", true)]
[TestCase("\"10\" != m", true)]
public void Can_use_string_equality_operators_on_custom_type(string expression, bool expected)
{
var target = new Interpreter()
.SetVariable("m", new MoneyWithStringRelationalOperators(50));

Assert.That(target.Eval<bool>(expression), Is.EqualTo(expected));
}

private sealed class MoneyWithStringRelationalOperators
{
private readonly decimal _amount;

public MoneyWithStringRelationalOperators(decimal amount)
{
_amount = amount;
}

public static implicit operator MoneyWithStringRelationalOperators(string value)
{
return new MoneyWithStringRelationalOperators(decimal.Parse(value));
}

public static bool operator <(MoneyWithStringRelationalOperators left, string right)
{
return left._amount < decimal.Parse(right);
}

public static bool operator >(MoneyWithStringRelationalOperators left, string right)
{
return left._amount > decimal.Parse(right);
}

public static bool operator <=(MoneyWithStringRelationalOperators left, string right)
{
return left._amount <= decimal.Parse(right);
}

public static bool operator >=(MoneyWithStringRelationalOperators left, string right)
{
return left._amount >= decimal.Parse(right);
}

public static bool operator <(string left, MoneyWithStringRelationalOperators right)
{
return decimal.Parse(left) < right._amount;
}

public static bool operator >(string left, MoneyWithStringRelationalOperators right)
{
return decimal.Parse(left) > right._amount;
}

public static bool operator <=(string left, MoneyWithStringRelationalOperators right)
{
return decimal.Parse(left) <= right._amount;
}

public static bool operator >=(string left, MoneyWithStringRelationalOperators right)
{
return decimal.Parse(left) >= right._amount;
}

public static bool operator ==(MoneyWithStringRelationalOperators left, string right)
{
return !ReferenceEquals(left, null) && left._amount == decimal.Parse(right);
}

public static bool operator !=(MoneyWithStringRelationalOperators left, string right)
{
return !(left == right);
}

public static bool operator ==(string left, MoneyWithStringRelationalOperators right)
{
return !ReferenceEquals(right, null) && decimal.Parse(left) == right._amount;
}

public static bool operator !=(string left, MoneyWithStringRelationalOperators right)
{
return !(left == right);
}

public override bool Equals(object obj)
{
var money = obj as MoneyWithStringRelationalOperators;
return money != null && _amount == money._amount;
}

public override int GetHashCode()
{
return _amount.GetHashCode();
}
}

private sealed class MoneyWithImplicitConversionOnlyRelationalOperators
{
private readonly decimal _amount;

public MoneyWithImplicitConversionOnlyRelationalOperators(decimal amount)
{
_amount = amount;
}

public static implicit operator MoneyWithImplicitConversionOnlyRelationalOperators(string value)
{
return new MoneyWithImplicitConversionOnlyRelationalOperators(decimal.Parse(value));
}

public static bool operator <(MoneyWithImplicitConversionOnlyRelationalOperators left, string right)
{
return left._amount < decimal.Parse(right);
}

public static bool operator >(MoneyWithImplicitConversionOnlyRelationalOperators left, string right)
{
return left._amount > decimal.Parse(right);
}

public static bool operator <=(MoneyWithImplicitConversionOnlyRelationalOperators left, string right)
{
return left._amount <= decimal.Parse(right);
}

public static bool operator >=(MoneyWithImplicitConversionOnlyRelationalOperators left, string right)
{
return left._amount >= decimal.Parse(right);
}

public static bool operator <(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right)
{
return left._amount < right._amount;
}

public static bool operator >(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right)
{
return left._amount > right._amount;
}

public static bool operator <=(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right)
{
return left._amount <= right._amount;
}

public static bool operator >=(MoneyWithImplicitConversionOnlyRelationalOperators left, MoneyWithImplicitConversionOnlyRelationalOperators right)
{
return left._amount >= right._amount;
}

public override bool Equals(object obj)
{
var money = obj as MoneyWithImplicitConversionOnlyRelationalOperators;
return money != null && _amount == money._amount;
}

public override int GetHashCode()
{
return _amount.GetHashCode();
}
}


[Test]
public void Throw_an_exception_if_a_custom_type_doesnt_define_equal_operator()
Expand Down
Loading