Skip to content

Commit a4b4d0a

Browse files
authored
Support implicit boolean operator for logical operations (And, Or) (#806)
1 parent 91d3c5f commit a4b4d0a

File tree

4 files changed

+359
-71
lines changed

4 files changed

+359
-71
lines changed

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

+59-24
Original file line numberDiff line numberDiff line change
@@ -2351,47 +2351,82 @@ private void CheckAndPromoteOperand(Type signatures, string opName, ref Expressi
23512351
expr = args[0];
23522352
}
23532353

2354-
private static string? GetOverloadedOperationName(TokenId tokenId)
2354+
private bool TryGetOverloadedEqualityOperator(TokenId tokenId, ref Expression left, ref Expression right, ref Expression[] args)
23552355
{
2356-
switch (tokenId)
2356+
if (tokenId is TokenId.DoubleEqual or TokenId.Equal)
23572357
{
2358-
case TokenId.DoubleEqual:
2359-
case TokenId.Equal:
2360-
return "op_Equality";
2361-
2362-
case TokenId.ExclamationEqual:
2363-
return "op_Inequality";
2358+
const string method = "op_Equality";
2359+
return _methodFinder.ContainsMethod(left.Type, method, true, null, ref args) ||
2360+
_methodFinder.ContainsMethod(right.Type, method, true, null, ref args);
2361+
}
23642362

2365-
default:
2366-
return null;
2363+
if (tokenId is TokenId.ExclamationEqual or TokenId.LessGreater)
2364+
{
2365+
const string method = "op_Inequality";
2366+
return _methodFinder.ContainsMethod(left.Type, method, true, null, ref args) ||
2367+
_methodFinder.ContainsMethod(right.Type, method, true, null, ref args);
23672368
}
2369+
2370+
return false;
23682371
}
23692372

2370-
private void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref Expression left, ref Expression right, int errorPos)
2373+
private bool TryGetOverloadedImplicitOperator(TokenId tokenId, ref Expression left, ref Expression right)
23712374
{
2372-
Expression[] args = { left, right };
2375+
const string methodName = "op_Implicit";
2376+
if (tokenId is not (TokenId.Ampersand or TokenId.DoubleAmpersand or TokenId.Bar or TokenId.DoubleBar))
2377+
{
2378+
return false;
2379+
}
2380+
2381+
Expression? expression = null;
2382+
var overloadedImplicitOperatorFound = false;
23732383

2374-
// support operator overloading
2375-
var nativeOperation = GetOverloadedOperationName(opId);
2376-
bool found = false;
2384+
// 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.
2385+
if (left.Type != typeof(bool))
2386+
{
2387+
var args = new[] { left };
2388+
if (_methodFinder.FindMethod(left.Type, methodName, true, ref expression, ref args, out var methodBase) > 0 && methodBase is MethodInfo methodInfo && methodInfo.ReturnType == typeof(bool))
2389+
{
2390+
left = Expression.Call(methodInfo, left);
2391+
overloadedImplicitOperatorFound = true;
2392+
}
2393+
}
23772394

2378-
if (nativeOperation != null)
2395+
// 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.
2396+
if (right.Type != typeof(bool))
23792397
{
2380-
// first try left operand's equality operators
2381-
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, null, ref args);
2382-
if (!found)
2398+
var args = new[] { right };
2399+
if (_methodFinder.FindMethod(right.Type, methodName, true, ref expression, ref args, out var methodBase) > 0 && methodBase is MethodInfo methodInfo && methodInfo.ReturnType == typeof(bool))
23832400
{
2384-
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, null, ref args);
2401+
right = Expression.Call(methodInfo, right);
2402+
overloadedImplicitOperatorFound = true;
23852403
}
23862404
}
23872405

2388-
if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, null, ref args))
2406+
return overloadedImplicitOperatorFound;
2407+
}
2408+
2409+
private void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref Expression left, ref Expression right, int errorPos)
2410+
{
2411+
Expression[] args = { left, right };
2412+
2413+
// 1. Try to find the Equality/Inequality operator
2414+
// 2. Try to find the method with the given signature
2415+
if (TryGetOverloadedEqualityOperator(opId, ref left, ref right, ref args) || _methodFinder.ContainsMethod(signatures, "F", false, null, ref args))
2416+
{
2417+
left = args[0];
2418+
right = args[1];
2419+
2420+
return;
2421+
}
2422+
2423+
// 3. Try to find the Implicit operator
2424+
if (TryGetOverloadedImplicitOperator(opId, ref left, ref right))
23892425
{
2390-
throw IncompatibleOperandsError(opName, left, right, errorPos);
2426+
return;
23912427
}
23922428

2393-
left = args[0];
2394-
right = args[1];
2429+
throw IncompatibleOperandsError(opName, left, right, errorPos);
23952430
}
23962431

