Skip to content

Commit

Permalink
null propagation: Fix Throw expression bug
Browse files Browse the repository at this point in the history
Throw expression is not allowed for technical
reasons inside more complex expression, such as
`TimeSpan.FromHours(1 + (throw ...)).Milliseconds`.
However, calling a method which throws is fine
(CIL stack doesn't need to be spilled).
  • Loading branch information
exyi committed Mar 18, 2024
1 parent 75dcef2 commit 265b8fe
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 11 deletions.
Expand Up @@ -206,15 +206,11 @@ protected Expression UnwrapNullableType(Expression expression, Type expectedType
return expression;
else if (expression.Type == typeof(Nullable<>).MakeGenericType(expectedType))
{
var tmp = Expression.Parameter(expression.Type);
var nreCtor = typeof(NullReferenceException).GetConstructor(new [] { typeof(string) })!;
return Expression.Block(new [] { tmp },
Expression.Assign(tmp, expression),
Expression.Condition(
Expression.Property(tmp, "HasValue"),
Expression.Property(tmp, "Value"),
Expression.Throw(Expression.New(nreCtor, Expression.Constant($"Binding expression '{formattedExpression}' of type '{expectedType}' has evaluated to null.")), expectedType)
)
return Expression.Call(
ThrowHelpers.UnwrapNullableMethod.MakeGenericMethod(expectedType),
expression,
Expression.Constant(formattedExpression, typeof(string)),
Expression.Constant(expectedType.FullName, typeof(string))
);
}
else
Expand Down Expand Up @@ -283,5 +279,17 @@ public static Expression PropagateNulls(Expression expr, Func<Expression, bool>
var v = new ExpressionNullPropagationVisitor(canBeNull);
return v.Visit(expr);
}


internal class ThrowHelpers
{
public static readonly MethodInfo ThrowNullReferenceMethod = typeof(ThrowHelpers).GetMethod(nameof(ThrowNullReference))!;
public static T ThrowNullReference<T>(string expression, string type) =>
throw new NullReferenceException($"Binding expression '{expression}' of type '{type}' has evaluated to null.");

public static readonly MethodInfo UnwrapNullableMethod = typeof(ThrowHelpers).GetMethod(nameof(UnwrapNullable))!;
public static T UnwrapNullable<T>(T? value, string expression, string type) where T : struct =>
value ?? ThrowNullReference<T>(expression, type);
}
}
}
4 changes: 2 additions & 2 deletions src/Tests/Binding/NullPropagationTests.cs
Expand Up @@ -155,7 +155,7 @@ private void TestExpression(Random rnd, Expression expression, ParameterExpressi
Func<TestViewModel[], object> compile(Expression e) =>
Expression.Lambda<Func<TestViewModel[], object>>(Expression.Convert(e, typeof(object)), parameter)
// .CompileFast();
.Compile(preferInterpretation: true);
.Compile(preferInterpretation: false);

var withNullChecks = compile(exprWithChecks);
var withoutNullChecks = compile(expr);
Expand Down Expand Up @@ -224,7 +224,7 @@ private void TestExpression(Random rnd, Expression expression, ParameterExpressi
private object EvalExpression<T>(Expression<Func<T, object>> a, T val)
{
var nullChecked = ExpressionNullPropagationVisitor.PropagateNulls(a.Body, _ => true);
var d = a.Update(body: nullChecked, a.Parameters).Compile(preferInterpretation: true);
var d = a.Update(body: nullChecked, a.Parameters).Compile(preferInterpretation: false);
return d(val);
}

Expand Down

0 comments on commit 265b8fe

Please sign in to comment.