Skip to content
Open
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
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root=true

[*]
insert_final_newline = true

[*.cs]
indent_style = space
indent_size = 2
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ private PartialEvaluatingExpressionVisitor (
PartialEvaluationInfo partialEvaluationInfo,
IEvaluatableExpressionFilter evaluatableExpressionFilter)
{
ArgumentUtility.CheckNotNull ("partialEvaluationInfo", partialEvaluationInfo);
ArgumentUtility.CheckNotNull ("evaluatableExpressionFilter", evaluatableExpressionFilter);
ArgumentUtility.CheckNotNull("partialEvaluationInfo", partialEvaluationInfo);
ArgumentUtility.CheckNotNull("evaluatableExpressionFilter", evaluatableExpressionFilter);

_partialEvaluationInfo = partialEvaluationInfo;
_evaluatableExpressionFilter = evaluatableExpressionFilter;
Expand All @@ -73,7 +73,10 @@ public override Expression Visit (Expression expression)
if (expression == null)
return null;

if (expression.NodeType == ExpressionType.Lambda || !_partialEvaluationInfo.IsEvaluatableExpression (expression))
if (expression.NodeType == ExpressionType.Lambda || !_partialEvaluationInfo.IsEvaluatableExpression (expression)
// inserted after allowing some parameters to be evaluatable; parameters should not be evaluated separately, but
// only as part of the method invoking lambda definining them
&& expression.NodeType != ExpressionType.Parameter)
return base.Visit (expression);

Expression evaluatedExpression;
Expand All @@ -91,7 +94,7 @@ public override Expression Visit (Expression expression)

if (evaluatedExpression != expression)
return EvaluateIndependentSubtrees (evaluatedExpression, _evaluatableExpressionFilter);

return evaluatedExpression;
}

