Skip to content

Commit 3324f21

Browse files
Add support to param array
1 parent 90b1ddf commit 3324f21

File tree

5 files changed

+192
-36
lines changed

5 files changed

+192
-36
lines changed

Z.Dynamic.Core.Lab/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class Program
66
{
77
static void Main(string[] args)
88
{
9-
Request_AddMethod.Execute();
9+
Request_DynamicLinqType.Execute();
1010
}
1111
}
1212
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Linq.Dynamic.Core;
6+
using System.Linq.Dynamic.Core.CustomTypeProviders;
7+
using System.Linq.Dynamic.Core.Parser;
8+
using System.Linq.Expressions;
9+
using System.Text;
10+
11+
namespace Z.Dynamic.Core.Lab
12+
{
13+
public class Request_DynamicLinqType
14+
{
15+
16+
[DynamicLinqType]
17+
public static class Utils
18+
{
19+
public static string[] ConvertToArray(params string[] values)
20+
{
21+
if (values == null)
22+
{
23+
return new string[0];
24+
}
25+
26+
return values.ToArray();
27+
}
28+
}
29+
30+
public static void Execute()
31+
{
32+
var externals = new Dictionary<string, object>
33+
{
34+
{"Users", false},
35+
{"x", new[] {"a", "b", "c"}},
36+
{"y", 2},
37+
};
38+
39+
var t1 = Utils.ConvertToArray(null);
40+
41+
string query = "Utils.ConvertToArray(null)";
42+
LambdaExpression expression = DynamicExpressionParser.ParseLambda(null, query, externals);
43+
Delegate del = expression.Compile();
44+
var result = del.DynamicInvoke();
45+
46+
}
47+
}
48+
}

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ Expression ParseIn()
350350

351351
var args = new[] { left };
352352

353-
if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1)
353+
if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, ref args, out MethodBase containsSignature) != 1)
354354
{
355355
throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains), string.Join(",", args.Select(a => a.Type.Name).ToArray()));
356356
}
@@ -1476,7 +1476,7 @@ Expression ParseLambdaInvocation(LambdaExpression lambda)
14761476
int errorPos = _textParser.CurrentToken.Pos;
14771477
_textParser.NextToken();
14781478
Expression[] args = ParseArgumentList();
1479-
if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, args, out MethodBase _) != 1)
1479+
if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, ref args, out MethodBase _) != 1)
14801480
{
14811481
throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda);
14821482
}
@@ -1528,7 +1528,7 @@ Expression ParseTypeAccess(Type type)
15281528
var constructorsWithOutPointerArguments = type.GetConstructors()
15291529
.Where(c => !c.GetParameters().Any(p => p.ParameterType.GetTypeInfo().IsPointer))
15301530
.ToArray();
1531-
switch (_methodFinder.FindBestMethod(constructorsWithOutPointerArguments, args, out MethodBase method))
1531+
switch (_methodFinder.FindBestMethod(constructorsWithOutPointerArguments, ref args, out MethodBase method))
15321532
{
15331533
case 0:
15341534
if (args.Length == 1)
@@ -1634,7 +1634,7 @@ Expression ParseMemberAccess(Type type, Expression instance)
16341634
}
16351635

16361636
Expression[] args = ParseArgumentList();
1637-
switch (_methodFinder.FindMethod(type, id, instance == null, args, out MethodBase mb))
1637+
switch (_methodFinder.FindMethod(type, id, instance == null, ref args, out MethodBase mb))
16381638
{
16391639
case 0:
16401640
throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type));
@@ -1731,7 +1731,7 @@ bool TryParseDictionary(Expression instance, string methodName, Type type, out E
17311731

17321732
Expression[] args = ParseArgumentList();
17331733

1734-
if (!_methodFinder.ContainsMethod(typeof(IDictionarySignatures), methodName, false, args))
1734+
if (!_methodFinder.ContainsMethod(typeof(IDictionarySignatures), methodName, false, ref args))
17351735
{
17361736
return false;
17371737
}
@@ -1765,13 +1765,13 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa
17651765
_it = outerIt;
17661766
_parent = oldParent;
17671767

1768-
if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, args))
1768+
if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, ref args))
17691769
{
17701770
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName, string.Join(",", args.Select(a => a.Type.Name).ToArray()));
17711771
}
17721772

