Skip to content

Commit

Permalink
Add support for property for Is,As and Cast (#567)
Browse files Browse the repository at this point in the history
* Add support for property for Is,As and Cast

* .

* .
  • Loading branch information
StefH authored Feb 19, 2022
1 parent d077db0 commit c59391a
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 34 deletions.
83 changes: 55 additions & 28 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -904,10 +904,9 @@ Expression ParseIdentifier()
}
else
{
var lambda = expr as LambdaExpression;
if (lambda != null)
if (expr is LambdaExpression lambdaExpression)
{
return ParseLambdaInvocation(lambda);
return ParseLambdaInvocation(lambdaExpression);
}
}

Expand Down Expand Up @@ -1021,14 +1020,25 @@ Expression ParseFunctionIs()

Expression[] args = ParseArgumentList();

if (args.Length != 1)
if (args.Length != 1 && args.Length != 2)
{
throw ParseError(errorPos, Res.FunctionRequiresOneArg, functionName);
throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName);
}

Type resolvedType = ResolveTypeFromArgumentExpression(functionName, args[0]);
Expression typeArgument;
Expression it;
if (args.Length == 1)
{
typeArgument = args[0];
it = _it;
}
else
{
typeArgument = args[1];
it = args[0];
}

return Expression.TypeIs(_it, resolvedType);
return Expression.TypeIs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length));
}

// As(...) function
Expand All @@ -1040,14 +1050,25 @@ Expression ParseFunctionAs()

Expression[] args = ParseArgumentList();

if (args.Length != 1)
if (args.Length != 1 && args.Length != 2)
{
throw ParseError(errorPos, Res.FunctionRequiresOneArg, functionName);
throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName);
}

Type resolvedType = ResolveTypeFromArgumentExpression(functionName, args[0]);
Expression typeArgument;
Expression it;
if (args.Length == 1)
{
typeArgument = args[0];
it = _it;
}
else
{
typeArgument = args[1];
it = args[0];
}

return Expression.TypeAs(_it, resolvedType);
return Expression.TypeAs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length));
}

// Cast(...) function
Expand All @@ -1059,14 +1080,25 @@ Expression ParseFunctionCast()

Expression[] args = ParseArgumentList();

if (args.Length != 1)
if (args.Length != 1 && args.Length != 2)
{
throw ParseError(errorPos, Res.FunctionRequiresOneArg, functionName);
throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName);
}

Type resolvedType = ResolveTypeFromArgumentExpression(functionName, args[0]);
Expression typeArgument;
Expression it;
if (args.Length == 1)
{
typeArgument = args[0];
it = _it;
}
else
{
typeArgument = args[1];
it = args[0];
}

return Expression.ConvertChecked(_it, resolvedType);
return Expression.ConvertChecked(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length));
}

Expression GenerateConditional(Expression test, Expression expressionIfTrue, Expression expressionIfFalse, bool nullPropagating, int errorPos)
Expand Down Expand Up @@ -1101,8 +1133,7 @@ Expression GenerateConditional(Expression test, Expression expressionIfTrue, Exp
{
// - create nullable constant from expressionIfTrue with type from expressionIfFalse
// - convert expressionIfFalse to nullable (unless it's already nullable)

Type nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type);
var nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type);
expressionIfTrue = Expression.Constant(null, nullableType);

if (!TypeHelper.IsNullableType(expressionIfFalse.Type))
Expand Down Expand Up @@ -1343,10 +1374,10 @@ private Expression CreateNewExpression(List<DynamicProperty> properties, List<Ex
#endif
}

IEnumerable<PropertyInfo> propertyInfos = type.GetProperties();
var propertyInfos = type.GetProperties();
if (type.GetTypeInfo().BaseType == typeof(DynamicClass))
{
propertyInfos = propertyInfos.Where(x => x.Name != "Item");
propertyInfos = propertyInfos.Where(x => x.Name != "Item").ToArray();
}

Type[] propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray();
Expand Down Expand Up @@ -1836,8 +1867,9 @@ Expression ParseEnumerable(Expression instance, Type elementType, string methodN
return Expression.Call(callType, methodName, typeArgs, args);
}

private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression)
private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null)
{
string argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second ";
switch (argumentExpression)
{
case ConstantExpression constantExpression:
Expand All @@ -1850,21 +1882,16 @@ private Type ResolveTypeFromArgumentExpression(string functionName, Expression a
return type;

default:
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneNotNullArgOfType, functionName, "string or System.Type");
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "string or System.Type");
}

