Skip to content

Commit c8edded

Browse files
StefHneilbgr
andauthored
Fixed ExpressionParser when WrappedValue-string is used for equals-operator 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 <[email protected]>
1 parent 5cf8357 commit c8edded

8 files changed

+463
-19
lines changed

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ public void Wrap(ref Expression expression)
227227
}
228228
}
229229

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

242+
public bool TryUnwrapAsConstantExpression<TValue>(MemberExpression? expression, [NotNullWhen(true)] out ConstantExpression? value)
243+
{
244+
if (TryUnwrapAsValue<TValue>(expression, out var wrappedValue))
245+
{
246+
value = Expression.Constant(wrappedValue);
247+
return true;
248+
}
249+
250+
value = default;
251+
return false;
252+
}
253+
242254
private static MemberExpression Wrap<TValue>(TValue value)
243255
{
244256
var wrapper = new WrappedValue<TValue>(value);

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

+36-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,43 @@ public void WrapConstantExpression(ref Expression argument)
2626
}
2727
}
2828

29-
public bool TryUnwrapConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value)
29+
public bool TryUnwrapAsValue<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value)
3030
{
31-
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrap(expression as MemberExpression, out value))
31+
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrapAsValue(expression as MemberExpression, out value))
32+
{
33+
return true;
34+
}
35+
36+
value = default;
37+
return false;
38+
}
39+
40+
public bool TryUnwrapAsConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value)
41+
{
42+
if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrapAsConstantExpression<TValue>(expression as MemberExpression, out value))
43+
{
44+
return true;
45+
}
46+
47+
value = default;
48+
return false;
49+
}
50+
51+
public bool TryUnwrapAsConstantExpression(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value)
52+
{
53+
if (!_parsingConfig.UseParameterizedNamesInDynamicQuery || expression is not MemberExpression memberExpression)
54+
{
55+
value = default;
56+
return false;
57+
}
58+
59+
if
60+
(
61+
_constantExpressionWrapper.TryUnwrapAsConstantExpression<string>(memberExpression, out value) ||
62+
_constantExpressionWrapper.TryUnwrapAsConstantExpression<int>(memberExpression, out value) ||
63+
_constantExpressionWrapper.TryUnwrapAsConstantExpression<long>(memberExpression, out value) ||
64+
_constantExpressionWrapper.TryUnwrapAsConstantExpression<short>(memberExpression, out value)
65+
)
3266
{
3367
return true;
3468
}

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

+33-12
Original file line numberDiff line numberDiff line change
@@ -313,9 +313,16 @@ private Expression ParseIn()
313313
Expression right = ParseUnary();
314314

315315
// if the identifier is an Enum, try to convert the right-side also to an Enum.
316-
if (left.Type.GetTypeInfo().IsEnum && right is ConstantExpression constantExpression)
316+
if (left.Type.GetTypeInfo().IsEnum)
317317
{
318-
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpression);
318+
if (right is ConstantExpression constantExprRight)
319+
{
320+
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExprRight);
321+
}
322+
else if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight))
323+
{
324+
right = ParseEnumToConstantExpression(op.Pos, left.Type, unwrappedConstantExprRight);
325+
}
319326
}
320327