17731773
Type callType = typeof(Enumerable);
1774-
if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, args))
1774+
if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, ref args))
17751775
{
17761776
callType = typeof(Queryable);
17771777
}
@@ -1967,7 +1967,7 @@ void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr,
19671967
{
19681968
Expression[] args = { expr };
19691969

1970-
if (!_methodFinder.ContainsMethod(signatures, "F", false, args))
1970+
if (!_methodFinder.ContainsMethod(signatures, "F", false, ref args))
19711971
{
19721972
throw IncompatibleOperandError(opName, expr, errorPos);
19731973
}
@@ -2000,12 +2000,12 @@ void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref E
20002000
if (nativeOperation != null)
20012001
{
20022002
// first try left operand's equality operators
2003-
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, args);
2003+
found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, ref args);
20042004
if (!found)
2005-
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, args);
2005+
found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, ref args);
20062006
}
20072007

2008-
if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, args))
2008+
if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, ref args))
20092009
{
20102010
throw IncompatibleOperandsError(opName, left, right, errorPos);
20112011
}

src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ public MethodFinder(ParsingConfig parsingConfig)
1717
_parsingConfig = parsingConfig;
1818
}
1919

20-
public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args)
20+
public bool ContainsMethod(Type type, string methodName, bool staticAccess, ref Expression[] args)
2121
{
22-
return FindMethod(type, methodName, staticAccess, args, out _) == 1;
22+
return FindMethod(type, methodName, staticAccess, ref args, out _) == 1;
2323
}
2424

