Skip to content

Commit c41f87d

Browse files
authored
Fix KeywordsHelper (#755)
1 parent 70a3298 commit c41f87d

File tree

5 files changed

+137
-81
lines changed

5 files changed

+137
-81
lines changed

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

-1
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,6 @@ private Expression ParseIdentifier()
957957

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

960-
961960
bool shouldPrioritizeType = true;
962961

963962
if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type)
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
namespace System.Linq.Dynamic.Core.Parser
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace System.Linq.Dynamic.Core.Parser;
4+
5+
interface IKeywordsHelper
26
{
3-
interface IKeywordsHelper
4-
{
5-
bool TryGetValue(string name, out object type);
6-
}
7-
}
7+
bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType);
8+
}
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,126 @@
11
using System.Collections.Generic;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Linq.Dynamic.Core.Validation;
24
using System.Linq.Expressions;
35

4-
namespace System.Linq.Dynamic.Core.Parser
6+
namespace System.Linq.Dynamic.Core.Parser;
7+
8+
internal class KeywordsHelper : IKeywordsHelper
59
{
6-
internal class KeywordsHelper : IKeywordsHelper
10+
public const string KEYWORD_IT = "it";
11+
public const string KEYWORD_PARENT = "parent";
12+
public const string KEYWORD_ROOT = "root";
13+
14+
public const string SYMBOL_IT = "$";
15+
public const string SYMBOL_PARENT = "^";
16+
public const string SYMBOL_ROOT = "~";
17+
18+
public const string FUNCTION_IIF = "iif";
19+
public const string FUNCTION_ISNULL = "isnull";
20+
public const string FUNCTION_NEW = "new";
21+
public const string FUNCTION_NULLPROPAGATION = "np";
22+
public const string FUNCTION_IS = "is";
23+
public const string FUNCTION_AS = "as";
24+
public const string FUNCTION_CAST = "cast";
25+
26+
private readonly ParsingConfig _config;
27+
28+
// Keywords are IgnoreCase
29+
private readonly Dictionary<string, object> _keywordMapping = new(StringComparer.OrdinalIgnoreCase)
730
{
8-
public const string SYMBOL_IT = "$";
9-
public const string SYMBOL_PARENT = "^";
10-
public const string SYMBOL_ROOT = "~";
11-
12-
public const string KEYWORD_IT = "it";
13-
public const string KEYWORD_PARENT = "parent";
14-
public const string KEYWORD_ROOT = "root";
15-
16-
public const string FUNCTION_IIF = "iif";
17-
public const string FUNCTION_ISNULL = "isnull";
18-
public const string FUNCTION_NEW = "new";
19-
public const string FUNCTION_NULLPROPAGATION = "np";
20-
public const string FUNCTION_IS = "is";
21-
public const string FUNCTION_AS = "as";
22-
public const string FUNCTION_CAST = "cast";
23-
24-
private readonly IDictionary<string, object> _keywords = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
25-
{
26-
{ "true", Expression.Constant(true) },
27-
{ "false", Expression.Constant(false) },
28-
{ "null", Expression.Constant(null) }
29-
};
31+
{ "true", Expression.Constant(true) },
32+
{ "false", Expression.Constant(false) },
33+
{ "null", Expression.Constant(null) },
34+
35+
{ SYMBOL_IT, SYMBOL_IT },
36+
{ SYMBOL_PARENT, SYMBOL_PARENT },
37+
{ SYMBOL_ROOT, SYMBOL_ROOT },
38+
39+
{ FUNCTION_IIF, FUNCTION_IIF },
40+
{ FUNCTION_ISNULL, FUNCTION_ISNULL },
41+
{ FUNCTION_NEW, FUNCTION_NEW },
42+
{ FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION },
43+
{ FUNCTION_IS, FUNCTION_IS },
44+
{ FUNCTION_AS, FUNCTION_AS },
45+
{ FUNCTION_CAST, FUNCTION_CAST }
46+
};
47+
48+
// PreDefined Types are not IgnoreCase
49+
private static readonly Dictionary<string, object> _preDefinedTypeMapping = new();
50+
51+
// Custom DefinedTypes are not IgnoreCase
52+
private readonly Dictionary<string, object> _customTypeMapping = new();
3053

31-
public KeywordsHelper(ParsingConfig config)
54+
static KeywordsHelper()
55+
{
56+
foreach (var type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key))
3257
{
33-
if (config.AreContextKeywordsEnabled)
34-
{
35-
_keywords.Add(KEYWORD_IT, KEYWORD_IT);
36-
_keywords.Add(KEYWORD_PARENT, KEYWORD_PARENT);
37-
_keywords.Add(KEYWORD_ROOT, KEYWORD_ROOT);
38-
}
58+
_preDefinedTypeMapping[type.FullName!] = type;
59+
_preDefinedTypeMapping[type.Name] = type;
60+
}
61+
}
3962

