Skip to content

Commit 81a4c0f

Browse files
david-garcia-garciaStefH
authored andcommitted
Make ExpressionPromoter plugable (#212)
* xx * xx * xx * xx * xx * ExpressionPromoterTests
1 parent a8e5143 commit 81a4c0f

File tree

11 files changed

+212
-81
lines changed

11 files changed

+212
-81
lines changed

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

+20-18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class ExpressionParser
2424
static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending);
2525

2626
private readonly ParsingConfig _parsingConfig;
27+
private readonly MethodFinder _methodFinder;
2728
private readonly KeywordsHelper _keywordsHelper;
2829
private readonly TextParser _textParser;
2930
private readonly Dictionary<string, object> _internals;
@@ -64,6 +65,7 @@ public ExpressionParser([CanBeNull] ParameterExpression[] parameters, [NotNull]
6465

6566
_keywordsHelper = new KeywordsHelper(_parsingConfig);
6667
_textParser = new TextParser(expression);
68+
_methodFinder = new MethodFinder(_parsingConfig);
6769
}
6870

6971
void ProcessParameters(ParameterExpression[] parameters)
@@ -130,7 +132,7 @@ public Expression Parse([CanBeNull] Type resultType, bool createParameterCtor =
130132

131133
if (resultType != null)
132134
{
133-
if ((expr = ExpressionPromoter.Promote(expr, resultType, true, false)) == null)
135+
if ((expr = _parsingConfig.ExpressionPromoter.Promote(expr, resultType, true, false)) == null)
134136
{
135137
throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType));
136138
}
@@ -352,7 +354,7 @@ Expression ParseIn()
352354

353355
var args = new[] { left };
354356

355-
if (MethodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1)
357+
if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1)
356358
{
357359
throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains));
358360
}
@@ -469,11 +471,11 @@ Expression ParseComparisonOperator()
469471
if (left.Type != right.Type)
470472
{
471473
Expression e;
472-
if ((e = ExpressionPromoter.Promote(right, left.Type, true, false)) != null)
474+
if ((e = _parsingConfig.ExpressionPromoter.Promote(right, left.Type, true, false)) != null)
473475
{
474476
right = e;
475477
}
476-
else if ((e = ExpressionPromoter.Promote(left, right.Type, true, false)) != null)
478+
else if ((e = _parsingConfig.ExpressionPromoter.Promote(left, right.Type, true, false)) != null)
477479
{
478480
left = e;
479481
}
@@ -1059,8 +1061,8 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp
10591061

10601062
if (expr1.Type != expr2.Type)
10611063
{
1062-
Expression expr1As2 = expr2 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr1, expr2.Type, true, false) : null;
1063-
Expression expr2As1 = expr1 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr2, expr1.Type, true, false) : null;
1064+
Expression expr1As2 = expr2 != Constants.NullLiteral ? _parsingConfig.ExpressionPromoter.Promote(expr1, expr2.Type, true, false) : null;
1065+
Expression expr2As1 = expr1 != Constants.NullLiteral ? _parsingConfig.ExpressionPromoter.Promote(expr2, expr1.Type, true, false) : null;
10641066
if (expr1As2 != null && expr2As1 == null)
10651067
{
10661068
expr1 = expr1As2;
@@ -1200,7 +1202,7 @@ private Expression CreateArrayInitializerExpression(List<Expression> expressions
12001202

12011203
if (newType != null)
12021204
{
1203-
return Expression.NewArrayInit(newType, expressions.Select(expression => ExpressionPromoter.Promote(expression, newType, true, true)));
1205+
return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true)));
12041206
}
12051207

12061208
return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions);
@@ -1270,7 +1272,7 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
12701272
Type expressionType = expressions[i].Type;
12711273

12721274
// Promote from Type to Nullable Type if needed
1273-
expressionsPromoted.Add(ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
1275+
expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
12741276
}
12751277

12761278
return Expression.New(ctor, expressionsPromoted, (IEnumerable<MemberInfo>)propertyInfos);
@@ -1284,7 +1286,7 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
12841286
Type expressionType = expressions[i].Type;
12851287

12861288
// Promote from Type to Nullable Type if needed
1287-
bindings[i] = Expression.Bind(property, ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
1289+
bindings[i] = Expression.Bind(property, _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
12881290
}
12891291

12901292
return Expression.MemberInit(Expression.New(type), bindings);
@@ -1295,7 +1297,7 @@ Expression ParseLambdaInvocation(LambdaExpression lambda)
12951297
int errorPos = _textParser.CurrentToken.Pos;
12961298
_textParser.NextToken();
12971299
Expression[] args = ParseArgumentList();
1298-
if (MethodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, args, out MethodBase _) != 1)
1300+
if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, args, out MethodBase _) != 1)
12991301
{
13001302
throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda);
13011303
}
@@ -1336,7 +1338,7 @@ Expression ParseTypeAccess(Type type)
13361338
}
13371339
}
13381340