25-
public int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method)
25+
public int FindMethod(Type type, string methodName, bool staticAccess, ref Expression[] args, out MethodBase method)
2626
{
2727
#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
2828
BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
2929
foreach (Type t in SelfAndBaseTypes(type))
3030
{
3131
MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName);
32-
int count = FindBestMethod(members.Cast<MethodBase>(), args, out method);
32+
int count = FindBestMethod(members.Cast<MethodBase>(), ref args, out method);
3333
if (count != 0)
3434
{
3535
return count;
@@ -39,7 +39,7 @@ public int FindMethod(Type type, string methodName, bool staticAccess, Expressio
3939
foreach (Type t in SelfAndBaseTypes(type))
4040
{
4141
MethodInfo[] methods = t.GetTypeInfo().DeclaredMethods.Where(x => (x.IsStatic || !staticAccess) && x.Name.ToLowerInvariant() == methodName.ToLowerInvariant()).ToArray();
42-
int count = FindBestMethod(methods, args, out method);
42+
int count = FindBestMethod(methods, ref args, out method);
4343
if (count != 0)
4444
{
4545
return count;
@@ -50,16 +50,20 @@ public int FindMethod(Type type, string methodName, bool staticAccess, Expressio
5050
return 0;
5151
}
5252

53-
public int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
53+
public int FindBestMethod(IEnumerable<MethodBase> methods, ref Expression[] args, out MethodBase method)
5454
{
55+
// passing args by reference is now required with the params array support.
56+
57+
var inlineArgs = args;
58+
5559
MethodData[] applicable = methods
5660
.Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() })
57-
.Where(m => IsApplicable(m, args))
61+
.Where(m => IsApplicable(m, inlineArgs))
5862
.ToArray();
5963

6064
if (applicable.Length > 1)
6165
{
62-
applicable = applicable.Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))).ToArray();
66+
applicable = applicable.Where(m => applicable.All(n => m == n || IsBetterThan(inlineArgs, m, n))).ToArray();
6367
}
6468

6569
if (args.Length == 2 && applicable.Length > 1 && (args[0].Type == typeof(Guid?) || args[1].Type == typeof(Guid?)))
@@ -70,11 +74,8 @@ public int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, ou
7074
if (applicable.Length == 1)
7175
{
7276
MethodData md = applicable[0];
73-
for (int i = 0; i < args.Length; i++)
74-
{
75-
args[i] = md.Args[i];
76-
}
7777
method = md.MethodBase;
78+
args = md.Args;
7879
}
7980
else
8081
{
@@ -98,7 +99,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase method)
9899
#else
99100
Select(p => (MethodBase)p.GetMethod);
100101
#endif
101-
int count = FindBestMethod(methods, args, out method);
102+
int count = FindBestMethod(methods, ref args, out method);
102103
if (count != 0)
103104
{
104105
return count;
@@ -112,27 +113,63 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase method)
112113

113114
bool IsApplicable(MethodData method, Expression[] args)
114115
{
115-
if (method.Parameters.Length != args.Length)
116+
// parameter count must be equal or the last one must be a param array
117+
bool isParamArray = method.Parameters.Length > 0 && method.Parameters.Last().IsDefined(typeof(ParamArrayAttribute), false);
118+
if (method.Parameters.Length != args.Length && !isParamArray)
116119
{
117120
return false;
118121
}
119122

120-
Expression[] promotedArgs = new Expression[args.Length];
121-
for (int i = 0; i < args.Length; i++)
123+
Expression[] promotedArgs = new Expression[method.Parameters.Length];
124+
for (int i = 0; i < method.Parameters.Length; i++)
122125
{
123-
ParameterInfo pi = method.Parameters[i];
124-
if (pi.IsOut)
126+
if (isParamArray && i == method.Parameters.Length - 1)
125127
{
126-
return false;
127-
}
128+
if (method.Parameters.Length == args.Length + 1
129+
|| (method.Parameters.Length == args.Length && args[i] is ConstantExpression constantExpression && constantExpression.Value == null))
130+
{
131+
promotedArgs[promotedArgs.Length - 1] = Expression.Constant(null, method.Parameters.Last().ParameterType);
132+
}
133+
else
134+
{
135+
var paramType = method.Parameters.Last().ParameterType;
136+
var paramElementType = paramType.GetElementType();
137+
138+
List<Expression> arrayInitializerExpressions = new List<Expression>();
139+
140+
for (int j = method.Parameters.Length - 1; j < args.Length; j++)
141+
{
142+
Expression promoted = this._parsingConfig.ExpressionPromoter.Promote(args[j], paramElementType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
143+
if (promoted == null)
144+
{
145+
return false;
146+
}
128147

129-
Expression promoted = this._parsingConfig.ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
130-
if (promoted == null)
148+
arrayInitializerExpressions.Add(promoted);
149+
}
150+
151+
var paramExpression = Expression.NewArrayInit(paramElementType, arrayInitializerExpressions);
152+
153+
promotedArgs[promotedArgs.Length - 1] = paramExpression;
154+
}
155+
}
156+
else
131157
{
132-
return false;
158+
ParameterInfo pi = method.Parameters[i];
159+
if (pi.IsOut)
160+
{
161+
return false;
162+
}
163+
164+
Expression promoted = this._parsingConfig.ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
165+
if (promoted == null)
166+
{
167+
return false;
168+
}
169+
promotedArgs[i] = promoted;
133170
}
134-
promotedArgs[i] = promoted;
135171
}
172+
136173
method.Args = promotedArgs;
137174
return true;
138175
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System.Linq.Dynamic.Core.CustomTypeProviders;
2+
using NFluent;
3+
using Xunit;
4+
5+
namespace System.Linq.Dynamic.Core.Tests.Parser
6+
{
7+
public class DynamicLinqTypeTest
8+
{
9+
[DynamicLinqType]
10+
public static class Utils
11+
{
12+
public static string[] ConvertToArray(params string[] values)
13+
{
14+
if (values == null)
15+
{
16+
return null;
17+
}
18+
19+
return values.ToArray();
20+
}
21+
}
22+
23+
24+
[Fact]
25+
public void ParamArray_EmptyValue()
26+
{
27+
var query = "Utils.ConvertToArray()";
28+
var expression = DynamicExpressionParser.ParseLambda(null, query, null);
29+
var del = expression.Compile();
30+
var result = (string[])del.DynamicInvoke();
31+
32+
Check.That(result).IsNull();
33+
}
34+
35+
[Fact]
36+
public void ParamArray_NullValue()
37+
{
38+
var query = "Utils.ConvertToArray(null)";
39+
var expression = DynamicExpressionParser.ParseLambda(null, query, null);
40+
var del = expression.Compile();
41+
var result = (string[]) del.DynamicInvoke();
42+
43+
Check.That(result).IsNull();
44+
}
45+
46+
[Fact]
47+
public void ParamArray_WithManyValue()
48+
{
49+
var query = "Utils.ConvertToArray(\"a\", \"b\")";
50+
var expression = DynamicExpressionParser.ParseLambda(null, query, null);
51+
var del = expression.Compile();
52+
var result = (string[]) del.DynamicInvoke();
53+
54+
Check.That(result.Length).Equals(2);
55+
Check.That(result[0]).Equals("a");
56+
Check.That(result[1]).Equals("b");
57+
}
58+
59+
[Fact]
60+
public void ParamArray_WithSingleValue()
61+
{
62+
var query = "Utils.ConvertToArray(\"a\")";
63+
var expression = DynamicExpressionParser.ParseLambda(null, query, null);
64+
var del = expression.Compile();
65+
var result = (string[]) del.DynamicInvoke();
66+
67+
Check.That(result.Length).Equals(1);
68+
Check.That(result[0]).Equals("a");
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)