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

Make ExpressionPromoter plugable #212

Merged
merged 6 commits into from
Oct 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class ExpressionParser
static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending);

private readonly ParsingConfig _parsingConfig;
private readonly MethodFinder _methodFinder;
private readonly KeywordsHelper _keywordsHelper;
private readonly TextParser _textParser;
private readonly Dictionary<string, object> _internals;
Expand Down Expand Up @@ -64,6 +65,7 @@ public ExpressionParser([CanBeNull] ParameterExpression[] parameters, [NotNull]

_keywordsHelper = new KeywordsHelper(_parsingConfig);
_textParser = new TextParser(expression);
_methodFinder = new MethodFinder(_parsingConfig);
}

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

if (resultType != null)
{
if ((expr = ExpressionPromoter.Promote(expr, resultType, true, false)) == null)
if ((expr = _parsingConfig.ExpressionPromoter.Promote(expr, resultType, true, false)) == null)
{
throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType));
}
Expand Down Expand Up @@ -352,7 +354,7 @@ Expression ParseIn()

var args = new[] { left };

if (MethodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1)
if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, args, out MethodBase containsSignature) != 1)
{
throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains));
}
Expand Down Expand Up @@ -469,11 +471,11 @@ Expression ParseComparisonOperator()
if (left.Type != right.Type)
{
Expression e;
if ((e = ExpressionPromoter.Promote(right, left.Type, true, false)) != null)
if ((e = _parsingConfig.ExpressionPromoter.Promote(right, left.Type, true, false)) != null)
{
right = e;
}
else if ((e = ExpressionPromoter.Promote(left, right.Type, true, false)) != null)
else if ((e = _parsingConfig.ExpressionPromoter.Promote(left, right.Type, true, false)) != null)
{
left = e;
}
Expand Down Expand Up @@ -1059,8 +1061,8 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp

if (expr1.Type != expr2.Type)
{
Expression expr1As2 = expr2 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr1, expr2.Type, true, false) : null;
Expression expr2As1 = expr1 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr2, expr1.Type, true, false) : null;
Expression expr1As2 = expr2 != Constants.NullLiteral ? _parsingConfig.ExpressionPromoter.Promote(expr1, expr2.Type, true, false) : null;
Expression expr2As1 = expr1 != Constants.NullLiteral ? _parsingConfig.ExpressionPromoter.Promote(expr2, expr1.Type, true, false) : null;
if (expr1As2 != null && expr2As1 == null)
{
expr1 = expr1As2;
Expand Down Expand Up @@ -1200,7 +1202,7 @@ private Expression CreateArrayInitializerExpression(List<Expression> expressions

if (newType != null)
{
return Expression.NewArrayInit(newType, expressions.Select(expression => ExpressionPromoter.Promote(expression, newType, true, true)));
return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true)));
}

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

// Promote from Type to Nullable Type if needed
expressionsPromoted.Add(ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
}

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

// Promote from Type to Nullable Type if needed
bindings[i] = Expression.Bind(property, ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
bindings[i] = Expression.Bind(property, _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyType, true, true));
}

return Expression.MemberInit(Expression.New(type), bindings);
Expand All @@ -1295,7 +1297,7 @@ Expression ParseLambdaInvocation(LambdaExpression lambda)
int errorPos = _textParser.CurrentToken.Pos;
_textParser.NextToken();
Expression[] args = ParseArgumentList();
if (MethodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, args, out MethodBase _) != 1)
if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, args, out MethodBase _) != 1)
{
throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda);
}
Expand Down Expand Up @@ -1336,7 +1338,7 @@ Expression ParseTypeAccess(Type type)
}
}

switch (MethodFinder.FindBestMethod(type.GetConstructors(), args, out MethodBase method))
switch (_methodFinder.FindBestMethod(type.GetConstructors(), args, out MethodBase method))
{
case 0:
if (args.Length == 1)
Expand Down Expand Up @@ -1443,7 +1445,7 @@ Expression ParseMemberAccess(Type type, Expression instance)
}

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

if (!MethodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, args))
if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, args))
{
throw ParseError(errorPos, Res.NoApplicableAggregate, methodName);
}

Type callType = typeof(Enumerable);
if (isQueryable && MethodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, args))
if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, args))
{
callType = typeof(Queryable);
}
Expand Down Expand Up @@ -1693,7 +1695,7 @@ Expression ParseElementAccess(Expression expr)
{
throw ParseError(errorPos, Res.CannotIndexMultiDimArray);
}
Expression index = ExpressionPromoter.Promote(args[0], typeof(int), true, false);
Expression index = _parsingConfig.ExpressionPromoter.Promote(args[0], typeof(int), true, false);

if (index == null)
{
Expand All @@ -1703,7 +1705,7 @@ Expression ParseElementAccess(Expression expr)
return Expression.ArrayIndex(expr, index);
}

