Skip to content
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
22 changes: 18 additions & 4 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,20 @@ public bool ExpressionQualifiesForNullPropagation(Expression expression)
{
return
expression is MemberExpression ||
expression is ParameterExpression ||
expression is ParameterExpression ||
expression is MethodCallExpression ||
expression is UnaryExpression;
}

public Expression GenerateDefaultExpression(Type type)
{
#if NET35
return Expression.Constant(Activator.CreateInstance(type));
#else
return Expression.Default(type);
#endif
}

private Expression GetMemberExpression(Expression expression)
{
if (ExpressionQualifiesForNullPropagation(expression))
Expand Down Expand Up @@ -330,11 +339,16 @@ private List<Expression> CollectExpressions(bool addSelf, Expression sourceExpre

var list = new List<Expression>();

if (addSelf && expression is MemberExpression memberExpressionFirst)
if (addSelf)
{
if (TypeHelper.IsNullableType(memberExpressionFirst.Type) || !memberExpressionFirst.Type.GetTypeInfo().IsValueType)
switch (expression)
{
list.Add(sourceExpression);
case MemberExpression _:
list.Add(sourceExpression);
break;

default:
break;
}
}

Expand Down
75 changes: 58 additions & 17 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ Expression ParseConditionalOperator()
_textParser.ValidateToken(TokenId.Colon, Res.ColonExpected);
_textParser.NextToken();
Expression expr2 = ParseConditionalOperator();
expr = GenerateConditional(expr, expr1, expr2, errorPos);
expr = GenerateConditional(expr, expr1, expr2, false, errorPos);
}
return expr;
}
Expand Down Expand Up @@ -1145,7 +1145,7 @@ Expression ParseFunctionIif()
throw ParseError(errorPos, Res.IifRequiresThreeArgs);
}

return GenerateConditional(args[0], args[1], args[2], errorPos);
return GenerateConditional(args[0], args[1], args[2], false, errorPos);
}

// np(...) function
Expand All @@ -1166,9 +1166,9 @@ Expression ParseFunctionNullPropagation()
bool hasDefaultParameter = args.Length == 2;
Expression expressionIfFalse = hasDefaultParameter ? args[1] : Expression.Constant(null);

if (_expressionHelper.TryGenerateAndAlsoNotNullExpression(args[0], hasDefaultParameter, out Expression generatedExpression))
if (_expressionHelper.TryGenerateAndAlsoNotNullExpression(args[0], true, out Expression generatedExpression))
{
return GenerateConditional(generatedExpression, args[0], expressionIfFalse, errorPos);
return GenerateConditional(generatedExpression, args[0], expressionIfFalse, true, errorPos);
}

return args[0];
Expand Down Expand Up @@ -1234,7 +1234,7 @@ Expression ParseFunctionCast()
return Expression.ConvertChecked(_it, resolvedType);
}

