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

Adding support for overloaded op_Equality #273

Merged
merged 5 commits into from
May 19, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\Models\UserProfile.cs">
<Link>Helpers\Models\UserProfile.cs</Link>
</Compile>
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\Models\SnowflakeId.cs">
<Link>Helpers\Models\SnowflakeId.cs</Link>
</Compile>
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\TestEnum.cs">
<Link>Helpers\TestEnum.cs</Link>
</Compile>
Expand All @@ -102,7 +105,7 @@
</Compile>
<Compile Include="..\..\test\System.Linq.Dynamic.Core.Tests\Helpers\Models\UserState.cs">
<Link>Helpers\Models\UserState.cs</Link>
</Compile>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Expand Down
47 changes: 36 additions & 11 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ Expression ParseOrOperator()
Token op = _textParser.CurrentToken;
_textParser.NextToken();
Expression right = ParseAndOperator();
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = Expression.OrElse(left, right);
}
return left;
Expand All @@ -266,7 +266,7 @@ Expression ParseAndOperator()
Token op = _textParser.CurrentToken;
_textParser.NextToken();
Expression right = ParseComparisonOperator();
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = Expression.AndAlso(left, right);
}
return left;
Expand Down Expand Up @@ -303,7 +303,7 @@ Expression ParseIn()
// else, check for direct type match
else if (left.Type != right.Type)
{
CheckAndPromoteOperands(typeof(IEqualitySignatures), "==", ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, op.Pos);
}

if (accumulate.Type != typeof(bool))
Expand Down Expand Up @@ -508,7 +508,7 @@ Expression ParseComparisonOperator()
}
else
{
CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
}
}
}
Expand Down Expand Up @@ -589,11 +589,11 @@ Expression ParseShiftOperator()
switch (op.Id)
{
case TokenId.DoubleLessThan:
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = Expression.LeftShift(left, right);
break;
case TokenId.DoubleGreaterThan:
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = Expression.RightShift(left, right);
break;
}
Expand All @@ -619,12 +619,12 @@ Expression ParseAdditive()
}
else
{
CheckAndPromoteOperands(typeof(IAddSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(IAddSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = _expressionHelper.GenerateAdd(left, right);
}
break;
case TokenId.Minus:
CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
left = _expressionHelper.GenerateSubtract(left, right);
break;
}
Expand All @@ -642,7 +642,7 @@ Expression ParseMultiplicative()
Token op = _textParser.CurrentToken;
_textParser.NextToken();
Expression right = ParseUnary();
CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.Text, ref left, ref right, op.Pos);
CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.Id, op.Text, ref left, ref right, op.Pos);
switch (op.Id)
{
case TokenId.Asterisk:
Expand Down Expand Up @@ -1897,11 +1897,36 @@ void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr,
expr = args[0];
}

void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos)
static string GetOverloadedOperationName(TokenId tokenId)
{
switch (tokenId)
{
case TokenId.DoubleEqual:
return "op_Equality";
case TokenId.ExclamationEqual:
return "op_Inequality";
default:
return null;
}
}

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

// support operator overloading
var nativeOperation = GetOverloadedOperationName(opId);
bool found = false;

if (!_methodFinder.ContainsMethod(signatures, "F", false, args))
if (nativeOperation != null)
{
// first try left operand's equality operators
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, args);
if (!found)
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, args);
}

if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, args))
{
throw IncompatibleOperandsError(opName, left, right, errorPos);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@
<Compile Include="..\System.Linq.Dynamic.Core.Tests\Helpers\Models\UserState.cs">
<Link>Helpers\Models\UserState.cs</Link>
</Compile>
<Compile Include="..\System.Linq.Dynamic.Core.Tests\Helpers\Models\SnowflakeId.cs">
<Link>Helpers\Models\SnowflakeId.cs</Link>
</Compile>
<Compile Include="..\System.Linq.Dynamic.Core.Tests\Helpers\TestEnum.cs">
<Link>Helpers\TestEnum.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using System.Linq.Expressions;
using System.Reflection;
using Xunit;
Expand Down Expand Up @@ -142,6 +143,29 @@ public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQu
Check.That(value).IsEqualTo("x");
}

