Skip to content

Commit 4698cc7

Browse files
authored
Fixed calling property on nested static class (#774)
* DynamicExpressionParser_ParseLambda_StaticClassWithStaticExpressionBody * works? * fixes
1 parent f861067 commit 4698cc7

File tree

11 files changed

+272
-110
lines changed

11 files changed

+272
-110
lines changed

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

+68-36
Original file line numberDiff line numberDiff line change
@@ -1767,24 +1767,27 @@ private bool TryGenerateConversion(Expression sourceExpression, Type destination
17671767
return false;
17681768
}
17691769

1770-
private Expression ParseMemberAccess(Type? type, Expression? expression)
1770+
private Expression ParseMemberAccess(Type? type, Expression? expression, string? id = null)
17711771
{
1772-
var isStaticAccess = false;
1772+
var errorPos = _textParser.CurrentToken.Pos;
1773+
1774+
// In case the expression is not null and the type is null, get the type from the expression.
17731775
if (expression != null)
17741776
{
1775-
type = expression.Type;
1777+
type ??= expression.Type;
17761778
}
1777-
else
1779+
1780+
// In case the id is not defined, get it and move the TextParser forward.
1781+
if (id == null)
17781782
{
1779-
isStaticAccess = true;
1783+
id = GetIdentifier();
1784+
_textParser.NextToken();
17801785
}
17811786

1782-
int errorPos = _textParser.CurrentToken.Pos;
1783-
string id = GetIdentifier();
1784-
_textParser.NextToken();
1785-
17861787
if (_textParser.CurrentToken.Id == TokenId.OpenParen)
17871788
{
1789+
var isStaticAccess = expression == null;
1790+
17881791
if (!isStaticAccess && type != typeof(string))
17891792
{
17901793
var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type);
@@ -1827,10 +1830,9 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
18271830
}
18281831
}
18291832

1830-
var @enum = TypeHelper.ParseEnum(id, type);
1831-
if (@enum != null)
1833+
if (TypeHelper.TryParseEnum(id, type, out var enumValue))
18321834
{
1833-
return Expression.Constant(@enum);
1835+
return Expression.Constant(enumValue);
18341836
}
18351837

18361838
#if UAP10_0 || NETSTANDARD1_3
@@ -1839,15 +1841,9 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
18391841
return Expression.MakeIndex(expression, typeof(DynamicClass).GetProperty("Item"), new[] { Expression.Constant(id) });
18401842
}
18411843
#endif
1842-
MemberInfo? member = FindPropertyOrField(type!, id, expression == null);
1843-
if (member is PropertyInfo property)
1844+
if (TryFindPropertyOrField(type!, id, expression, out var propertyOrFieldExpression))
18441845
{
1845-
return Expression.Property(expression, property);
1846-
}
1847-
1848-
if (member is FieldInfo field)
1849-
{
1850-
return Expression.Field(expression, field);
1846+
return propertyOrFieldExpression;
18511847
}
18521848

18531849
// #357 #662
@@ -1876,15 +1872,38 @@ private Expression ParseMemberAccess(Type? type, Expression? expression)
18761872
return ParseAsLambda(id);
18771873
}
18781874

1879-
// This could be enum like "A.B.C.MyEnum.Value1" or "A.B.C+MyEnum.Value1"
1875+
// This could be enum like "A.B.C.MyEnum.Value1" or "A.B.C+MyEnum.Value1".
1876+
//
1877+
// Or it's a nested (static) class with a
1878+
// - static property like "NestedClass.MyProperty"
1879+
// - static method like "NestedClass.MyMethod"
18801880
if (_textParser.CurrentToken.Id is TokenId.Dot or TokenId.Plus)
18811881
{
1882-
return ParseAsEnum(id);
1882+
return ParseAsEnumOrNestedClass(id);
18831883
}
18841884

18851885
throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type));
18861886
}
18871887

1888+
private bool TryFindPropertyOrField(Type type, string id, Expression? expression, [NotNullWhen(true)] out Expression? propertyOrFieldExpression)
1889+
{
1890+
var member = FindPropertyOrField(type, id, expression == null);
1891+
switch (member)
1892+
{
1893+
case PropertyInfo property:
1894+
propertyOrFieldExpression = Expression.Property(expression, property);
1895+
return true;
1896+
1897+
case FieldInfo field:
1898+
propertyOrFieldExpression = Expression.Field(expression, field);
1899+
return true;
1900+
1901+
default:
1902+
propertyOrFieldExpression = null;
1903+
return false;
1904+
}
1905+
}
1906+
18881907
private static Expression CallMethod(Expression? expression, MethodInfo methodToCall, Expression[] args)
18891908
{
18901909
#if NET35
@@ -1963,13 +1982,13 @@ private Expression ParseAsLambda(string id)
19631982
return exp;
19641983
}
19651984

