Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/FastExpressionCompiler/FastExpressionCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5556,13 +5556,11 @@ private static bool TryEmitMethodCall(Expression expr, ILGenerator il, ref Compi
var ok = true;
if (!objIsValueType)
ok = EmitMethodCallOrVirtualCall(il, method);
else if (method.DeclaringType != typeof(Enum) &&
(!method.IsVirtual ||
method.DeclaringType == objExpr.Type ||
objExpr is ParameterExpression pe && pe.IsByRef))
else if (method.DeclaringType == objExpr.Type)
ok = EmitMethodCall(il, method);
else
{
// method on base class (System.Enum, System.Object) or interface
il.Demit(OpCodes.Constrained, objExpr.Type);
ok = EmitVirtualMethodCall(il, method);
}
Expand Down
2 changes: 2 additions & 0 deletions test/FastExpressionCompiler.TestsRunner.Net472/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ void Run(Func<int> run, string name = null)
Run(new LightExpression.UnitTests.BinaryExpressionTests().Run);
Run(new BlockTests().Run);
Run(new LightExpression.UnitTests.BlockTests().Run);
Run(new CallTests().Run);
Run(new LightExpression.UnitTests.CallTests().Run);
Run(new ClosureConstantTests().Run);
Run(new LightExpression.UnitTests.ClosureConstantTests().Run);
Run(new CoalesceTests().Run);
Expand Down
2 changes: 2 additions & 0 deletions test/FastExpressionCompiler.TestsRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ void Run(Func<int> run, string name = null)
Run(new LightExpression.UnitTests.BinaryExpressionTests().Run);
Run(new BlockTests().Run);
Run(new LightExpression.UnitTests.BlockTests().Run);
Run(new CallTests().Run);
Run(new LightExpression.UnitTests.CallTests().Run);
Run(new ClosureConstantTests().Run);
Run(new LightExpression.UnitTests.ClosureConstantTests().Run);
Run(new CoalesceTests().Run);
Expand Down
226 changes: 226 additions & 0 deletions test/FastExpressionCompiler.UnitTests/CallTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System;

#if LIGHT_EXPRESSION
using static FastExpressionCompiler.LightExpression.Expression;
namespace FastExpressionCompiler.LightExpression.UnitTests;
#else
using static System.Linq.Expressions.Expression;
namespace FastExpressionCompiler.UnitTests;
#endif

public class CallTests : ITest
{
public int Run()
{
GetType_on_int_parameter();
GetType_on_local_int();
GetType_on_nullable();
GetType_on_enum();
Nonvirtual_struct_method();
Virtual_struct_method();
Inherited_virtual_struct_method();
Inherited_ValueType_method();
Enum_HasFlag();
Enum_ToString();
Interface_method_on_struct();
Virtual_class_method();
Ignored_call_result();
Call_result_as_argument();
Call_result_field_access();
Void_call();
return 16;
}

public void GetType_on_int_parameter()
{
var value = Parameter(typeof(int), "value");
var expression = Lambda<Func<int, Type>>(
Call(value, typeof(object).GetMethod(nameof(GetType))), value);

Asserts.AreEqual(typeof(int), expression.CompileSys()(42));
Asserts.AreEqual(typeof(int), expression.CompileFast(true)(42));
}

public void GetType_on_local_int()
{
var value = Variable(typeof(int), "a");
var expression = Lambda<Func<Type>>(Block(new[] { value },
Assign(value, Constant(1)), Call(value, typeof(object).GetMethod(nameof(GetType)))));

Asserts.AreEqual(typeof(int), expression.CompileSys()());
Asserts.AreEqual(typeof(int), expression.CompileFast(true)());
}

public void GetType_on_nullable()
{
var value = Parameter(typeof(int?), "value");
var expression = Lambda<Func<int?, Type>>(
Call(value, typeof(object).GetMethod(nameof(GetType))), value);

var system = expression.CompileSys();
var fast = expression.CompileFast(true);
Asserts.AreEqual(typeof(int), system(42));
Asserts.AreEqual(typeof(int), fast(42));
Asserts.Throws<NullReferenceException>(() => system(null));
Asserts.Throws<NullReferenceException>(() => fast(null));
}

public void GetType_on_enum()
{
var value = Parameter(typeof(Bits), "value");
var expression = Lambda<Func<Bits, Type>>(
Call(value, typeof(object).GetMethod(nameof(GetType))), value);

Asserts.AreEqual(typeof(Bits), expression.CompileSys()(Bits.A));
Asserts.AreEqual(typeof(Bits), expression.CompileFast(true)(Bits.A));
}

public void Nonvirtual_struct_method()
{
var counter = Parameter(typeof(Counter), "counter");
var expression = Lambda<Func<Counter, Result>>(
Call(counter, typeof(Counter).GetMethod(nameof(Counter.Add)), Constant(1)), counter);

Asserts.AreEqual(43, expression.CompileSys()(new Counter { Value = 42 }).Value);
Asserts.AreEqual(43, expression.CompileFast(true)(new Counter { Value = 42 }).Value);
}

public void Virtual_struct_method()
{
var counter = Parameter(typeof(Counter), "counter");
var expression = Lambda<Func<Counter, string>>(
Call(counter, typeof(Counter).GetMethod(nameof(ToString))), counter);

Asserts.AreEqual("counter", expression.CompileSys()(new Counter()));
Asserts.AreEqual("counter", expression.CompileFast(true)(new Counter()));
}

public void Inherited_virtual_struct_method()
{
var counter = Parameter(typeof(Counter), "counter");
var expression = Lambda<Func<Counter, string>>(
Call(counter, typeof(object).GetMethod(nameof(ToString))), counter);

Asserts.AreEqual("counter", expression.CompileSys()(new Counter()));
Asserts.AreEqual("counter", expression.CompileFast(true)(new Counter()));
}

public void Inherited_ValueType_method()
{
var value = Parameter(typeof(PlainValue), "value");
var expression = Lambda<Func<PlainValue, string>>(
Call(value, typeof(ValueType).GetMethod(nameof(ToString))), value);

Asserts.AreEqual(typeof(PlainValue).FullName, expression.CompileSys()(new PlainValue()));
Asserts.AreEqual(typeof(PlainValue).FullName, expression.CompileFast(true)(new PlainValue()));
}

public void Enum_HasFlag()
{
var value = Parameter(typeof(Bits), "value");
var expression = Lambda<Func<Bits, bool>>(Call(value,
typeof(Enum).GetMethod(nameof(Enum.HasFlag)), Constant(Bits.B, typeof(Enum))), value);

var system = expression.CompileSys();
var fast = expression.CompileFast(true);
Asserts.IsTrue(system(Bits.A | Bits.B));
Asserts.IsTrue(fast(Bits.A | Bits.B));
Asserts.IsFalse(system(Bits.A));
Asserts.IsFalse(fast(Bits.A));
}

public void Enum_ToString()
{
var value = Parameter(typeof(Bits), "value");
var expression = Lambda<Func<Bits, string>>(
Call(value, typeof(Enum).GetMethod(nameof(ToString), Type.EmptyTypes)), value);

Asserts.AreEqual("A, B", expression.CompileSys()(Bits.A | Bits.B));
Asserts.AreEqual("A, B", expression.CompileFast(true)(Bits.A | Bits.B));
}

public void Interface_method_on_struct()
{
var counter = Parameter(typeof(Counter), "counter");
var expression = Lambda<Func<Counter, Result>>(
Call(counter, typeof(IAdder).GetMethod(nameof(IAdder.Add)), Constant(1)), counter);

Asserts.AreEqual(143, expression.CompileSys()(new Counter { Value = 42 }).Value);
Asserts.AreEqual(143, expression.CompileFast(true)(new Counter { Value = 42 }).Value);
}

public void Virtual_class_method()
{
var target = Parameter(typeof(Target), "target");
var expression = Lambda<Func<Target, int>>(
Call(target, typeof(Target).GetMethod(nameof(Target.GetValue))), target);

Asserts.AreEqual(2, expression.CompileSys()(new DerivedTarget()));
Asserts.AreEqual(2, expression.CompileFast(true)(new DerivedTarget()));
}

public void Ignored_call_result()
{
var counter = Variable(typeof(Counter), "counter");
var expression = Lambda<Func<int>>(Block(new[] { counter },
Assign(counter, Default(typeof(Counter))),
Call(counter, typeof(Counter).GetMethod(nameof(Counter.Add)), Constant(1)),
Field(counter, nameof(Counter.Value))));

Asserts.AreEqual(1, expression.CompileSys()());
Asserts.AreEqual(1, expression.CompileFast(true)());
}

public void Call_result_as_argument()
{
var counter = Parameter(typeof(Counter), "counter");
var expression = Lambda<Func<Counter, int>>(
Call(typeof(CallTests).GetMethod(nameof(ReadResult)),
Call(counter, typeof(Counter).GetMethod(nameof(Counter.Add)), Constant(1))), counter);

Asserts.AreEqual(43, expression.CompileSys()(new Counter { Value = 42 }));
Asserts.AreEqual(43, expression.CompileFast(true)(new Counter { Value = 42 }));
}

public void Call_result_field_access()
{
var counter = Parameter(typeof(Counter), "counter");
var expression = Lambda<Func<Counter, int>>(
Field(Call(counter, typeof(Counter).GetMethod(nameof(Counter.Add)), Constant(1)),
nameof(Result.Value)), counter);

Asserts.AreEqual(43, expression.CompileSys()(new Counter { Value = 42 }));
Asserts.AreEqual(43, expression.CompileFast(true)(new Counter { Value = 42 }));
}

public void Void_call()
{
var counter = Variable(typeof(Counter), "counter");
var expression = Lambda<Func<int>>(Block(new[] { counter },
Assign(counter, Default(typeof(Counter))),
Call(counter, typeof(Counter).GetMethod(nameof(Counter.Increment))),
Field(counter, nameof(Counter.Value))));

Asserts.AreEqual(1, expression.CompileSys()());
Asserts.AreEqual(1, expression.CompileFast(true)());
}

[Flags]
public enum Bits { A = 1, B = 2 }
public struct PlainValue { }
public struct Result { public int Value; }
public interface IAdder { Result Add(int amount); }

public struct Counter : IAdder
{
public int Value;
public Result Add(int amount) => new Result { Value = Value += amount };
Result IAdder.Add(int amount) => new Result { Value = Value += amount + 100 };
public void Increment() => ++Value;
public override string ToString() => "counter";
}

public class Target { public virtual int GetValue() => 1; }
public class DerivedTarget : Target { public override int GetValue() => 2; }
public static int ReadResult(Result result) => result.Value;
}
Loading