Skip to content

Commit

Permalink
Allow explicit return type object (#186)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
metoule authored Nov 25, 2021
1 parent afaeaaf commit ae65ecb
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 30 deletions.
11 changes: 10 additions & 1 deletion src/DynamicExpresso.Core/Interpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,16 @@ public Expression<TDelegate> ParseAsExpression<TDelegate>(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);
}

Expand Down
64 changes: 35 additions & 29 deletions src/DynamicExpresso.Core/Parsing/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -1126,31 +1126,41 @@ 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)
{
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<MethodInfo>(),
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)
Expand All @@ -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;
Expand Down Expand Up @@ -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<MethodBase>()
.SingleOrDefault();

if (method != null)
return MethodData.Gen(method);
}

return null;
}

private MethodData[] FindExtensionMethods(string methodName, Expression[] args)
{
var matchMethods = _arguments.GetExtensionMethods(methodName);
Expand Down
21 changes: 21 additions & 0 deletions test/DynamicExpresso.UnitTest/GithubIssues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Func<object>>("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<Func<dynamic>>("a*2");
var result = del();
Assert.AreEqual(246, result);
}

[Test]
public void GitHub_Issue_191()
{
Expand Down

0 comments on commit ae65ecb

Please sign in to comment.