diff --git a/src/FastExpressionCompiler.LightExpression/Expression.cs b/src/FastExpressionCompiler.LightExpression/Expression.cs index 94f993c8..819aafde 100644 --- a/src/FastExpressionCompiler.LightExpression/Expression.cs +++ b/src/FastExpressionCompiler.LightExpression/Expression.cs @@ -50,6 +50,9 @@ public abstract class Expression /// Converts to Expression and outputs its as string public override string ToString() => ToExpression().ToString(); + /// Reduces the Expression to simple ones + public virtual Expression Reduce() => this; + public static ParameterExpression Parameter(Type type, string name = null) => new ParameterExpression(type.IsByRef ? type.GetElementType() : type, name, type.IsByRef); @@ -159,12 +162,27 @@ public static LambdaExpression Lambda(Expression body, params ParameterExpressio public static LambdaExpression Lambda(Type delegateType, Expression body, params ParameterExpression[] parameters) => new LambdaExpression(delegateType, body, parameters); + public static UnaryExpression Not(Expression operand) => + new UnaryExpression(ExpressionType.Not, operand, operand.Type); + + public static TypeBinaryExpression TypeAs(Expression operand, Type type) => + new TypeBinaryExpression(ExpressionType.TypeAs, operand, type); + + public static UnaryExpression TypeIs(Expression operand, Type type) => + new UnaryExpression(ExpressionType.TypeIs, operand, type); + public static UnaryExpression Convert(Expression operand, Type targetType) => new UnaryExpression(ExpressionType.Convert, operand, targetType); public static UnaryExpression Convert(Expression operand, Type targetType, MethodInfo method) => new UnaryExpression(ExpressionType.Convert, operand, targetType, method); + public static UnaryExpression ConvertChecked(Expression operand, Type targetType) => + new UnaryExpression(ExpressionType.ConvertChecked, operand, targetType); + + public static UnaryExpression ConvertChecked(Expression operand, Type targetType, MethodInfo method) => + new UnaryExpression(ExpressionType.ConvertChecked, operand, targetType, method); + public static UnaryExpression PreIncrementAssign(Expression operand) => new UnaryExpression(ExpressionType.PreIncrementAssign, operand, operand.Type); @@ -568,6 +586,26 @@ protected BinaryExpression(ExpressionType nodeType, Expression left, Expression } } + public class TypeBinaryExpression : Expression + { + public override ExpressionType NodeType { get; } + public override Type Type { get; } + + public Type TypeOperand { get; } + + public readonly Expression Expression; + + public override SysExpr ToExpression() => SysExpr.TypeIs(Expression.ToExpression(), TypeOperand); + + internal TypeBinaryExpression(ExpressionType nodeType, Expression expression, Type typeOperand) + { + NodeType = nodeType; + Expression = expression; + Type = typeof(bool); + TypeOperand = typeOperand; + } + } + public sealed class SimpleBinaryExpression : BinaryExpression { public override SysExpr ToExpression() diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index 406fa0fa..4c3ad170 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -347,6 +347,8 @@ private struct ClosureInfo public bool HasClosure => ClosureType != null; + public bool LastEmitIsAddress; + // Constant expressions to find an index (by reference) of constant expression from compiled expression. public ConstantExpression[] Constants; @@ -358,6 +360,8 @@ private struct ClosureInfo public NestedLambdaInfo[] NestedLambdas; public LambdaExpression[] NestedLambdaExprs; + public Dictionary ReducedExpressions; + public int ClosedItemCount => Constants.Length + NonPassedParameters.Length + NestedLambdas.Length; // FieldInfos are needed to load field of closure object on stack in emitter. @@ -384,6 +388,8 @@ public ClosureInfo(bool isConstructed, object closure = null, CurrentBlock = BlockInfo.Empty; Labels = null; LabelCount = 0; + LastEmitIsAddress = false; + ReducedExpressions = null; if (closure == null) { @@ -878,7 +884,7 @@ internal static Action private static bool IsClosureBoundConstant(object value, TypeInfo type) => value is Delegate || - !type.IsPrimitive && !type.IsEnum && !(value is string) && !(value is Type); + !type.IsPrimitive && !type.IsEnum && !(value is string) && !(value is Type) && !(value is decimal); // @paramExprs is required for nested lambda compilation private static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression expr, IReadOnlyList paramExprs) @@ -1006,6 +1012,14 @@ private static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression return false; return true; + case ExpressionType.Extension: + var reducedExpr = expr.Reduce(); + if (closure.ReducedExpressions == null) + closure.ReducedExpressions = new Dictionary(); + closure.ReducedExpressions.Add(expr, reducedExpr); + expr = reducedExpr; + continue; + case ExpressionType.Default: return true; @@ -1024,6 +1038,12 @@ private static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression continue; } + if (expr is TypeBinaryExpression typeBinaryExpr) + { + expr = typeBinaryExpr.Expression; + continue; + } + return false; } } @@ -1129,9 +1149,10 @@ internal enum ParentFlags Empty = 0, IgnoreResult = 1 << 1, Call = 1 << 2, - MemberAccess = 1 << 3, + MemberAccess = 1 << 3, // Any Parent Expression is a MemberExpression Arithmetic = 1 << 4, - Coalesce = 1 << 5 + Coalesce = 1 << 5, + InstanceAccess = 1 << 6 } internal static bool ShouldIgnoreResult(ParentFlags parent) => (parent & ParentFlags.IgnoreResult) != 0; @@ -1159,14 +1180,22 @@ public static bool TryEmit(Expression expr, IReadOnlyList p { while (true) { + closure.LastEmitIsAddress = false; + switch (expr.NodeType) { case ExpressionType.Parameter: return ShouldIgnoreResult(parent) || TryEmitParameter((ParameterExpression)expr, paramExprs, il, ref closure, parent, byRefIndex); - + case ExpressionType.TypeAs: + return TryEmitTypeAs((UnaryExpression)expr, paramExprs, il, ref closure, parent); + case ExpressionType.TypeIs: + return TryEmitTypeIs((TypeBinaryExpression)expr, paramExprs, il, ref closure, parent); + case ExpressionType.Not: + return TryEmitNot((UnaryExpression)expr, paramExprs, il, ref closure, parent); case ExpressionType.Convert: + case ExpressionType.ConvertChecked: return TryEmitConvert((UnaryExpression)expr, paramExprs, il, ref closure, parent); case ExpressionType.ArrayIndex: @@ -1176,8 +1205,9 @@ public static bool TryEmit(Expression expr, IReadOnlyList p TryEmitArrayIndex(expr.Type, il); case ExpressionType.Constant: + var constantExpression = (ConstantExpression)expr; return ShouldIgnoreResult(parent) || - TryEmitConstant((ConstantExpression)expr, il, ref closure); + TryEmitConstant(constantExpression, constantExpression.Type, constantExpression.Value, il, ref closure); case ExpressionType.Call: return TryEmitMethodCall((MethodCallExpression)expr, paramExprs, il, ref closure, parent); @@ -1189,8 +1219,11 @@ public static bool TryEmit(Expression expr, IReadOnlyList p var newExpr = (NewExpression)expr; var argExprs = newExpr.Arguments; for (var i = 0; i < argExprs.Count; i++) - if (!TryEmit(argExprs[i], paramExprs, il, ref closure, parent, i)) + { + var idx = argExprs[i].Type.IsByRef ? i : -1; + if (!TryEmit(argExprs[i], paramExprs, il, ref closure, parent, idx)) return false; + } return TryEmitNew(newExpr.Constructor, newExpr.Type, il); case ExpressionType.NewArrayBounds: @@ -1233,9 +1266,9 @@ public static bool TryEmit(Expression expr, IReadOnlyList p var arithmeticExpr = (BinaryExpression)expr; return TryEmit(arithmeticExpr.Left, paramExprs, il, ref closure, - parent | ParentFlags.Arithmetic) && + (parent | ParentFlags.Arithmetic) & ~ParentFlags.InstanceAccess) && TryEmit(arithmeticExpr.Right, paramExprs, il, ref closure, - parent | ParentFlags.Arithmetic) && + (parent | ParentFlags.Arithmetic) & ~ParentFlags.InstanceAccess) && TryEmitArithmeticOperation(expr.NodeType, expr.Type, il); case ExpressionType.AndAlso: @@ -1306,8 +1339,11 @@ when Tools.GetArithmeticFromArithmeticAssignOrSelf(arithmeticAssign) != arithmet var indexArgExprs = indexExpr.Arguments; for (var i = 0; i < indexArgExprs.Count; i++) - if (!TryEmit(indexArgExprs[i], paramExprs, il, ref closure, parent, i)) + { + var idx = indexArgExprs[i].Type.IsByRef ? i : -1; + if (!TryEmit(indexArgExprs[i], paramExprs, il, ref closure, parent, idx)) return false; + } return TryEmitIndex((IndexExpression)expr, il); @@ -1320,6 +1356,10 @@ when Tools.GetArithmeticFromArithmeticAssignOrSelf(arithmeticAssign) != arithmet case ExpressionType.Switch: return TryEmitSwitch((SwitchExpression)expr, paramExprs, il, ref closure, parent); + case ExpressionType.Extension: + expr = closure.ReducedExpressions[expr]; + continue; + default: return false; } @@ -1583,7 +1623,8 @@ private static bool TryEmitParameter(ParameterExpression paramExpr, var asAddress = paramType.IsValueType() && !paramExpr.IsByRef && - (parent & (ParentFlags.Call | ParentFlags.MemberAccess)) != 0; + ((parent & (ParentFlags.Call | ParentFlags.InstanceAccess)) == (ParentFlags.Call | ParentFlags.InstanceAccess) || + (parent & ParentFlags.MemberAccess) != 0); EmitLoadParamArg(il, paramIndex, asAddress); @@ -1694,6 +1735,53 @@ private static void EmitLoadParamArg(ILGenerator il, int paramIndex, bool asAddr } } + private static bool TryEmitTypeAs(UnaryExpression expr, + IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + ParentFlags parent) + { + if (!TryEmit(expr.Operand, paramExprs, il, ref closure, parent)) + return false; + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + else + { + il.Emit(OpCodes.Isinst, expr.Type); + } + return true; + } + private static bool TryEmitTypeIs(TypeBinaryExpression expr, + IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + ParentFlags parent) + { + if (!TryEmit(expr.Expression, paramExprs, il, ref closure, parent)) + return false; + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + else + { + il.Emit(OpCodes.Isinst, expr.TypeOperand); + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Cgt_Un); + } + return true; + } + + private static bool TryEmitNot(UnaryExpression expr, + IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, + ParentFlags parent) + { + if (!TryEmit(expr.Operand, paramExprs, il, ref closure, parent)) + return false; + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + else + { + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ceq); + } + return true; + } + private static bool TryEmitConvert(UnaryExpression expr, IReadOnlyList paramExprs, ILGenerator il, ref ClosureInfo closure, ParentFlags parent) { @@ -1701,7 +1789,7 @@ private static bool TryEmitConvert(UnaryExpression expr, var opExpr = expr.Operand; var method = expr.Method; if (method != null && method.Name != "op_Implicit" && method.Name != "op_Explicit") - return TryEmit(opExpr, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult | ParentFlags.Call, 0) + return TryEmit(opExpr, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult | ParentFlags.Call | ParentFlags.InstanceAccess, 0) && EmitMethodCall(il, method, parent); if (!TryEmit(opExpr, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult)) @@ -1739,34 +1827,45 @@ private static bool TryEmitConvert(UnaryExpression expr, // Conversion to Nullable: new Nullable(T val); else if (targetType.IsNullable()) + { + if (sourceType.IsNullable()) + { + var labelFalse = il.DefineLabel(); + var labelDone = il.DefineLabel(); + var loc = il.DeclareLocal(sourceType); + var locT = il.DeclareLocal(targetType); + il.Emit(OpCodes.Stloc_S, loc); + il.Emit(OpCodes.Ldloca_S, loc); + if (!EmitMethodCall(il, sourceType.FindNullableHasValueGetterMethod())) + return false; + il.Emit(OpCodes.Brfalse, labelFalse); + il.Emit(OpCodes.Ldloca_S, loc); + if (!EmitMethodCall(il, sourceType.FindNullableValueOrDefaultMethod())) + return false; + TryEmitValueConvert(Nullable.GetUnderlyingType(targetType), il, + expr.NodeType == ExpressionType.ConvertChecked); + il.Emit(OpCodes.Newobj, targetType.FindConstructor(targetTypeInfo.GenericTypeArguments[0])); + il.Emit(OpCodes.Stloc_S, locT); + il.Emit(OpCodes.Br_S, labelDone); + il.MarkLabel(labelFalse); + il.Emit(OpCodes.Ldloca_S, locT); + il.Emit(OpCodes.Initobj, targetType); + il.MarkLabel(labelDone); + il.Emit(OpCodes.Ldloc_S, locT); + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + return true; + } + il.Emit(OpCodes.Newobj, targetType.FindConstructor(targetTypeInfo.GenericTypeArguments[0])); + } else { if (targetType.GetTypeInfo().IsEnum) targetType = Enum.GetUnderlyingType(targetType); - if (targetType == typeof(int)) - il.Emit(OpCodes.Conv_I4); - else if (targetType == typeof(float)) - il.Emit(OpCodes.Conv_R4); - else if (targetType == typeof(uint)) - il.Emit(OpCodes.Conv_U4); - else if (targetType == typeof(sbyte)) - il.Emit(OpCodes.Conv_I1); - else if (targetType == typeof(byte)) - il.Emit(OpCodes.Conv_U1); - else if (targetType == typeof(short)) - il.Emit(OpCodes.Conv_I2); - else if (targetType == typeof(ushort)) - il.Emit(OpCodes.Conv_U2); - else if (targetType == typeof(long)) - il.Emit(OpCodes.Conv_I8); - else if (targetType == typeof(ulong)) - il.Emit(OpCodes.Conv_U8); - else if (targetType == typeof(double)) - il.Emit(OpCodes.Conv_R8); - - else // cast as the last resort and let's it fail if unlucky + // cast as the last resort and let's it fail if unlucky + if (!TryEmitValueConvert(targetType, il, expr.NodeType == ExpressionType.ConvertChecked)) il.Emit(OpCodes.Castclass, targetType); } @@ -1774,16 +1873,41 @@ private static bool TryEmitConvert(UnaryExpression expr, return true; } + private static bool TryEmitValueConvert(Type targetType, ILGenerator il, bool @checked) + { + if (targetType == typeof(int)) + il.Emit(@checked ? OpCodes.Conv_Ovf_I4 : OpCodes.Conv_I4); + else if (targetType == typeof(float)) + il.Emit(OpCodes.Conv_R4); + else if (targetType == typeof(uint)) + il.Emit(@checked ? OpCodes.Conv_Ovf_U4 : OpCodes.Conv_U4); + else if (targetType == typeof(sbyte)) + il.Emit(@checked ? OpCodes.Conv_Ovf_I1 : OpCodes.Conv_I1); + else if (targetType == typeof(byte)) + il.Emit(@checked ? OpCodes.Conv_Ovf_U1 : OpCodes.Conv_U1); + else if (targetType == typeof(short)) + il.Emit(@checked ? OpCodes.Conv_Ovf_I2 : OpCodes.Conv_I2); + else if (targetType == typeof(ushort) || targetType == typeof(char)) + il.Emit(@checked ? OpCodes.Conv_Ovf_U2 : OpCodes.Conv_U2); + else if (targetType == typeof(long)) + il.Emit(@checked ? OpCodes.Conv_Ovf_I8 : OpCodes.Conv_I8); + else if (targetType == typeof(ulong)) + il.Emit(@checked ? OpCodes.Conv_Ovf_U8 : OpCodes.Conv_U8); + else if (targetType == typeof(double)) + il.Emit(OpCodes.Conv_R8); + else + return false; + return true; + } + private static MethodInfo FirstConvertOperatorOrDefault(TypeInfo typeInfo, Type targetType, Type sourceType) => typeInfo.DeclaredMethods.GetFirst(m => m.IsStatic && m.ReturnType == targetType && (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.GetParameters()[0].ParameterType == sourceType); - private static bool TryEmitConstant(ConstantExpression expr, ILGenerator il, ref ClosureInfo closure) + private static bool TryEmitConstant(ConstantExpression expr, Type exprType, object constantValue, ILGenerator il, ref ClosureInfo closure) { - var exprType = expr.Type; - var constantValue = expr.Value; if (constantValue == null) { if (exprType.IsValueType()) // handles the conversion of null to Nullable @@ -1794,7 +1918,7 @@ private static bool TryEmitConstant(ConstantExpression expr, ILGenerator il, ref } var constantType = constantValue.GetType(); - if (IsClosureBoundConstant(constantValue, constantType.GetTypeInfo())) + if (expr != null && IsClosureBoundConstant(constantValue, constantType.GetTypeInfo())) { var constIndex = closure.Constants.GetFirstIndex(expr); if (constIndex == -1 || !LoadClosureFieldOrItem(ref closure, il, constIndex, exprType)) @@ -1880,6 +2004,45 @@ private static bool TryEmitConstant(ConstantExpression expr, ILGenerator il, ref il.Emit(OpCodes.Ldc_I8, (long)((UIntPtr)constantValue).ToUInt64()); } } + else if (constantType == typeof(decimal)) + { + //check if decimal has decimal places, if not use shorter IL code (constructor from int or long) + var value = (decimal)constantValue; + if (value % 1 == 0) + { + if (value <= int.MaxValue && value >= int.MinValue) + { + EmitLoadConstantInt(il, decimal.ToInt32(value)); + il.Emit(OpCodes.Newobj, + typeof(decimal).GetTypeInfo().DeclaredConstructors.First(x => + x.GetParameters().Length == 1 && + x.GetParameters()[0].ParameterType == typeof(int))); + } + else if (value <= long.MaxValue && value >= long.MinValue) + { + il.Emit(OpCodes.Ldc_I8, decimal.ToInt64(value)); + il.Emit(OpCodes.Newobj, + typeof(decimal).GetTypeInfo().DeclaredConstructors.First(x => + x.GetParameters().Length == 1 && + x.GetParameters()[0].ParameterType == typeof(int))); + } + } + else + { + int[] parts = Decimal.GetBits(value); + bool sign = (parts[3] & 0x80000000) != 0; + byte scale = (byte)((parts[3] >> 16) & 0x7F); + EmitLoadConstantInt(il, parts[0]); + EmitLoadConstantInt(il, parts[1]); + EmitLoadConstantInt(il, parts[2]); + il.Emit(sign ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + EmitLoadConstantInt(il, scale); + il.Emit(OpCodes.Conv_U1); + il.Emit(OpCodes.Newobj, + typeof(decimal).GetTypeInfo().DeclaredConstructors.First(x => + x.GetParameters().Length == 5)); + } + } else return false; } @@ -2279,7 +2442,7 @@ private static bool TryEmitAssign(BinaryExpression expr, var member = memberExpr.Member; var objExpr = memberExpr.Expression; - if (objExpr != null && !TryEmit(objExpr, paramExprs, il, ref closure, flags | ParentFlags.MemberAccess) || + if (objExpr != null && !TryEmit(objExpr, paramExprs, il, ref closure, flags | ParentFlags.MemberAccess | ParentFlags.InstanceAccess) || !TryEmit(right, paramExprs, il, ref closure, ParentFlags.Empty)) return false; @@ -2377,10 +2540,10 @@ private static bool TryEmitMethodCall(MethodCallExpression expr, var callFlags = parent & ~ParentFlags.IgnoreResult | ParentFlags.Call; if (objExpr != null) { - if (!TryEmit(objExpr, paramExprs, il, ref closure, callFlags)) + if (!TryEmit(objExpr, paramExprs, il, ref closure, callFlags | ParentFlags.InstanceAccess)) return false; - if (objExpr.Type.IsValueType() && objExpr.NodeType != ExpressionType.Parameter) + if (objExpr.Type.IsValueType() && objExpr.NodeType != ExpressionType.Parameter && !closure.LastEmitIsAddress) { var theVar = il.DeclareLocal(objExpr.Type); il.Emit(OpCodes.Stloc, theVar); @@ -2404,6 +2567,8 @@ private static bool TryEmitMethodCall(MethodCallExpression expr, if (expr.Method.IsVirtual && objExpr?.Type.IsValueType() == true) il.Emit(OpCodes.Constrained, objExpr.Type); + closure.LastEmitIsAddress = false; + return EmitMethodCall(il, expr.Method, parent); } @@ -2414,7 +2579,7 @@ private static bool TryEmitMemberAccess(MemberExpression expr, var instanceExpr = expr.Expression; if (instanceExpr != null && !TryEmit(instanceExpr, paramExprs, il, ref closure, - parent | (prop != null ? ParentFlags.Call : parent) | ParentFlags.MemberAccess)) + parent | (prop != null ? ParentFlags.Call : parent) | ParentFlags.MemberAccess | ParentFlags.InstanceAccess)) return false; if (prop != null) @@ -2422,20 +2587,35 @@ private static bool TryEmitMemberAccess(MemberExpression expr, // Value type special treatment to load address of value instance in order to access a field or call a method. // Parameter should be excluded because it already loads an address via Ldarga, and you don't need to. // And for field access no need to load address, cause the field stored on stack nearby - if (instanceExpr != null && instanceExpr.NodeType != ExpressionType.Parameter && instanceExpr.Type.IsValueType()) + if (instanceExpr != null && !closure.LastEmitIsAddress && instanceExpr.NodeType != ExpressionType.Parameter && instanceExpr.Type.IsValueType()) { var theVar = il.DeclareLocal(instanceExpr.Type); il.Emit(OpCodes.Stloc, theVar); il.Emit(OpCodes.Ldloca, theVar); } + closure.LastEmitIsAddress = false; return EmitMethodCall(il, prop.FindPropertyGetMethod()); } var field = expr.Member as FieldInfo; if (field != null) { - il.Emit(field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, field); + if (field.IsStatic) + { + if (field.IsLiteral) + { + var value = field.GetValue(null); + TryEmitConstant(null, field.FieldType, value, il, ref closure); + } + else + il.Emit(OpCodes.Ldsfld, field); + } + else + { + closure.LastEmitIsAddress = (field.FieldType.GetTypeInfo().IsValueType && (parent & ParentFlags.InstanceAccess) > 0); + il.Emit(closure.LastEmitIsAddress ? OpCodes.Ldflda : OpCodes.Ldfld, field); + } return true; } @@ -2626,8 +2806,11 @@ private static bool TryEmitInvoke(InvocationExpression expr, var argExprs = expr.Arguments; for (var i = 0; i < argExprs.Count; i++) - if (!TryEmit(argExprs[i], paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult, i)) + { + var byRefIndex = argExprs[i].Type.IsByRef ? i : -1; + if (!TryEmit(argExprs[i], paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult, byRefIndex)) return false; + } return EmitMethodCall(il, lambda.Type.FindDelegateInvokeMethod(), parent); } @@ -2691,11 +2874,8 @@ private static bool TryEmitComparison(Expression exprLeft, Expression exprRight, if (exprRight is ConstantExpression c && c.Value == null && exprRight.Type == typeof(object)) rightOpType = leftOpType; - if (leftOpType != rightOpType) - return false; - LocalBuilder lVar = null, rVar = null; - if (!TryEmit(exprLeft, paramExprs, il, ref closure, parent)) + if (!TryEmit(exprLeft, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess)) return false; if (leftIsNull) @@ -2710,9 +2890,35 @@ private static bool TryEmitComparison(Expression exprLeft, Expression exprRight, leftOpType = Nullable.GetUnderlyingType(leftOpType); } - if (!TryEmit(exprRight, paramExprs, il, ref closure, parent)) + if (!TryEmit(exprRight, paramExprs, il, ref closure, parent & ~ParentFlags.IgnoreResult & ~ParentFlags.InstanceAccess)) return false; + if (leftOpType != rightOpType) + { + if (leftOpType.GetTypeInfo().IsClass && rightOpType.GetTypeInfo().IsClass && (leftOpType == typeof(object) || rightOpType == typeof(object))) + { + if (expressionType == ExpressionType.Equal) + { + il.Emit(OpCodes.Ceq); + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + } + else if (expressionType == ExpressionType.NotEqual) + { + il.Emit(OpCodes.Ceq); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ceq); + } + else + return false; + + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + + return true; + } + } + if (rightOpType.IsNullable()) { rVar = il.DeclareLocal(rightOpType); @@ -2758,6 +2964,9 @@ var methodName if (leftIsNull) goto nullCheck; + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + return true; } @@ -2839,6 +3048,9 @@ var methodName } } + if ((parent & ParentFlags.IgnoreResult) > 0) + il.Emit(OpCodes.Pop); + return true; } @@ -2846,18 +3058,34 @@ private static bool TryEmitArithmeticOperation(ExpressionType exprNodeType, Type { if (!exprType.IsPrimitive()) { - var methodName - = exprNodeType == ExpressionType.Add ? "op_Addition" - : exprNodeType == ExpressionType.AddChecked ? "op_Addition" - : exprNodeType == ExpressionType.Subtract ? "op_Subtraction" - : exprNodeType == ExpressionType.SubtractChecked ? "op_Subtraction" - : exprNodeType == ExpressionType.Multiply ? "op_Multiply" - : exprNodeType == ExpressionType.MultiplyChecked ? "op_Multiply" - : exprNodeType == ExpressionType.Divide ? "op_Division" - : exprNodeType == ExpressionType.Modulo ? "op_Modulus" - : null; + if (exprType.IsNullable()) + exprType = Nullable.GetUnderlyingType(exprType); - return methodName != null && EmitMethodCall(il, exprType.FindMethod(methodName)); + if (!exprType.IsPrimitive()) + { + MethodInfo method = null; + if (exprType == typeof(string)) + method = typeof(string).GetTypeInfo().DeclaredMethods.First(x => + x.Name == "Concat" && x.GetParameters().Length == 2 && + x.GetParameters()[0].ParameterType == typeof(string)); + else + { + var methodName + = exprNodeType == ExpressionType.Add ? "op_Addition" + : exprNodeType == ExpressionType.AddChecked ? "op_Addition" + : exprNodeType == ExpressionType.Subtract ? "op_Subtraction" + : exprNodeType == ExpressionType.SubtractChecked ? "op_Subtraction" + : exprNodeType == ExpressionType.Multiply ? "op_Multiply" + : exprNodeType == ExpressionType.MultiplyChecked ? "op_Multiply" + : exprNodeType == ExpressionType.Divide ? "op_Division" + : exprNodeType == ExpressionType.Modulo ? "op_Modulus" + : null; + + if (methodName != null) + method = exprType.FindMethod(methodName); + } + return method != null && EmitMethodCall(il, method); + } } switch (exprNodeType) diff --git a/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj b/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj index eda4e03a..3adaebb3 100644 --- a/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj +++ b/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj @@ -19,4 +19,10 @@ + + + ..\libs\ILDebugging.Decoder.dll + + + \ No newline at end of file diff --git a/test/FastExpressionCompiler.IssueTests/Issue146_bool_par_error.cs b/test/FastExpressionCompiler.IssueTests/Issue146_bool_par_error.cs new file mode 100644 index 00000000..e231717a --- /dev/null +++ b/test/FastExpressionCompiler.IssueTests/Issue146_bool_par_error.cs @@ -0,0 +1,78 @@ +using System; +using NUnit.Framework; + +#pragma warning disable IDE1006 // Naming Styles for linq2db +#pragma warning disable 649 // Unaasigned fields + +#if LIGHT_EXPRESSION +using static FastExpressionCompiler.LightExpression.Expression; +namespace FastExpressionCompiler.LightExpression.UnitTests +#else +using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; +namespace FastExpressionCompiler.UnitTests +#endif +{ +[TestFixture] + public class Issue146_bool_par_error + { + + class MyObject + { + public bool a(b i) + { + return Equals(i, false); + } + } + + + [Test] + public void Test1() + { + var objParam = Parameter(typeof(MyObject), "myObj"); + var boolParam = Parameter(typeof(bool), "isSomething"); + var myMethod = typeof(MyObject).GetMethod("a").MakeGenericMethod(typeof(bool)); + var call = Call(objParam, myMethod, boolParam); + + var lambda = Lambda>( + call, + objParam, + boolParam); + + var func = lambda.CompileFast(); + + var ret = func.Invoke(new MyObject(), false); + + Assert.AreEqual(true, ret); + } + + + private class MyClass + { + public bool MyMethod(bool i) + { + Console.WriteLine("Got " + i); + + return Equals(i, false); + } + } + + [Test] + public void Test2() + { + var objParam = Parameter(typeof(MyClass), "myObj"); + var boolParam = Parameter(typeof(bool), "isSomething"); + var myMethod = typeof(MyClass).GetMethod("MyMethod").MakeGenericMethod(typeof(object)); + var call = Call(objParam, myMethod, boolParam); + + var lambda = Lambda>( + call, + objParam, + boolParam); + + var func = lambda.CompileFast(); + + func.Invoke(new MyClass(), false); + } + } +} diff --git a/test/FastExpressionCompiler.IssueTests/Issue147_int_try_parse.cs b/test/FastExpressionCompiler.IssueTests/Issue147_int_try_parse.cs new file mode 100644 index 00000000..99f04368 --- /dev/null +++ b/test/FastExpressionCompiler.IssueTests/Issue147_int_try_parse.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using System.Reflection; +using NUnit.Framework; + +#pragma warning disable IDE1006 // Naming Styles for linq2db +#pragma warning disable 649 // Unaasigned fields + +#if LIGHT_EXPRESSION +using static FastExpressionCompiler.LightExpression.Expression; +namespace FastExpressionCompiler.LightExpression.UnitTests +#else +using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; +namespace FastExpressionCompiler.UnitTests +#endif +{ +[TestFixture] + public class Issue147_int_try_parse + { + + class MyObject + { + public bool a(b i) + { + return Equals(i, false); + } + } + +#if !LIGHT_EXPRESSION + [Test] + public void Test1() + { + var intValueParameter = Parameter(typeof(int), "intValue"); + + var tryParseMethod = typeof(int) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .First(m => m.Name == "TryParse" && m.GetParameters().Length == 2); + + var tryParseCall = Call( + tryParseMethod, + Constant("123", typeof(string)), + intValueParameter); + + var parsedValueOrDefault = Condition( + tryParseCall, + intValueParameter, + Default(typeof(int))); + + var conditionBlock = Block(new[] { intValueParameter }, parsedValueOrDefault); + + var conditionLambda = Lambda>(conditionBlock); + + var conditionFunc = conditionLambda.Compile(); + + var parsedValue = conditionFunc.Invoke(); + + + var conditionFuncFast = conditionLambda.CompileFast(); + + var parsedValueFast = conditionFuncFast.Invoke(); + + Assert.AreEqual(parsedValue, parsedValueFast); + } +#endif + } +} diff --git a/test/FastExpressionCompiler.IssueTests/Issue150_New_AttemptToReadProtectedMemory.cs b/test/FastExpressionCompiler.IssueTests/Issue150_New_AttemptToReadProtectedMemory.cs new file mode 100644 index 00000000..853af5e8 --- /dev/null +++ b/test/FastExpressionCompiler.IssueTests/Issue150_New_AttemptToReadProtectedMemory.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using NUnit.Framework; +#pragma warning disable 659 + +#if LIGHT_EXPRESSION +using static FastExpressionCompiler.LightExpression.Expression; +namespace FastExpressionCompiler.LightExpression.UnitTests +#else + using static System.Linq.Expressions.Expression; + namespace FastExpressionCompiler.UnitTests +#endif +{ + using System; + using System.Linq; + using System.Reflection; + + [TestFixture] + public class Issue150_New_AttemptToReadProtectedMemory + { + [Test] + public void Nested_Assignments_Should_Work() + { + // Builds: + // + // dsosToPpssData => + // { + // var publicPropertyStruct_String = dsosToPpssData.Target; + // string valueKey; + // object value; + // publicPropertyStruct_String.Value = ((valueKey = dsosToPpssData.Source.Keys.FirstOrDefault(key => key.MatchesKey("Value"))) != null) + // ? ((value = dsosToPpssData.Source[valueKey]) != null) ? value.ToString() : null + // : null; + + // return publicPropertyStruct_String; + // } + + var mappingDataParameter = Parameter( + typeof(MappingData, PublicPropertyStruct>), + "dsosToPpssData"); + + var structVariable = Variable(typeof(PublicPropertyStruct), "publicPropertyStruct_String"); + + var structVariableAssignment = Assign(structVariable, Property(mappingDataParameter, "Target")); + + var sourceDictionary = Property(mappingDataParameter, "Source"); + var nullString = Default(typeof(string)); + var valueKeyVariable = Variable(typeof(string), "valueKey"); + + var linqFirstOrDefaultMethod = typeof(Enumerable) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .First(m => m.Name == "FirstOrDefault" && m.GetParameters().Length == 2) + .MakeGenericMethod(typeof(string)); + + var dictionaryKeys = Property(sourceDictionary, "Keys"); + + var keyParameter = Parameter(typeof(string), "key"); + var matchesKeyMethod = typeof(MyIssueExtensions).GetMethod("MatchesKey"); + var matchesKeyCall = Call(matchesKeyMethod, keyParameter, Constant("Value")); + var matchesKeyLambda = Lambda>(matchesKeyCall, keyParameter); + + var firstOrDefaultKeyCall = Call(linqFirstOrDefaultMethod, dictionaryKeys, matchesKeyLambda); + + var valueKeyAssignment = Assign(valueKeyVariable, firstOrDefaultKeyCall); + var valueKeyNotNull = NotEqual(valueKeyAssignment, nullString); + + var valueVariable = Variable(typeof(object), "value"); + + var dictionaryIndexer = sourceDictionary.Type.GetProperties().First(p => p.GetIndexParameters().Length != 0); + var dictionaryIndexAccess = MakeIndex(sourceDictionary, dictionaryIndexer, new[] { valueKeyVariable }); + var dictionaryValueAsObject = Convert(dictionaryIndexAccess, typeof(object)); + + var valueAssignment = Assign(valueVariable, dictionaryValueAsObject); + var valueNotNull = NotEqual(valueAssignment, Default(valueVariable.Type)); + + var objectToStringMethod = valueVariable.Type.GetMethod("ToString"); + var valueToString = Call(valueVariable, objectToStringMethod); + + var valueToStringOrNull = Condition(valueNotNull, valueToString, nullString); + + var dictionaryEntryOrNull = Condition(valueKeyNotNull, valueToStringOrNull, nullString); + + var structValueProperty = Property(structVariable, "Value"); + + var structValueAssignment = Assign(structValueProperty, dictionaryEntryOrNull); + + var structPopulation = Block( + new[] { valueKeyVariable, valueVariable }, + structValueAssignment); + + var structMapping = Block( + new[] { structVariable }, + structVariableAssignment, + structPopulation, + structVariable); + + var populationLambda = Lambda, PublicPropertyStruct>, PublicPropertyStruct>>( + structMapping, + mappingDataParameter); + + var populationFunc = populationLambda.CompileFast(); + populationFunc.Invoke(new MappingData, PublicPropertyStruct> + { + Source = new Dictionary { ["Value"] = 123 }, + Target = new PublicPropertyStruct() + }); + } + + public class MappingData + { + public TSource Source { get; set; } + + public TTarget Target { get; set; } + } + + public struct PublicPropertyStruct + { + public T Value { get; set; } + } + } + + public static class MyIssueExtensions + { + public static bool MatchesKey(this string value, string other) => value == other; + } +} \ No newline at end of file diff --git a/test/FastExpressionCompiler.IssueTests/Issue153_MinValueMethodNotSupported.cs b/test/FastExpressionCompiler.IssueTests/Issue153_MinValueMethodNotSupported.cs new file mode 100644 index 00000000..4ef12735 --- /dev/null +++ b/test/FastExpressionCompiler.IssueTests/Issue153_MinValueMethodNotSupported.cs @@ -0,0 +1,26 @@ +using System; +using NUnit.Framework; +#pragma warning disable 659 +#if LIGHT_EXPRESSION +using static FastExpressionCompiler.LightExpression.Expression; +namespace FastExpressionCompiler.LightExpression.UnitTests +#else +using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; +namespace FastExpressionCompiler.UnitTests +#endif +{ + [TestFixture] + public class Issue153_MinValueMethodNotSupported + { + [Test] + public void Int_MinValue_Should_Work() + { + var minValueField = typeof(int).GetField("MinValue"); + var minValue = Field(null, minValueField); + var minValueLambda = Lambda>(minValue); + var res = minValueLambda.CompileFast(); + Assert.AreEqual(int.MinValue, res()); + } + } +} \ No newline at end of file diff --git a/test/FastExpressionCompiler.IssueTests/Issue67_Equality_comparison_with_nullables_throws_at_delegate_invoke.cs b/test/FastExpressionCompiler.IssueTests/Issue67_Equality_comparison_with_nullables_throws_at_delegate_invoke.cs index 42a626fe..15a30bd0 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue67_Equality_comparison_with_nullables_throws_at_delegate_invoke.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue67_Equality_comparison_with_nullables_throws_at_delegate_invoke.cs @@ -16,6 +16,30 @@ class Foo public Point? PropP { get; set; } public int Prop3 { get; set; } + + public aa Prop4 { get; set; } + } + + public struct aa + { + public int b; + + public override bool Equals(Object obj) + { + return obj is aa && this == (aa)obj; + } + public override int GetHashCode() + { + return b.GetHashCode(); + } + public static bool operator ==(aa x, aa y) + { + return x.b == y.b; + } + public static bool operator !=(aa x, aa y) + { + return !(x == y); + } } [Test] @@ -34,6 +58,19 @@ public void Comparing_nullable_equal_works() Assert.AreEqual(f2(new Foo() { Prop = 0 }), f(new Foo() { Prop = 0 })); } + [Test] + public void Comparing_struct_equal_works() + { + var aaComparand = new aa(); + Expression> e = foo => foo.Prop4 == aaComparand; + + var f = e.CompileFast(true); + var f2 = e.Compile(); + Assert.IsNotNull(f); + + Assert.AreEqual(f2(new Foo() { Prop4 = aaComparand }), f(new Foo() { Prop4 = aaComparand })); + } + [Test] public void Comparing_int_equal_works() { diff --git a/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs b/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs index 5365b639..28950747 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using ILDebugging.Decoder; using NUnit.Framework; #if LIGHT_EXPRESSION @@ -23,6 +26,20 @@ public void BlockWithConstanReturnIsSupported() Assert.AreEqual(7, fastCompiled()); } + [Test] + public void MultipleConstantReturnsAreRemoved() + { + var ret = Block(Constant(7), Constant(7), Constant(7)); + var lambda = Lambda>(ret); + var fastCompiled = lambda.CompileFast>(true); + Assert.IsNotNull(fastCompiled); + Assert.AreEqual(7, fastCompiled()); + + var il = ILReaderFactory.Create(fastCompiled.Method); + CollectionAssert.AreEqual(il.Select(x => x.OpCode), + new[] {OpCodes.Ldc_I4_7, OpCodes.Ret}); + } + [Test] public void ConstantReturnIsSupported() { diff --git a/test/FastExpressionCompiler.IssueTests/Issue83_linq2db.cs b/test/FastExpressionCompiler.IssueTests/Issue83_linq2db.cs index 1283ce99..3b2a7142 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue83_linq2db.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue83_linq2db.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using NUnit.Framework; @@ -278,6 +279,88 @@ enum Enum3 Value2 = 2, } +#if !LIGHT_EXPRESSION + [Test] + public void Equal1_Test() + { + var p = Parameter(typeof(object)); + var pp = new Patient(); + var body = Equal(Constant(pp), p); + Expression> e = (o) => o == pp; + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + var c = expr.Compile(); + + Assert.AreEqual(c(pp), compiled(pp)); + Assert.AreEqual(c(new Patient()), compiled(new Patient())); + } + + [Test] + public void Equal2_Test() + { + var p = Parameter(typeof(Patient)); + var pp = new Patient(); + var body = Equal(Constant(pp), p); + Expression> e = (o) => o == pp; + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + var c = expr.Compile(); + + Assert.AreEqual(c(pp), compiled(pp)); + Assert.AreEqual(c(new Patient()), compiled(new Patient())); + } + + [Test] + public void Equal3_Test() + { + var p = Parameter(typeof(Patient)); + var pp = new Patient2(); + var body = Equal(Constant(pp), p); + Expression> e = (o) => o == pp; + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + var c = expr.Compile(); + + Assert.AreEqual(c(pp), compiled(pp)); + Assert.AreEqual(c(new Patient()), compiled(new Patient())); + } + + [Test] + public void TypeAs_Test() + { + var p = Parameter(typeof(object)); + var body = TypeAs(p, typeof(Patient)); + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + var c = expr.Compile(); + + var pp = new Patient(); + var s = "a"; + Assert.AreEqual(c(pp), compiled(pp)); + Assert.AreEqual(c(s), compiled(s)); + } + + [Test] + public void TypeIs_Test() + { + var p = Parameter(typeof(object)); + var body = TypeIs(p, typeof(Patient)); + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + var c = expr.Compile(); + + var pp = new Patient(); + var s = "a"; + Assert.AreEqual(c(pp), compiled(pp)); + Assert.AreEqual(c(s), compiled(s)); + } +#endif + [Test] public void Enum_to_enum_conversion() { @@ -623,6 +706,53 @@ public void TestConverterFailure() compiled1(null); compiled2(null); } + + class sPrp { + public short? v; + } + + [Test] + public void TestConverterNullable() + { + var p = Parameter(typeof(sPrp), "p"); + + var mapperBody = /*Convert(*/Convert(Field(p, nameof(sPrp.v)), typeof(int?))/*, typeof(object))*/; + var mapper = Lambda>(mapperBody, p); + + var compiled1 = mapper.Compile(); + var compiled2 = mapper.CompileFast(true); + + var a = compiled1(new sPrp() { v = short.MaxValue }); + var b = compiled2(new sPrp() { v = short.MaxValue }); + + Assert.AreEqual(a, b); + + var c = compiled1(new sPrp() { v = short.MinValue }); + var d = compiled2(new sPrp() { v = short.MinValue }); + + Assert.AreEqual(c, d); + } + + public static string aa(int nr) { + return nr.ToString(); + } + + [Test] + public void TestLdArg() + { + var p = Parameter(typeof(int), "p"); + + var mapperBody = Call(typeof(Issue83_linq2db).GetTypeInfo().GetMethod("aa"), p); + var mapper = Lambda>(mapperBody, p); + + var compiled1 = mapper.Compile(); + var compiled2 = mapper.CompileFast(true); + + var a = compiled1(5); + var b = compiled2(5); + + Assert.AreEqual(a, b); + } #endif [Test] @@ -774,6 +904,237 @@ public void Struct_test2() Assert.That(obj.Class2.Struct1P.Class3P.Class4.Field1, Is.EqualTo(42)); } + [Test] + public void NullableEnum() + { + var objParam = Parameter(typeof(TestClass2), "obj"); + + var body = Block( + Assign(Field(objParam, nameof(TestClass2.NullEnum2)), Constant(Enum2.Value1, typeof(Enum2?))) + ); + + var expr = Lambda>(body, objParam); + + var compiled = expr.CompileFast(true); + + var obj = new TestClass2(); + + compiled(obj); + + Assert.That(obj.NullEnum2, Is.EqualTo(Enum2.Value1)); + } + + [Test] + public void NullableEnum2() + { + var objParam = Parameter(typeof(TestClass2), "obj"); + + var body = Block( + Equal(Field(objParam, nameof(TestClass2.NullEnum2)), Constant(Enum2.Value1, typeof(Enum2?))) + ); + + var expr = Lambda>(body, objParam); + + + var compiled = expr.CompileFast(true); + + var obj = new TestClass2(); + + compiled(obj); + } + + [Test] + public void NewNullableTest() + { + var body = New(typeof(int?).GetTypeInfo().DeclaredConstructors.First(), Constant(6, typeof(int))); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + compiled(); + } + + [Test] + public void TestToString() + { + var body = Call(Constant(true), + typeof(bool).GetTypeInfo().DeclaredMethods + .First(x => x.Name == "ToString" && x.GetParameters().Length == 0)); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + var ret = compiled(); + + Assert.AreEqual("True", ret); + } + + [Test] + public void Test2ToString() + { + var p = Parameter(typeof(bool)); + var body = Call(p, + typeof(bool).GetTypeInfo().DeclaredMethods + .First(x => x.Name == "ToString" && x.GetParameters().Length == 0)); + + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + + var ret = compiled(true); + + Assert.AreEqual("True", ret); + } + + [Test] + public void TestDecimal() + { + var body = Constant(5.64m); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + var ret = compiled(); + Assert.AreEqual(5.64m, ret); + } + + [Test] + public void TestDecimal1() + { + var body = Constant(5m); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + var ret = compiled(); + Assert.AreEqual(5m, ret); + } + + [Test] + public void Test3Bool() + { + var p = Parameter(typeof(bool)); + var body = Not(p); + + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + + var ret = compiled(true); + + Assert.AreEqual(false, ret); + } + + [Test] + public void Test4Bool() + { + var p = Parameter(typeof(bool)); + var body = Not(p); + + var expr = Lambda>(body, p); + + var compiled = expr.CompileFast(true); + + var ret = compiled(false); + + Assert.AreEqual(true, ret); + } + + [Test] + public void ConvertNullableTest() + { + var body = Convert(ConvertChecked(Constant(long.MaxValue-1, typeof(long)), typeof(int)), typeof(int?)); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + Assert.Throws(()=> compiled()); + } + + [Test] + public void ConvertNullable2Test() + { + var body = Convert(ConvertChecked(Constant(5l, typeof(long)), typeof(int)), typeof(int?)); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + compiled(); + } + + [Test] + public void ConvertTest() + { + var body = ConvertChecked(Constant(0x10, typeof(int)), typeof(char)); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + var ret = compiled(); + + Assert.AreEqual('\x10', ret); + } + + [Test] + public void ConvertTest2() + { + var body = ConvertChecked(Constant('\x10', typeof(char)), typeof(int)); + + var expr = Lambda>(body); + + var compiled = expr.CompileFast(true); + + var ret = compiled(); + + Assert.AreEqual(0x10, ret); + } + + public class Patient2 : Patient { } + + public class Patient + { + public int PersonID; + public string Diagnosis; + + public static bool operator ==(Patient a, Patient b) + { + return Equals(a, b); + } + public static bool operator !=(Patient a, Patient b) + { + return !Equals(a, b); + } + + public override bool Equals(object obj) + { + return Equals(obj as Patient); + } + + public bool Equals(Patient other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other.PersonID == PersonID && Equals(other.Diagnosis, Diagnosis); + } + + public override int GetHashCode() + { + unchecked + { + var result = PersonID; + result = (result * 397) ^ (Diagnosis != null ? Diagnosis.GetHashCode() : 0); + return result; + } + } + } + class TestClass1 { public int Prop1 @@ -794,6 +1155,7 @@ public int Prop3 class TestClass2 { + public Enum2? NullEnum2; public TestClass3 Class3; public TestStruct1 Struct1; public TestStruct1 Struct1P { get; set; } diff --git a/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs b/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs index fa8cbc86..a7964b9b 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs @@ -1,4 +1,8 @@ using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using ILDebugging.Decoder; using NUnit.Framework; #if LIGHT_EXPRESSION @@ -37,6 +41,12 @@ public void NullComparisonTest() var lambda = Lambda>(condition, pParam); var convert1 = lambda.CompileFast(true); Assert.NotNull(convert1); + Assert.AreEqual(1, convert1("aaa")); + + // Check TryEmitInvertedNullComparison is used + var il = ILReaderFactory.Create(convert1.Method); + CollectionAssert.AreEqual(il.Select(x => x.OpCode), + new[] {OpCodes.Ldarg_0, OpCodes.Brfalse, OpCodes.Ldc_I4_1, OpCodes.Br, OpCodes.Ldc_I4_0, OpCodes.Ret}); } [Test] diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj index 06a4173d..a537d143 100644 --- a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj @@ -32,4 +32,10 @@ + + + ..\libs\ILDebugging.Decoder.dll + + + diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj index 98898bdd..37227218 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj @@ -31,4 +31,10 @@ + + + ..\libs\ILDebugging.Decoder.dll + + + diff --git a/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj b/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj index 2d4e4847..d13af86f 100644 --- a/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj +++ b/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj @@ -24,4 +24,10 @@ + + + ..\libs\ILDebugging.Decoder.dll + + + diff --git a/test/FastExpressionCompiler.UnitTests/ValueTypeTests.cs b/test/FastExpressionCompiler.UnitTests/ValueTypeTests.cs index 0fe1513c..4b204f01 100644 --- a/test/FastExpressionCompiler.UnitTests/ValueTypeTests.cs +++ b/test/FastExpressionCompiler.UnitTests/ValueTypeTests.cs @@ -1,8 +1,14 @@ using System; -using System.Linq.Expressions; using NUnit.Framework; +#if LIGHT_EXPRESSION +using static FastExpressionCompiler.LightExpression.Expression; +namespace FastExpressionCompiler.LightExpression.UnitTests +#else +using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; namespace FastExpressionCompiler.UnitTests +#endif { [TestFixture] public class ValueTypeTests @@ -91,7 +97,7 @@ struct A public override string ToString() => N.ToString(); } - + [Test] public void Action_using_with_struct_closure_field() { @@ -99,9 +105,8 @@ public void Action_using_with_struct_closure_field() Expression> expr = a => s.SetValue(a); var lambda = expr.CompileFast(ifFastFailedReturnNull: true); - lambda("a"); - Assert.IsNull(s.Value); + Assert.AreEqual("a", s.Value); } public struct SS diff --git a/test/libs/ILDebugging.Decoder.dll b/test/libs/ILDebugging.Decoder.dll new file mode 100644 index 00000000..3b1cae9c Binary files /dev/null and b/test/libs/ILDebugging.Decoder.dll differ diff --git a/test/libs/ILDebugging.Visualizer.dll b/test/libs/ILDebugging.Visualizer.dll new file mode 100644 index 00000000..b31449e8 Binary files /dev/null and b/test/libs/ILDebugging.Visualizer.dll differ