Skip to content

Commit

Permalink
Add support for out keyword (#743)
Browse files Browse the repository at this point in the history
* wip

* ok!

* .

* .

* res

* tests

* ...

* func
  • Loading branch information
StefH committed Sep 10, 2023
1 parent 06d7065 commit d5122c9
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 160 deletions.
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

0 comments on commit d5122c9

Please sign in to comment.