From ae65ecb766bf61ca267f10efbd504bf8b93ad1a7 Mon Sep 17 00:00:00 2001 From: metoule Date: Thu, 25 Nov 2021 11:53:32 +0100 Subject: [PATCH] Allow explicit return type object (#186) * No longer omit the conversion expression when the return type is explicitly object. Force the return type of a lambda expression to typeof(void) to prevent the emission of a conversion expression. Fixes #185 * Remove PrepareDelegateInvoke to avoid modifying the arguments list. --- src/DynamicExpresso.Core/Interpreter.cs | 11 +++- src/DynamicExpresso.Core/Parsing/Parser.cs | 64 ++++++++++--------- test/DynamicExpresso.UnitTest/GithubIssues.cs | 21 ++++++ 3 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/DynamicExpresso.Core/Interpreter.cs b/src/DynamicExpresso.Core/Interpreter.cs index f9430d8d..b8fdcff0 100644 --- a/src/DynamicExpresso.Core/Interpreter.cs +++ b/src/DynamicExpresso.Core/Interpreter.cs @@ -405,7 +405,16 @@ public Expression ParseAsExpression(string expressionText, internal LambdaExpression ParseAsExpression(Type delegateType, string expressionText, params string[] parametersNames) { - var lambda = ParseAs(delegateType, expressionText, parametersNames); + var delegateInfo = ReflectionExtensions.GetDelegateInfo(delegateType, parametersNames); + + // return type is object means that we have no information beforehand + // => we force it to typeof(void) so that no conversion expression is emitted by the parser + // and the actual expression type is preserved + var returnType = delegateInfo.ReturnType; + if (returnType == typeof(object)) + returnType = typeof(void); + + var lambda = ParseAsLambda(expressionText, returnType, delegateInfo.Parameters); return lambda.LambdaExpression(delegateType); } diff --git a/src/DynamicExpresso.Core/Parsing/Parser.cs b/src/DynamicExpresso.Core/Parsing/Parser.cs index e2a26da5..1ae3e6fe 100644 --- a/src/DynamicExpresso.Core/Parsing/Parser.cs +++ b/src/DynamicExpresso.Core/Parsing/Parser.cs @@ -101,7 +101,7 @@ private Expression ParseExpressionSegment(Type returnType) int errorPos = _token.pos; var expression = ParseExpressionSegment(); - if (returnType != typeof(void) && returnType != typeof(object)) + if (returnType != typeof(void)) { return GenerateConversion(expression, returnType, errorPos); } @@ -1126,13 +1126,24 @@ private MemberBinding[] ParseMemberInitializerList(Type newType) } private Expression ParseLambdaInvocation(LambdaExpression lambda, int errorPos) + { + return ParseInvocation(lambda, errorPos, ErrorMessages.ArgsIncompatibleWithLambda); + } + + private Expression ParseDelegateInvocation(Expression delegateExp, int errorPos) + { + return ParseInvocation(delegateExp, errorPos, ErrorMessages.ArgsIncompatibleWithDelegate); + } + + private Expression ParseInvocation(Expression expr, int errorPos, string error) { var args = ParseArgumentList(); - if (!PrepareDelegateInvoke(lambda.Type, ref args)) - throw CreateParseException(errorPos, ErrorMessages.ArgsIncompatibleWithLambda); + var invokeMethod = FindInvokeMethod(expr.Type); + if (invokeMethod == null || !CheckIfMethodIsApplicableAndPrepareIt(invokeMethod, args)) + throw CreateParseException(errorPos, error); - return Expression.Invoke(lambda, args); + return Expression.Invoke(expr, invokeMethod.PromotedParameters); } private Expression ParseMethodGroupInvocation(MethodGroupExpression methodGroup, int errorPos) @@ -1140,17 +1151,16 @@ private Expression ParseMethodGroupInvocation(MethodGroupExpression methodGroup, var args = ParseArgumentList(); // find the best delegates that can be used with the provided arguments - var flags = BindingFlags.Public | BindingFlags.Instance; var candidates = methodGroup.Overloads .Select(_ => new { Delegate = _, Method = _.Method, - InvokeMethods = _.GetType().FindMembers(MemberTypes.Method, flags, Type.FilterName, "Invoke").Cast(), + InvokeMethod = FindInvokeMethod(_.GetType()), }) .ToList(); - var applicableMethods = FindBestMethod(candidates.SelectMany(_ => _.InvokeMethods), args); + var applicableMethods = FindBestMethod(candidates.Select(_ => _.InvokeMethod), args); // no method found: retry with the delegate's method // (the parameters might be different, e.g. params array, default value, etc) @@ -1164,31 +1174,10 @@ private Expression ParseMethodGroupInvocation(MethodGroupExpression methodGroup, throw CreateParseException(errorPos, ErrorMessages.AmbiguousDelegateInvocation); var applicableMethod = applicableMethods[0]; - var usedDeledate = candidates.Single(_ => new[] { _.Method }.Concat(_.InvokeMethods).Any(m => m == applicableMethod.MethodBase)).Delegate; + var usedDeledate = candidates.Single(_ => new[] { _.Method, _.InvokeMethod?.MethodBase }.Any(m => m == applicableMethod.MethodBase)).Delegate; return Expression.Invoke(Expression.Constant(usedDeledate), applicableMethod.PromotedParameters); } - private Expression ParseDelegateInvocation(Expression delegateExp, int errorPos) - { - var args = ParseArgumentList(); - - if (!PrepareDelegateInvoke(delegateExp.Type, ref args)) - throw CreateParseException(errorPos, ErrorMessages.ArgsIncompatibleWithDelegate); - - return Expression.Invoke(delegateExp, args); - } - - private bool PrepareDelegateInvoke(Type type, ref Expression[] args) - { - var applicableMethods = FindMethods(type, "Invoke", false, args); - if (applicableMethods.Length != 1) - return false; - - args = applicableMethods[0].PromotedParameters; - - return true; - } - private Type ParseKnownType() { var name = _token.text; @@ -1797,6 +1786,23 @@ private MethodData[] FindMethods(Type type, string methodName, bool staticAccess return new MethodData[0]; } + private MethodData FindInvokeMethod(Type type) + { + var flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + BindingFlags.Instance | _bindingCase; + foreach (var t in SelfAndBaseTypes(type)) + { + var method = t.FindMembers(MemberTypes.Method, flags, _memberFilterCase, "Invoke") + .Cast() + .SingleOrDefault(); + + if (method != null) + return MethodData.Gen(method); + } + + return null; + } + private MethodData[] FindExtensionMethods(string methodName, Expression[] args) { var matchMethods = _arguments.GetExtensionMethods(methodName); diff --git a/test/DynamicExpresso.UnitTest/GithubIssues.cs b/test/DynamicExpresso.UnitTest/GithubIssues.cs index 8e04f274..0d590542 100644 --- a/test/DynamicExpresso.UnitTest/GithubIssues.cs +++ b/test/DynamicExpresso.UnitTest/GithubIssues.cs @@ -347,6 +347,27 @@ public void GitHub_Issue_169_quatro() Assert.AreEqual("56", result); } + [Test] + public void GitHub_Issue_185() + { + var interpreter = new Interpreter().SetVariable("a", 123L); + + // forcing the return type to object should work + // (ie a conversion expression should be emitted from long to object) + var del = interpreter.ParseAsDelegate>("a*2"); + var result = del(); + Assert.AreEqual(246, result); + } + + [Test] + public void GitHub_Issue_185_2() + { + var interpreter = new Interpreter().SetVariable("a", 123L); + var del = interpreter.ParseAsDelegate>("a*2"); + var result = del(); + Assert.AreEqual(246, result); + } + [Test] public void GitHub_Issue_191() {