Skip to content

Commit

Permalink
Fixed ExpressionParser when WrappedValue-string is used for equals-op…
Browse files Browse the repository at this point in the history
…erator for Enum (#672)

* Fixed ExpressionParser when WrappedValue-string is used for equals-operator to compare Enum

* .

* .

* Partial fixed ExpressionParser when WrappedValue-string is used for in-operator and left operand is enum (#668)

* Fix

* .

---------

Co-authored-by: neilbgr <neilbgr@laposte.net>
  • Loading branch information
StefH and neilbgr committed Feb 14, 2023
1 parent 5cf8357 commit c8edded
Show file tree
Hide file tree
Showing 8 changed files with 463 additions and 19 deletions.
14 changes: 13 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ public void Wrap(ref Expression expression)
}
}

public bool TryUnwrap<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value)
public bool TryUnwrapAsValue<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value)
{
if (expression?.Expression is ConstantExpression { Value: WrappedValue<TValue> wrapper })
{
Expand All @@ -239,6 +239,18 @@ public bool TryUnwrap<TValue>(MemberExpression? expression, [NotNullWhen(true)]
return false;
}

public bool TryUnwrapAsConstantExpression<TValue>(MemberExpression? expression, [NotNullWhen(true)] out ConstantExpression? value)
{
if (TryUnwrapAsValue<TValue>(expression, out var wrappedValue))
{
value = Expression.Constant(wrappedValue);
return true;
}

value = default;
return false;
}

private static MemberExpression Wrap<TValue>(TValue value)
{
var wrapper = new WrappedValue<TValue>(value);
Expand Down
38 changes: 36 additions & 2 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,43 @@ public void WrapConstantExpression(ref Expression argument)
}
}

public bool TryUnwrapConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value)
public bool TryUnwrapAsValue<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value)
{
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrap(expression as MemberExpression, out value))
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrapAsValue(expression as MemberExpression, out value))
{
return true;
}

value = default;
return false;
}

public bool TryUnwrapAsConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value)
{
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrapAsConstantExpression<TValue>(expression as MemberExpression, out value))
{
return true;
}

value = default;
return false;
}

public bool TryUnwrapAsConstantExpression(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value)
{
if (!_parsingConfig.UseParameterizedNamesInDynamicQuery || expression is not MemberExpression memberExpression)
{
value = default;
return false;
}

if
(
_constantExpressionWrapper.TryUnwrapAsConstantExpression<string>(memberExpression, out value) ||
_constantExpressionWrapper.TryUnwrapAsConstantExpression<int>(memberExpression, out value) ||
_constantExpressionWrapper.TryUnwrapAsConstantExpression<long>(memberExpression, out value) ||
_constantExpressionWrapper.TryUnwrapAsConstantExpression<short>(memberExpression, out value)
)
{
return true;
}
Expand Down
45 changes: 33 additions & 12 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,16 @@ private Expression ParseIn()
Expression right = ParseUnary();

// if the identifier is an Enum, try to convert the right-side also to an Enum.
if (left.Type.GetTypeInfo().IsEnum && right is ConstantExpression constantExpression)
if (left.Type.GetTypeInfo().IsEnum)
{
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpression);
if (right is ConstantExpression constantExprRight)
{
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExprRight);
}
else if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight))
{
right = ParseEnumToConstantExpression(op.Pos, left.Type, unwrappedConstantExprRight);
}
}

// else, check for direct type match
Expand Down Expand Up @@ -476,13 +483,27 @@ private Expression ParseComparisonOperator()
{
left = e;
}
else if (TypeHelper.IsEnumType(left.Type) && (constantExpr = right as ConstantExpression) != null)
else if (TypeHelper.IsEnumType(left.Type))
{
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpr);
if (right is ConstantExpression constantExprRight)
{
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExprRight);
}
else if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight))
{
right = ParseEnumToConstantExpression(op.Pos, left.Type, unwrappedConstantExprRight);
}
}
else if (TypeHelper.IsEnumType(right.Type) && (constantExpr = left as ConstantExpression) != null)
else if (TypeHelper.IsEnumType(right.Type))
{
left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExpr);
if (left is ConstantExpression constantExprLeft)
{
left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExprLeft);
}
else if (_expressionHelper.TryUnwrapAsConstantExpression(left, out var unwrappedConstantExprLeft))
{
left = ParseEnumToConstantExpression(op.Pos, right.Type, unwrappedConstantExprLeft);
}
}
else
{
Expand All @@ -498,11 +519,11 @@ private Expression ParseComparisonOperator()
{
left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type);
}
else if (_expressionHelper.TryUnwrapConstantExpression<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
else if (_expressionHelper.TryUnwrapAsValue<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
{
right = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueR), left.Type);
}
else if (_expressionHelper.TryUnwrapConstantExpression<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
else if (_expressionHelper.TryUnwrapAsValue<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
{
left = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueL), right.Type);
}
Expand Down Expand Up @@ -581,7 +602,7 @@ private Expression ParseComparisonOperator()
return left;
}

private bool HasImplicitConversion(Type baseType, Type targetType)
private static bool HasImplicitConversion(Type baseType, Type targetType)
{
var baseTypeHasConversion = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
Expand All @@ -597,12 +618,12 @@ private bool HasImplicitConversion(Type baseType, Type targetType)
.Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);
}

private ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
private static ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
{
return Expression.Constant(ParseConstantExpressionToEnum(pos, leftType, constantExpr), leftType);
}

private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr)
private static object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr)
{
try
{
Expand All @@ -618,7 +639,7 @@ private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExp

try
{
return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value);
return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value!);
}
catch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ internal interface IConstantExpressionWrapper
{
void Wrap(ref Expression expression);

bool TryUnwrap<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value);
bool TryUnwrapAsValue<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value);

bool TryUnwrapAsConstantExpression<TValue>(MemberExpression? expression, [NotNullWhen(true)] out ConstantExpression? value);
}
6 changes: 5 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ internal interface IExpressionHelper

void WrapConstantExpression(ref Expression argument);

bool TryUnwrapConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value);
bool TryUnwrapAsValue<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value);

bool TryUnwrapAsConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value);

bool TryUnwrapAsConstantExpression(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value);

bool MemberExpressionIsDynamic(Expression expression);

Expand Down
74 changes: 73 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/WrappedValue.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace System.Linq.Dynamic.Core.Parser;
using System.Collections.Generic;

namespace System.Linq.Dynamic.Core.Parser;

internal class WrappedValue<TValue>
{
Expand All @@ -8,4 +10,74 @@ public WrappedValue(TValue value)
{
Value = value;
}

public static bool operator ==(WrappedValue<TValue>? left, WrappedValue<TValue>? right)
{
if (ReferenceEquals(left, right))
{
return true;
}

if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
{
return false;
}

return EqualityComparer<TValue>.Default.Equals(left.Value, right.Value);
}

public static bool operator !=(WrappedValue<TValue>? left, WrappedValue<TValue>? right)
{
return !(left == right);
}

public static bool operator ==(WrappedValue<TValue>? left, TValue? right)
{
if (ReferenceEquals(left, null))
{
return false;
}

return EqualityComparer<TValue>.Default.Equals(left.Value, right);
}

public static bool operator !=(WrappedValue<TValue>? left, TValue? right)
{
return !(left == right);
}

public static bool operator ==(TValue? left, WrappedValue<TValue>? right)
{
if (ReferenceEquals(right, null))
{
return false;
}

return EqualityComparer<TValue>.Default.Equals(left, right.Value);
}

public static bool operator !=(TValue? left, WrappedValue<TValue>? right)
{
return !(left == right);
}

public override bool Equals(object? obj)
{
if (ReferenceEquals(this, obj))
{
return true;
}

if (obj is not WrappedValue<TValue> other)
{
return false;
}

return EqualityComparer<TValue>.Default.Equals(Value, other.Value);
}

public override int GetHashCode()
{
return Value?.GetHashCode() ?? 0;
}
}
88 changes: 88 additions & 0 deletions test/System.Linq.Dynamic.Core.Tests/Parser/WrappedValueTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Linq.Dynamic.Core.Parser;
using FluentAssertions;
using Xunit;

namespace System.Linq.Dynamic.Core.Tests.Parser;

public class WrappedValueTests
{
[Fact]
public void WrappedValue_OfTypeString_OperatorEquals_String()
{
// Arrange
var wrapped = new WrappedValue<string>("str");

// Act
var result1A = wrapped == "str";
var result1B = "str" == wrapped;
var result2A = wrapped == "x";
var result2B = "x" == wrapped;

// Assert
result1A.Should().BeTrue();
result1B.Should().BeTrue();
result2A.Should().BeFalse();
result2B.Should().BeFalse();
}

[Fact]
public void WrappedValue_OfTypeString_OperatorNotEquals_String()
{
// Arrange
var wrapped = new WrappedValue<string>("str");

// Act
var result1A = wrapped != "str";
var result1B = "str" != wrapped;
var result2A = wrapped == "x";
var result2B = "x" == wrapped;

// Assert
result1A.Should().BeFalse();
result1B.Should().BeFalse();
result2A.Should().BeFalse();
result2B.Should().BeFalse();
}

[Fact]
public void WrappedValue_OfTypeString_OperatorEquals_WrappedValue_OfTypeString()
{
// Arrange
var wrapped = new WrappedValue<string>("str");
var testEqual = new WrappedValue<string>("str");
var testNotEqual = new WrappedValue<string>("x");

// Act
var result1A = wrapped == testEqual;
var result1B = testEqual == wrapped;
var result2A = wrapped == testNotEqual;
var result2B = testNotEqual == wrapped;

// Assert
result1A.Should().BeTrue();
result1B.Should().BeTrue();
result2A.Should().BeFalse();
result2B.Should().BeFalse();
}

[Fact]
public void WrappedValue_OfTypeString_OperatorNotEquals_WrappedValue_OfTypeString()
{
// Arrange
var wrapped = new WrappedValue<string>("str");
var testEqual = new WrappedValue<string>("str");
var testNotEqual = new WrappedValue<string>("x");

// Act
var result1A = wrapped != testEqual;
var result1B = testEqual != wrapped;
var result2A = wrapped != testNotEqual;
var result2B = testNotEqual != wrapped;

// Assert
result1A.Should().BeFalse();
result1B.Should().BeFalse();
result2A.Should().BeTrue();
result2B.Should().BeTrue();
}
}

0 comments on commit c8edded

Please sign in to comment.