Skip to content

Commit 833008a

Browse files
committed
Merge branch 'master' into stef-PredefinedMethodsHelper
2 parents e5a101c + 6cd8fbe commit 833008a

File tree

13 files changed

+614
-243
lines changed

13 files changed

+614
-243
lines changed

src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs

Lines changed: 49 additions & 18 deletions
Large diffs are not rendered by default.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, boo
328328
{
329329
var expressions = CollectExpressions(addSelf, sourceExpression);
330330

331-
if (expressions.Count == 1 && !(expressions[0] is MethodCallExpression))
331+
if (expressions.Count == 1 && expressions[0] is not MethodCallExpression)
332332
{
333333
generatedExpression = sourceExpression;
334334
return false;

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

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,11 @@ internal IList<DynamicOrdering> ParseOrdering(bool forceThenBy = false)
220220
{
221221
var expr = ParseConditionalOperator();
222222
var ascending = true;
223-
if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending"))
223+
if (TokenIsIdentifier("asc") || TokenIsIdentifier("ascending"))
224224
{
225225
_textParser.NextToken();
226226
}
227-
else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending"))
227+
else if (TokenIsIdentifier("desc") || TokenIsIdentifier("descending"))
228228
{
229229
_textParser.NextToken();
230230
ascending = false;
@@ -339,19 +339,34 @@ private Expression ParseAndOperator()
339339
return left;
340340
}
341341

342-
// in operator for literals - example: "x in (1,2,3,4)"
343-
// in operator to mimic contains - example: "x in @0", compare to @0.Contains(x)
344-
// Adapted from ticket submitted by github user mlewis9548
342+
// "in" / "not in" / "not_in" operator for literals - example: "x in (1,2,3,4)"
343+
// "in" / "not in" / "not_in" operator to mimic contains - example: "x in @0", compare to @0.Contains(x)
345344
private Expression ParseIn()
346345
{
347346
Expression left = ParseLogicalAndOrOperator();
348347
Expression accumulate = left;
349348

350-
while (TokenIdentifierIs("in"))
349+
while (_textParser.TryGetToken(["in", "not_in", "not"], [TokenId.Exclamation], out var token))
351350
{
352-
var op = _textParser.CurrentToken;
351+
var not = false;
352+
if (token.Text == "not_in")
353+
{
354+
not = true;
355+
}
356+
else if (token.Text == "not" || token.Id == TokenId.Exclamation)
357+
{
358+
not = true;
359+
360+
_textParser.NextToken();
361+
362+
if (!TokenIsIdentifier("in"))
363+
{
364+
throw ParseError(token.Pos, Res.TokenExpected, "in");
365+
}
366+
}
353367

354368
_textParser.NextToken();
369+
355370
if (_textParser.CurrentToken.Id == TokenId.OpenParen) // literals (or other inline list)
356371
{
357372
while (_textParser.CurrentToken.Id != TokenId.CloseParen)
@@ -366,18 +381,18 @@ private Expression ParseIn()
366381
{
367382
if (right is ConstantExpression constantExprRight)
368383
{
369-
right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExprRight);
384+
right = ParseEnumToConstantExpression(token.Pos, left.Type, constantExprRight);
370385
}
371386
else if (_expressionHelper.TryUnwrapAsConstantExpression(right, out var unwrappedConstantExprRight))
372387
{
373-
right = ParseEnumToConstantExpression(op.Pos, left.Type, unwrappedConstantExprRight);
388+
right = ParseEnumToConstantExpression(token.Pos, left.Type, unwrappedConstantExprRight);
374389
}
375390
}
376391

377392
// else, check for direct type match
378393
else if (left.Type != right.Type)
379394
{
380-
CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, op.Pos);
395+
CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, token.Pos);
381396
}
382397

383398
if (accumulate.Type != typeof(bool))
@@ -391,7 +406,7 @@ private Expression ParseIn()
391406

392407
if (_textParser.CurrentToken.Id == TokenId.End)
393408
{
394-
throw ParseError(op.Pos, Res.CloseParenOrCommaExpected);
409+
throw ParseError(token.Pos, Res.CloseParenOrCommaExpected);
395410
}
396411
}
397412

@@ -415,7 +430,12 @@ private Expression ParseIn()
415430
}
416431
else
417432
{
418-
throw ParseError(op.Pos, Res.OpenParenOrIdentifierExpected);
433+
throw ParseError(token.Pos, Res.OpenParenOrIdentifierExpected);
434+
}
435+
436+
if (not)
437+
{
438+
accumulate = Expression.Not(accumulate);
419439
}
420440
}
421441

