Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unary and binary operators: find the proper overload by also checking the base classes #136

Merged
merged 1 commit into from
Apr 27, 2021
Merged
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
172 changes: 144 additions & 28 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private Expression ParseConditionalOr()
NextToken();
var right = ParseConditionalAnd();
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
left = Expression.OrElse(left, right);
left = GenerateBinary(ExpressionType.OrElse, left, right);
}
return left;
}
Expand All @@ -165,7 +165,7 @@ private Expression ParseConditionalAnd()
NextToken();
var right = ParseLogicalOr();
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
left = Expression.AndAlso(left, right);
left = GenerateBinary(ExpressionType.AndAlso, left, right);
}
return left;
}
Expand All @@ -179,7 +179,7 @@ private Expression ParseLogicalOr()
NextToken();
var right = ParseLogicalXor();
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
left = Expression.Or(left, right);
left = GenerateBinary(ExpressionType.Or, left, right);
}
return left;
}
Expand All @@ -193,7 +193,7 @@ private Expression ParseLogicalXor()
NextToken();
var right = ParseLogicalAnd();
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
left = Expression.ExclusiveOr(left, right);
left = GenerateBinary(ExpressionType.ExclusiveOr, left, right);
}
return left;
}
Expand All @@ -207,7 +207,7 @@ private Expression ParseLogicalAnd()
NextToken();
var right = ParseComparison();
CheckAndPromoteOperands(typeof(ParseSignatures.ILogicalSignatures), ref left, ref right);
left = Expression.And(left, right);
left = GenerateBinary(ExpressionType.And, left, right);
}
return left;
}
Expand Down Expand Up @@ -354,12 +354,12 @@ private Expression ParseAdditive()
else
{
CheckAndPromoteOperands(typeof(ParseSignatures.IAddSignatures), ref left, ref right);
left = GenerateAdd(left, right);
left = GenerateBinary(ExpressionType.Add, left, right);
}
break;
case TokenId.Minus:
CheckAndPromoteOperands(typeof(ParseSignatures.ISubtractSignatures), ref left, ref right);
left = GenerateSubtract(left, right);
left = GenerateBinary(ExpressionType.Subtract, left, right);
break;
}
}
Expand All @@ -382,13 +382,13 @@ private Expression ParseMultiplicative()
switch (op.id)
{
case TokenId.Asterisk:
left = Expression.Multiply(left, right);
left = GenerateBinary(ExpressionType.Multiply, left, right);
break;
case TokenId.Slash:
left = Expression.Divide(left, right);
left = GenerateBinary(ExpressionType.Divide, left, right);
break;
case TokenId.Percent:
left = Expression.Modulo(left, right);
left = GenerateBinary(ExpressionType.Modulo, left, right);
break;
}
}
Expand Down Expand Up @@ -423,7 +423,7 @@ private Expression ParseUnary()
if (op.id == TokenId.Minus)
{
CheckAndPromoteOperand(typeof(ParseSignatures.INegationSignatures), ref expr);
expr = Expression.Negate(expr);
expr = GenerateUnary(ExpressionType.Negate, expr);
}
else if (op.id == TokenId.Plus)
{
Expand All @@ -432,13 +432,58 @@ private Expression ParseUnary()
else if (op.id == TokenId.Exclamation)
{
CheckAndPromoteOperand(typeof(ParseSignatures.INotSignatures), ref expr);
expr = Expression.Not(expr);
expr = GenerateUnary(ExpressionType.Not, expr);
}
return expr;
}
return ParsePrimary();
}

private Expression GenerateUnary(ExpressionType unaryType, Expression expr)
{
// find the overloaded unary operator
string opName;
switch (unaryType)
{
case ExpressionType.Negate: opName = "op_UnaryNegation"; break;
case ExpressionType.Not: opName = "op_LogicalNot"; break;
default: opName = null; break;
}

var applicableMethod = FindUnaryOperator(opName, expr);

MethodInfo operatorMethod = null;
if (applicableMethod != null)
{
operatorMethod = applicableMethod.MethodBase as MethodInfo;
expr = applicableMethod.PromotedParameters[0];
}

// if no operator was found, the default Linq resolution will occur
return Expression.MakeUnary(unaryType, expr, null, operatorMethod);
}