switch (MethodFinder.FindIndexer(expr.Type, args, out var mb))
switch (_methodFinder.FindIndexer(expr.Type, args, out var mb))
{
case 0:
throw ParseError(errorPos, Res.NoApplicableIndexer,
Expand Down Expand Up @@ -1758,7 +1760,7 @@ void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr,
{
Expression[] args = { expr };

if (!MethodFinder.ContainsMethod(signatures, "F", false, args))
if (!_methodFinder.ContainsMethod(signatures, "F", false, args))
{
throw IncompatibleOperandError(opName, expr, errorPos);
}
Expand All @@ -1770,7 +1772,7 @@ void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left
{
Expression[] args = { left, right };

if (!MethodFinder.ContainsMethod(signatures, "F", false, args))
if (!_methodFinder.ContainsMethod(signatures, "F", false, args))
{
throw IncompatibleOperandsError(opName, left, right, errorPos);
}
Expand Down
5 changes: 3 additions & 2 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

namespace System.Linq.Dynamic.Core.Parser
{
internal static class ExpressionPromoter
internal class ExpressionPromoter : IExpressionPromoter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
internal class ExpressionPromoter : IExpressionPromoter
maybe if you have time ; can you create a simple test which tests this ExpressionPromoter?

{
public static Expression Promote(Expression expr, Type type, bool exact, bool convertExpr)
/// <inheritdoc cref="IExpressionPromoter.Promote(Expression, Type, bool, bool)"/>
public virtual Expression Promote(Expression expr, Type type, bool exact, bool convertExpr)
{
if (expr.Type == type)
{
Expand Down
22 changes: 22 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/IExpressionPromoter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Linq.Expressions;

namespace System.Linq.Dynamic.Core.Parser
{
/// <summary>
/// Expression promoter is used to promote object or value types
/// to their destination type when an automatic promotion is available
/// such as: int to int?
/// </summary>
public interface IExpressionPromoter
{
/// <summary>
/// Promote an expression
/// </summary>
/// <param name="expr">Source expression</param>
/// <param name="type">Destionation data type to promote</param>
/// <param name="exact">If the match must be exact</param>
/// <param name="convertExpr">Convert expression</param>
/// <returns></returns>
Expression Promote(Expression expr, Type type, bool exact, bool convertExpr);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@

namespace System.Linq.Dynamic.Core.Parser.SupportedMethods
{
internal static class MethodFinder
internal class MethodFinder
{
public static bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args)
private readonly ParsingConfig _parsingConfig;

/// <summary>
/// Get an instance
/// </summary>
/// <param name="parsingConfig"></param>
public MethodFinder(ParsingConfig parsingConfig)
{
_parsingConfig = parsingConfig;
}

public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args)
{
return FindMethod(type, methodName, staticAccess, args, out var _) == 1;
}

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

public static int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
public int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method)
{
MethodData[] applicable = methods.
Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }).
Expand Down Expand Up @@ -73,7 +84,7 @@ public static int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] a
return applicable.Length;
}

public static int FindIndexer(Type type, Expression[] args, out MethodBase method)
public int FindIndexer(Type type, Expression[] args, out MethodBase method)
{
foreach (Type t in SelfAndBaseTypes(type))
{
Expand All @@ -99,7 +110,7 @@ public static int FindIndexer(Type type, Expression[] args, out MethodBase metho
return 0;
}

static bool IsApplicable(MethodData method, Expression[] args)
bool IsApplicable(MethodData method, Expression[] args)
{
if (method.Parameters.Length != args.Length)
{
Expand All @@ -115,7 +126,7 @@ static bool IsApplicable(MethodData method, Expression[] args)
return false;
}

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

static bool IsBetterThan(Expression[] args, MethodData first, MethodData second)
bool IsBetterThan(Expression[] args, MethodData first, MethodData second)
{
bool better = false;
for (int i = 0; i < args.Length; i++)
Expand Down Expand Up @@ -158,7 +169,7 @@ static bool IsBetterThan(Expression[] args, MethodData first, MethodData second)
// Return "First" if s -> t1 is a better conversion than s -> t2
// Return "Second" if s -> t2 is a better conversion than s -> t1
// Return "Both" if neither conversion is better
static CompareConversionType CompareConversions(Type source, Type first, Type second)
CompareConversionType CompareConversions(Type source, Type first, Type second)
{
if (first == second)
{
Expand Down Expand Up @@ -197,7 +208,7 @@ static CompareConversionType CompareConversions(Type source, Type first, Type se
return CompareConversionType.Both;
}

static IEnumerable<Type> SelfAndBaseTypes(Type type)
IEnumerable<Type> SelfAndBaseTypes(Type type)
{
if (type.GetTypeInfo().IsInterface)
{
Expand All @@ -208,7 +219,7 @@ static IEnumerable<Type> SelfAndBaseTypes(Type type)
return SelfAndBaseClasses(type);
}

static IEnumerable<Type> SelfAndBaseClasses(Type type)
IEnumerable<Type> SelfAndBaseClasses(Type type)
{
while (type != null)
{
Expand All @@ -217,7 +228,7 @@ static IEnumerable<Type> SelfAndBaseClasses(Type type)
}
}

static void AddInterface(List<Type> types, Type type)
void AddInterface(List<Type> types, Type type)
{
if (!types.Contains(type))
{
Expand Down
22 changes: 22 additions & 0 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Linq.Dynamic.Core.Parser;

namespace System.Linq.Dynamic.Core
{
Expand All @@ -22,6 +23,8 @@ public class ParsingConfig

private IDynamicLinkCustomTypeProvider _customTypeProvider;

private IExpressionPromoter _expressionPromoter;

/// <summary>
/// Gets or sets the <see cref="IDynamicLinkCustomTypeProvider"/>.
/// </summary>
Expand All @@ -46,6 +49,25 @@ public IDynamicLinkCustomTypeProvider CustomTypeProvider
}
}

/// <summary>
/// Gets or sets the <see cref="IExpressionPromoter"/>.
/// </summary>
public IExpressionPromoter ExpressionPromoter
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a unit test where you implement a different ExpressionPromoter or use Mock<IExpressionPromoter>

{
get
{
return _expressionPromoter ?? (_expressionPromoter = new ExpressionPromoter());
}

set
{
if (_expressionPromoter != value)
{
_expressionPromoter = value;
}
}
}

/// <summary>
/// Determines if the context keywords (it, parent, and root) are valid and usable inside a Dynamic Linq string expression.
/// Does not affect the usability of the equivalent context symbols ($, ^ and ~).
Expand Down
Loading