[Fact]
public void DynamicExpressionParser_ParseLambda_WithStructWithEquality()
{
// Assign
var testList = User.GenerateSampleModels(51);
var qry = testList.AsQueryable();

// Act
ulong expectedX = (ulong) long.MaxValue + 3;

string query = $"Where(x => x.SnowflakeId == {expectedX})";
LambdaExpression expression = DynamicExpressionParser.ParseLambda(qry.GetType(), null, query);
Delegate del = expression.Compile();
IEnumerable<dynamic> result = del.DynamicInvoke(qry) as IEnumerable<dynamic>;

var expected = qry.Where(gg => gg.SnowflakeId == new SnowflakeId(expectedX)).ToList();

// Assert
Check.That(result).IsNotNull();
Check.That(result).HasSize(expected.Count);
Check.That(result.ToArray()[0]).Equals(expected[0]);
}

[Fact]
public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_false()
{
Expand Down
57 changes: 57 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,63 @@ public void ExpressionTests_Type_Integer_Qualifiers()
Assert.Equal(resultValuesUL.ToArray(), resultUL.ToArray());
}

[Fact]
public void ExpressionTests_Type_StructWithIntegerEquality()
{
// Arrange
var valuesL = new[] { new SnowflakeId(1L), new SnowflakeId(100), new SnowflakeId(5) }.AsQueryable();
var resultValuesL = new[] { new SnowflakeId(100) }.AsQueryable();

// Act
var resultL = valuesL.Where("it == 100");
var resultNL = valuesL.Where("it != 1 && it != 5");
var resultArg = valuesL.Where("it == @0", 100);
var resultIn = valuesL.Where("it in (100)");

// Assert
Assert.Equal(resultValuesL.ToArray(), resultL);
Assert.Equal(resultValuesL.ToArray(), resultNL);
Assert.Equal(resultValuesL.ToArray(), resultArg);
Assert.Equal(resultValuesL.ToArray(), resultIn);
}

[Fact]
public void ExpressionTests_Type_StructWithIntegerEquality_BothVariablesInStruct()
{
// Arrange
var valuesL = new[]
{
new
{
Id = new SnowflakeId(1L),
Var = 1
},
new
{
Id = new SnowflakeId(2L),
Var = 1
},
new
{
Id = new SnowflakeId(1L),
Var = 2
}
}.AsQueryable();

var resultValuesL = new[] {
new {
Id = new SnowflakeId(1L),
Var = 1
}
}.AsQueryable();

// Act
var resultL = valuesL.Where("it.Id == it.Var");

// Assert
Assert.Equal(resultValuesL.ToArray(), resultL);
}

[Fact]
public void ExpressionTests_Type_Integer_Qualifiers_Negative()
{
Expand Down
57 changes: 57 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/Helpers/Models/SnowflakeId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace System.Linq.Dynamic.Core.Tests.Helpers.Models
{
public struct SnowflakeId : IEquatable<SnowflakeId>
{
public bool Equals(SnowflakeId other)
{
return Value == other.Value;
}

public override bool Equals(object obj)
{
return obj is SnowflakeId other && Equals(other);
}

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

public static bool operator ==(SnowflakeId left, SnowflakeId right)
{
return left.Equals(right);
}

public static bool operator !=(SnowflakeId left, SnowflakeId right)
{
return !left.Equals(right);
}

public static bool operator ==(SnowflakeId left, int right)
{
return (int)left.Value == right;
}

public static bool operator !=(SnowflakeId left, int right)
{
return (int)left.Value != right;
}

public static bool operator ==(SnowflakeId left, ulong right)
{
return left.Value == right;
}

public static bool operator !=(SnowflakeId left, ulong right)
{
return left.Value != right;
}

public ulong Value { get; }

public SnowflakeId(ulong value)
{
Value = value;
}
}
}
3 changes: 3 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ public class User
{
public Guid Id { get; set; }

public SnowflakeId SnowflakeId { get; set; }

public string UserName { get; set; }

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