Skip to content

Commit

Permalink
Fix KeywordsHelper (#755)
Browse files Browse the repository at this point in the history
  • Loading branch information
StefH authored Oct 29, 2023
1 parent 70a3298 commit c41f87d
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 81 deletions.
1 change: 0 additions & 1 deletion src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,6 @@ private Expression ParseIdentifier()

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


bool shouldPrioritizeType = true;

if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type)
Expand Down
13 changes: 7 additions & 6 deletions src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace System.Linq.Dynamic.Core.Parser
using System.Diagnostics.CodeAnalysis;

namespace System.Linq.Dynamic.Core.Parser;

interface IKeywordsHelper
{
interface IKeywordsHelper
{
bool TryGetValue(string name, out object type);
}
}
bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType);
}
175 changes: 106 additions & 69 deletions src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs
Original file line number Diff line number Diff line change
@@ -1,89 +1,126 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Dynamic.Core.Validation;
using System.Linq.Expressions;

namespace System.Linq.Dynamic.Core.Parser
namespace System.Linq.Dynamic.Core.Parser;

internal class KeywordsHelper : IKeywordsHelper
{
internal class KeywordsHelper : IKeywordsHelper
public const string KEYWORD_IT = "it";
public const string KEYWORD_PARENT = "parent";
public const string KEYWORD_ROOT = "root";

public const string SYMBOL_IT = "$";
public const string SYMBOL_PARENT = "^";
public const string SYMBOL_ROOT = "~";

public const string FUNCTION_IIF = "iif";
public const string FUNCTION_ISNULL = "isnull";
public const string FUNCTION_NEW = "new";
public const string FUNCTION_NULLPROPAGATION = "np";
public const string FUNCTION_IS = "is";
public const string FUNCTION_AS = "as";
public const string FUNCTION_CAST = "cast";

private readonly ParsingConfig _config;

// Keywords are IgnoreCase
private readonly Dictionary<string, object> _keywordMapping = new(StringComparer.OrdinalIgnoreCase)
{
public const string SYMBOL_IT = "$";
public const string SYMBOL_PARENT = "^";
public const string SYMBOL_ROOT = "~";

public const string KEYWORD_IT = "it";
public const string KEYWORD_PARENT = "parent";
public const string KEYWORD_ROOT = "root";

public const string FUNCTION_IIF = "iif";
public const string FUNCTION_ISNULL = "isnull";
public const string FUNCTION_NEW = "new";
public const string FUNCTION_NULLPROPAGATION = "np";
public const string FUNCTION_IS = "is";
public const string FUNCTION_AS = "as";
public const string FUNCTION_CAST = "cast";

private readonly IDictionary<string, object> _keywords = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "true", Expression.Constant(true) },
{ "false", Expression.Constant(false) },
{ "null", Expression.Constant(null) }
};
{ "true", Expression.Constant(true) },
{ "false", Expression.Constant(false) },
{ "null", Expression.Constant(null) },

{ SYMBOL_IT, SYMBOL_IT },
{ SYMBOL_PARENT, SYMBOL_PARENT },
{ SYMBOL_ROOT, SYMBOL_ROOT },

{ FUNCTION_IIF, FUNCTION_IIF },
{ FUNCTION_ISNULL, FUNCTION_ISNULL },
{ FUNCTION_NEW, FUNCTION_NEW },
{ FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION },
{ FUNCTION_IS, FUNCTION_IS },
{ FUNCTION_AS, FUNCTION_AS },
{ FUNCTION_CAST, FUNCTION_CAST }
};

// PreDefined Types are not IgnoreCase
private static readonly Dictionary<string, object> _preDefinedTypeMapping = new();

// Custom DefinedTypes are not IgnoreCase
private readonly Dictionary<string, object> _customTypeMapping = new();

public KeywordsHelper(ParsingConfig config)
static KeywordsHelper()
{
foreach (var type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key))
{
if (config.AreContextKeywordsEnabled)
{
_keywords.Add(KEYWORD_IT, KEYWORD_IT);
_keywords.Add(KEYWORD_PARENT, KEYWORD_PARENT);
_keywords.Add(KEYWORD_ROOT, KEYWORD_ROOT);
}
_preDefinedTypeMapping[type.FullName!] = type;
_preDefinedTypeMapping[type.Name] = type;
}
}

