Skip to content

Commit 2df8263

Browse files
nothrowStefH
authored andcommitted
Adding support for overloaded op_Equality (#273)
* Added SnowflakeId struct + tests * Implemented op_Equality/op_Inequality calls * Fixed formatting * Added unit test with parameter * Equality operators now work for non-constants as well
1 parent 8b3e9f7 commit 2df8263

File tree

7 files changed

+184
-12
lines changed

7 files changed

+184
-12
lines changed

src-console/System.Linq.Dynamic.Core.ConsoleTestApp.net40/ConsoleApp_net40_sqlite_original.csproj

+4-1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@
9494
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\Models\UserProfile.cs">
9595
<Link>Helpers\Models\UserProfile.cs</Link>
9696
</Compile>
97+
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\Models\SnowflakeId.cs">
98+
<Link>Helpers\Models\SnowflakeId.cs</Link>
99+
</Compile>
97100
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\TestEnum.cs">
98101
<Link>Helpers\TestEnum.cs</Link>
99102
</Compile>
@@ -102,7 +105,7 @@
102105
</Compile>
103106
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\Models\UserState.cs">
104107
<Link>Helpers\Models\UserState.cs</Link>
105-
</Compile>
108+
</Compile>
106109
<Compile Include="Program.cs" />
107110
<Compile Include="Properties\AssemblyInfo.cs" />
108111
</ItemGroup>

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

+36-11
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ Expression ParseOrOperator()
251251
Token op = _textParser.CurrentToken;
252252
_textParser.NextToken();
253253
Expression right = ParseAndOperator();
254-
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Text, ref left, ref right, op.Pos);
254+
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
255255
left = Expression.OrElse(left, right);
256256
}
257257
return left;
@@ -266,7 +266,7 @@ Expression ParseAndOperator()
266266
Token op = _textParser.CurrentToken;
267267
_textParser.NextToken();
268268
Expression right = ParseComparisonOperator();
269-
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Text, ref left, ref right, op.Pos);
269+
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
270270
left = Expression.AndAlso(left, right);
271271
}
272272
return left;
@@ -303,7 +303,7 @@ Expression ParseIn()
303303
// else, check for direct type match
304304
else if (left.Type != right.Type)
305305
{
306-
CheckAndPromoteOperands(typeof(IEqualitySignatures), "==", ref left, ref right, op.Pos);
306+
CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, op.Pos);
307307
}
308308

309309
if (accumulate.Type != typeof(bool))
@@ -508,7 +508,7 @@ Expression ParseComparisonOperator()
508508
}
509509
else
510510
{
511-
CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.Text, ref left, ref right, op.Pos);
511+
CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
512512
}
513513
}
514514
}
@@ -589,11 +589,11 @@ Expression ParseShiftOperator()
589589
switch (op.Id)
590590
{
591591
case TokenId.DoubleLessThan:
592-
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Text, ref left, ref right, op.Pos);
592+
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
593593
left = Expression.LeftShift(left, right);
594594
break;
595595
case TokenId.DoubleGreaterThan:
596-
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Text, ref left, ref right, op.Pos);
596+
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
597597
left = Expression.RightShift(left, right);
598598
break;
599599
}
@@ -619,12 +619,12 @@ Expression ParseAdditive()
619619
}
620620
else
621621
{
622-
CheckAndPromoteOperands(typeof(IAddSignatures), op.Text, ref left, ref right, op.Pos);
622+
CheckAndPromoteOperands(typeof(IAddSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
623623
left = _expressionHelper.GenerateAdd(left, right);
624624
}
625625
break;
626626
case TokenId.Minus:
627-
CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Text, ref left, ref right, op.Pos);
627+
CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
628628
left = _expressionHelper.GenerateSubtract(left, right);
629629
break;
630630
}
@@ -642,7 +642,7 @@ Expression ParseMultiplicative()
642642
Token op = _textParser.CurrentToken;
643643
_textParser.NextToken();
644644
Expression right = ParseUnary();
645-
CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.Text, ref left, ref right, op.Pos);
645+
CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
646646
switch (op.Id)
647647
{
648648
case TokenId.Asterisk:
@@ -1897,11 +1897,36 @@ void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr,
18971897
expr = args[0];
18981898
}
18991899