Expand All @@ -106,6 +109,11 @@ private Expression EvaluateSubtree (Expression subtree)
{
ArgumentUtility.CheckNotNull ("subtree", subtree);

// inserted after allowing some parameters to be evaluatable; parameters should not be evaluated separately, but
// only as part of the method invoking lambda definining them
if (subtree.NodeType == ExpressionType.Parameter)
return subtree;

if (subtree.NodeType == ExpressionType.Constant)
{
var constantExpression = (ConstantExpression) subtree;
Expand All @@ -120,7 +128,7 @@ private Expression EvaluateSubtree (Expression subtree)
Expression<Func<object>> lambdaWithoutParameters = Expression.Lambda<Func<object>> (Expression.Convert (subtree, typeof (object)));
var compiledLambda = lambdaWithoutParameters.Compile();

object value = compiledLambda ();
var value = compiledLambda ();
return Expression.Constant (value, subtree.Type);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ protected EvaluatableExpressionFilterBase ()
{
}

/// <summary>
/// Historically all parameters were considered non-evaluatable, this property allows to change this behavior, default is false.
/// </summary>
protected bool AllowParameterEvaluation { get; set; }

public virtual bool IsEvaluatableBinary (BinaryExpression node)
{
ArgumentUtility.CheckNotNull ("node", node);
Expand Down Expand Up @@ -151,6 +156,25 @@ public virtual bool IsEvaluatableUnary (UnaryExpression node)
return true;
}

/// <summary>
/// Historically all parameters were considered non-evaluatable, the behavior remains default that can be overridden.
/// </summary>
/// <param name="node">
/// Expression being tested.
/// </param>
/// <param name="definingLambdaExpression">
/// Lambda expression defining parameter.
/// </param>
/// <returns>
/// <see cref="AllowParameterEvaluation"/>
/// </returns>
public virtual bool IsEvaluatableParameter (ParameterExpression node, LambdaExpression definingLambdaExpression)
{
ArgumentUtility.CheckNotNull (nameof(node), node);

return AllowParameterEvaluation;
}

#if !NET_3_5
public virtual bool IsEvaluatableBlock (BlockExpression node)
{
Expand Down Expand Up @@ -237,4 +261,4 @@ public virtual bool IsEvaluatableTry (TryExpression node)
}
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
Expand Down Expand Up @@ -49,6 +50,19 @@ namespace Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation
/// </remarks>
public sealed class EvaluatableTreeFindingExpressionVisitor : RelinqExpressionVisitor, IPartialEvaluationExceptionExpressionVisitor
{
private class ParameterStatus
{
public ParameterExpression Expression { get; set; }

public bool IsEvaluatable { get; set; }

public LambdaExpression OwningExpression { get; set; }

public MethodCallExpression MethodCallInvokingLambda { get; set; }

public Expression MethodArgumentAcceptingLambda { get; set; }
}

public static PartialEvaluationInfo Analyze (
[NotNull] Expression expressionTree,
[NotNull] IEvaluatableExpressionFilter evaluatableExpressionFilter)
Expand All @@ -70,13 +84,18 @@ public static PartialEvaluationInfo Analyze (
private readonly PartialEvaluationInfo _partialEvaluationInfo = new PartialEvaluationInfo();
private bool _isCurrentSubtreeEvaluatable;

private readonly Stack<Expression> _ancestors = new Stack<Expression>(20);
private readonly Dictionary<string, ParameterStatus> _parameters = new Dictionary<string, ParameterStatus>();

private EvaluatableTreeFindingExpressionVisitor (IEvaluatableExpressionFilter evaluatableExpressionFilter)
{
_evaluatableExpressionFilter = evaluatableExpressionFilter;
}

public override Expression Visit (Expression expression)
{
_ancestors.Push(expression);

if (expression == null)
return base.Visit ((Expression) null);

Expand All @@ -103,6 +122,17 @@ public override Expression Visit (Expression expression)
// - it was evaluatable before, and
// - the current subtree (i.e. the child of the parent node) is evaluatable.
_isCurrentSubtreeEvaluatable &= isParentNodeEvaluatable; // the _isCurrentSubtreeEvaluatable flag now relates to the parent node again

_ancestors.Pop();

if (expression?.NodeType == ExpressionType.Lambda)
{
// defined parameters go out of scope; chained extension methods often use the same parameter names
var parameterNames = _parameters.Where(p => p.Value.OwningExpression == expression).Select(p => p.Key).ToArray();
foreach (var name in parameterNames)
_parameters.Remove(name);
}

return visitedExpression;
}

Expand Down Expand Up @@ -256,17 +286,9 @@ protected override Expression VisitMethodCall (MethodCallExpression expression)
{
ArgumentUtility.CheckNotNull ("expression", expression);

// Method calls are only evaluatable if they do not involve IQueryable objects.

if (IsQueryableExpression (expression.Object))
if (!IsEvaluatableMethodCall(expression))
_isCurrentSubtreeEvaluatable = false;

for (int i = 0; i < expression.Arguments.Count && _isCurrentSubtreeEvaluatable; i++)
{
if (IsQueryableExpression (expression.Arguments[i]))
_isCurrentSubtreeEvaluatable = false;
}

var vistedExpression = base.VisitMethodCall (expression);

// Testing the parent expression is only required if all children are evaluatable
Expand Down Expand Up @@ -326,9 +348,22 @@ protected override Expression VisitParameter (ParameterExpression expression)
{
ArgumentUtility.CheckNotNull ("expression", expression);

// Parameters are not evaluatable.
_isCurrentSubtreeEvaluatable = false;
return base.VisitParameter (expression);
// Parameters are evaluatable if they are supplied by evaluatable expression.
// look up lambda defining the parameter, up the ancestor list and check if method call to which it is passed is on evaluatable instance of extension method accepting evaluatable arguments
// note that lambda body is visited prior to parameters
// since method call is visited in the order {instance, arguments} and extension methods get instance as first parameter
// the source of the parameter is already visited and its evaluatability established
var status = GetParameterStatus (expression);
if (!status.IsEvaluatable)
_isCurrentSubtreeEvaluatable = false;

var visitedExpression = base.VisitParameter(expression);

// default filter will prevent evaluation of expressions involving parameters for backward compatibility, but it can be overridden
if (_isCurrentSubtreeEvaluatable)
_isCurrentSubtreeEvaluatable = _evaluatableExpressionFilter.IsEvaluatableParameter(expression, status.OwningExpression);

return visitedExpression;
}

protected override Expression VisitNewArray (NewArrayExpression expression)
Expand Down Expand Up @@ -631,9 +666,9 @@ private bool IsCurrentExpressionEvaluatable (Expression expression)
}
#endif

private bool IsQueryableExpression (Expression expression)
private static bool IsQueryableExpression(Expression expression)
{
return expression != null && typeof (IQueryable).GetTypeInfo().IsAssignableFrom (expression.Type.GetTypeInfo());
return expression != null && typeof(IQueryable).GetTypeInfo().IsAssignableFrom(expression.Type.GetTypeInfo());
}

public Expression VisitPartialEvaluationException (PartialEvaluationExceptionExpression partialEvaluationExceptionExpression)
Expand All @@ -644,5 +679,120 @@ public Expression VisitPartialEvaluationException (PartialEvaluationExceptionExp
_isCurrentSubtreeEvaluatable = false;
return partialEvaluationExceptionExpression;
}

/// <summary>
/// Crude implementation, only direct checks of queryable instance and arguments.
/// </summary>
/// <returns>
/// false if instance (on which method is invoked) or any of its arguments implements <see cref="IQueryable"/>.
/// </returns>
public static bool IsEvaluatableMethodCall(MethodCallExpression expression)
{
ArgumentUtility.CheckNotNull (nameof(expression), expression);

// Method calls are only evaluatable if they do not involve IQueryable objects.

return !IsQueryableExpression(expression.Object)
&& expression.Arguments.All(a => !IsQueryableExpression(a));
}

private ParameterStatus GetParameterStatus(ParameterExpression expression)
{
// consider nameless parameters as non-evaluatable as their handling is unclear at the time of writing
if (expression?.Name == null)
return new ParameterStatus() {Expression = expression, IsEvaluatable = false};

ParameterStatus status;
if (!_parameters.TryGetValue(expression.Name, out status))
{
status = CalcParameterStatus(expression);
_parameters.Add(expression.Name, status);
}
return status;
}

/// <remarks>
/// Parameters are evaluatable if they are supplied by evaluatable expression.
/// Look up lambda defining the parameter, up the ancestor list and check if method call to which it is passed is on evaluatable
/// instance or extension method accepting evaluatable arguments.
/// Note that lambda body is visited prior to parameters.
/// Since method call is visited in the order [instance, arguments] and extension methods get instance as first parameter
/// the source of the parameter is already visited and its evaluatability established.
/// Note that if parameter is evaluated method call must be evaluatad too eliminating lambda and all the parameters.
/// If lambda is not eliminated, evaluated parameter produces an error complaining that evaluating lambda parameter
/// must return non-null expression of the same type.
/// </remarks>
private ParameterStatus CalcParameterStatus(ParameterExpression expression)
Copy link

Choose a reason for hiding this comment

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

You can use full name without shortening 😃
CalculateParameterStatus

{
ArgumentUtility.CheckNotNull (nameof(expression), expression);

var result = new ParameterStatus { Expression = expression };

foreach (var ancestor in _ancestors.Where(x => x != null))
{
if (result.MethodArgumentAcceptingLambda == null && IsParameterOwner(ancestor, expression))
{
result.MethodArgumentAcceptingLambda = result.OwningExpression = (LambdaExpression)ancestor;
}
else if (result.MethodArgumentAcceptingLambda != null)
{
if (ancestor.NodeType == ExpressionType.Call)
{
result.MethodCallInvokingLambda = (MethodCallExpression)ancestor;

result.IsEvaluatable = IsMethodSupplyingEvaluatableParameterValues(result.MethodCallInvokingLambda, result.MethodArgumentAcceptingLambda);

return result;
}

result.MethodArgumentAcceptingLambda = ancestor;
}
}

return result;
}

/// <summary>
/// Can be used to determine whether parameters defined by lambda expression are evaluatable by examining method which will
/// supply parameters to it. Relies on the visiting sequence: lambda parameters are visited after its body. When parameter
/// is visited, the status of the defining lambda and method call supplying parameters to it has not been established and
/// <see cref="_partialEvaluationInfo"/> would not contain them. There's also a
/// chicken-and-egg situation as parameter is visited as a child of lambda body (potentially deep in the tree), but its
/// evaluatability is determined at the level above lambda. To solve this here we examine the method that accepts the
/// lambda as one of its parameters and determine if all other arguments are evaluatable, expecting all of them to be
/// already visited and present in <see cref="_partialEvaluationInfo"/>.
/// </summary>
/// <param name="methodExpression">
/// Method invoking lambda.
/// </param>
/// <param name="methodArgumentAcceptingLambda">
/// Argument (quote) for the lambda expression to which the method passes parameter values.
/// </param>
/// <remarks>
/// Member expressions on e.g. repository creating query instances must be evaluated into constants
/// and queryable constants must be evaluated (e.g. for subqueries to be expanded properly),
/// but parameters accepting values from them are not evaluatable.
/// </remarks>
private bool IsMethodSupplyingEvaluatableParameterValues(MethodCallExpression methodExpression, Expression methodArgumentAcceptingLambda)
{
ArgumentUtility.CheckNotNull (nameof(methodExpression), methodExpression);
ArgumentUtility.CheckNotNull (nameof(methodArgumentAcceptingLambda), methodArgumentAcceptingLambda);

if (!IsEvaluatableMethodCall(methodExpression))
return false;

if (methodExpression.Object != null && !_partialEvaluationInfo.IsEvaluatableExpression(methodExpression.Object))
return false;

return methodExpression.Arguments.All(a => a == methodArgumentAcceptingLambda || _partialEvaluationInfo.IsEvaluatableExpression(a));
}

private bool IsParameterOwner(Expression expression, ParameterExpression parameterExpression)
{
ArgumentUtility.CheckNotNull (nameof(expression), expression);
ArgumentUtility.CheckNotNull (nameof(parameterExpression), parameterExpression);

return (expression as LambdaExpression)?.Parameters.Any(x => x.Name == parameterExpression.Name) == true;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ public interface IEvaluatableExpressionFilter
bool IsEvaluatableNewArray ([NotNull] NewArrayExpression node);
bool IsEvaluatableTypeBinary ([NotNull] TypeBinaryExpression node);
bool IsEvaluatableUnary ([NotNull] UnaryExpression node);

/// <param name="node">
/// Expression being tested.
/// </param>
/// <param name="definingLambdaExpression">
/// Lambda expression defining parameter. Null for nameless parameters.
/// </param>
bool IsEvaluatableParameter ([NotNull] ParameterExpression node, LambdaExpression definingLambdaExpression);

#if !NET_3_5
bool IsEvaluatableBlock ([NotNull] BlockExpression node);
bool IsEvaluatableCatchBlock ([NotNull] CatchBlock node);
Expand All @@ -70,4 +79,4 @@ public interface IEvaluatableExpressionFilter
bool IsEvaluatableTry ([NotNull] TryExpression node);
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ public NullEvaluatableExpressionFilter ()
{
}
}
}
}
Loading