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