From eadd9012caba267d5a68d8107cfc70c482980bd9 Mon Sep 17 00:00:00 2001 From: Chaplygin Anton Date: Tue, 24 Jul 2018 11:58:09 +0500 Subject: [PATCH 1/2] Test for some non-trivial constants that can actually be dealt with in LambdaCompiler.CompileToMethod --- GrobExp.Compiler.Tests/TestCompileToMethod.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 GrobExp.Compiler.Tests/TestCompileToMethod.cs diff --git a/GrobExp.Compiler.Tests/TestCompileToMethod.cs b/GrobExp.Compiler.Tests/TestCompileToMethod.cs new file mode 100644 index 0000000..e22f4cb --- /dev/null +++ b/GrobExp.Compiler.Tests/TestCompileToMethod.cs @@ -0,0 +1,93 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; + +using NUnit.Framework; + +namespace GrobExp.Compiler.Tests +{ + [TestFixture] + public class TestCompileToMethod + { + [OneTimeSetUp] + public void SetUp() + { + var assemblyName = "TestAssembly"; + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run); + moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName); + } + + [Test] + public void TestNullablePrimitiveConstant() + { + var typeBuilder = moduleBuilder.DefineType(Guid.NewGuid().ToString(), TypeAttributes.Class); + var methodName = nameof(TestNullablePrimitiveConstant); + var methodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static); + + // Cannot declare constant of nullable type via Expression> interface + // Expression> expression = x => x == (int? 2) ? "two" : x.ToString(); + var parameter = Expression.Parameter(typeof(int?)); + var constant = Expression.Constant((int?)2, typeof(int?)); + var condition = Expression.Condition(Expression.Equal(parameter, constant), + Expression.Constant("two", typeof(string)), + Expression.Call(parameter, typeof(int?).GetMethod("ToString", Type.EmptyTypes))); + var lambda = Expression.Lambda(condition, parameter); + + LambdaCompiler.CompileToMethod(lambda, methodBuilder, CompilerOptions.All); + + var type = typeBuilder.CreateType(); + var method = type.GetMethod(methodName, new[] {typeof(int?)}); + Assert.That(method.Invoke(null, new object[] {new int?(2)}), Is.EqualTo("two")); + Assert.That(method.Invoke(null, new object[] {new int?(3)}), Is.EqualTo("3")); + } + + [Test] + public void TestEnumConstant() + { + var typeBuilder = moduleBuilder.DefineType(Guid.NewGuid().ToString(), TypeAttributes.Class); + var methodName = nameof(TestEnumConstant); + var methodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static); + + // Cannot use Expression> interface here because enum type gets optimized to int + // Expression> expression = x => x == TestEnum.Two ? "2" : x.ToString(); + var parameter = Expression.Parameter(typeof(TestEnum)); + var constant = Expression.Constant(TestEnum.Two, typeof(TestEnum)); + var condition = Expression.Condition(Expression.Equal(parameter, constant), + Expression.Constant("2", typeof(string)), + Expression.Call(parameter, typeof(object).GetMethod("ToString", Type.EmptyTypes))); + var lambda = Expression.Lambda(condition, parameter); + + LambdaCompiler.CompileToMethod(lambda, methodBuilder, CompilerOptions.All); + + var type = typeBuilder.CreateType(); + var method = type.GetMethod(methodName, new[] {typeof(TestEnum)}); + Assert.That(method.Invoke(null, new object[] {TestEnum.Two}), Is.EqualTo("2")); + Assert.That(method.Invoke(null, new object[] {TestEnum.One}), Is.EqualTo("One")); + } + + [Test] + public void TestDecimalConstant() + { + var typeBuilder = moduleBuilder.DefineType(Guid.NewGuid().ToString(), TypeAttributes.Class); + var methodName = nameof(TestDecimalConstant); + var methodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static); + + Expression> expression = x => x == 2m ? "two" : x.ToString(); + LambdaCompiler.CompileToMethod(expression, methodBuilder, CompilerOptions.All); + + var type = typeBuilder.CreateType(); + var method = type.GetMethod(methodName, new[] {typeof(decimal)}); + Assert.That(method.Invoke(null, new object[] {2m}), Is.EqualTo("two")); + Assert.That(method.Invoke(null, new object[] {3m}), Is.EqualTo("3")); + } + + public enum TestEnum + { + One, + Two, + } + + private ModuleBuilder moduleBuilder; + } +} \ No newline at end of file From 59d237644d51323dae0776ad3c2ef64d26263519 Mon Sep 17 00:00:00 2001 From: Chaplygin Anton Date: Tue, 24 Jul 2018 12:00:34 +0500 Subject: [PATCH 2/2] Supported some trivial constant types in LambdaCompiler.CompileToMethod method --- GrobExp.Compiler/Closures/ExpressionClosureBuilder.cs | 9 ++++++++- .../ExpressionEmitters/ConstantExpressionEmitter.cs | 3 +++ GrobExp.Compiler/GrobExp.Compiler.csproj | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/GrobExp.Compiler/Closures/ExpressionClosureBuilder.cs b/GrobExp.Compiler/Closures/ExpressionClosureBuilder.cs index 59f57b4..e7ba143 100644 --- a/GrobExp.Compiler/Closures/ExpressionClosureBuilder.cs +++ b/GrobExp.Compiler/Closures/ExpressionClosureBuilder.cs @@ -144,13 +144,20 @@ protected override CatchBlock VisitCatchBlock(CatchBlock node) protected override Expression VisitConstant(ConstantExpression node) { - if(quoteDepth > 0 || node.Value == null || node.Type.IsPrimitive || node.Type == typeof(string)) + if(quoteDepth > 0 || node.Value == null || IsPrimitiveConstantType(node.Type)) return node; if(!constants.ContainsKey(node)) constants.Add(node, BuildConstField(node.Type, node.Value)); return node; } + private static bool IsPrimitiveConstantType(Type type) + { + if (type.IsNullable()) + type = type.GetGenericArguments()[0]; + return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal); + } + protected override Expression VisitParameter(ParameterExpression node) { var peek = localParameters.Peek(); diff --git a/GrobExp.Compiler/ExpressionEmitters/ConstantExpressionEmitter.cs b/GrobExp.Compiler/ExpressionEmitters/ConstantExpressionEmitter.cs index 7e78912..4d0803a 100644 --- a/GrobExp.Compiler/ExpressionEmitters/ConstantExpressionEmitter.cs +++ b/GrobExp.Compiler/ExpressionEmitters/ConstantExpressionEmitter.cs @@ -61,6 +61,9 @@ protected override bool EmitInternal(ConstantExpression node, EmittingContext co case TypeCode.String: context.Il.Ldstr((string)node.Value); break; + case TypeCode.Decimal: + context.Il.LdDec((decimal)node.Value); + break; default: throw new NotSupportedException("Constant of type '" + node.Type + "' is not supported"); } diff --git a/GrobExp.Compiler/GrobExp.Compiler.csproj b/GrobExp.Compiler/GrobExp.Compiler.csproj index 2d15e47..b007f8d 100644 --- a/GrobExp.Compiler/GrobExp.Compiler.csproj +++ b/GrobExp.Compiler/GrobExp.Compiler.csproj @@ -15,6 +15,6 @@ git - +