Skip to content

Commit d5122c9

Browse files
authored
Add support for out keyword (#743)
* wip * ok! * . * . * res * tests * ... * func
1 parent 06d7065 commit d5122c9

File tree

9 files changed

+350
-160
lines changed

9 files changed

+350
-160
lines changed

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -1802,10 +1802,15 @@ public static IQueryable<TResult> Select<TResult>(this IQueryable source, Parsin
18021802
bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source);
18031803
LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, typeof(TResult), selector, args);
18041804

1805-
var optimized = OptimizeExpression(Expression.Call(
1806-
typeof(Queryable), nameof(Queryable.Select),
1805+
var methodCallExpression = Expression.Call(
1806+
typeof(Queryable),
1807+
nameof(Queryable.Select),
18071808
new[] { source.ElementType, typeof(TResult) },
1808-
source.Expression, Expression.Quote(lambda)));
1809+
source.Expression,
1810+
Expression.Quote(lambda)
1811+
);
1812+
1813+
var optimized = OptimizeExpression(methodCallExpression);
18091814

18101815
return source.Provider.CreateQuery<TResult>(optimized);
18111816
}

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

+99-12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ namespace System.Linq.Dynamic.Core.Parser;
2020
/// </summary>
2121
public class ExpressionParser
2222
{
23+
private static readonly string[] OutKeywords = { "out", "$out" };
24+
private const string DiscardVariable = "_";
25+
2326
private const string MethodOrderBy = nameof(Queryable.OrderBy);
2427
private const string MethodOrderByDescending = nameof(Queryable.OrderByDescending);
2528
private const string MethodThenBy = nameof(Queryable.ThenBy);
@@ -165,7 +168,31 @@ public Expression Parse(Type? resultType, bool createParameterCtor = true)
165168
return expr;
166169
}
167170

168-
#pragma warning disable 0219
171+
// out keyword
172+
private Expression ParseOutKeyword()
173+
{
174+
if (_textParser.CurrentToken.Id == TokenId.Identifier && OutKeywords.Contains(_textParser.CurrentToken.Text))
175+
{
176+
// Go to next token (which should be a '_')
177+
_textParser.NextToken();
178+
179+
var variableName = _textParser.CurrentToken.Text;
180+
if (variableName != DiscardVariable)
181+
{
182+
throw ParseError(_textParser.CurrentToken.Pos, Res.OutKeywordRequiresDiscard);
183+
}
184+
185+
// Advance to next token
186+
_textParser.NextToken();
187+
188+
// Use MakeByRefType() to indicate that it's a by-reference type because C# uses this for both 'ref' and 'out' parameters.
189+
// The "typeof(object).MakeByRefType()" is used, this will be changed later in the flow to the real type.
190+
return Expression.Parameter(typeof(object).MakeByRefType(), variableName);
191+
}
192+
193+
return ParseConditionalOperator();
194+
}
195+
169196
internal IList<DynamicOrdering> ParseOrdering(bool forceThenBy = false)
170197
{
171198
var orderings = new List<DynamicOrdering>();
@@ -206,7 +233,6 @@ internal IList<DynamicOrdering> ParseOrdering(bool forceThenBy = false)
206233
_textParser.ValidateToken(TokenId.End, Res.SyntaxError);
207234
return orderings;
208235
}
209-
#pragma warning restore 0219
210236

211237
// ?: operator
212238
private Expression ParseConditionalOperator()
@@ -931,9 +957,9 @@ private Expression ParseIdentifier()
931957

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

934-
960+
935961
bool shouldPrioritizeType = true;
936-
962+
937963
if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type)
938964
{
939965
bool isPropertyOrField = _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null;
@@ -1774,16 +1800,19 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
17741800
throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!));
17751801
}
17761802

1777-
if (method.IsGenericMethod)
1803+
MethodInfo methodToCall;
1804+
if (!method.IsGenericMethod)
1805+
{
1806+
methodToCall = method;
1807+
}
1808+
else
17781809
{
17791810
var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter);
17801811
var typeArguments = genericParameters.Select(a => args[a.Position].Type);
1781-
var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray());
1782-
1783-
return Expression.Call(expression, constructedMethod, args);
1812+
methodToCall = method.MakeGenericMethod(typeArguments.ToArray());
17841813
}
1785-
1786-
return Expression.Call(expression, method, args);
1814+
1815+
return CallMethod(expression, methodToCall, args);
17871816

