Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow explicit return type object #186

Merged
merged 9 commits into from
Nov 25, 2021
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