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

Support implicit boolean operator for logical operations (And, Or) #806

Merged
merged 1 commit into from
Apr 28, 2024
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
83 changes: 59 additions & 24 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2351,47 +2351,82 @@ private void CheckAndPromoteOperand(Type signatures, string opName, ref Expressi
expr = args[0];
}

private static string? GetOverloadedOperationName(TokenId tokenId)
private bool TryGetOverloadedEqualityOperator(TokenId tokenId, ref Expression left, ref Expression right, ref Expression[] args)
{
switch (tokenId)
if (tokenId is TokenId.DoubleEqual or TokenId.Equal)
{
case TokenId.DoubleEqual:
case TokenId.Equal:
return "op_Equality";

case TokenId.ExclamationEqual:
return "op_Inequality";
const string method = "op_Equality";
return _methodFinder.ContainsMethod(left.Type, method, true, null, ref args) ||
_methodFinder.ContainsMethod(right.Type, method, true, null, ref args);
}

default:
return null;
if (tokenId is TokenId.ExclamationEqual or TokenId.LessGreater)
{
const string method = "op_Inequality";
return _methodFinder.ContainsMethod(left.Type, method, true, null, ref args) ||
_methodFinder.ContainsMethod(right.Type, method, true, null, ref args);
}

return false;
}

private void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref Expression left, ref Expression right, int errorPos)
private bool TryGetOverloadedImplicitOperator(TokenId tokenId, ref Expression left, ref Expression right)
{
Expression[] args = { left, right };
const string methodName = "op_Implicit";
if (tokenId is not (TokenId.Ampersand or TokenId.DoubleAmpersand or TokenId.Bar or TokenId.DoubleBar))
{
return false;
}

Expression? expression = null;
var overloadedImplicitOperatorFound = false;

// support operator overloading
var nativeOperation = GetOverloadedOperationName(opId);
bool found = false;
// If the left is not a boolean, try to find the "op_Implicit" method on the left which takes the left as parameter and returns a boolean.
if (left.Type != typeof(bool))
{
var args = new[] { left };
if (_methodFinder.FindMethod(left.Type, methodName, true, ref expression, ref args, out var methodBase) > 0 && methodBase is MethodInfo methodInfo && methodInfo.ReturnType == typeof(bool))
{
left = Expression.Call(methodInfo, left);
overloadedImplicitOperatorFound = true;
}
}

if (nativeOperation != null)
// If the right is not a boolean, try to find the "op_Implicit" method on the right which takes the right as parameter and returns a boolean.
if (right.Type != typeof(bool))
{
// first try left operand's equality operators
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, null, ref args);
if (!found)
var args = new[] { right };
if (_methodFinder.FindMethod(right.Type, methodName, true, ref expression, ref args, out var methodBase) > 0 && methodBase is MethodInfo methodInfo && methodInfo.ReturnType == typeof(bool))
{
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, null, ref args);
right = Expression.Call(methodInfo, right);
overloadedImplicitOperatorFound = true;
}
}

if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, null, ref args))
return overloadedImplicitOperatorFound;
}

private void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref Expression left, ref Expression right, int errorPos)
{
Expression[] args = { left, right };

// 1. Try to find the Equality/Inequality operator
// 2. Try to find the method with the given signature
if (TryGetOverloadedEqualityOperator(opId, ref left, ref right, ref args) || _methodFinder.ContainsMethod(signatures, "F", false, null, ref args))
{
left = args[0];
right = args[1];

return;
}

// 3. Try to find the Implicit operator
if (TryGetOverloadedImplicitOperator(opId, ref left, ref right))
{
throw IncompatibleOperandsError(opName, left, right, errorPos);
return;
}

left = args[0];
right = args[1];
throw IncompatibleOperandsError(opName, left, right, errorPos);
}

private static Exception IncompatibleOperandError(string opName, Expression expr, int errorPos)
Expand Down
170 changes: 123 additions & 47 deletions src/System.Linq.Dynamic.Core/Tokenizer/TokenId.cs
Original file line number Diff line number Diff line change
@@ -1,49 +1,125 @@
#pragma warning disable CS1591
namespace System.Linq.Dynamic.Core.Tokenizer
namespace System.Linq.Dynamic.Core.Tokenizer;

/// <summary>
/// TokenId which defines the text which is parsed.
/// </summary>
public enum TokenId
{
/// <summary>
/// TokenId which defines the text which is parsed.
/// </summary>
public enum TokenId
{
Unknown,
End,
Identifier,
StringLiteral,
IntegerLiteral,
RealLiteral,
Exclamation,
Percent,
Ampersand,
OpenParen,
CloseParen,
OpenCurlyParen,
CloseCurlyParen,
Asterisk,
Plus,
Comma,
Minus,
Dot,
Slash,
Colon,
LessThan,
Equal,
GreaterThan,
Question,
OpenBracket,
CloseBracket,
Bar,
ExclamationEqual,
DoubleAmpersand,
LessThanEqual,
LessGreater,
DoubleEqual,
GreaterThanEqual,
DoubleBar,
DoubleGreaterThan,
DoubleLessThan,
NullCoalescing,
Lambda,
NullPropagation
}
}
Unknown,


End,


Identifier,


StringLiteral,


IntegerLiteral,


RealLiteral,

// !
Exclamation,

// % (Modulus Operator)
Percent,

// &
Ampersand,

// (
OpenParen,

// )
CloseParen,

// {
OpenCurlyParen,

// }
CloseCurlyParen,

// *
Asterisk,

// +
Plus,

// ,
Comma,

// -
Minus,

// .
Dot,

// /
Slash,

// :
Colon,

// <
LessThan,

// =
Equal,

// >
GreaterThan,

// ?
Question,

// [
OpenBracket,

// ]
CloseBracket,

// |
Bar,

// !=
ExclamationEqual,

// &&
DoubleAmpersand,

// <=
LessThanEqual,

// <>
LessGreater,

// ==
DoubleEqual,


// >=
GreaterThanEqual,

// ||
DoubleBar,

// >> (Shift Operator)
DoubleGreaterThan,

// << (Shift Operator)
DoubleLessThan,

// ??
NullCoalescing,

// =>
Lambda,

// ?.
NullPropagation
}
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,36 @@ public void DynamicExpressionParser_ParseLambda_WithStructWithEquality(string qu
Check.That(result.ToArray()[0]).Equals(expected[0]);
}

// #626
[Theory]
[InlineData("BooleanVariable1 && Bool2", true)]
[InlineData("BooleanVariable1 || Bool2", true)]
[InlineData("BooleanVariable1 && Bool3", false)]
[InlineData("BooleanVariable1 || Bool3", true)]
[InlineData("BooleanVariable1 && BooleanVariable4", false)]
[InlineData("BooleanVariable1 || BooleanVariable4", true)]
public void DynamicExpressionParser_ParseLambda_WithStruct_UsingOperators(string query, bool expectedResult)
{
var x = new BooleanVariable(true) && new BooleanVariable(false);

// Assign
var model = new
{
BooleanVariable1 = new BooleanVariable(true),
Bool2 = true,
Bool3 = false,
BooleanVariable4 = new BooleanVariable(false)
};

// Act
var expr = DynamicExpressionParser.ParseLambda(model.GetType(), null, query);
var compiled = expr.Compile();
var result = compiled.DynamicInvoke(model);

// Assert
result.Should().Be(expectedResult);
}

[Fact]
public void DynamicExpressionParser_ParseLambda_ToList()
{
Expand Down
Loading
Loading