1966-
private Expression ParseAsEnum(string id)
1985+
private Expression ParseAsEnumOrNestedClass(string id)
19671986
{
19681987
var parts = new List<string> { id };
19691988

1970-
while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus)
1989+
while (_textParser.CurrentToken.Id is TokenId.Dot or TokenId.Plus)
19711990
{
1972-
if (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus)
1991+
if (_textParser.CurrentToken.Id is TokenId.Dot or TokenId.Plus)
19731992
{
19741993
parts.Add(_textParser.CurrentToken.Text);
19751994
_textParser.NextToken();
@@ -1982,26 +2001,37 @@ private Expression ParseAsEnum(string id)
19822001
}
19832002
}
19842003

1985-
var enumTypeAsString = string.Concat(parts.Take(parts.Count - 2).ToArray());
1986-
var enumType = _typeFinder.FindTypeByName(enumTypeAsString, null, true);
1987-
if (enumType == null)
2004+
var typeAsString = string.Concat(parts.Take(parts.Count - 2).ToArray());
2005+
var type = _typeFinder.FindTypeByName(typeAsString, null, true);
2006+
if (type == null)
19882007
{
1989-
throw ParseError(_textParser.CurrentToken.Pos, Res.EnumTypeNotFound, enumTypeAsString);
2008+
throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeAsString);
19902009
}
19912010

1992-
var enumValueAsString = parts.LastOrDefault();
1993-
if (enumValueAsString == null)
2011+
var isEnum = TypeHelper.IsEnumType(type);
2012+
2013+
var identifier = parts.LastOrDefault();
2014+
if (identifier == null)
19942015
{
1995-
throw ParseError(_textParser.CurrentToken.Pos, Res.EnumValueExpected);
2016+
if (isEnum)
2017+
{
2018+
throw ParseError(_textParser.CurrentToken.Pos, Res.EnumTypeNotFound, typeAsString);
2019+
}
2020+
2021+
throw ParseError(_textParser.CurrentToken.Pos, Res.UnknownIdentifier, typeAsString);
19962022
}
19972023

1998-
var enumValue = TypeHelper.ParseEnum(enumValueAsString, enumType);
1999-
if (enumValue == null)
2024+
if (isEnum)
20002025
{
2001-
throw ParseError(_textParser.CurrentToken.Pos, Res.EnumValueNotDefined, enumValueAsString, enumTypeAsString);
2026+
if (TypeHelper.TryParseEnum(identifier, type, out var enumValue))
2027+
{
2028+
return Expression.Constant(enumValue);
2029+
}
2030+
2031+
throw ParseError(_textParser.CurrentToken.Pos, Res.EnumValueNotDefined, identifier, typeAsString);
20022032
}
20032033

2004-
return Expression.Constant(enumValue);
2034+
return ParseMemberAccess(type, null, identifier);
20052035
}
20062036

20072037
private Expression ParseEnumerable(Expression instance, Type elementType, string methodName, int errorPos, Type? type)
@@ -2301,8 +2331,10 @@ private void CheckAndPromoteOperand(Type signatures, string opName, ref Expressi
23012331
case TokenId.DoubleEqual:
23022332
case TokenId.Equal:
23032333
return "op_Equality";
2334+
23042335
case TokenId.ExclamationEqual:
23052336
return "op_Inequality";
2337+
23062338
default:
23072339
return null;
23082340
}

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public ExpressionPromoter(ParsingConfig config)
6262
case TypeCode.UInt32:
6363
case TypeCode.Int64:
6464
case TypeCode.UInt64:
65-
value = _numberParser.ParseNumber(text!, target);
65+
value = _numberParser.ParseNumber(text, target);
6666

6767
// Make sure an enum value stays an enum value
6868
if (target.IsEnum)
@@ -74,12 +74,12 @@ public ExpressionPromoter(ParsingConfig config)
7474
case TypeCode.Double:
7575
if (target == typeof(decimal) || target == typeof(double))
7676
{
77-
value = _numberParser.ParseNumber(text!, target);
77+
value = _numberParser.ParseNumber(text, target);
7878
}
7979
break;
8080

8181
case TypeCode.String:
82-
value = TypeHelper.ParseEnum(text!, target);
82+
TypeHelper.TryParseEnum(text, target, out value);
8383
break;
8484
}
8585
#else
@@ -99,12 +99,12 @@ public ExpressionPromoter(ParsingConfig config)
9999
{
100100
if (target == typeof(decimal) || target == typeof(double))
101101
{
102-
value = _numberParser.ParseNumber(text!, target);
102+
value = _numberParser.ParseNumber(text, target);
103103
}
104104
}
105-
else if (ce.Type == typeof(string))
105+
else if (ce.Type == typeof(string) && TypeHelper.TryParseEnum(text, target, out value))
106106
{
107-
value = TypeHelper.ParseEnum(text!, target);
107+
// Empty if
108108
}
109109
#endif
110110
if (value != null)

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Diagnostics.CodeAnalysis;
23
using System.Linq.Dynamic.Core.Validation;
34
using System.Reflection;
45