1900-
void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos)
1900+
static string GetOverloadedOperationName(TokenId tokenId)
1901+
{
1902+
switch (tokenId)
1903+
{
1904+
case TokenId.DoubleEqual:
1905+
return "op_Equality";
1906+
case TokenId.ExclamationEqual:
1907+
return "op_Inequality";
1908+
default:
1909+
return null;
1910+
}
1911+
}
1912+
1913+
void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref Expression left, ref Expression right, int errorPos)
19011914
{
19021915
Expression[] args = { left, right };
1916+
1917+
// support operator overloading
1918+
var nativeOperation = GetOverloadedOperationName(opId);
1919+
bool found = false;
19031920

1904-
if (!_methodFinder.ContainsMethod(signatures, "F", false, args))
1921+
if (nativeOperation != null)
1922+
{
1923+
// first try left operand's equality operators
1924+
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, args);
1925+
if (!found)
1926+
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, args);
1927+
}
1928+
1929+
if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, args))
19051930
{
19061931
throw IncompatibleOperandsError(opName, left, right, errorPos);
19071932
}

test/EntityFramework.DynamicLinq.Tests.net452/EntityFramework.DynamicLinq.Tests.net452.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@
250250
<Compile Include="..\System.Linq.Dynamic.Core.Tests\Helpers\Models\UserState.cs">
251251
<Link>Helpers\Models\UserState.cs</Link>
252252
</Compile>
253+
<Compile Include="..\System.Linq.Dynamic.Core.Tests\Helpers\Models\SnowflakeId.cs">
254+
<Link>Helpers\Models\SnowflakeId.cs</Link>
255+
</Compile>
253256
<Compile Include="..\System.Linq.Dynamic.Core.Tests\Helpers\TestEnum.cs">
254257
<Link>Helpers\TestEnum.cs</Link>
255258
</Compile>

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

+24
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq.Dynamic.Core.CustomTypeProviders;
44
using System.Linq.Dynamic.Core.Exceptions;
5+
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
56
using System.Linq.Expressions;
67
using System.Reflection;
78
using Xunit;
@@ -142,6 +143,29 @@ public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQu
142143
Check.That(value).IsEqualTo("x");
143144
}
144145

146+
[Fact]
147+
public void DynamicExpressionParser_ParseLambda_WithStructWithEquality()
148+
{
149+
// Assign
150+
var testList = User.GenerateSampleModels(51);
151+
var qry = testList.AsQueryable();
152+
153+
// Act
154+
ulong expectedX = (ulong) long.MaxValue + 3;
155+
156+
string query = $"Where(x => x.SnowflakeId == {expectedX})";
157+
LambdaExpression expression = DynamicExpressionParser.ParseLambda(qry.GetType(), null, query);
158+
Delegate del = expression.Compile();
159+
IEnumerable<dynamic> result = del.DynamicInvoke(qry) as IEnumerable<dynamic>;
160+
161+
var expected = qry.Where(gg => gg.SnowflakeId == new SnowflakeId(expectedX)).ToList();
162+
163+
// Assert
164+
Check.That(result).IsNotNull();
165+
Check.That(result).HasSize(expected.Count);
166+
Check.That(result.ToArray()[0]).Equals(expected[0]);
167+
}
168+
145169
[Fact]
146170
public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_false()
147171
{

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

+57
Original file line numberDiff line numberDiff line change
@@ -1695,6 +1695,63 @@ public void ExpressionTests_Type_Integer_Qualifiers()
16951695
Assert.Equal(resultValuesUL.ToArray(), resultUL.ToArray());
16961696
}
16971697