1339-
switch (MethodFinder.FindBestMethod(type.GetConstructors(), args, out MethodBase method))
1341+
switch (_methodFinder.FindBestMethod(type.GetConstructors(), args, out MethodBase method))
13401342
{
13411343
case 0:
13421344
if (args.Length == 1)
@@ -1443,7 +1445,7 @@ Expression ParseMemberAccess(Type type, Expression instance)
14431445
}
14441446

14451447
Expression[] args = ParseArgumentList();
1446-
switch (MethodFinder.FindMethod(type, id, instance == null, args, out MethodBase mb))
1448+
switch (_methodFinder.FindMethod(type, id, instance == null, args, out MethodBase mb))
14471449
{
14481450
case 0:
14491451
throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type));
@@ -1587,13 +1589,13 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa
15871589
_it = outerIt;
15881590
_parent = oldParent;
15891591

1590-
if (!MethodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, args))
1592+
if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, args))
15911593
{
15921594
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName);
15931595
}
15941596

15951597
Type callType = typeof(Enumerable);
1596-
if (isQueryable && MethodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, args))
1598+
if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, args))
15971599
{
15981600
callType = typeof(Queryable);
15991601
}
@@ -1693,7 +1695,7 @@ Expression ParseElementAccess(Expression expr)
16931695
{
16941696
throw ParseError(errorPos, Res.CannotIndexMultiDimArray);
16951697
}
1696-
Expression index = ExpressionPromoter.Promote(args[0], typeof(int), true, false);
1698+
Expression index = _parsingConfig.ExpressionPromoter.Promote(args[0], typeof(int), true, false);
16971699

16981700
if (index == null)
16991701
{
@@ -1703,7 +1705,7 @@ Expression ParseElementAccess(Expression expr)
17031705
return Expression.ArrayIndex(expr, index);
17041706
}
17051707