40-
_keywords.Add(SYMBOL_IT, SYMBOL_IT);
41-
_keywords.Add(SYMBOL_PARENT, SYMBOL_PARENT);
42-
_keywords.Add(SYMBOL_ROOT, SYMBOL_ROOT);
63+
public KeywordsHelper(ParsingConfig config)
64+
{
65+
_config = Check.NotNull(config);
4366

44-
_keywords.Add(FUNCTION_IIF, FUNCTION_IIF);
45-
_keywords.Add(FUNCTION_ISNULL, FUNCTION_ISNULL);
46-
_keywords.Add(FUNCTION_NEW, FUNCTION_NEW);
47-
_keywords.Add(FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION);
48-
_keywords.Add(FUNCTION_IS, FUNCTION_IS);
49-
_keywords.Add(FUNCTION_AS, FUNCTION_AS);
50-
_keywords.Add(FUNCTION_CAST, FUNCTION_CAST);
67+
if (config.AreContextKeywordsEnabled)
68+
{
69+
_keywordMapping.Add(KEYWORD_IT, KEYWORD_IT);
70+
_keywordMapping.Add(KEYWORD_PARENT, KEYWORD_PARENT);
71+
_keywordMapping.Add(KEYWORD_ROOT, KEYWORD_ROOT);
72+
}
5173

52-
foreach (Type type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key))
74+
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
75+
if (config.CustomTypeProvider != null)
76+
{
77+
foreach (var type in config.CustomTypeProvider.GetCustomTypes())
5378
{
54-
if (!string.IsNullOrEmpty(type.FullName))
55-
{
56-
_keywords[type.FullName] = type;
57-
}
58-
_keywords[type.Name] = type;
79+
_customTypeMapping[type.FullName!] = type;
80+
_customTypeMapping[type.Name] = type;
5981
}
82+
}
83+
}
6084

61-
foreach (var pair in PredefinedTypesHelper.PredefinedTypesShorthands)
62-
{
63-
_keywords.Add(pair.Key, pair.Value);
64-
}
85+
public bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType)
86+
{
87+
// 1. Try to get as keyword
88+
if (_keywordMapping.TryGetValue(name, out var keyWord))
89+
{
90+
keyWordOrType = keyWord;
91+
return true;
92+
}
6593

66-
if (config.SupportEnumerationsFromSystemNamespace)
67-
{
68-
foreach (var pair in EnumerationsFromMscorlib.PredefinedEnumerationTypes)
69-
{
70-
_keywords.Add(pair.Key, pair.Value);
71-
}
72-
}
94+
// 2. Try to get as predefined shorttype ("bool", "char", ...)
95+
if (PredefinedTypesHelper.PredefinedTypesShorthands.TryGetValue(name, out var predefinedShortHandType))
96+
{
97+
keyWordOrType = predefinedShortHandType;
98+
return true;
99+
}
73100

74-
if (config.CustomTypeProvider != null)
75-
{
76-
foreach (Type type in config.CustomTypeProvider.GetCustomTypes())
77-
{
78-
_keywords[type.FullName] = type;
79-
_keywords[type.Name] = type;
80-
}
81-
}
101+
// 3. Try to get as predefined type ("Boolean", "System.Boolean", ..., "DateTime", "System.DateTime", ...)
102+
if (_preDefinedTypeMapping.TryGetValue(name, out var predefinedType))
103+
{
104+
keyWordOrType = predefinedType;
105+
return true;
82106
}
83107

84-
public bool TryGetValue(string name, out object type)
108+
// 4. Try to get as an enum from the system namespace
109+
if (_config.SupportEnumerationsFromSystemNamespace && EnumerationsFromMscorlib.PredefinedEnumerationTypes.TryGetValue(name, out var predefinedEnumType))
85110
{
86-
return _keywords.TryGetValue(name, out type);
111+
keyWordOrType = predefinedEnumType;
112+
return true;
87113
}
114+
115+
// 5. Try to get as custom type
116+
if (_customTypeMapping.TryGetValue(name, out var customType))
117+
{
118+
keyWordOrType = customType;
119+
return true;
120+
}
121+
122+
// 6. Not found, return false
123+
keyWordOrType = null;
124+
return false;
88125
}
89-
}
126+
}

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

+12-5
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,21 @@ internal static class PredefinedTypesHelper
1313
// These shorthands have different name than actual type and therefore not recognized by default from the PredefinedTypes.
1414
public static readonly IDictionary<string, Type> PredefinedTypesShorthands = new Dictionary<string, Type>
1515
{
16+
{ "bool", typeof(bool) },
17+
{ "byte", typeof(byte) },
18+
{ "char", typeof(char) },
19+
{ "decimal", typeof(decimal) },
20+
{ "double", typeof(double) },
21+
{ "float", typeof(float) },
1622
{ "int", typeof(int) },
17-
{ "uint", typeof(uint) },
18-
{ "short", typeof(short) },
19-
{ "ushort", typeof(ushort) },
2023
{ "long", typeof(long) },
24+
{ "object", typeof(object) },
25+
{ "sbyte", typeof(sbyte) },
26+
{ "short", typeof(short) },
27+
{ "string", typeof(string) },
28+
{ "uint", typeof(uint) },
2129
{ "ulong", typeof(ulong) },
22-
{ "bool", typeof(bool) },
23-
{ "float", typeof(float) }
30+
{ "ushort", typeof(ushort) },
2431
};
2532

2633
public static readonly IDictionary<Type, int> PredefinedTypes = new ConcurrentDictionary<Type, int>(new Dictionary<Type, int>

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

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ namespace System.Linq.Dynamic.Core.Tests;
1616

1717
public class DynamicExpressionParserTests
1818
{
19+
[DynamicLinqType]
20+
public class Z
21+
{
22+
}
23+
1924
public class Foo
2025
{
2126
public Foo FooValue { get; set; }
@@ -1786,6 +1791,13 @@ public void DynamicExpressionParser_ParseLambda_HandleIntArray_For_StringJoin()
17861791
result.Should().Be("1,2,3");
17871792
}
17881793

1794+
[Fact]
1795+
public void DynamicExpressionParser_ParseLambda_LambdaParameter_SameNameAsDynamicType()
1796+
{
1797+
// Act
1798+
DynamicExpressionParser.ParseLambda<bool>(new ParsingConfig(), false, "new[]{1,2,3}.Any(z => z > 0)");
1799+
}
1800+
17891801
public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : DefaultDynamicLinqCustomTypeProvider
17901802
{
17911803
public override HashSet<Type> GetCustomTypes() => new HashSet<Type>(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) };

0 commit comments

Comments
 (0)