Skip to content

Commit 36a9212

Browse files
committed
more from #558 - BinaryExpression And with bool?
1 parent 0227bdf commit 36a9212

3 files changed

Lines changed: 134 additions & 8 deletions

File tree

‎src/FastExpressionCompiler.LightExpression/Expression.cs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,7 +1436,10 @@ public static BinaryExpression ExclusiveOr(Expression left, Expression right) =>
14361436
[RequiresUnreferencedCode(Trimming.Message)]
14371437
public static BinaryExpression GetLeftTypedBinary(ExpressionType nodeType, Expression left, Expression right, MethodInfo method = null) =>
14381438
method == null
1439-
? new LeftTypedBinaryExpression(nodeType, left, right)
1439+
? left.Type == right.Type ||
1440+
left.Type.IsNullable() && left.Type.GetNonNullableOrSelf() == right.Type
1441+
? new LeftTypedBinaryExpression(nodeType, left, right)
1442+
: throw new InvalidOperationException("Incompatible types for binary operation: left is " + left.Type + ", right is " + right.Type)
14401443
: GetMethodBasedBinaryOperator(nodeType, left, right, method, liftToNull: true);
14411444

14421445
/// <summary>Creates a BinaryExpression that represents a bitwise XOR operation.</summary>
@@ -1539,7 +1542,9 @@ public static BinaryExpression Power(Expression left, Expression right) =>
15391542

15401543
/// <summary>Creates a BinaryExpression that represents a bitwise AND operation.</summary>
15411544
public static BinaryExpression And(Expression left, Expression right) =>
1542-
new LogicalBinaryExpression(ExpressionType.And, left, right);
1545+
left.Type == typeof(bool?)
1546+
? new LeftTypedBinaryExpression(ExpressionType.And, left, right)
1547+
: new LogicalBinaryExpression(ExpressionType.And, left, right);
15431548

15441549
/// <summary>Creates a BinaryExpression that represents a bitwise AND operation.</summary>
15451550
[RequiresUnreferencedCode(Trimming.Message)]

‎src/FastExpressionCompiler/FastExpressionCompiler.cs‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2037,9 +2037,16 @@ public static bool TryEmit(Expression expr, ILGenerator il, ref CompilerContext
20372037
case ExpressionType.LeftShift:
20382038
case ExpressionType.RightShift:
20392039
{
2040+
var binary = (BinaryExpression)expr;
2041+
var binaryLeft = binary.Left;
2042+
var binaryRight = binary.Right;
2043+
if (nodeType == ExpressionType.And && binary.Method == null &&
2044+
exprType == typeof(bool?) && binaryLeft.Type == typeof(bool?) && binaryRight.Type == typeof(bool?))
2045+
return TryEmitNullableBooleanAnd(binaryLeft, binaryRight, il, ref context, parent);
2046+
20402047
return exprType.IsPrimitive
20412048
&& TryInterpretAndEmitResult(expr, il, parent, context.CompilerFlags)
2042-
|| TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, il, ref context, parent);
2049+
|| TryEmitArithmetic(binaryLeft, binaryRight, nodeType, exprType, il, ref context, parent);
20432050
}
20442051
// todo: @feature #472 add interpretation when those node types are supported
20452052
case ExpressionType.AddChecked:
@@ -5991,6 +5998,63 @@ private static bool TryEmitArithmetic(Expression left, Expression right, Express
59915998
return true;
59925999
}
59936000