321328
// else, check for direct type match
@@ -476,13 +483,27 @@ private Expression ParseComparisonOperator()
476483
{
477484
left = e;
478485
}
479-
else if (TypeHelper.IsEnumType(left.Type) && (constantExpr = right as ConstantExpression) != null)
486+
else if (TypeHelper.IsEnumType(left.Type))
480487
{
481-
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpr);
488+
if (right is ConstantExpression constantExprRight)
489+
{
490+
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExprRight);
491+
}
492+
else if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight))
493+
{
494+
right = ParseEnumToConstantExpression(op.Pos, left.Type, unwrappedConstantExprRight);
495+
}
482496
}
483-
else if (TypeHelper.IsEnumType(right.Type) && (constantExpr = left as ConstantExpression) != null)
497+
else if (TypeHelper.IsEnumType(right.Type))
484498
{
485-
left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExpr);
499+
if (left is ConstantExpression constantExprLeft)
500+
{
501+
left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExprLeft);
502+
}
503+
else if (_expressionHelper.TryUnwrapAsConstantExpression(left, out var unwrappedConstantExprLeft))
504+
{
505+
left = ParseEnumToConstantExpression(op.Pos, right.Type, unwrappedConstantExprLeft);
506+
}
486507
}
487508
else
488509
{
@@ -498,11 +519,11 @@ private Expression ParseComparisonOperator()
498519
{
499520
left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type);
500521
}
501-
else if (_expressionHelper.TryUnwrapConstantExpression<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
522+
else if (_expressionHelper.TryUnwrapAsValue<string>(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null && typeConverter.CanConvertFrom(right.Type))
502523
{
503524
right = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueR), left.Type);
504525
}
505-
else if (_expressionHelper.TryUnwrapConstantExpression<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
526+
else if (_expressionHelper.TryUnwrapAsValue<string>(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null && typeConverter.CanConvertFrom(left.Type))
506527
{
507528
left = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueL), right.Type);
508529
}
@@ -581,7 +602,7 @@ private Expression ParseComparisonOperator()
581602
return left;
582603
}
583604

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

600-
private ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
621+
private static ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
601622
{
602623
return Expression.Constant(ParseConstantExpressionToEnum(pos, leftType, constantExpr), leftType);
603624
}
604625

605-
private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr)
626+
private static object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr)
606627
{
607628
try
608629
{
@@ -618,7 +639,7 @@ private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExp
618639

619640
try
620641
{
621-
return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value);
642+
return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value!);
622643
}
623644
catch
624645
{

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ internal interface IConstantExpressionWrapper
77
{
88
void Wrap(ref Expression expression);
99

10-
bool TryUnwrap<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value);
10+
bool TryUnwrapAsValue<TValue>(MemberExpression? expression, [NotNullWhen(true)] out TValue? value);
11+
12+
bool TryUnwrapAsConstantExpression<TValue>(MemberExpression? expression, [NotNullWhen(true)] out ConstantExpression? value);
1113
}

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ internal interface IExpressionHelper
3535

3636
void WrapConstantExpression(ref Expression argument);
3737

38-
bool TryUnwrapConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value);
38+
bool TryUnwrapAsValue<TValue>(Expression? expression, [NotNullWhen(true)] out TValue? value);
39+
40+
bool TryUnwrapAsConstantExpression<TValue>(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value);
41+
42+
bool TryUnwrapAsConstantExpression(Expression? expression, [NotNullWhen(true)] out ConstantExpression? value);
3943

4044
bool MemberExpressionIsDynamic(Expression expression);
4145

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

+73-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace System.Linq.Dynamic.Core.Parser;
1+
using System.Collections.Generic;
2+
3+
namespace System.Linq.Dynamic.Core.Parser;
24