private MethodData FindUnaryOperator(string operatorName, Expression expr)
{
if (operatorName == null)
return null;

var errorPos = _token.pos;
var type = expr.Type;
var args = new[] { expr };

// try to find the user defined operator on both operands
var applicableMethods = FindMethods(type, operatorName, true, args);
if (applicableMethods.Length > 1)
throw CreateParseException(errorPos, ErrorMessages.AmbiguousUnaryOperatorInvocation, operatorName, GetTypeName(type));

MethodData userDefinedOperator = null;
if (applicableMethods.Length == 1)
userDefinedOperator = applicableMethods[0];

return userDefinedOperator;
}

private Expression ParsePrimary()
{
var tokenPos = _token.pos;
Expand Down Expand Up @@ -1757,17 +1802,17 @@ private static int CompareConversions(Type s, Type t1, Type t2)
return 0;
}

private static Expression GenerateEqual(Expression left, Expression right)
private Expression GenerateEqual(Expression left, Expression right)
{
return Expression.Equal(left, right);
return GenerateBinary(ExpressionType.Equal, left, right);
}

private static Expression GenerateNotEqual(Expression left, Expression right)
private Expression GenerateNotEqual(Expression left, Expression right)
{
return Expression.NotEqual(left, right);
return GenerateBinary(ExpressionType.NotEqual, left, right);
}

private static Expression GenerateGreaterThan(Expression left, Expression right)
private Expression GenerateGreaterThan(Expression left, Expression right)
{
if (left.Type == typeof(string))
{
Expand All @@ -1776,10 +1821,10 @@ private static Expression GenerateGreaterThan(Expression left, Expression right)
Expression.Constant(0)
);
}
return Expression.GreaterThan(left, right);
return GenerateBinary(ExpressionType.GreaterThan, left, right);
}

private static Expression GenerateGreaterThanEqual(Expression left, Expression right)
private Expression GenerateGreaterThanEqual(Expression left, Expression right)
{
if (left.Type == typeof(string))
{
Expand All @@ -1788,10 +1833,10 @@ private static Expression GenerateGreaterThanEqual(Expression left, Expression r
Expression.Constant(0)
);
}
return Expression.GreaterThanOrEqual(left, right);
return GenerateBinary(ExpressionType.GreaterThanOrEqual, left, right);
}

private static Expression GenerateLessThan(Expression left, Expression right)
private Expression GenerateLessThan(Expression left, Expression right)
{
if (left.Type == typeof(string))
{
Expand All @@ -1801,10 +1846,10 @@ private static Expression GenerateLessThan(Expression left, Expression right)
);
}

return Expression.LessThan(left, right);
return GenerateBinary(ExpressionType.LessThan, left, right);
}

private static Expression GenerateLessThanEqual(Expression left, Expression right)
private Expression GenerateLessThanEqual(Expression left, Expression right)
{
if (left.Type == typeof(string))
{
Expand All @@ -1813,17 +1858,88 @@ private static Expression GenerateLessThanEqual(Expression left, Expression righ
Expression.Constant(0)
);
}
return Expression.LessThanOrEqual(left, right);
return GenerateBinary(ExpressionType.LessThanOrEqual, left, right);
}