6001+
private static bool TryEmitNullableBooleanAnd(Expression left, Expression right, ILGenerator il,
6002+
ref CompilerContext context, ParentFlags parent)
6003+
{
6004+
var flags = (parent
6005+
& ~(ParentFlags.IgnoreResult | ParentFlags.InstanceCall |
6006+
ParentFlags.LambdaCall | ParentFlags.ReturnByRef))
6007+
| ParentFlags.Arithmetic;
6008+
6009+
if (!TryEmit(left, il, ref context, flags))
6010+
return false;
6011+
var leftVar = EmitStoreLocalVariable(il, typeof(bool?));
6012+
6013+
if (!TryEmit(right, il, ref context, flags))
6014+
return false;
6015+
var rightVar = EmitStoreLocalVariable(il, typeof(bool?));
6016+
6017+
var falseLabel = il.DefineLabel();
6018+
var nullLabel = il.DefineLabel();
6019+
var doneLabel = il.DefineLabel();
6020+
6021+
EmitLoadLocalVariableAddress(il, leftVar);
6022+
EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod());
6023+
var leftHasValueLabel = il.DefineLabel();
6024+
il.Demit(OpCodes.Brtrue, leftHasValueLabel);
6025+
6026+
EmitLoadLocalVariableAddress(il, rightVar);
6027+
EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod());
6028+
il.Demit(OpCodes.Brfalse, nullLabel);
6029+
EmitLoadLocalVariableAddress(il, rightVar);
6030+
il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
6031+
il.Demit(OpCodes.Brtrue, nullLabel);
6032+
il.Demit(OpCodes.Br, falseLabel);
6033+
6034+
il.DmarkLabel(leftHasValueLabel);
6035+
EmitLoadLocalVariableAddress(il, leftVar);
6036+
il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
6037+
il.Demit(OpCodes.Brfalse, falseLabel);
6038+
EmitLoadLocalVariableAddress(il, rightVar);
6039+
EmitMethodCall(il, typeof(bool?).GetNullableHasValueGetterMethod());
6040+
il.Demit(OpCodes.Brfalse, nullLabel);
6041+
EmitLoadLocalVariableAddress(il, rightVar);
6042+
il.Demit(OpCodes.Ldfld, typeof(bool?).GetNullableValueUnsafeAkaGetValueOrDefaultMethod());
6043+
il.Demit(OpCodes.Newobj, typeof(bool?).GetNullableConstructor());
6044+
il.Demit(OpCodes.Br, doneLabel);
6045+
6046+
il.DmarkLabel(falseLabel);
6047+
il.Demit(OpCodes.Ldc_I4_0);
6048+
il.Demit(OpCodes.Newobj, typeof(bool?).GetNullableConstructor());
6049+
il.Demit(OpCodes.Br, doneLabel);
6050+
6051+
il.DmarkLabel(nullLabel);
6052+
EmitLoadLocalVariable(il, InitValueTypeVariable(il, typeof(bool?)));
6053+
6054+
il.DmarkLabel(doneLabel);
6055+
return il.EmitPopIfIgnoreResult(parent);
6056+
}
6057+
59946058
private static MethodInfo _stringStringConcatMethod, _stringObjectConcatMethod;
59956059
private static MethodInfo GetStringConcatMethod(Type paraType)
59966060
{

‎test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs‎

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,61 @@ public int Run()
6161
Flat_equal_nested_lambdas_with_captures_are_structurally_equal_and_hash_equal();
6262
Flat_standalone_parameters_use_name_in_structural_equality();
6363
Flat_structural_hash_supports_dictionary_lookup();
64+
Can_compile_nullable_boolean_and();
6465
Can_compile_shifts_with_nullable_shift_count();
6566

66-
return 45;
67+
return 46;
68+
}
69+
70+
private static int AndInts(int left, int right) => left & right;
71+
72+
public void Can_compile_nullable_boolean_and()
73+
{
74+
var left = Parameter(typeof(bool?), "left");
75+
var right = Parameter(typeof(bool?), "right");
76+
var and = And(left, right);
77+
Asserts.AreEqual(typeof(bool?), and.Type);
78+
79+
var lambda = Lambda<Func<bool?, bool?, bool?>>(and, left, right);
80+
var fast = lambda.CompileFast(true);
81+
Asserts.IsNotNull(fast);
82+
var sys = lambda.CompileSys();
83+
84+
void AssertResult(bool? leftValue, bool? rightValue, bool? expected)
85+
{
86+
var sysResult = sys(leftValue, rightValue);
87+
Asserts.AreEqual(expected, sysResult);
88+
Asserts.AreEqual(sysResult, fast(leftValue, rightValue));
89+
}
90+
91+
AssertResult(true, true, true);
92+
AssertResult(true, false, false);
93+
AssertResult(false, true, false);
94+
AssertResult(false, false, false);
95+
AssertResult(true, null, null);
96+
AssertResult(false, null, false);
97+
AssertResult(null, true, null);
98+
AssertResult(null, false, false);
99+
AssertResult(null, null, null);
100+
101+
var andWithNullMethod = And(left, right, (MethodInfo)null);
102+
Asserts.AreEqual(typeof(bool?), andWithNullMethod.Type);
103+
var nullMethodLambda = Lambda<Func<bool?, bool?, bool?>>(andWithNullMethod, left, right);
104+
var nullMethodFast = nullMethodLambda.CompileFast(true);
105+
Asserts.IsNotNull(nullMethodFast);
106+
var nullMethodSys = nullMethodLambda.CompileSys();
107+
Asserts.AreEqual(nullMethodSys(false, null), nullMethodFast(false, null));
108+
109+
var nonNullableLeft = Parameter(typeof(bool), "nonNullableLeft");
110+
Asserts.Throws<InvalidOperationException>(() =>
111+
And(nonNullableLeft, right, (MethodInfo)null));
112+
Asserts.Throws<InvalidOperationException>(() =>
113+
And(left, Parameter(typeof(int?), "unrelatedRight"), (MethodInfo)null));
114+
115+
var incompatibleMethod = typeof(LightExpressionTests).GetMethod(
116+
nameof(AndInts), BindingFlags.Static | BindingFlags.NonPublic);
117+
Asserts.IsNotNull(incompatibleMethod);
118+
Asserts.Throws<InvalidOperationException>(() => And(left, right, incompatibleMethod));
67119
}
68120

69121
public void Can_compile_shifts_with_nullable_shift_count()
@@ -72,10 +124,15 @@ public void Can_compile_shifts_with_nullable_shift_count()
72124

73125
var leftShift = LeftShift(Constant(16), shift);
74126
Asserts.AreEqual(typeof(int?), leftShift.Type);
75-
var leftShiftFunc = Lambda<Func<int?, int?>>(leftShift, shift).CompileFast(true);
76-
Asserts.IsNotNull(leftShiftFunc);
77-
Asserts.AreEqual(64, leftShiftFunc(2));
78-
Asserts.AreEqual(null, leftShiftFunc(null));
127+
var lambda = Lambda<Func<int?, int?>>(leftShift, shift);
128+
var fast = lambda.CompileFast(true);
129+
Asserts.IsNotNull(fast);
130+
var sys = lambda.CompileSys();
131+
132+
Asserts.AreEqual(64, fast(2));
133+
Asserts.AreEqual(64, sys(2));
134+
Asserts.AreEqual(null, fast(null));
135+
Asserts.AreEqual(null, sys(null));
79136
}
80137

81138
public void Can_compile_lambda_without_converting_to_expression()

0 commit comments

Comments
 (0)