35
internal class WrappedValue<TValue>
46
{
@@ -8,4 +10,74 @@ public WrappedValue(TValue value)
810
{
911
Value = value;
1012
}
13+
14+
public static bool operator ==(WrappedValue<TValue>? left, WrappedValue<TValue>? right)
15+
{
16+
if (ReferenceEquals(left, right))
17+
{
18+
return true;
19+
}
20+
21+
if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
22+
{
23+
return false;
24+
}
25+
26+
return EqualityComparer<TValue>.Default.Equals(left.Value, right.Value);
27+
}
28+
29+
public static bool operator !=(WrappedValue<TValue>? left, WrappedValue<TValue>? right)
30+
{
31+
return !(left == right);
32+
}
33+
34+
public static bool operator ==(WrappedValue<TValue>? left, TValue? right)
35+
{
36+
if (ReferenceEquals(left, null))
37+
{
38+
return false;
39+
}
40+
41+
return EqualityComparer<TValue>.Default.Equals(left.Value, right);
42+
}
43+
44+
public static bool operator !=(WrappedValue<TValue>? left, TValue? right)
45+
{
46+
return !(left == right);
47+
}
48+
49+
public static bool operator ==(TValue? left, WrappedValue<TValue>? right)
50+
{
51+
if (ReferenceEquals(right, null))
52+
{
53+
return false;
54+
}
55+
56+
return EqualityComparer<TValue>.Default.Equals(left, right.Value);
57+
}
58+
59+
public static bool operator !=(TValue? left, WrappedValue<TValue>? right)
60+
{
61+
return !(left == right);
62+
}
63+
64+
public override bool Equals(object? obj)
65+
{
66+
if (ReferenceEquals(this, obj))
67+
{
68+
return true;
69+
}
70+
71+
if (obj is not WrappedValue<TValue> other)
72+
{
73+
return false;
74+
}
75+
76+
return EqualityComparer<TValue>.Default.Equals(Value, other.Value);
77+
}
78+
79+
public override int GetHashCode()
80+
{
81+
return Value?.GetHashCode() ?? 0;
82+
}
1183
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.Linq.Dynamic.Core.Parser;
2+
using FluentAssertions;
3+
using Xunit;
4+
5+
namespace System.Linq.Dynamic.Core.Tests.Parser;
6+
7+
public class WrappedValueTests
8+
{
9+
[Fact]
10+
public void WrappedValue_OfTypeString_OperatorEquals_String()
11+
{
12+
// Arrange
13+
var wrapped = new WrappedValue<string>("str");
14+
15+
// Act
16+
var result1A = wrapped == "str";
17+
var result1B = "str" == wrapped;
18+
var result2A = wrapped == "x";
19+
var result2B = "x" == wrapped;
20+
21+
// Assert
22+
result1A.Should().BeTrue();
23+
result1B.Should().BeTrue();
24+
result2A.Should().BeFalse();
25+
result2B.Should().BeFalse();
26+
}
27+
28+
[Fact]
29+
public void WrappedValue_OfTypeString_OperatorNotEquals_String()
30+
{
31+
// Arrange
32+
var wrapped = new WrappedValue<string>("str");
33+
34+
// Act
35+
var result1A = wrapped != "str";
36+
var result1B = "str" != wrapped;
37+
var result2A = wrapped == "x";
38+
var result2B = "x" == wrapped;
39+
40+
// Assert
41+
result1A.Should().BeFalse();
42+
result1B.Should().BeFalse();
43+
result2A.Should().BeFalse();
44+
result2B.Should().BeFalse();
45+
}
46+
47+
[Fact]
48+
public void WrappedValue_OfTypeString_OperatorEquals_WrappedValue_OfTypeString()
49+
{
50+
// Arrange
51+
var wrapped = new WrappedValue<string>("str");
52+
var testEqual = new WrappedValue<string>("str");
53+
var testNotEqual = new WrappedValue<string>("x");
54+
55+
// Act
56+
var result1A = wrapped == testEqual;
57+
var result1B = testEqual == wrapped;
58+
var result2A = wrapped == testNotEqual;
59+
var result2B = testNotEqual == wrapped;
60+
61+
// Assert
62+
result1A.Should().BeTrue();
63+
result1B.Should().BeTrue();
64+
result2A.Should().BeFalse();
65+
result2B.Should().BeFalse();
66+
}
67+
68+
[Fact]
69+
public void WrappedValue_OfTypeString_OperatorNotEquals_WrappedValue_OfTypeString()
70+
{
71+
// Arrange
72+
var wrapped = new WrappedValue<string>("str");
73+
var testEqual = new WrappedValue<string>("str");
74+
var testNotEqual = new WrappedValue<string>("x");
75+
76+
// Act
77+
var result1A = wrapped != testEqual;
78+
var result1B = testEqual != wrapped;
79+
var result2A = wrapped != testNotEqual;
80+
var result2B = testNotEqual != wrapped;
81+
82+
// Assert
83+
result1A.Should().BeFalse();
84+
result1B.Should().BeFalse();
85+
result2A.Should().BeTrue();
86+
result2B.Should().BeTrue();
87+
}
88+
}

0 commit comments

Comments
 (0)