default:
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneNotNullArgOfType, functionName, "ConstantExpression");
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "ConstantExpression");
}
}

private Type ResolveTypeStringFromArgument(string functionName, string typeName)
{
if (string.IsNullOrEmpty(typeName))
{
throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneNotNullArg, functionName, typeName);
}

Type resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true);
if (resultType == null)
{
Expand Down Expand Up @@ -1962,7 +1989,7 @@ static bool TryGetMemberName(Expression expression, out string memberName)
var memberExpression = expression as MemberExpression;
if (memberExpression == null && expression.NodeType == ExpressionType.Coalesce)
{
memberExpression = (expression as BinaryExpression).Left as MemberExpression;
memberExpression = (expression as BinaryExpression)?.Left as MemberExpression;
}

if (memberExpression != null)
Expand Down
3 changes: 1 addition & 2 deletions src/System.Linq.Dynamic.Core/Parser/TypeFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public Type FindTypeByName(string name, ParameterExpression[] expressions, bool

_keywordsHelper.TryGetValue(name, out object type);

Type result = type as Type;
if (result != null)
if (type is Type result)
{
return result;
}
Expand Down
3 changes: 2 additions & 1 deletion src/System.Linq.Dynamic.Core/Res.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ internal static class Res
public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'";
public const string FunctionRequiresOneArg = "The '{0}' function requires one argument";
public const string FunctionRequiresOneNotNullArg = "The '{0}' function requires one argument which is not null.";
public const string FunctionRequiresOneNotNullArgOfType = "The '{0}' function requires one argument of type {1} which is not null.";
public const string FunctionRequiresNotNullArgOfType = "The '{0}' function requires the {1}argument to be not null and of type {2}.";
public const string FunctionRequiresOneOrTwoArgs = "The '{0}' function requires 1 or 2 arguments";
public const string HexCharExpected = "Hexadecimal character expected";
public const string IQueryableProviderNotAsync = "The provider for the source IQueryable doesn't implement IAsyncQueryProvider/IDbAsyncQueryProvider. Only providers that implement IAsyncQueryProvider/IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations.";
public const string IdentifierExpected = "Identifier expected";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace System.Linq.Dynamic.Core.Tests.Entities
{
public class EmployeeWrapper
{
public BaseEmployee Employee { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ public void CastToType_Dynamic_ActingOnIt()
// Assign
var qry = new BaseEmployee[]
{
new Worker { Name = "1", Other = "x" }, new Worker { Name = "2" }
new Worker { Name = "1", Other = "x" },
new Worker { Name = "2" }
}.AsQueryable();

// Act
Expand All @@ -306,17 +307,36 @@ public void IsAndCastToType_Dynamic_ActingOnIt_And_GetProperty()
// Assign
var qry = new BaseEmployee[]
{
new Worker { Name = "1", Other = "x" }, new Boss { Name = "2", Function = "y" }
new Worker { Name = "1", Other = "x" },
new Boss { Name = "2", Function = "y" }
}.AsQueryable();

// Act
var cast = qry.Select(c => (c is Worker) ? (c as Worker).Other : "-").ToArray();
var cast = qry.Select(c => c is Worker ? ((Worker)c).Other : "-").ToArray();
var castDynamic = qry.Select("iif(Is(\"System.Linq.Dynamic.Core.Tests.Entities.Worker\"), Cast(\"System.Linq.Dynamic.Core.Tests.Entities.Worker\").Other, \"-\")").ToDynamicArray();

// Assert
Check.That(cast.Length).Equals(castDynamic.Length);
}

[Fact]
public void IsAndCastToType_Dynamic_ActingOnProperty_And_GetProperty()
{
// Assign
var qry = new []
{
new EmployeeWrapper { Employee = new Worker { Name = "1", Other = "x" } },
new EmployeeWrapper { Employee = new Boss { Name = "2", Function = "y" } }
}.AsQueryable();

// Act
var cast = qry.Select(c => c.Employee is Worker ? ((Worker) c.Employee).Other : "-").ToArray();
var castDynamic = qry.Select("iif(Is(Employee, \"System.Linq.Dynamic.Core.Tests.Entities.Worker\"), Cast(Employee, \"System.Linq.Dynamic.Core.Tests.Entities.Worker\").Other, \"-\")").ToDynamicArray();

// Assert
Check.That(cast.Length).Equals(castDynamic.Length);
}

[Fact]
public void CastToType_Dynamic_ActingOnIt_WithType()
{
Expand Down

0 comments on commit c59391a

Please sign in to comment.