17881817
default:
17891818
throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type));
@@ -1848,6 +1877,59 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
18481877
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type));
18491878
}
18501879

1880+
private static Expression CallMethod(Expression? expression, MethodInfo methodToCall, Expression[] args)
1881+
{
1882+
#if NET35
1883+
return Expression.Call(expression, methodToCall, args);
1884+
#else
1885+
if (!args.OfType<ParameterExpression>().Any(p => p.IsByRef))
1886+
{
1887+
return Expression.Call(expression, methodToCall, args);
1888+
}
1889+
1890+
// A list which is used to store all method arguments.
1891+
var newList = new List<Expression>();
1892+
1893+
// A list which contains the variable expression for the 'out' parameter, and also contains the returnValue variable.
1894+
var blockList = new List<ParameterExpression>();
1895+
1896+
foreach (var arg in args)
1897+
{
1898+
if (arg is ParameterExpression { IsByRef: true } parameterExpression)
1899+
{
1900+
// Create a variable expression to hold the 'out' parameter.
1901+
var variable = Expression.Variable(parameterExpression.Type, parameterExpression.Name);
1902+
1903+
newList.Add(variable);
1904+
blockList.Add(variable);
1905+
}
1906+
else
1907+
{
1908+
newList.Add(arg);
1909+
}
1910+
}
1911+
1912+
// Create a method call expression to call the method
1913+
var methodCall = Expression.Call(expression, methodToCall, newList);
1914+
1915+
// Create a variable to hold the return value
1916+
var returnValue = Expression.Variable(methodToCall.ReturnType);
1917+
1918+
// Add this return variable to the blockList
1919+
blockList.Add(returnValue);
1920+
1921+
// Create the block to return the boolean value.
1922+
var block = Expression.Block(
1923+
blockList.ToArray(),
1924+
Expression.Assign(returnValue, methodCall),
1925+
returnValue
1926+
);
1927+
1928+
// Create the lambda expression (note that expression must be a ParameterExpression).
1929+
return Expression.Lambda(block, (ParameterExpression)expression!);
1930+
#endif
1931+
}
1932+
18511933
private Expression ParseAsLambda(string id)
18521934
{
18531935
// This might be an internal variable for use within a lambda expression, so store it as such
@@ -2021,7 +2103,7 @@ private Expression ParseEnumerable(Expression instance, Type elementType, string
20212103

20222104
private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)
20232105
{
2024-
string argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";
2106+
var argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";
20252107

20262108
switch (argumentExpression)
20272109
{
@@ -2088,7 +2170,7 @@ private Expression[] ParseArguments()
20882170
var argList = new List<Expression>();
20892171
while (true)
20902172
{
2091-
var argumentExpression = ParseConditionalOperator();
2173+
var argumentExpression = ParseOutKeyword();
20922174

20932175
_expressionHelper.WrapConstantExpression(ref argumentExpression);
20942176

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

2187+
//if (argList.OfType<ParameterExpression>().Count() > 1)
2188+
//{
2189+
// throw ParseError(_textParser.CurrentToken.Pos, Res.OutVariableSingleRequired);
2190+
//}
2191+
21052192
return argList.ToArray();
21062193
}
21072194

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

+20-8
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase? method)
170170
return 0;
171171
}
172172

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

@@ -221,18 +221,30 @@ bool IsApplicable(MethodData method, Expression[] args)
221221
}
222222
else
223223
{
224-
ParameterInfo pi = method.Parameters[i];
225-
if (pi.IsOut)
224+
var methodParameter = method.Parameters[i];
225+
if (methodParameter.IsOut && args[i] is ParameterExpression parameterExpression)
226226
{
227+
#if NET35
227228
return false;
228-
}
229+
#else
230+
if (!parameterExpression.IsByRef)
231+
{
232+
return false;
233+
}
229234

230-
var promotedExpression = _parsingConfig.ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
231-
if (promotedExpression == null)
235+
promotedArgs[i] = Expression.Parameter(methodParameter.ParameterType, methodParameter.Name);
236+
#endif
237+
}
238+
else
232239
{
233-
return false;
240+
var promotedExpression = _parsingConfig.ExpressionPromoter.Promote(args[i], methodParameter.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures));
241+
if (promotedExpression == null)
242+
{
243+
return false;
244+
}
245+
246+
promotedArgs[i] = promotedExpression;
234247
}
235-
promotedArgs[i] = promotedExpression;
236248
}
237249
}
238250

0 commit comments

Comments
 (0)