@@ -761,7 +781,7 @@ private Expression ParseAdditive()
761781
private Expression ParseArithmetic()
762782
{
763783
Expression left = ParseUnary();
764-
while (_textParser.CurrentToken.Id is TokenId.Asterisk or TokenId.Slash or TokenId.Percent || TokenIdentifierIs("mod"))
784+
while (_textParser.CurrentToken.Id is TokenId.Asterisk or TokenId.Slash or TokenId.Percent || TokenIsIdentifier("mod"))
765785
{
766786
Token op = _textParser.CurrentToken;
767787
_textParser.NextToken();
@@ -789,11 +809,11 @@ private Expression ParseArithmetic()
789809
// -, !, not unary operators
790810
private Expression ParseUnary()
791811
{
792-
if (_textParser.CurrentToken.Id == TokenId.Minus || _textParser.CurrentToken.Id == TokenId.Exclamation || TokenIdentifierIs("not"))
812+
if (_textParser.CurrentToken.Id == TokenId.Minus || _textParser.CurrentToken.Id == TokenId.Exclamation || TokenIsIdentifier("not"))
793813
{
794814
Token op = _textParser.CurrentToken;
795815
_textParser.NextToken();
796-
if (op.Id == TokenId.Minus && (_textParser.CurrentToken.Id == TokenId.IntegerLiteral || _textParser.CurrentToken.Id == TokenId.RealLiteral))
816+
if (op.Id == TokenId.Minus && _textParser.CurrentToken.Id is TokenId.IntegerLiteral or TokenId.RealLiteral)
797817
{
798818
_textParser.CurrentToken.Text = "-" + _textParser.CurrentToken.Text;
799819
_textParser.CurrentToken.Pos = op.Pos;
@@ -1447,7 +1467,7 @@ private Expression ParseNew()
14471467
if (!arrayInitializer)
14481468
{
14491469
string? propName;
1450-
if (TokenIdentifierIs("as"))
1470+
if (TokenIsIdentifier("as"))
14511471
{
14521472
_textParser.NextToken();
14531473
propName = GetIdentifierAs();
@@ -2531,11 +2551,11 @@ private static Exception IncompatibleOperandsError(string opName, Expression lef
25312551
#endif
25322552
}
25332553

2534-
private bool TokenIdentifierIs(string id)
2554+
private bool TokenIsIdentifier(string id)
25352555
{
2536-
return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase);
2556+
return _textParser.TokenIsIdentifier(id);
25372557
}
2538-
2558+
25392559
private string GetIdentifier()
25402560
{
25412561
_textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected);

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

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ internal class MethodFinder
1010
{
1111
private readonly ParsingConfig _parsingConfig;
1212
private readonly IExpressionHelper _expressionHelper;
13+
private readonly IDictionary<Type, MethodInfo[]> _cachedMethods;
1314

1415
/// <summary>
1516
/// #794
@@ -43,19 +44,32 @@ public MethodFinder(ParsingConfig parsingConfig, IExpressionHelper expressionHel
4344
{
4445
_parsingConfig = Check.NotNull(parsingConfig);
4546
_expressionHelper = Check.NotNull(expressionHelper);
47+
_cachedMethods = new Dictionary<Type, MethodInfo[]>
48+
{
49+
{ typeof(Enumerable), typeof(Enumerable).GetMethods().Where(m => !m.IsGenericMethodDefinition).ToArray() },
50+
{ typeof(Queryable), typeof(Queryable).GetMethods().Where(m => !m.IsGenericMethodDefinition).ToArray() }
51+
};
4652
}
4753

4854
public bool TryFindAggregateMethod(Type callType, string methodName, Type parameterType, [NotNullWhen(true)] out MethodInfo? aggregateMethod)
4955
{
50-
aggregateMethod = callType
51-
.GetMethods()
52-
.Where(m => m.Name == methodName && !m.IsGenericMethodDefinition)
53-
.SelectMany(m => m.GetParameters(), (m, p) => new { Method = m, Parameter = p })
54-
.Where(x => x.Parameter.ParameterType == parameterType)
55-
.Select(x => x.Method)
56-
.FirstOrDefault();
57-
58-
return aggregateMethod != null;
56+
var nonGenericMethodsByName = _cachedMethods[callType]
57+
.Where(m => m.Name == methodName)
58+
.ToArray();
59+
60+
if (TypeHelper.TryGetAsEnumerable(parameterType, out var parameterTypeAsEnumerable))
61+
{
62+
aggregateMethod = nonGenericMethodsByName
63+
.SelectMany(m => m.GetParameters(), (m, p) => new { Method = m, Parameter = p })
64+
.Where(x => x.Parameter.ParameterType == parameterTypeAsEnumerable)
65+
.Select(x => x.Method)
66+
.FirstOrDefault();
67+
68+
return aggregateMethod != null;
69+
}
70+
71+
aggregateMethod = null;
72+
return false;
5973
}
6074

6175
public bool CheckAggregateMethodAndTryUpdateArgsToMatchMethodArgs(string methodName, ref Expression[] args)

0 commit comments

Comments
 (0)