Expression GenerateConditional(Expression test, Expression expressionIfTrue, Expression expressionIfFalse, int errorPos)
Expression GenerateConditional(Expression test, Expression expressionIfTrue, Expression expressionIfFalse, bool nullPropagating, int errorPos)
{
if (test.Type != typeof(bool))
{
Expand All @@ -1244,30 +1244,71 @@ Expression GenerateConditional(Expression test, Expression expressionIfTrue, Exp
if (expressionIfTrue.Type != expressionIfFalse.Type)
{
// If expressionIfTrue is a null constant and expressionIfFalse is ValueType:
// - create nullable constant from expressionIfTrue with type from expressionIfFalse
// - convert expressionIfFalse to nullable (unless it's already nullable)
if (Constants.IsNull(expressionIfTrue) && expressionIfFalse.Type.GetTypeInfo().IsValueType)
{
Type nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type);
expressionIfTrue = Expression.Constant(null, nullableType);
if (!TypeHelper.IsNullableType(expressionIfFalse.Type))
if (nullPropagating && _parsingConfig.NullPropagatingUseDefaultValueForNonNullableValueTypes)
{
expressionIfFalse = Expression.Convert(expressionIfFalse, nullableType);
// If expressionIfFalse is a non-nullable type:
// generate default expression from the expressionIfFalse-type for expressionIfTrue
// Else
// create nullable constant from expressionIfTrue with type from expressionIfFalse

if (!TypeHelper.IsNullableType(expressionIfFalse.Type))
{
expressionIfTrue = _expressionHelper.GenerateDefaultExpression(expressionIfFalse.Type);
}
else
{
expressionIfTrue = Expression.Constant(null, expressionIfFalse.Type);
}
}
else
{
// - create nullable constant from expressionIfTrue with type from expressionIfFalse
// - convert expressionIfFalse to nullable (unless it's already nullable)

Type nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type);
expressionIfTrue = Expression.Constant(null, nullableType);

if (!TypeHelper.IsNullableType(expressionIfFalse.Type))
{
expressionIfFalse = Expression.Convert(expressionIfFalse, nullableType);
}
}

return Expression.Condition(test, expressionIfTrue, expressionIfFalse);
}

// If expressionIfFalse is a null constant and expressionIfTrue is a ValueType:
// - create nullable constant from expressionIfFalse with type from expressionIfTrue
// - convert expressionIfTrue to nullable (unless it's already nullable)
if (Constants.IsNull(expressionIfFalse) && expressionIfTrue.Type.GetTypeInfo().IsValueType)
{
Type nullableType = TypeHelper.ToNullableType(expressionIfTrue.Type);
expressionIfFalse = Expression.Constant(null, nullableType);
if (!TypeHelper.IsNullableType(expressionIfTrue.Type))
if (nullPropagating && _parsingConfig.NullPropagatingUseDefaultValueForNonNullableValueTypes)
{
expressionIfTrue = Expression.Convert(expressionIfTrue, nullableType);
// If expressionIfTrue is a non-nullable type:
// generate default expression from the expressionIfFalse-type for expressionIfFalse
// Else
// create nullable constant from expressionIfFalse with type from expressionIfTrue

if (!TypeHelper.IsNullableType(expressionIfTrue.Type))
{
expressionIfFalse = _expressionHelper.GenerateDefaultExpression(expressionIfTrue.Type);
}
else
{
expressionIfFalse = Expression.Constant(null, expressionIfTrue.Type);
}
}
else
{
// - create nullable constant from expressionIfFalse with type from expressionIfTrue
// - convert expressionIfTrue to nullable (unless it's already nullable)

Type nullableType = TypeHelper.ToNullableType(expressionIfTrue.Type);
expressionIfFalse = Expression.Constant(null, nullableType);
if (!TypeHelper.IsNullableType(expressionIfTrue.Type))
{
expressionIfTrue = Expression.Convert(expressionIfTrue, nullableType);
}
}

return Expression.Condition(test, expressionIfTrue, expressionIfFalse);
Expand Down
2 changes: 2 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ internal interface IExpressionHelper
bool MemberExpressionIsDynamic(Expression expression);

Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName);

Expression GenerateDefaultExpression(Type type);
}
}
7 changes: 7 additions & 0 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,12 @@ public IQueryableAnalyzer QueryableAnalyzer
/// Additional TypeConverters
/// </summary>
public IDictionary<Type, TypeConverter> TypeConverters { get; set; }

/// <summary>
/// When using the NullPropagating function np(...), use a "default value" for non-nullable value types instead of "null value".
///
/// Default value is false.
/// </summary>
public bool NullPropagatingUseDefaultValueForNonNullableValueTypes { get; set; } = false;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Linq.Dynamic.Core.Exceptions;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
Expand Down Expand Up @@ -327,9 +328,11 @@ public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQu
public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_ForNullableProperty_true(string propName, string valueString)
{
// Assign
var culture = CultureInfo.CreateSpecificCulture("en-US");
var config = new ParsingConfig
{
UseParameterizedNamesInDynamicQuery = true
UseParameterizedNamesInDynamicQuery = true,
NumberParseCulture = culture
};

// Act
Expand All @@ -348,7 +351,7 @@ public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQu
var propertyInfo = wrapperObj.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public);
object value = propertyInfo.GetValue(wrapperObj);

Check.That(value).IsEqualTo(Convert.ChangeType(valueString, Nullable.GetUnderlyingType(queriedPropType) ?? queriedPropType));
value.Should().Be(Convert.ChangeType(valueString, Nullable.GetUnderlyingType(queriedPropType) ?? queriedPropType, culture));
}

[Theory]
Expand Down Expand Up @@ -1070,7 +1073,7 @@ public void DynamicExpressionParser_ParseLambda_Operator_Less_Greater_With_Guids

// Assert
Assert.Equal(anotherId, result);
}
}

[Theory]
[InlineData("c => c.Age == 8", "c => (c.Age == 8)")]
Expand Down Expand Up @@ -1239,7 +1242,7 @@ public void DynamicExpressionParser_ParseLambda_String_TrimEnd_0_Parameters()

var @delegate = expression.Compile();

var result = (bool) @delegate.DynamicInvoke("This is a test ");
var result = (bool)@delegate.DynamicInvoke("This is a test ");

// Assert
result.Should().BeTrue();
Expand Down
Loading