1706-
switch (MethodFinder.FindIndexer(expr.Type, args, out var mb))
1708+
switch (_methodFinder.FindIndexer(expr.Type, args, out var mb))
17071709
{
17081710
case 0:
17091711
throw ParseError(errorPos, Res.NoApplicableIndexer,
@@ -1758,7 +1760,7 @@ void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr,
17581760
{
17591761
Expression[] args = { expr };
17601762

1761-
if (!MethodFinder.ContainsMethod(signatures, "F", false, args))
1763+
if (!_methodFinder.ContainsMethod(signatures, "F", false, args))
17621764
{
17631765
throw IncompatibleOperandError(opName, expr, errorPos);
17641766
}
@@ -1770,7 +1772,7 @@ void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left
17701772
{
17711773
Expression[] args = { left, right };
17721774

1773-
if (!MethodFinder.ContainsMethod(signatures, "F", false, args))
1775+
if (!_methodFinder.ContainsMethod(signatures, "F", false, args))
17741776
{
17751777
throw IncompatibleOperandsError(opName, left, right, errorPos);
17761778
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
namespace System.Linq.Dynamic.Core.Parser
55
{
6-
internal static class ExpressionPromoter
6+
internal class ExpressionPromoter : IExpressionPromoter
77
{
8-
public static Expression Promote(Expression expr, Type type, bool exact, bool convertExpr)
8+
/// <inheritdoc cref="IExpressionPromoter.Promote(Expression, Type, bool, bool)"/>
9+
public virtual Expression Promote(Expression expr, Type type, bool exact, bool convertExpr)
910
{
1011
if (expr.Type == type)
1112
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System.Linq.Expressions;
2+
3+
namespace System.Linq.Dynamic.Core.Parser
4+
{
5+
/// <summary>
6+
/// Expression promoter is used to promote object or value types
7+
/// to their destination type when an automatic promotion is available
8+
/// such as: int to int?
9+
/// </summary>
10+
public interface IExpressionPromoter
11+
{
12+
/// <summary>
13+
/// Promote an expression
14+
/// </summary>
15+
/// <param name="expr">Source expression</param>
16+
/// <param name="type">Destionation data type to promote</param>
17+
/// <param name="exact">If the match must be exact</param>
18+
/// <param name="convertExpr">Convert expression</param>
19+
/// <returns></returns>
20+
Expression Promote(Expression expr, Type type, bool exact, bool convertExpr);
21+
}
22+
}

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

+23-12
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,25 @@
44

55
namespace System.Linq.Dynamic.Core.Parser.SupportedMethods
66
{
7-
internal static class MethodFinder
7+
internal class MethodFinder
88
{
9-
public static bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args)
9+
private readonly ParsingConfig _parsingConfig;
10+
11+
/// <summary>
12+
/// Get an instance
13+
/// </summary>
14+
/// <param name="parsingConfig"></param>
15+
public MethodFinder(ParsingConfig parsingConfig)
16+
{
17+
_parsingConfig = parsingConfig;
18+
}
19+
20+
public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args)
1021
{
1122
return FindMethod(type, methodName, staticAccess, args, out var _) == 1;
1223
}
1324

14-
public static int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method)
25+
public int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method)
1526
{
1627
#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD)
1728
BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance);
@@ -39,7 +50,7 @@ public static int FindMethod(Type type, string methodName, bool staticAccess, Ex
3950
return 0;
4051
}
4152

42-
public static int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
53+
public int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
4354
{
4455
MethodData[] applicable = methods.
4556
Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }).
@@ -73,7 +84,7 @@ public static int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] a
7384
return applicable.Length;
7485
}
7586

76-
public static int FindIndexer(Type type, Expression[] args, out MethodBase method)
87+
public int FindIndexer(Type type, Expression[] args, out MethodBase method)
7788
{
7889
foreach (Type t in SelfAndBaseTypes(type))
7990
{
@@ -99,7 +110,7 @@ public static int FindIndexer(Type type, Expression[] args, out MethodBase metho
99110
return 0;
100111
}
101112

102-
static bool IsApplicable(MethodData method, Expression[] args)
113+
bool IsApplicable(MethodData method, Expression[] args)
103114
{
104115
if (method.Parameters.Length != args.Length)
105116
{
@@ -115,7 +126,7 @@ static bool IsApplicable(MethodData method, Expression[] args)
115126
return false;
116127
}
117128

118-
Expression promoted = ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
129+
Expression promoted = this._parsingConfig.ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
119130
if (promoted == null)
120131
{
121132
return false;
@@ -126,7 +137,7 @@ static bool IsApplicable(MethodData method, Expression[] args)
126137
return true;
127138
}
128139

129-
static bool IsBetterThan(Expression[] args, MethodData first, MethodData second)
140+
bool IsBetterThan(Expression[] args, MethodData first, MethodData second)
130141
{
131142
bool better = false;
132143
for (int i = 0; i < args.Length; i++)
@@ -158,7 +169,7 @@ static bool IsBetterThan(Expression[] args, MethodData first, MethodData second)
158169
// Return "First" if s -> t1 is a better conversion than s -> t2
159170
// Return "Second" if s -> t2 is a better conversion than s -> t1
160171
// Return "Both" if neither conversion is better
161-
static CompareConversionType CompareConversions(Type source, Type first, Type second)
172+
CompareConversionType CompareConversions(Type source, Type first, Type second)
162173
{
163174
if (first == second)
164175
{
@@ -197,7 +208,7 @@ static CompareConversionType CompareConversions(Type source, Type first, Type se
197208
return CompareConversionType.Both;
198209
}
199210

200-
static IEnumerable<Type> SelfAndBaseTypes(Type type)
211+
IEnumerable<Type> SelfAndBaseTypes(Type type)
201212
{
202213
if (type.GetTypeInfo().IsInterface)
203214
{
@@ -208,7 +219,7 @@ static IEnumerable<Type> SelfAndBaseTypes(Type type)
208219
return SelfAndBaseClasses(type);
209220
}
210221

211-
static IEnumerable<Type> SelfAndBaseClasses(Type type)
222+
IEnumerable<Type> SelfAndBaseClasses(Type type)
212223
{
213224
while (type != null)
214225
{
@@ -217,7 +228,7 @@ static IEnumerable<Type> SelfAndBaseClasses(Type type)
217228
}
218229
}
219230

220-
static void AddInterface(List<Type> types, Type type)
231+
void AddInterface(List<Type> types, Type type)
221232
{
222233
if (!types.Contains(type))
223234
{

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

+22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Linq.Dynamic.Core.CustomTypeProviders;
2+
using System.Linq.Dynamic.Core.Parser;
23

34
namespace System.Linq.Dynamic.Core
45
{
@@ -22,6 +23,8 @@ public class ParsingConfig
2223

2324
private IDynamicLinkCustomTypeProvider _customTypeProvider;
2425

26+
private IExpressionPromoter _expressionPromoter;
27+
2528
/// <summary>
2629
/// Gets or sets the <see cref="IDynamicLinkCustomTypeProvider"/>.
2730
/// </summary>
@@ -46,6 +49,25 @@ public IDynamicLinkCustomTypeProvider CustomTypeProvider
4649
}
4750
}
4851

52+
/// <summary>
53+
/// Gets or sets the <see cref="IExpressionPromoter"/>.
54+
/// </summary>
55+
public IExpressionPromoter ExpressionPromoter
56+
{
57+
get
58+
{
59+
return _expressionPromoter ?? (_expressionPromoter = new ExpressionPromoter());
60+
}
61+
62+
set
63+
{
64+
if (_expressionPromoter != value)
65+
{
66+
_expressionPromoter = value;
67+
}
68+
}
69+
}
70+
4971
/// <summary>
5072
/// Determines if the context keywords (it, parent, and root) are valid and usable inside a Dynamic Linq string expression.
5173
/// Does not affect the usability of the equivalent context symbols ($, ^ and ~).

0 commit comments

Comments
 (0)