private static Expression GenerateAdd(Expression left, Expression right)
private Expression GenerateBinary(ExpressionType binaryType, Expression left, Expression right)
{
return Expression.Add(left, right);
// find the overloaded binary operator
string opName;

var liftToNull = true;
switch (binaryType)
{
case ExpressionType.OrElse: opName = "op_BitwiseOr"; break;
case ExpressionType.Or: opName = "op_BitwiseOr"; break;
case ExpressionType.ExclusiveOr: opName = "op_ExclusiveOr"; break;
case ExpressionType.AndAlso: opName = "op_BitwiseAnd"; break;
case ExpressionType.And: opName = "op_BitwiseAnd"; break;
case ExpressionType.Add: opName = "op_Addition"; break;
case ExpressionType.Subtract: opName = "op_Subtraction"; break;
case ExpressionType.Multiply: opName = "op_Multiply"; break;
case ExpressionType.Divide: opName = "op_Division"; break;
case ExpressionType.Modulo: opName = "op_Modulus"; break;
case ExpressionType.Equal: opName = "op_Equality"; liftToNull = false; break;
case ExpressionType.NotEqual: opName = "op_Inequality"; liftToNull = false; break;
case ExpressionType.GreaterThan: opName = "op_GreaterThan"; liftToNull = false; break;
case ExpressionType.GreaterThanOrEqual: opName = "op_GreaterThanOrEqual"; liftToNull = false; break;
case ExpressionType.LessThan: opName = "op_LessThan"; liftToNull = false; break;
case ExpressionType.LessThanOrEqual: opName = "op_LessThanOrEqual"; liftToNull = false; break;
default: opName = null; break;
}

var applicableMethod = FindBinaryOperator(opName, left, right);

MethodInfo operatorMethod = null;
if (applicableMethod != null)
{
operatorMethod = applicableMethod.MethodBase as MethodInfo;
left = applicableMethod.PromotedParameters[0];
right = applicableMethod.PromotedParameters[1];
}

// if no operator was found, the default Linq resolution will occur
return Expression.MakeBinary(binaryType, left, right, liftToNull, operatorMethod);
}

private static Expression GenerateSubtract(Expression left, Expression right)
private MethodData FindBinaryOperator(string operatorName, Expression left, Expression right)
{
return Expression.Subtract(left, right);
if (operatorName == null)
return null;

var errorPos = _token.pos;
var leftType = left.Type;
var rightType = right.Type;
var error = CreateParseException(errorPos, ErrorMessages.AmbiguousBinaryOperatorInvocation, operatorName, GetTypeName(leftType), GetTypeName(rightType));

var args = new[] { left, right };

MethodData userDefinedOperator = null;

// try to find the user defined operator on both operands
var opOnLeftType = FindMethods(leftType, operatorName, true, args);
if (opOnLeftType.Length > 1)
throw error;

if (opOnLeftType.Length == 1)
userDefinedOperator = opOnLeftType[0];

if (leftType != rightType)
{
var opOnRightType = FindMethods(rightType, operatorName, true, args);
if (opOnRightType.Length > 1)
throw error;

MethodData rightOperator = null;
if (opOnRightType.Length == 1)
rightOperator = opOnRightType[0];

// we found a matching user defined operator on either type, but it might be the same method
if (userDefinedOperator != null && rightOperator != null && !ReferenceEquals(userDefinedOperator.MethodBase, rightOperator.MethodBase))
throw error;
}

return userDefinedOperator;
}

private static Expression GenerateStringConcat(Expression left, Expression right)
Expand Down
24 changes: 23 additions & 1 deletion src/DynamicExpresso.Core/Resources/ErrorMessages.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/DynamicExpresso.Core/Resources/ErrorMessages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,10 @@
<data name="InvalidOperation" xml:space="preserve">
<value>Invalid Operation</value>
</data>
<data name="AmbiguousBinaryOperatorInvocation" xml:space="preserve">
<value>Ambiguous invocation of user defined operator '{0}' in types '{1}' and '{2}'</value>
</data>
<data name="AmbiguousUnaryOperatorInvocation" xml:space="preserve">
<value>Ambiguous invocation of user defined operator '{0}' in type '{1}'</value>
</data>
</root>
Loading