1698+
[Fact]
1699+
public void ExpressionTests_Type_StructWithIntegerEquality()
1700+
{
1701+
// Arrange
1702+
var valuesL = new[] { new SnowflakeId(1L), new SnowflakeId(100), new SnowflakeId(5) }.AsQueryable();
1703+
var resultValuesL = new[] { new SnowflakeId(100) }.AsQueryable();
1704+
1705+
// Act
1706+
var resultL = valuesL.Where("it == 100");
1707+
var resultNL = valuesL.Where("it != 1 && it != 5");
1708+
var resultArg = valuesL.Where("it == @0", 100);
1709+
var resultIn = valuesL.Where("it in (100)");
1710+
1711+
// Assert
1712+
Assert.Equal(resultValuesL.ToArray(), resultL);
1713+
Assert.Equal(resultValuesL.ToArray(), resultNL);
1714+
Assert.Equal(resultValuesL.ToArray(), resultArg);
1715+
Assert.Equal(resultValuesL.ToArray(), resultIn);
1716+
}
1717+
1718+
[Fact]
1719+
public void ExpressionTests_Type_StructWithIntegerEquality_BothVariablesInStruct()
1720+
{
1721+
// Arrange
1722+
var valuesL = new[]
1723+
{
1724+
new
1725+
{
1726+
Id = new SnowflakeId(1L),
1727+
Var = 1
1728+
},
1729+
new
1730+
{
1731+
Id = new SnowflakeId(2L),
1732+
Var = 1
1733+
},
1734+
new
1735+
{
1736+
Id = new SnowflakeId(1L),
1737+
Var = 2
1738+
}
1739+
}.AsQueryable();
1740+
1741+
var resultValuesL = new[] {
1742+
new {
1743+
Id = new SnowflakeId(1L),
1744+
Var = 1
1745+
}
1746+
}.AsQueryable();
1747+
1748+
// Act
1749+
var resultL = valuesL.Where("it.Id == it.Var");
1750+
1751+
// Assert
1752+
Assert.Equal(resultValuesL.ToArray(), resultL);
1753+
}
1754+
16981755
[Fact]
16991756
public void ExpressionTests_Type_Integer_Qualifiers_Negative()
17001757
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace System.Linq.Dynamic.Core.Tests.Helpers.Models
2+
{
3+
public struct SnowflakeId : IEquatable<SnowflakeId>
4+
{
5+
public bool Equals(SnowflakeId other)
6+
{
7+
return Value == other.Value;
8+
}
9+
10+
public override bool Equals(object obj)
11+
{
12+
return obj is SnowflakeId other && Equals(other);
13+
}
14+
15+
public override int GetHashCode()
16+
{
17+
return Value.GetHashCode();
18+
}
19+
20+
public static bool operator ==(SnowflakeId left, SnowflakeId right)
21+
{
22+
return left.Equals(right);
23+
}
24+
25+
public static bool operator !=(SnowflakeId left, SnowflakeId right)
26+
{
27+
return !left.Equals(right);
28+
}
29+
30+
public static bool operator ==(SnowflakeId left, int right)
31+
{
32+
return (int)left.Value == right;
33+
}
34+
35+
public static bool operator !=(SnowflakeId left, int right)
36+
{
37+
return (int)left.Value != right;
38+
}
39+
40+
public static bool operator ==(SnowflakeId left, ulong right)
41+
{
42+
return left.Value == right;
43+
}
44+
45+
public static bool operator !=(SnowflakeId left, ulong right)
46+
{
47+
return left.Value != right;
48+
}
49+
50+
public ulong Value { get; }
51+
52+
public SnowflakeId(ulong value)
53+
{
54+
Value = value;
55+
}
56+
}
57+
}

test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ public class User
66
{
77
public Guid Id { get; set; }
88

9+
public SnowflakeId SnowflakeId { get; set; }
10+
911
public string UserName { get; set; }
1012

1113
public int? NullableInt { get; set; }
@@ -42,6 +44,7 @@ public static IList<User> GenerateSampleModels(int total, bool allowNullableProf
4244
var user = new User
4345
{
4446
Id = Guid.NewGuid(),
47+
SnowflakeId = new SnowflakeId(((ulong)long.MaxValue + (ulong)i + 2UL)),
4548
UserName = "User" + i,
4649
Income = 1 + (i % 15) * 100
4750
};

0 commit comments

Comments
 (0)