23972432
private static Exception IncompatibleOperandError(string opName, Expression expr, int errorPos)
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,125 @@
11
#pragma warning disable CS1591
2-
namespace System.Linq.Dynamic.Core.Tokenizer
2+
namespace System.Linq.Dynamic.Core.Tokenizer;
3+
4+
/// <summary>
5+
/// TokenId which defines the text which is parsed.
6+
/// </summary>
7+
public enum TokenId
38
{
4-
/// <summary>
5-
/// TokenId which defines the text which is parsed.
6-
/// </summary>
7-
public enum TokenId
8-
{
9-
Unknown,
10-
End,
11-
Identifier,
12-
StringLiteral,
13-
IntegerLiteral,
14-
RealLiteral,
15-
Exclamation,
16-
Percent,
17-
Ampersand,
18-
OpenParen,
19-
CloseParen,
20-
OpenCurlyParen,
21-
CloseCurlyParen,
22-
Asterisk,
23-
Plus,
24-
Comma,
25-
Minus,
26-
Dot,
27-
Slash,
28-
Colon,
29-
LessThan,
30-
Equal,
31-
GreaterThan,
32-
Question,
33-
OpenBracket,
34-
CloseBracket,
35-
Bar,
36-
ExclamationEqual,
37-
DoubleAmpersand,
38-
LessThanEqual,
39-
LessGreater,
40-
DoubleEqual,
41-
GreaterThanEqual,
42-
DoubleBar,
43-
DoubleGreaterThan,
44-
DoubleLessThan,
45-
NullCoalescing,
46-
Lambda,
47-
NullPropagation
48-
}
49-
}
9+
Unknown,
10+
11+
12+
End,
13+
14+
15+
Identifier,
16+
17+
18+
StringLiteral,
19+
20+
21+
IntegerLiteral,
22+
23+
24+
RealLiteral,
25+
26+
// !
27+
Exclamation,
28+
29+
// % (Modulus Operator)
30+
Percent,
31+
32+
// &
33+
Ampersand,
34+
35+
// (
36+
OpenParen,
37+
38+
// )
39+
CloseParen,
40+
41+
// {
42+
OpenCurlyParen,
43+
44+
// }
45+
CloseCurlyParen,
46+
47+
// *
48+
Asterisk,
49+
50+
// +
51+
Plus,
52+
53+
// ,
54+
Comma,
55+
56+
// -
57+
Minus,
58+
59+
// .
60+
Dot,
61+
62+
// /
63+
Slash,
64+
65+
// :
66+
Colon,
67+
68+
// <
69+
LessThan,
70+
71+
// =
72+
Equal,
73+
74+
// >
75+
GreaterThan,
76+
77+
// ?
78+
Question,
79+
80+
// [
81+
OpenBracket,
82+
83+
// ]
84+
CloseBracket,
85+
86+
// |
87+
Bar,
88+
89+
// !=
90+
ExclamationEqual,
91+
92+
// &&
93+
DoubleAmpersand,
94+
95+
// <=
96+
LessThanEqual,
97+
98+
// <>
99+
LessGreater,
100+
101+
// ==
102+
DoubleEqual,
103+
104+
105+
// >=
106+
GreaterThanEqual,
107+
108+
// ||
109+
DoubleBar,
110+
111+
// >> (Shift Operator)
112+
DoubleGreaterThan,
113+
114+
// << (Shift Operator)
115+
DoubleLessThan,
116+
117+
// ??
118+
NullCoalescing,
119+
120+
// =>
121+
Lambda,
122+
123+
// ?.
124+
NullPropagation
125+
}

test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs

+30
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,36 @@ public void DynamicExpressionParser_ParseLambda_WithStructWithEquality(string qu
423423
Check.That(result.ToArray()[0]).Equals(expected[0]);
424424
}
425425

426+
// #626
427+
[Theory]
428+
[InlineData("BooleanVariable1 && Bool2", true)]
429+
[InlineData("BooleanVariable1 || Bool2", true)]
430+
[InlineData("BooleanVariable1 && Bool3", false)]
431+
[InlineData("BooleanVariable1 || Bool3", true)]
432+
[InlineData("BooleanVariable1 && BooleanVariable4", false)]
433+
[InlineData("BooleanVariable1 || BooleanVariable4", true)]
434+
public void DynamicExpressionParser_ParseLambda_WithStruct_UsingOperators(string query, bool expectedResult)
435+
{
436+
var x = new BooleanVariable(true) && new BooleanVariable(false);
437+
438+
// Assign
439+
var model = new
440+
{
441+
BooleanVariable1 = new BooleanVariable(true),
442+
Bool2 = true,
443+
Bool3 = false,
444+
BooleanVariable4 = new BooleanVariable(false)
445+
};
446+
447+
// Act
448+
var expr = DynamicExpressionParser.ParseLambda(model.GetType(), null, query);
449+
var compiled = expr.Compile();
450+
var result = compiled.DynamicInvoke(model);
451+
452+
// Assert
453+
result.Should().Be(expectedResult);
454+
}
455+
426456
[Fact]
427457
public void DynamicExpressionParser_ParseLambda_ToList()
428458
{

0 commit comments

Comments
 (0)