@@ -480,14 +481,16 @@ private static void AddInterface(ICollection<Type> types, Type type)
480481
}
481482
}
482483

483-
public static object? ParseEnum(string value, Type? type)
484+
public static bool TryParseEnum(string value, Type? type, [NotNullWhen(true)] out object? enumValue)
484485
{
485486
if (type is { } && type.GetTypeInfo().IsEnum && Enum.IsDefined(type, value))
486487
{
487-
return Enum.Parse(type, value, true);
488+
enumValue = Enum.Parse(type, value, true);
489+
return true;
488490
}
489491

490-
return null;
492+
enumValue = null;
493+
return false;
491494
}
492495

493496
public static bool IsDictionary(Type? type)

test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs

+108
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,114 @@ public void DynamicExpressionParser_ParseLambda_With_One_Way_Implicit_Conversion
12971297
Assert.NotNull(lambda);
12981298
}
12991299

1300+
[Fact]
1301+
public void DynamicExpressionParser_ParseLambda_StaticClassWithStaticPropertyWithSameNameAsNormalProperty()
1302+
{
1303+
// Arrange
1304+
var config = new ParsingConfig
1305+
{
1306+
CustomTypeProvider = new TestCustomTypeProvider()
1307+
};
1308+
1309+
var user = new User
1310+
{
1311+
Id = new Guid("854f6ac8-71f9-4f79-8cd7-3ca46eaa02e6")
1312+
};
1313+
1314+
var expressionText = "Id == System.Linq.Dynamic.Core.Tests.Helpers.Models.UserInfo.Key";
1315+
1316+
// Act
1317+
var lambdaExpression = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText);
1318+
var boolExpression = (Expression<Func<User, bool>>)lambdaExpression;
1319+
1320+
var del = boolExpression.Compile();
1321+
var result = (bool?)del.DynamicInvoke(user);
1322+
1323+
// Assert
1324+
result.Should().Be(false);
1325+
}
1326+
1327+
[Fact]
1328+
public void DynamicExpressionParser_ParseLambda_StaticClassWithStaticProperty()
1329+
{
1330+
// Arrange
1331+
var config = new ParsingConfig
1332+
{
1333+
CustomTypeProvider = new TestCustomTypeProvider()
1334+
};
1335+
1336+
var user = new User
1337+
{
1338+
Id = new Guid("854f6ac8-71f9-4f79-8cd7-3ca46eaa02e6")
1339+
};
1340+
1341+
var expressionText = "Id == StaticHelper.NewStaticGuid";
1342+
1343+
// Act
1344+
var lambdaExpression = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText);
1345+
var boolExpression = (Expression<Func<User, bool>>)lambdaExpression;
1346+
1347+
var del = boolExpression.Compile();
1348+
var result = (bool?)del.DynamicInvoke(user);
1349+
1350+
// Assert
1351+
result.Should().Be(false);
1352+
}
1353+
1354+
[Fact]
1355+
public void DynamicExpressionParser_ParseLambda_NestedStaticClassWithStaticProperty()
1356+
{
1357+
// Arrange
1358+
var config = new ParsingConfig
1359+
{
1360+
CustomTypeProvider = new TestCustomTypeProvider()
1361+
};
1362+
1363+
var user = new User
1364+
{
1365+
Id = new Guid("854f6ac8-71f9-4f79-8cd7-3ca46eaa02e6")
1366+
};
1367+
1368+
var expressionText = "Id == StaticHelper.Nested.NewNestedStaticProperty";
1369+
1370+
// Act
1371+
var lambdaExpression = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText);
1372+
var boolExpression = (Expression<Func<User, bool>>)lambdaExpression;
1373+
1374+
var del = boolExpression.Compile();
1375+
var result = (bool?)del.DynamicInvoke(user);
1376+
1377+
// Assert
1378+
result.Should().Be(false);
1379+
}
1380+
1381+
[Fact]
1382+
public void DynamicExpressionParser_ParseLambda_NestedStaticClassWithStaticMethod()
1383+
{
1384+
// Arrange
1385+
var config = new ParsingConfig
1386+
{
1387+
CustomTypeProvider = new TestCustomTypeProvider()
1388+
};
1389+
1390+
var user = new User
1391+
{
1392+
Id = new Guid("854f6ac8-71f9-4f79-8cd7-3ca46eaa02e6")
1393+
};
1394+
1395+
var expressionText = "Id == StaticHelper.Nested.NewNestedStaticMethod()";
1396+
1397+
// Act
1398+
var lambdaExpression = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText);
1399+
var boolExpression = (Expression<Func<User, bool>>)lambdaExpression;
1400+
1401+
var del = boolExpression.Compile();
1402+
var result = (bool?)del.DynamicInvoke(user);
1403+
1404+
// Assert
1405+
result.Should().Be(false);
1406+
}
1407+
13001408
[Fact]
13011409
public void DynamicExpressionParser_ParseLambda_Operator_Less_Greater_With_Guids()
13021410
{

0 commit comments

Comments
 (0)