Skip to content
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
11 changes: 8 additions & 3 deletions src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1802,10 +1802,15 @@ public static IQueryable<TResult> Select<TResult>(this IQueryable source, Parsin
bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source);
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, typeof(TResult), selector, args);

var optimized = OptimizeExpression(Expression.Call(
typeof(Queryable), nameof(Queryable.Select),
var methodCallExpression = Expression.Call(
typeof(Queryable),
nameof(Queryable.Select),
new[] { source.ElementType, typeof(TResult) },
source.Expression, Expression.Quote(lambda)));
source.Expression,
Expression.Quote(lambda)
);

var optimized = OptimizeExpression(methodCallExpression);

return source.Provider.CreateQuery<TResult>(optimized);
}
Expand Down
111 changes: 99 additions & 12 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ namespace System.Linq.Dynamic.Core.Parser;
/// </summary>
public class ExpressionParser
{
private static readonly string[] OutKeywords = { "out", "$out" };
private const string DiscardVariable = "_";

private const string MethodOrderBy = nameof(Queryable.OrderBy);
private const string MethodOrderByDescending = nameof(Queryable.OrderByDescending);
private const string MethodThenBy = nameof(Queryable.ThenBy);
Expand Down Expand Up @@ -165,7 +168,31 @@ public Expression Parse(Type? resultType, bool createParameterCtor = true)
return expr;
}

#pragma warning disable 0219
// out keyword
private Expression ParseOutKeyword()
{
if (_textParser.CurrentToken.Id == TokenId.Identifier && OutKeywords.Contains(_textParser.CurrentToken.Text))
{
// Go to next token (which should be a '_')
_textParser.NextToken();

var variableName = _textParser.CurrentToken.Text;
if (variableName != DiscardVariable)
{
throw ParseError(_textParser.CurrentToken.Pos, Res.OutKeywordRequiresDiscard);
}

// Advance to next token
_textParser.NextToken();

// Use MakeByRefType() to indicate that it's a by-reference type because C# uses this for both 'ref' and 'out' parameters.
// The "typeof(object).MakeByRefType()" is used, this will be changed later in the flow to the real type.
return Expression.Parameter(typeof(object).MakeByRefType(), variableName);
}

return ParseConditionalOperator();
}

internal IList<DynamicOrdering> ParseOrdering(bool forceThenBy = false)
{
var orderings = new List<DynamicOrdering>();
Expand Down Expand Up @@ -206,7 +233,6 @@ internal IList<DynamicOrdering> ParseOrdering(bool forceThenBy = false)
_textParser.ValidateToken(TokenId.End, Res.SyntaxError);
return orderings;
}
#pragma warning restore 0219

// ?: operator
private Expression ParseConditionalOperator()
Expand Down Expand Up @@ -931,9 +957,9 @@ private Expression ParseIdentifier()

var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value);


bool shouldPrioritizeType = true;

if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type)
{
bool isPropertyOrField = _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null;
Expand Down Expand Up @@ -1774,16 +1800,19 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!));
}

if (method.IsGenericMethod)
MethodInfo methodToCall;
if (!method.IsGenericMethod)
{
methodToCall = method;
}
else
{
var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter);
var typeArguments = genericParameters.Select(a => args[a.Position].Type);
var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray());

return Expression.Call(expression, constructedMethod, args);
methodToCall = method.MakeGenericMethod(typeArguments.ToArray());
}
return Expression.Call(expression, method, args);

return CallMethod(expression, methodToCall, args);

default:
throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type));
Expand Down Expand Up @@ -1848,6 +1877,59 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type));
}

private static Expression CallMethod(Expression? expression, MethodInfo methodToCall, Expression[] args)
{
#if NET35
return Expression.Call(expression, methodToCall, args);
#else
if (!args.OfType<ParameterExpression>().Any(p => p.IsByRef))
{
return Expression.Call(expression, methodToCall, args);
}

// A list which is used to store all method arguments.
var newList = new List<Expression>();

// A list which contains the variable expression for the 'out' parameter, and also contains the returnValue variable.
var blockList = new List<ParameterExpression>();

foreach (var arg in args)
{
if (arg is ParameterExpression { IsByRef: true } parameterExpression)
{
// Create a variable expression to hold the 'out' parameter.
var variable = Expression.Variable(parameterExpression.Type, parameterExpression.Name);

newList.Add(variable);
blockList.Add(variable);
}
else
{
newList.Add(arg);
}
}

// Create a method call expression to call the method
var methodCall = Expression.Call(expression, methodToCall, newList);

// Create a variable to hold the return value
var returnValue = Expression.Variable(methodToCall.ReturnType);

// Add this return variable to the blockList
blockList.Add(returnValue);

// Create the block to return the boolean value.
var block = Expression.Block(
blockList.ToArray(),
Expression.Assign(returnValue, methodCall),
returnValue
);

// Create the lambda expression (note that expression must be a ParameterExpression).
return Expression.Lambda(block, (ParameterExpression)expression!);
#endif
}

private Expression ParseAsLambda(string id)
{
// This might be an internal variable for use within a lambda expression, so store it as such
Expand Down Expand Up @@ -2021,7 +2103,7 @@ private Expression ParseEnumerable(Expression instance, Type elementType, string

private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)
{
string argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";
var argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";

switch (argumentExpression)
{
Expand Down Expand Up @@ -2088,7 +2170,7 @@ private Expression[] ParseArguments()
var argList = new List<Expression>();
while (true)
{
var argumentExpression = ParseConditionalOperator();
var argumentExpression = ParseOutKeyword();

_expressionHelper.WrapConstantExpression(ref argumentExpression);

Expand All @@ -2102,6 +2184,11 @@ private Expression[] ParseArguments()
_textParser.NextToken();
}

//if (argList.OfType<ParameterExpression>().Count() > 1)
//{
// throw ParseError(_textParser.CurrentToken.Pos, Res.OutVariableSingleRequired);
//}

return argList.ToArray();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase? method)
return 0;
}

bool IsApplicable(MethodData method, Expression[] args)
private bool IsApplicable(MethodData method, Expression[] args)
{
bool isParamArray = method.Parameters.Length > 0 && method.Parameters.Last().IsDefined(typeof(ParamArrayAttribute), false);

Expand Down Expand Up @@ -221,18 +221,30 @@ bool IsApplicable(MethodData method, Expression[] args)
}
else
{
ParameterInfo pi = method.Parameters[i];
if (pi.IsOut)
var methodParameter = method.Parameters[i];
if (methodParameter.IsOut && args[i] is ParameterExpression parameterExpression)
{
#if NET35
return false;
}
#else
if (!parameterExpression.IsByRef)
{
return false;
}

var promotedExpression = _parsingConfig.ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
if (promotedExpression == null)
promotedArgs[i] = Expression.Parameter(methodParameter.ParameterType, methodParameter.Name);
#endif
}
else
{
return false;
var promotedExpression = _parsingConfig.ExpressionPromoter.Promote(args[i], methodParameter.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
if (promotedExpression == null)
{
return false;
}

promotedArgs[i] = promotedExpression;
}
promotedArgs[i] = promotedExpression;
}
}

Expand Down
Loading