_keywords.Add(SYMBOL_IT, SYMBOL_IT);
_keywords.Add(SYMBOL_PARENT, SYMBOL_PARENT);
_keywords.Add(SYMBOL_ROOT, SYMBOL_ROOT);
public KeywordsHelper(ParsingConfig config)
{
_config = Check.NotNull(config);

_keywords.Add(FUNCTION_IIF, FUNCTION_IIF);
_keywords.Add(FUNCTION_ISNULL, FUNCTION_ISNULL);
_keywords.Add(FUNCTION_NEW, FUNCTION_NEW);
_keywords.Add(FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION);
_keywords.Add(FUNCTION_IS, FUNCTION_IS);
_keywords.Add(FUNCTION_AS, FUNCTION_AS);
_keywords.Add(FUNCTION_CAST, FUNCTION_CAST);
if (config.AreContextKeywordsEnabled)
{
_keywordMapping.Add(KEYWORD_IT, KEYWORD_IT);
_keywordMapping.Add(KEYWORD_PARENT, KEYWORD_PARENT);
_keywordMapping.Add(KEYWORD_ROOT, KEYWORD_ROOT);
}

foreach (Type type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key))
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (config.CustomTypeProvider != null)
{
foreach (var type in config.CustomTypeProvider.GetCustomTypes())
{
if (!string.IsNullOrEmpty(type.FullName))
{
_keywords[type.FullName] = type;
}
_keywords[type.Name] = type;
_customTypeMapping[type.FullName!] = type;
_customTypeMapping[type.Name] = type;
}
}
}

foreach (var pair in PredefinedTypesHelper.PredefinedTypesShorthands)
{
_keywords.Add(pair.Key, pair.Value);
}
public bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType)
{
// 1. Try to get as keyword
if (_keywordMapping.TryGetValue(name, out var keyWord))
{
keyWordOrType = keyWord;
return true;
}

if (config.SupportEnumerationsFromSystemNamespace)
{
foreach (var pair in EnumerationsFromMscorlib.PredefinedEnumerationTypes)
{
_keywords.Add(pair.Key, pair.Value);
}
}
// 2. Try to get as predefined shorttype ("bool", "char", ...)
if (PredefinedTypesHelper.PredefinedTypesShorthands.TryGetValue(name, out var predefinedShortHandType))
{
keyWordOrType = predefinedShortHandType;
return true;
}

if (config.CustomTypeProvider != null)
{
foreach (Type type in config.CustomTypeProvider.GetCustomTypes())
{
_keywords[type.FullName] = type;
_keywords[type.Name] = type;
}
}
// 3. Try to get as predefined type ("Boolean", "System.Boolean", ..., "DateTime", "System.DateTime", ...)
if (_preDefinedTypeMapping.TryGetValue(name, out var predefinedType))
{
keyWordOrType = predefinedType;
return true;
}

public bool TryGetValue(string name, out object type)
// 4. Try to get as an enum from the system namespace
if (_config.SupportEnumerationsFromSystemNamespace && EnumerationsFromMscorlib.PredefinedEnumerationTypes.TryGetValue(name, out var predefinedEnumType))
{
return _keywords.TryGetValue(name, out type);
keyWordOrType = predefinedEnumType;
return true;
}

// 5. Try to get as custom type
if (_customTypeMapping.TryGetValue(name, out var customType))
{
keyWordOrType = customType;
return true;
}

// 6. Not found, return false
keyWordOrType = null;
return false;
}
}
}
17 changes: 12 additions & 5 deletions src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,21 @@ internal static class PredefinedTypesHelper
// These shorthands have different name than actual type and therefore not recognized by default from the PredefinedTypes.
public static readonly IDictionary<string, Type> PredefinedTypesShorthands = new Dictionary<string, Type>
{
{ "bool", typeof(bool) },
{ "byte", typeof(byte) },
{ "char", typeof(char) },
{ "decimal", typeof(decimal) },
{ "double", typeof(double) },
{ "float", typeof(float) },
{ "int", typeof(int) },
{ "uint", typeof(uint) },
{ "short", typeof(short) },
{ "ushort", typeof(ushort) },
{ "long", typeof(long) },
{ "object", typeof(object) },
{ "sbyte", typeof(sbyte) },
{ "short", typeof(short) },
{ "string", typeof(string) },
{ "uint", typeof(uint) },
{ "ulong", typeof(ulong) },
{ "bool", typeof(bool) },
{ "float", typeof(float) }
{ "ushort", typeof(ushort) },
};

public static readonly IDictionary<Type, int> PredefinedTypes = new ConcurrentDictionary<Type, int>(new Dictionary<Type, int>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ namespace System.Linq.Dynamic.Core.Tests;

public class DynamicExpressionParserTests
{
[DynamicLinqType]
public class Z
{
}

public class Foo
{
public Foo FooValue { get; set; }
Expand Down Expand Up @@ -1786,6 +1791,13 @@ public void DynamicExpressionParser_ParseLambda_HandleIntArray_For_StringJoin()
result.Should().Be("1,2,3");
}

[Fact]
public void DynamicExpressionParser_ParseLambda_LambdaParameter_SameNameAsDynamicType()
{
// Act
DynamicExpressionParser.ParseLambda<bool>(new ParsingConfig(), false, "new[]{1,2,3}.Any(z => z > 0)");
}

public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : DefaultDynamicLinqCustomTypeProvider
{
public override HashSet<Type> GetCustomTypes() => new HashSet<Type>(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) };
Expand Down

0 comments on commit c41f87d

Please sign in to comment.