diff --git a/src-console/ConsoleAppEF1.1/Program.cs b/src-console/ConsoleAppEF1.1/Program.cs index a91e1f0b..dee34406 100644 --- a/src-console/ConsoleAppEF1.1/Program.cs +++ b/src-console/ConsoleAppEF1.1/Program.cs @@ -11,6 +11,14 @@ static void Main(string[] args) { using (var db = new MyDbContext()) { + var all = new + { + test1 = new List { 1, 2, 3 }.ToDynamicList(typeof(int)), + test2 = new List { 4, 5, 6 }.ToDynamicList(typeof(int)), + test3 = new List { 7, 8, 9 }.ToDynamicList(typeof(int)) + }; + Console.WriteLine("all {0}", JsonConvert.SerializeObject(all, Formatting.Indented)); + var persons = new List { new Person { Name = "a", Age = 18, Address = new Address { Street = "s1" } }, diff --git a/src-console/ConsoleAppEF2.0/Program.cs b/src-console/ConsoleAppEF2.0/Program.cs index 349812aa..4f084fa4 100644 --- a/src-console/ConsoleAppEF2.0/Program.cs +++ b/src-console/ConsoleAppEF2.0/Program.cs @@ -27,7 +27,16 @@ public HashSet GetCustomTypes() static void Main(string[] args) { - GlobalConfig.CustomTypeProvider = new C(); + var all = new + { + test1 = new List { 1, 2, 3 }.ToDynamicList(typeof(int)), + test2 = new List { 4, 5, 6 }.ToDynamicList(typeof(int)), + test3 = new List { 7, 8, 9 }.ToDynamicList(typeof(int)) + }; + Console.WriteLine("all {0}", JsonConvert.SerializeObject(all, Formatting.Indented)); + + var config = new ParsingConfig(); + config.CustomTypeProvider = new C(); var context = new TestContext(); @@ -42,7 +51,7 @@ static void Main(string[] args) context.SaveChanges(); } - var carFirstOrDefault = context.Cars.Where("Brand == \"Ford\""); + var carFirstOrDefault = context.Cars.Where(config, "Brand == \"Ford\""); Console.WriteLine("carFirstOrDefault {0}", JsonConvert.SerializeObject(carFirstOrDefault, Formatting.Indented)); var carsLike1 = @@ -54,16 +63,16 @@ where EF.Functions.Like(c.Brand, "%a%") var cars2Like = context.Cars.Where(c => EF.Functions.Like(c.Brand, "%a%")); Console.WriteLine("cars2Like {0}", JsonConvert.SerializeObject(cars2Like, Formatting.Indented)); - var dynamicCarsLike1 = context.Cars.Where("TestContext.Like(Brand, \"%a%\")"); + var dynamicCarsLike1 = context.Cars.Where(config, "TestContext.Like(Brand, \"%a%\")"); Console.WriteLine("dynamicCarsLike1 {0}", JsonConvert.SerializeObject(dynamicCarsLike1, Formatting.Indented)); - var dynamicCarsLike2 = context.Cars.Where("TestContext.Like(Brand, \"%d%\")"); + var dynamicCarsLike2 = context.Cars.Where(config, "TestContext.Like(Brand, \"%d%\")"); Console.WriteLine("dynamicCarsLike2 {0}", JsonConvert.SerializeObject(dynamicCarsLike2, Formatting.Indented)); - var dynamicFunctionsLike1 = context.Cars.Where("DynamicFunctions.Like(Brand, \"%a%\")"); + var dynamicFunctionsLike1 = context.Cars.Where(config, "DynamicFunctions.Like(Brand, \"%a%\")"); Console.WriteLine("dynamicFunctionsLike1 {0}", JsonConvert.SerializeObject(dynamicFunctionsLike1, Formatting.Indented)); - var dynamicFunctionsLike2 = context.Cars.Where("DynamicFunctions.Like(Vin, \"%a.%b%\", \".\")"); + var dynamicFunctionsLike2 = context.Cars.Where(config, "DynamicFunctions.Like(Vin, \"%a.%b%\", \".\")"); Console.WriteLine("dynamicFunctionsLike2 {0}", JsonConvert.SerializeObject(dynamicFunctionsLike2, Formatting.Indented)); } } diff --git a/src-console/ConsoleApp_net452_EF6/ConsoleApp_net452_EF6.csproj b/src-console/ConsoleApp_net452_EF6/ConsoleApp_net452_EF6.csproj index 36d3e03a..212f5e8d 100644 --- a/src-console/ConsoleApp_net452_EF6/ConsoleApp_net452_EF6.csproj +++ b/src-console/ConsoleApp_net452_EF6/ConsoleApp_net452_EF6.csproj @@ -62,6 +62,9 @@ ..\..\packages\Linq.Expression.Optimizer.1.0.9\lib\net45\Linq.Expression.Optimizer.dll + + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + diff --git a/src-console/ConsoleApp_net452_EF6/Program.cs b/src-console/ConsoleApp_net452_EF6/Program.cs index ca73ea02..c082a483 100644 --- a/src-console/ConsoleApp_net452_EF6/Program.cs +++ b/src-console/ConsoleApp_net452_EF6/Program.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; using ConsoleApp_net452_EF6.Entities; using System.Linq.Dynamic.Core; +using Newtonsoft.Json; namespace ConsoleApp_net452_EF6 { @@ -9,6 +11,14 @@ class Program { static void Main(string[] args) { + var all = new + { + test1 = new List { 1, 2, 3 }.ToDynamicList(typeof(int)), + test2 = new List { 4, 5, 6 }.ToDynamicList(typeof(int)), + test3 = new List { 7, 8, 9 }.ToDynamicList(typeof(int)) + }; + + Console.WriteLine("all {0}", JsonConvert.SerializeObject(all, Formatting.Indented)); using (var context = new KendoGridDbContext()) { string search = "2"; diff --git a/src-console/ConsoleApp_net452_EF6/packages.config b/src-console/ConsoleApp_net452_EF6/packages.config index e0c19b49..45f617f1 100644 --- a/src-console/ConsoleApp_net452_EF6/packages.config +++ b/src-console/ConsoleApp_net452_EF6/packages.config @@ -3,4 +3,5 @@ + \ No newline at end of file diff --git a/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj b/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj index 7d08da18..d41b922f 100644 --- a/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj +++ b/src/EntityFramework.DynamicLinq/EntityFramework.DynamicLinq.csproj @@ -2,7 +2,7 @@ Dynamic Linq extensions for EntityFramework which adds Async support EntityFramework.DynamicLinq - 1.0.4.7 + 1.0.8.0 Stef Heyenrath net45;net46 EF diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj b/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj index 937594b7..8acd31d2 100644 --- a/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq/Microsoft.EntityFrameworkCore.DynamicLinq.csproj @@ -2,7 +2,7 @@ Dynamic Linq extensions for Microsoft.EntityFrameworkCore which adds Async support Microsoft.EntityFrameworkCore.DynamicLinq - 1.0.4.8 + 1.0.8.0 Stef Heyenrath net451;net46;netstandard1.3;netstandard2.0;uap10.0 $(DefineConstants);EFCORE diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs index 47e82e46..0e1c024c 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DefaultDynamicLinqCustomTypeProvider.cs @@ -21,7 +21,9 @@ public class DefaultDynamicLinqCustomTypeProvider : AbstractDynamicLinqCustomTyp public virtual HashSet GetCustomTypes() { if (_customTypes != null) + { return _customTypes; + } IEnumerable assemblies = _assemblyHelper.GetAssemblies(); _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(assemblies)); @@ -29,4 +31,4 @@ public virtual HashSet GetCustomTypes() } } } -#endif \ No newline at end of file +#endif diff --git a/src/System.Linq.Dynamic.Core/DynamicEnumerableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicEnumerableExtensions.cs index 6ec003b3..85bc93b7 100644 --- a/src/System.Linq.Dynamic.Core/DynamicEnumerableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicEnumerableExtensions.cs @@ -65,11 +65,11 @@ public static dynamic[] ToDynamicArray([NotNull] this IEnumerable source, [NotNu Check.NotNull(source, nameof(source)); Check.NotNull(type, nameof(type)); - object result = ToDynamicArrayGenericMethod.MakeGenericMethod(type).Invoke(source, new object[] { source }); + IEnumerable result = (IEnumerable)ToDynamicArrayGenericMethod.MakeGenericMethod(type).Invoke(source, new object[] { source }); #if NET35 - return (object[])result; + return CastToArray(result); #else - return (dynamic[])result; + return CastToArray(result); #endif } @@ -132,4 +132,4 @@ internal static List CastToList(IEnumerable source) return source.Cast().ToList(); } } -} \ No newline at end of file +} diff --git a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs index 75c111d2..efd4d58f 100644 --- a/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/DynamicExpressionParser.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Linq.Dynamic.Core.Parser; +using JetBrains.Annotations; using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; @@ -14,43 +15,41 @@ public static class DynamicExpressionParser /// /// Type of the result. If not specified, it will be generated dynamically. /// The expression. + /// The Configuration for the parsing. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda([CanBeNull] Type resultType, string expression, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { - return ParseLambda(true, resultType, expression, values); + return ParseLambda(parsingConfig, true, resultType, expression, values); } /// - /// Parses an expression into a LambdaExpression. + /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) /// - /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull] Type resultType, string expression, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { Check.NotEmpty(expression, nameof(expression)); - var parser = new ExpressionParser(new ParameterExpression[0], expression, values, null); - - return Expression.Lambda(parser.Parse(resultType, true)); + return ParseLambda(null, true, resultType, expression, values); } /// /// Parses an expression into a LambdaExpression. /// + /// The Configuration for the parsing. /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. - /// The Configuration for the parsing. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda(bool createParameterCtor, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [CanBeNull] Type resultType, string expression, params object[] values) { Check.NotEmpty(expression, nameof(expression)); @@ -83,7 +82,7 @@ public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Ty /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda([NotNull] Type itType, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) { return ParseLambda(true, itType, resultType, expression, parsingConfig, values); } @@ -109,20 +108,20 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] T /// /// Parses an expression into a LambdaExpression. /// + /// The Configuration for the parsing. /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. /// The main type from the dynamic class expression. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. - /// The Configuration for the parsing. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [NotNull] Type itType, [CanBeNull] Type resultType, string expression, params object[] values) { Check.NotNull(itType, nameof(itType)); Check.NotEmpty(expression, nameof(expression)); - return ParseLambda(createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, parsingConfig, values); + return ParseLambda(parsingConfig, createParameterCtor, new[] { Expression.Parameter(itType, "") }, resultType, expression, values); } /// @@ -136,7 +135,22 @@ public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] T [PublicAPI] public static LambdaExpression ParseLambda([NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, params object[] values) { - return ParseLambda(true, parameters, resultType, expression, values); + return ParseLambda(null, true, parameters, resultType, expression, values); + } + + /// + /// Parses an expression into a LambdaExpression. (Also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities.) + /// + /// The Configuration for the parsing. + /// A array from ParameterExpressions. + /// Type of the result. If not specified, it will be generated dynamically. + /// The expression. + /// An object array that contains zero or more objects which are used as replacement values. + /// The generated + [PublicAPI] + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, params object[] values) + { + return ParseLambda(parsingConfig, true, parameters, resultType, expression, values); } /// @@ -149,29 +163,23 @@ public static LambdaExpression ParseLambda([NotNull] ParameterExpression[] param /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, params object[] values) + public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { - Check.NotNull(parameters, nameof(parameters)); - Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters)); - Check.NotEmpty(expression, nameof(expression)); - - var parser = new ExpressionParser(parameters, expression, values, null); - - return Expression.Lambda(parser.Parse(resultType, createParameterCtor), parameters); + return ParseLambda(null, createParameterCtor, parameters, resultType, expression, values); } /// /// Parses an expression into a LambdaExpression. /// + /// The Configuration for the parsing. /// if set to true then also create a constructor for all the parameters. Note that this doesn't work for Linq-to-Database entities. /// A array from ParameterExpressions. /// Type of the result. If not specified, it will be generated dynamically. /// The expression. - /// The Configuration for the parsing. /// An object array that contains zero or more objects which are used as replacement values. /// The generated [PublicAPI] - public static LambdaExpression ParseLambda(bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, string expression, ParsingConfig parsingConfig, params object[] values) + public static LambdaExpression ParseLambda([CanBeNull] ParsingConfig parsingConfig, bool createParameterCtor, [NotNull] ParameterExpression[] parameters, [CanBeNull] Type resultType, [NotNull] string expression, params object[] values) { Check.NotNull(parameters, nameof(parameters)); Check.Condition(parameters, p => p.Count(x => x == null) == 0, nameof(parameters)); diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index 08279849..fc6b593f 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -10,6 +10,7 @@ using System.Linq.Expressions; using System.Reflection; using JetBrains.Annotations; +using System.Linq.Dynamic.Core.Parser; #if WINDOWS_APP using System; using System.Linq; @@ -76,7 +77,7 @@ public static object Aggregate([NotNull] this IQueryable source, [NotNull] strin { ParameterInfo lastParameter = m.GetParameters().LastOrDefault(); - return lastParameter != null ? ExpressionParser.GetUnderlyingType(lastParameter.ParameterType) == property.PropertyType : false; + return lastParameter != null ? TypeHelper.GetUnderlyingType(lastParameter.ParameterType) == property.PropertyType : false; }); // Sum, Average @@ -85,20 +86,18 @@ public static object Aggregate([NotNull] this IQueryable source, [NotNull] strin return source.Provider.Execute( Expression.Call( null, - aggregateMethod.MakeGenericMethod(new[] { source.ElementType }), + aggregateMethod.MakeGenericMethod(source.ElementType), new[] { source.Expression, Expression.Quote(selector) })); } + // Min, Max - else - { - aggregateMethod = methods.SingleOrDefault(m => m.Name == function && m.GetGenericArguments().Length == 2); + aggregateMethod = methods.SingleOrDefault(m => m.Name == function && m.GetGenericArguments().Length == 2); - return source.Provider.Execute( - Expression.Call( - null, - aggregateMethod.MakeGenericMethod(new[] { source.ElementType, property.PropertyType }), - new[] { source.Expression, Expression.Quote(selector) })); - } + return source.Provider.Execute( + Expression.Call( + null, + aggregateMethod.MakeGenericMethod(source.ElementType, property.PropertyType), + new[] { source.Expression, Expression.Quote(selector) })); } #endregion Aggregate @@ -129,6 +128,7 @@ public static bool Any([NotNull] this IQueryable source) /// Determines whether a sequence contains any elements. /// /// A sequence to check for being empty. + /// The . /// A function to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// @@ -140,17 +140,23 @@ public static bool Any([NotNull] this IQueryable source) /// /// /// true if the source sequence contains any elements; otherwise, false. - public static bool Any([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + public static bool Any([NotNull] this IQueryable source, [CanBeNull] ParsingConfig config, [NotNull] string predicate, params object[] args) { Check.NotNull(source, nameof(source)); Check.NotEmpty(predicate, nameof(predicate)); bool createParameterCtor = source.IsLinqToObjects(); - LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args); + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); return Execute(_anyPredicate, source, lambda); } + /// + public static bool Any([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + { + return Any(source, null, predicate, args); + } + /// /// Determines whether a sequence contains any elements. /// @@ -217,6 +223,7 @@ public static int Count([NotNull] this IQueryable source) /// Returns the number of elements in a sequence. /// /// The that contains the elements to be counted. + /// The . /// A function to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// @@ -228,17 +235,23 @@ public static int Count([NotNull] this IQueryable source) /// /// /// The number of elements in the specified sequence that satisfies a condition. - public static int Count([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + public static int Count([NotNull] this IQueryable source, [CanBeNull] ParsingConfig config, [NotNull] string predicate, params object[] args) { Check.NotNull(source, nameof(source)); Check.NotEmpty(predicate, nameof(predicate)); bool createParameterCtor = source.IsLinqToObjects(); - LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, null, predicate, args); + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, null, predicate, args); return Execute(_countPredicate, source, lambda); } + /// + public static int Count([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + { + return Count(source, null, predicate, args); + } + /// /// Returns the number of elements in a sequence. /// @@ -1585,6 +1598,7 @@ public static IOrderedQueryable ThenBy([NotNull] this IOrderedQueryable source, /// /// The type of the elements of source. /// A to filter. + /// The . /// An expression string to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// A that contains elements from the input sequence that satisfy the condition specified by predicate. @@ -1597,18 +1611,25 @@ public static IOrderedQueryable ThenBy([NotNull] this IOrderedQueryable source, /// var result5 = queryable.Where("StringProperty = @0", "abc"); /// /// - public static IQueryable Where([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + public static IQueryable Where([NotNull] this IQueryable source, [CanBeNull] ParsingConfig config, [NotNull] string predicate, params object[] args) { Check.NotNull(source, nameof(source)); Check.NotEmpty(predicate, nameof(predicate)); - return (IQueryable)Where((IQueryable)source, predicate, args); + return (IQueryable)Where((IQueryable)source, config, predicate, args); + } + + /// + public static IQueryable Where([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + { + return Where(source, null, predicate, args); } /// /// Filters a sequence of values based on a predicate. /// /// A to filter. + /// The . /// An expression string to test each element for a condition. /// An object array that contains zero or more objects to insert into the predicate as parameters. Similar to the way String.Format formats strings. /// A that contains elements from the input sequence that satisfy the condition specified by predicate. @@ -1621,18 +1642,24 @@ public static IQueryable Where([NotNull] this IQueryable /// - public static IQueryable Where([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + public static IQueryable Where([NotNull] this IQueryable source, [CanBeNull] ParsingConfig config, [NotNull] string predicate, params object[] args) { Check.NotNull(source, nameof(source)); Check.NotEmpty(predicate, nameof(predicate)); bool createParameterCtor = source.IsLinqToObjects(); - LambdaExpression lambda = DynamicExpressionParser.ParseLambda(createParameterCtor, source.ElementType, typeof(bool), predicate, args); + LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, typeof(bool), predicate, args); var optimized = OptimizeExpression(Expression.Call(typeof(Queryable), "Where", new[] { source.ElementType }, source.Expression, Expression.Quote(lambda))); return source.Provider.CreateQuery(optimized); } + /// + public static IQueryable Where([NotNull] this IQueryable source, [NotNull] string predicate, params object[] args) + { + return Where(source, null, predicate, args); + } + /// /// Filters a sequence of values based on a predicate. /// @@ -1664,12 +1691,12 @@ private static void CheckOuterAndInnerTypes(bool createParameterCtor, Type outer //} - if (ExpressionParser.IsNullableType(outerSelectorReturnType) && !ExpressionParser.IsNullableType(innerSelectorReturnType)) + if (TypeHelper.IsNullableType(outerSelectorReturnType) && !TypeHelper.IsNullableType(innerSelectorReturnType)) { innerSelectorReturnType = ExpressionParser.ToNullableType(innerSelectorReturnType); innerSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, innerType, innerSelectorReturnType, innerKeySelector, args); } - else if (!ExpressionParser.IsNullableType(outerSelectorReturnType) && ExpressionParser.IsNullableType(innerSelectorReturnType)) + else if (!TypeHelper.IsNullableType(outerSelectorReturnType) && TypeHelper.IsNullableType(innerSelectorReturnType)) { outerSelectorReturnType = ExpressionParser.ToNullableType(outerSelectorReturnType); outerSelectorLambda = DynamicExpressionParser.ParseLambda(createParameterCtor, outerType, outerSelectorReturnType, outerKeySelector, args); diff --git a/src/System.Linq.Dynamic.Core/GlobalConfig.cs b/src/System.Linq.Dynamic.Core/GlobalConfig.cs deleted file mode 100644 index aedf4fa5..00000000 --- a/src/System.Linq.Dynamic.Core/GlobalConfig.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Linq.Dynamic.Core.CustomTypeProviders; - -namespace System.Linq.Dynamic.Core -{ - /// - /// Static configuration class for Dynamic Linq. - /// - public static class GlobalConfig - { - private static IDynamicLinkCustomTypeProvider _customTypeProvider; - - /// - /// Gets or sets the . - /// - public static IDynamicLinkCustomTypeProvider CustomTypeProvider - { - get - { -#if !(DOTNET5_1 || WINDOWS_APP || UAP10_0 || NETSTANDARD) - // only use DefaultDynamicLinqCustomTypeProvider if not WINDOWS_APP || UAP10_0 || NETSTANDARD - return _customTypeProvider ?? (_customTypeProvider = new DefaultDynamicLinqCustomTypeProvider()); -#else - return _customTypeProvider; -#endif - } - - set - { - if (_customTypeProvider != value) - { - _customTypeProvider = value; - - ExpressionParser.ResetDynamicLinqTypes(); - } - } - } - - static bool _contextKeywordsEnabled = true; - - /// - /// Determines if the context keywords (it, parent, and root) are valid and usable inside a Dynamic Linq string expression. - /// Does not affect the usability of the equivalent context symbols ($, ^ and ~). - /// - public static bool AreContextKeywordsEnabled - { - get => _contextKeywordsEnabled; - set - { - if (value != _contextKeywordsEnabled) - { - _contextKeywordsEnabled = value; - - ExpressionParser.ResetDynamicLinqTypes(); - } - } - } - - /// - /// Gets or sets a value indicating whether to use dynamic object class for anonymous types. - /// - /// - /// true if wether to use dynamic object class for anonymous types; otherwise, false. - /// - public static bool UseDynamicObjectClassForAnonymousTypes { get; set; } - } -} diff --git a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs new file mode 100644 index 00000000..92a00c10 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs @@ -0,0 +1,24 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Parser +{ + internal static class ConstantExpressionHelper + { + private static readonly IDictionary Literals = new ConcurrentDictionary(); + + public static bool TryGetText(Expression expresion, out string text) + { + return Literals.TryGetValue(expresion, out text); + } + + public static Expression CreateLiteral(object value, string text) + { + ConstantExpression expresion = Expression.Constant(value); + + Literals.Add(expresion, text); + return expresion; + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/Constants.cs b/src/System.Linq.Dynamic.Core/Parser/Constants.cs new file mode 100644 index 00000000..da3e2e45 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/Constants.cs @@ -0,0 +1,11 @@ +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Parser +{ + internal static class Constants + { + public static readonly Expression TrueLiteral = Expression.Constant(true); + public static readonly Expression FalseLiteral = Expression.Constant(false); + public static readonly Expression NullLiteral = Expression.Constant(null); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs new file mode 100644 index 00000000..69e78fed --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs @@ -0,0 +1,199 @@ +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Linq.Dynamic.Core.Parser +{ + internal static class ExpressionHelper + { + public static void ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref Expression left, ref Expression right) + { + if (left.Type == right.Type) + { + return; + } + + if (left.Type == typeof(UInt64) || right.Type == typeof(UInt64)) + { + right = right.Type != typeof(UInt64) ? Expression.Convert(right, typeof(UInt64)) : right; + left = left.Type != typeof(UInt64) ? Expression.Convert(left, typeof(UInt64)) : left; + } + else if (left.Type == typeof(Int64) || right.Type == typeof(Int64)) + { + right = right.Type != typeof(Int64) ? Expression.Convert(right, typeof(Int64)) : right; + left = left.Type != typeof(Int64) ? Expression.Convert(left, typeof(Int64)) : left; + } + else if (left.Type == typeof(UInt32) || right.Type == typeof(UInt32)) + { + right = right.Type != typeof(UInt32) ? Expression.Convert(right, typeof(UInt32)) : right; + left = left.Type != typeof(UInt32) ? Expression.Convert(left, typeof(UInt32)) : left; + } + else if (left.Type == typeof(Int32) || right.Type == typeof(Int32)) + { + right = right.Type != typeof(Int32) ? Expression.Convert(right, typeof(Int32)) : right; + left = left.Type != typeof(Int32) ? Expression.Convert(left, typeof(Int32)) : left; + } + else if (left.Type == typeof(UInt16) || right.Type == typeof(UInt16)) + { + right = right.Type != typeof(UInt16) ? Expression.Convert(right, typeof(UInt16)) : right; + left = left.Type != typeof(UInt16) ? Expression.Convert(left, typeof(UInt16)) : left; + } + else if (left.Type == typeof(Int16) || right.Type == typeof(Int16)) + { + right = right.Type != typeof(Int16) ? Expression.Convert(right, typeof(Int16)) : right; + left = left.Type != typeof(Int16) ? Expression.Convert(left, typeof(Int16)) : left; + } + else if (left.Type == typeof(Byte) || right.Type == typeof(Byte)) + { + right = right.Type != typeof(Byte) ? Expression.Convert(right, typeof(Byte)) : right; + left = left.Type != typeof(Byte) ? Expression.Convert(left, typeof(Byte)) : left; + } + } + + public static Expression GenerateAdd(Expression left, Expression right) + { + return Expression.Add(left, right); + } + + public static Expression GenerateStringConcat(Expression left, Expression right) + { + return GenerateStaticMethodCall("Concat", left, right); + } + + public static Expression GenerateSubtract(Expression left, Expression right) + { + return Expression.Subtract(left, right); + } + + public static Expression GenerateEqual(Expression left, Expression right) + { + OptimizeForEqualityIfPossible(ref left, ref right); + return Expression.Equal(left, right); + } + + public static Expression GenerateNotEqual(Expression left, Expression right) + { + OptimizeForEqualityIfPossible(ref left, ref right); + return Expression.NotEqual(left, right); + } + + public static Expression GenerateGreaterThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); + } + + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) + { + var leftPart = left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left; + var rightPart = right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right; + return Expression.GreaterThan(leftPart, rightPart); + } + + return Expression.GreaterThan(left, right); + } + + public static Expression GenerateGreaterThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); + } + + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) + { + return Expression.GreaterThanOrEqual(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left, + right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right); + } + + return Expression.GreaterThanOrEqual(left, right); + } + + public static Expression GenerateLessThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); + } + + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) + { + return Expression.LessThan(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left, + right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right); + } + + return Expression.LessThan(left, right); + } + + public static Expression GenerateLessThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); + } + + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) + { + return Expression.LessThanOrEqual(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left, + right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right); + } + + return Expression.LessThanOrEqual(left, right); + } + + public static void OptimizeForEqualityIfPossible(ref Expression left, ref Expression right) + { + // The goal here is to provide the way to convert some types from the string form in a way that is compatible with Linq to Entities. + // + // The Expression.Call(typeof(Guid).GetMethod("Parse"), right); does the job only for Linq to Object but Linq to Entities. + // + Type leftType = left.Type; + Type rightType = right.Type; + + if (rightType == typeof(string) && right.NodeType == ExpressionType.Constant) + { + right = OptimizeStringForEqualityIfPossible((string)((ConstantExpression)right).Value, leftType) ?? right; + } + + if (leftType == typeof(string) && left.NodeType == ExpressionType.Constant) + { + left = OptimizeStringForEqualityIfPossible((string)((ConstantExpression)left).Value, rightType) ?? left; + } + } + + public static Expression OptimizeStringForEqualityIfPossible(string text, Type type) + { + if (type == typeof(DateTime) && DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime)) + { + return Expression.Constant(dateTime, typeof(DateTime)); + } +#if !NET35 + if (type == typeof(Guid) && Guid.TryParse(text, out Guid guid)) + { + return Expression.Constant(guid, typeof(Guid)); + } +#else + try + { + return Expression.Constant(new Guid(text)); + } + catch + { + // Doing it in old fashion way when no TryParse interface was provided by .NET + } +#endif + return null; + } + + static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) + { + return left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); + } + + static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) + { + return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); + } + } +} diff --git a/src/System.Linq.Dynamic.Core/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs similarity index 51% rename from src/System.Linq.Dynamic.Core/ExpressionParser.cs rename to src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 5c8a8e03..aef9eee5 100644 --- a/src/System.Linq.Dynamic.Core/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -1,390 +1,90 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq.Expressions; -using System.Reflection; +using JetBrains.Annotations; using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.Linq.Dynamic.Core.Exceptions; +using System.Linq.Dynamic.Core.Parser.SupportedMethods; +using System.Linq.Dynamic.Core.Parser.SupportedOperands; using System.Linq.Dynamic.Core.Tokenizer; using System.Linq.Dynamic.Core.Validation; +using System.Linq.Expressions; +using System.Reflection; -namespace System.Linq.Dynamic.Core +namespace System.Linq.Dynamic.Core.Parser { internal class ExpressionParser { - interface ILogicalSignatures - { - void F(bool x, bool y); - void F(bool? x, bool? y); - } - - interface IShiftSignatures - { - void F(int x, int y); - void F(uint x, int y); - void F(long x, int y); - void F(ulong x, int y); - void F(int? x, int y); - void F(uint? x, int y); - void F(long? x, int y); - void F(ulong? x, int y); - void F(int x, int? y); - void F(uint x, int? y); - void F(long x, int? y); - void F(ulong x, int? y); - void F(int? x, int? y); - void F(uint? x, int? y); - void F(long? x, int? y); - void F(ulong? x, int? y); - } + static readonly string methodOrderBy = nameof(Queryable.OrderBy); + static readonly string methodOrderByDescending = nameof(Queryable.OrderByDescending); + static readonly string methodThenBy = nameof(Queryable.ThenBy); + static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending); - interface IArithmeticSignatures - { - void F(int x, int y); - void F(uint x, uint y); - void F(long x, long y); - void F(ulong x, ulong y); - void F(float x, float y); - void F(double x, double y); - void F(decimal x, decimal y); - void F(int? x, int? y); - void F(uint? x, uint? y); - void F(long? x, long? y); - void F(ulong? x, ulong? y); - void F(float? x, float? y); - void F(double? x, double? y); - void F(decimal? x, decimal? y); - } + private readonly ParsingConfig _parsingConfig; + private readonly KeywordsHelper _keywordsHelper; + private readonly PredefinedTypesHelper _predefinedTypesHelper; + private readonly TextParser _textParser; + private readonly Dictionary _internals; + private readonly Dictionary _symbols; - interface IRelationalSignatures : IArithmeticSignatures - { - void F(string x, string y); - void F(char x, char y); - void F(DateTime x, DateTime y); - void F(DateTimeOffset x, DateTimeOffset y); - void F(TimeSpan x, TimeSpan y); - void F(char? x, char? y); - void F(DateTime? x, DateTime? y); - void F(DateTimeOffset? x, DateTimeOffset? y); - void F(TimeSpan? x, TimeSpan? y); - } + private IDictionary _externals; + private ParameterExpression _it; + private ParameterExpression _parent; + private ParameterExpression _root; + private Type _resultType; + private bool _createParameterCtor; - interface IEqualitySignatures : IRelationalSignatures + public ExpressionParser([CanBeNull] ParameterExpression[] parameters, [NotNull] string expression, [CanBeNull] object[] values, [CanBeNull] ParsingConfig parsingConfig) { - void F(bool x, bool y); - void F(bool? x, bool? y); - - // Disabled 4 lines below because of : https://github.com/StefH/System.Linq.Dynamic.Core/issues/19 - //void F(DateTime x, string y); - //void F(DateTime? x, string y); - //void F(string x, DateTime y); - //void F(string x, DateTime? y); - - void F(Guid x, Guid y); - void F(Guid? x, Guid? y); - void F(Guid x, string y); - void F(Guid? x, string y); - void F(string x, Guid y); - void F(string x, Guid? y); - } + Check.NotEmpty(expression, nameof(expression)); - interface IAddSignatures : IArithmeticSignatures - { - void F(DateTime x, TimeSpan y); - void F(TimeSpan x, TimeSpan y); - void F(DateTime? x, TimeSpan? y); - void F(TimeSpan? x, TimeSpan? y); - } + _symbols = new Dictionary(StringComparer.OrdinalIgnoreCase); + _internals = new Dictionary(); - interface ISubtractSignatures : IAddSignatures - { - void F(DateTime x, DateTime y); - void F(DateTime? x, DateTime? y); - } + if (parameters != null) + { + ProcessParameters(parameters); + } - interface INegationSignatures - { - void F(int x); - void F(long x); - void F(float x); - void F(double x); - void F(decimal x); - void F(int? x); - void F(long? x); - void F(float? x); - void F(double? x); - void F(decimal? x); - } + if (values != null) + { + ProcessValues(values); + } - interface INotSignatures - { - void F(bool x); - void F(bool? x); - } + _parsingConfig = parsingConfig ?? ParsingConfig.Default; - interface IEnumerableSignatures - { - void All(bool predicate); - void Any(); - void Any(bool predicate); - void Average(decimal? selector); - void Average(decimal selector); - void Average(double? selector); - void Average(double selector); - void Average(float? selector); - void Average(float selector); - void Average(int? selector); - void Average(int selector); - void Average(long? selector); - void Average(long selector); - void Contains(object selector); - void Count(); - void Count(bool predicate); - void DefaultIfEmpty(); - void DefaultIfEmpty(object defaultValue); - void Distinct(); - void First(bool predicate); - void FirstOrDefault(bool predicate); - void GroupBy(object keySelector); - void GroupBy(object keySelector, object elementSelector); - void Last(bool predicate); - void LastOrDefault(bool predicate); - void Max(object selector); - void Min(object selector); - void OrderBy(object selector); - void OrderByDescending(object selector); - void Select(object selector); - void SelectMany(object selector); - void Single(bool predicate); - void SingleOrDefault(bool predicate); - void Skip(int count); - void SkipWhile(bool predicate); - void Sum(decimal? selector); - void Sum(decimal selector); - void Sum(double? selector); - void Sum(double selector); - void Sum(float? selector); - void Sum(float selector); - void Sum(int? selector); - void Sum(int selector); - void Sum(long? selector); - void Sum(long selector); - void Take(int count); - void TakeWhile(bool predicate); - void ThenBy(object selector); - void ThenByDescending(object selector); - void Where(bool predicate); - - // Executors - void First(); - void FirstOrDefault(); - void Last(); - void LastOrDefault(); - void Single(); - void SingleOrDefault(); - void ToArray(); - void ToList(); - } + _predefinedTypesHelper = new PredefinedTypesHelper(_parsingConfig); + InitPredefinedTypes(); - interface IQueryableSignatures - { - void All(bool predicate); - void Any(); - void Any(bool predicate); - void Average(decimal? selector); - void Average(decimal selector); - void Average(double? selector); - void Average(double selector); - void Average(float? selector); - void Average(float selector); - void Average(int? selector); - void Average(int selector); - void Average(long? selector); - void Average(long selector); - void Count(); - void Count(bool predicate); - void DefaultIfEmpty(); - void DefaultIfEmpty(object defaultValue); - void Distinct(); - void First(bool predicate); - void FirstOrDefault(bool predicate); - void GroupBy(object keySelector); - void GroupBy(object keySelector, object elementSelector); - void Last(bool predicate); - void LastOrDefault(bool predicate); - void Max(object selector); - void Min(object selector); - void OrderBy(object selector); - void OrderByDescending(object selector); - void Select(object selector); - void SelectMany(object selector); - void Single(bool predicate); - void SingleOrDefault(bool predicate); - void Skip(int count); - void SkipWhile(bool predicate); - void Sum(decimal? selector); - void Sum(decimal selector); - void Sum(double? selector); - void Sum(double selector); - void Sum(float? selector); - void Sum(float selector); - void Sum(int? selector); - void Sum(int selector); - void Sum(long? selector); - void Sum(long selector); - void Take(int count); - void TakeWhile(bool predicate); - void ThenBy(object selector); - void ThenByDescending(object selector); - void Where(bool predicate); - - // Executors - void First(); - void FirstOrDefault(); - void Last(); - void LastOrDefault(); - void Single(); - void SingleOrDefault(); + _keywordsHelper = new KeywordsHelper(_parsingConfig, _predefinedTypesHelper); + _textParser = new TextParser(expression); } - // These shorthands have different name than actual type and therefore not recognized by default from the _predefinedTypes - static readonly Dictionary _predefinedTypesShorthands = new Dictionary - { - { "int", typeof(int) }, - { "uint", typeof(uint) }, - { "short", typeof(short) }, - { "ushort", typeof(ushort) }, - { "long", typeof(long) }, - { "ulong", typeof(ulong) }, - { "bool", typeof(bool) }, - { "float", typeof(float) }, - }; - - static readonly Dictionary _predefinedTypes = new Dictionary { - { typeof(object), 0 }, - { typeof(bool), 0 }, - { typeof(char), 0 }, - { typeof(string), 0 }, - { typeof(sbyte), 0 }, - { typeof(byte), 0 }, - { typeof(short), 0 }, - { typeof(ushort), 0 }, - { typeof(int), 0 }, - { typeof(uint), 0 }, - { typeof(long), 0 }, - { typeof(ulong), 0 }, - { typeof(float), 0 }, - { typeof(double), 0 }, - { typeof(decimal), 0 }, - { typeof(DateTime), 0 }, - { typeof(DateTimeOffset), 0 }, - { typeof(TimeSpan), 0 }, - { typeof(Guid), 0 }, - { typeof(Math), 0 }, - { typeof(Convert), 0 }, - { typeof(Uri), 0 } - }; - - static readonly Expression TrueLiteral = Expression.Constant(true); - static readonly Expression FalseLiteral = Expression.Constant(false); - static readonly Expression NullLiteral = Expression.Constant(null); - - const string SYMBOL_IT = "$"; - const string SYMBOL_PARENT = "^"; - const string SYMBOL_ROOT = "~"; - - const string KEYWORD_IT = "it"; - const string KEYWORD_PARENT = "parent"; - const string KEYWORD_ROOT = "root"; - const string KEYWORD_IIF = "iif"; - const string KEYWORD_NEW = "new"; - const string KEYWORD_ISNULL = "isnull"; - - static readonly string methodOrderBy = nameof(Queryable.OrderBy); - static readonly string methodOrderByDescending = nameof(Queryable.OrderByDescending); - static readonly string methodThenBy = nameof(Queryable.ThenBy); - static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending); - - static Dictionary _keywords; - - readonly Dictionary _symbols; - IDictionary _externals; - readonly Dictionary _internals; - readonly Dictionary _literals; - ParameterExpression _it; - ParameterExpression _parent; - ParameterExpression _root; - - private readonly TextParser _textParser; - - private readonly ParsingConfig _parsingConfig; - - static ExpressionParser() + void InitPredefinedTypes() { #if !(NET35 || SILVERLIGHT || NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) //System.Data.Entity is always here, so overwrite short name of it with EntityFramework if EntityFramework is found. //EF5(or 4.x??), System.Data.Objects.DataClasses.EdmFunctionAttribute //There is also an System.Data.Entity, Version=3.5.0.0, but no Functions. - UpdatePredefinedTypes("System.Data.Objects.EntityFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); - UpdatePredefinedTypes("System.Data.Objects.SqlClient.SqlFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); - UpdatePredefinedTypes("System.Data.Objects.SqlClient.SqlSpatialFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); + _predefinedTypesHelper.TryAdd("System.Data.Objects.EntityFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); + _predefinedTypesHelper.TryAdd("System.Data.Objects.SqlClient.SqlFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); + _predefinedTypesHelper.TryAdd("System.Data.Objects.SqlClient.SqlSpatialFunctions, System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 1); //EF6,System.Data.Entity.DbFunctionAttribute - UpdatePredefinedTypes("System.Data.Entity.Core.Objects.EntityFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); - UpdatePredefinedTypes("System.Data.Entity.DbFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); - UpdatePredefinedTypes("System.Data.Entity.SqlServer.SqlFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); - UpdatePredefinedTypes("System.Data.Entity.SqlServer.SqlSpatialFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + _predefinedTypesHelper.TryAdd("System.Data.Entity.Core.Objects.EntityFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + _predefinedTypesHelper.TryAdd("System.Data.Entity.DbFunctions, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + _predefinedTypesHelper.TryAdd("System.Data.Entity.SqlServer.SqlFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); + _predefinedTypesHelper.TryAdd("System.Data.Entity.SqlServer.SqlSpatialFunctions, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", 2); #endif #if NETSTANDARD2_0 - UpdatePredefinedTypes("Microsoft.EntityFrameworkCore.DynamicLinq.DynamicFunctions, Microsoft.EntityFrameworkCore.DynamicLinq, Version=1.0.0.0, Culture=neutral, PublicKeyToken=974e7e1b462f3693", 3); + _predefinedTypesHelper.TryAdd("Microsoft.EntityFrameworkCore.DynamicLinq.DynamicFunctions, Microsoft.EntityFrameworkCore.DynamicLinq, Version=1.0.0.0, Culture=neutral, PublicKeyToken=974e7e1b462f3693", 3); #endif // detect NumberDecimalSeparator from current culture //NumberFormatInfo numberFormatInfo = CultureInfo.CurrentCulture.NumberFormat; //NumberDecimalSeparatorFromCulture = numberFormatInfo.NumberDecimalSeparator[0]; } - private static void UpdatePredefinedTypes(string typeName, int x) - { - try - { - Type efType = Type.GetType(typeName); - if (efType != null) - { - _predefinedTypes.Add(efType, x); - } - } - catch - { - // in case of exception, do not add - } - } - - public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values, ParsingConfig parsingConfig) - { - if (_keywords == null) - { - _keywords = CreateKeywords(); - } - - _symbols = new Dictionary(StringComparer.OrdinalIgnoreCase); - _internals = new Dictionary(); - _literals = new Dictionary(); - - if (parameters != null) - { - ProcessParameters(parameters); - } - - if (values != null) - { - ProcessValues(values); - } - - _textParser = new TextParser(expression); - _parsingConfig = parsingConfig; - } - void ProcessParameters(ParameterExpression[] parameters) { foreach (ParameterExpression pe in parameters.Where(p => !string.IsNullOrEmpty(p.Name))) @@ -433,8 +133,6 @@ void AddSymbol(string name, object value) _symbols.Add(name, value); } - private Type _resultType; - private bool _createParameterCtor; public Expression Parse(Type resultType, bool createParameterCtor) { _resultType = resultType; @@ -445,9 +143,9 @@ public Expression Parse(Type resultType, bool createParameterCtor) if (resultType != null) { - if ((expr = PromoteExpression(expr, resultType, true, false)) == null) + if ((expr = ExpressionPromoter.Promote(expr, resultType, true, false)) == null) { - throw ParseError(exprPos, Res.ExpressionTypeMismatch, GetTypeName(resultType)); + throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType)); } } @@ -476,14 +174,20 @@ public IList ParseOrdering(bool forceThenBy = false) string methodName; if (forceThenBy || orderings.Count > 0) + { methodName = ascending ? methodThenBy : methodThenByDescending; + } else + { methodName = ascending ? methodOrderBy : methodOrderByDescending; + } orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending, MethodName = methodName }); if (_textParser.CurrentToken.Id != TokenId.Comma) + { break; + } _textParser.NextToken(); } @@ -530,8 +234,7 @@ Expression ParseLambdaOperator() if (_textParser.CurrentToken.Id == TokenId.Lambda && _it.Type == expr.Type) { _textParser.NextToken(); - if (_textParser.CurrentToken.Id == TokenId.Identifier || - _textParser.CurrentToken.Id == TokenId.OpenParen) + if (_textParser.CurrentToken.Id == TokenId.Identifier || _textParser.CurrentToken.Id == TokenId.OpenParen) { var right = ParseConditionalOperator(); return Expression.Lambda(right, new[] { (ParameterExpression)expr }); @@ -548,7 +251,9 @@ Expression ParseIsNull() _textParser.NextToken(); Expression[] args = ParseArgumentList(); if (args.Length != 2) + { throw ParseError(errorPos, Res.IsNullRequiresTwoArgs); + } return Expression.Coalesce(args[0], args[1]); } @@ -619,11 +324,11 @@ Expression ParseIn() if (accumulate.Type != typeof(bool)) { - accumulate = GenerateEqual(left, right); + accumulate = ExpressionHelper.GenerateEqual(left, right); } else { - accumulate = Expression.OrElse(accumulate, GenerateEqual(left, right)); + accumulate = Expression.OrElse(accumulate, ExpressionHelper.GenerateEqual(left, right)); } if (_textParser.CurrentToken.Id == TokenId.End) @@ -643,7 +348,7 @@ Expression ParseIn() var args = new[] { left }; - if (FindMethod(typeof(IEnumerableSignatures), "Contains", false, args, out MethodBase containsSignature) != 1) + if (MethodFinder.FindMethod(typeof(IEnumerableSignatures), "Contains", false, args, out MethodBase containsSignature) != 1) { throw ParseError(op.Pos, Res.NoApplicableAggregate, "Contains"); } @@ -692,17 +397,17 @@ Expression ParseLogicalAndOrOperator() // Doesn't break any other function since logical AND with a string is invalid anyway. if (left.Type == typeof(string) || right.Type == typeof(string)) { - left = GenerateStringConcat(left, right); + left = ExpressionHelper.GenerateStringConcat(left, right); } else { - ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); + ExpressionHelper.ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); left = Expression.And(left, right); } break; case TokenId.Bar: - ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); + ExpressionHelper.ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); left = Expression.Or(left, right); break; } @@ -729,7 +434,7 @@ Expression ParseComparisonOperator() if (isEquality && (!left.Type.GetTypeInfo().IsValueType && !right.Type.GetTypeInfo().IsValueType || left.Type == typeof(Guid) && right.Type == typeof(Guid))) { // If left or right is NullLiteral, just continue. Else check if the types differ. - if (!(left == NullLiteral || right == NullLiteral) && left.Type != right.Type) + if (!(left == Constants.NullLiteral || right == Constants.NullLiteral) && left.Type != right.Type) { if (left.Type.IsAssignableFrom(right.Type)) { @@ -745,24 +450,24 @@ Expression ParseComparisonOperator() } } } - else if (IsEnumType(left.Type) || IsEnumType(right.Type)) + else if (TypeHelper.IsEnumType(left.Type) || TypeHelper.IsEnumType(right.Type)) { if (left.Type != right.Type) { Expression e; - if ((e = PromoteExpression(right, left.Type, true, false)) != null) + if ((e = ExpressionPromoter.Promote(right, left.Type, true, false)) != null) { right = e; } - else if ((e = PromoteExpression(left, right.Type, true, false)) != null) + else if ((e = ExpressionPromoter.Promote(left, right.Type, true, false)) != null) { left = e; } - else if (IsEnumType(left.Type) && (constantExpr = right as ConstantExpression) != null) + else if (TypeHelper.IsEnumType(left.Type) && (constantExpr = right as ConstantExpression) != null) { right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpr); } - else if (IsEnumType(right.Type) && (constantExpr = left as ConstantExpression) != null) + else if (TypeHelper.IsEnumType(right.Type) && (constantExpr = left as ConstantExpression) != null) { left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExpr); } @@ -806,23 +511,23 @@ Expression ParseComparisonOperator() { case TokenId.Equal: case TokenId.DoubleEqual: - left = GenerateEqual(left, right); + left = ExpressionHelper.GenerateEqual(left, right); break; case TokenId.ExclamationEqual: case TokenId.LessGreater: - left = GenerateNotEqual(left, right); + left = ExpressionHelper.GenerateNotEqual(left, right); break; case TokenId.GreaterThan: - left = GenerateGreaterThan(left, right); + left = ExpressionHelper.GenerateGreaterThan(left, right); break; case TokenId.GreaterThanEqual: - left = GenerateGreaterThanEqual(left, right); + left = ExpressionHelper.GenerateGreaterThanEqual(left, right); break; case TokenId.LessThan: - left = GenerateLessThan(left, right); + left = ExpressionHelper.GenerateLessThan(left, right); break; case TokenId.LessThanEqual: - left = GenerateLessThanEqual(left, right); + left = ExpressionHelper.GenerateLessThanEqual(left, right); break; } } @@ -841,7 +546,7 @@ private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExp { if (constantExpr.Value is string) { - return Enum.Parse(GetNonNullableType(leftType), (string)constantExpr.Value, true); + return Enum.Parse(TypeHelper.GetNonNullableType(leftType), (string)constantExpr.Value, true); } return Enum.ToObject(leftType, constantExpr.Value); @@ -890,17 +595,17 @@ Expression ParseAdditive() case TokenId.Plus: if (left.Type == typeof(string) || right.Type == typeof(string)) { - left = GenerateStringConcat(left, right); + left = ExpressionHelper.GenerateStringConcat(left, right); } else { CheckAndPromoteOperands(typeof(IAddSignatures), op.Text, ref left, ref right, op.Pos); - left = GenerateAdd(left, right); + left = ExpressionHelper.GenerateAdd(left, right); } break; case TokenId.Minus: CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Text, ref left, ref right, op.Pos); - left = GenerateSubtract(left, right); + left = ExpressionHelper.GenerateSubtract(left, right); break; } } @@ -1021,12 +726,14 @@ Expression ParseStringLiteral() if (quote == '\'') { if (s.Length != 1) + { throw ParseError(Res.InvalidCharacterLiteral); + } _textParser.NextToken(); - return CreateLiteral(s[0], s); + return ConstantExpressionHelper.CreateLiteral(s[0], s); } _textParser.NextToken(); - return CreateLiteral(s, s); + return ConstantExpressionHelper.CreateLiteral(s, s); } Expression ParseIntegerLiteral() @@ -1036,7 +743,7 @@ Expression ParseIntegerLiteral() string text = _textParser.CurrentToken.Text; string qualifier = null; char last = text[text.Length - 1]; - bool isHexadecimal = text.StartsWith(text[0] == '-' ? "-0x" : "0x", StringComparison.CurrentCultureIgnoreCase); + bool isHexadecimal = text.StartsWith(text[0] == '-' ? "-0x" : "0x", StringComparison.OrdinalIgnoreCase); char[] qualifierLetters = isHexadecimal ? new[] { 'U', 'u', 'L', 'l' } : new[] { 'U', 'u', 'L', 'l', 'F', 'f', 'D', 'd', 'M', 'm' }; @@ -1060,26 +767,27 @@ Expression ParseIntegerLiteral() text = text.Substring(2); } - ulong value; - if (!ulong.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, CultureInfo.CurrentCulture, out value)) + if (!ulong.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, CultureInfo.CurrentCulture, out ulong value)) + { throw ParseError(Res.InvalidIntegerLiteral, text); + } _textParser.NextToken(); if (!string.IsNullOrEmpty(qualifier)) { - if (qualifier == "U" || qualifier == "u") return CreateLiteral((uint)value, text); - if (qualifier == "L" || qualifier == "l") return CreateLiteral((long)value, text); + if (qualifier == "U" || qualifier == "u") return ConstantExpressionHelper.CreateLiteral((uint)value, text); + if (qualifier == "L" || qualifier == "l") return ConstantExpressionHelper.CreateLiteral((long)value, text); // in case of UL, just return - return CreateLiteral(value, text); + return ConstantExpressionHelper.CreateLiteral(value, text); } - // if (value <= (int)short.MaxValue) return CreateLiteral((short)value, text); - if (value <= int.MaxValue) return CreateLiteral((int)value, text); - if (value <= uint.MaxValue) return CreateLiteral((uint)value, text); - if (value <= long.MaxValue) return CreateLiteral((long)value, text); + // if (value <= (int)short.MaxValue) return ConstantExpressionHelper.CreateLiteral((short)value, text); + if (value <= int.MaxValue) return ConstantExpressionHelper.CreateLiteral((int)value, text); + if (value <= uint.MaxValue) return ConstantExpressionHelper.CreateLiteral((uint)value, text); + if (value <= long.MaxValue) return ConstantExpressionHelper.CreateLiteral((long)value, text); - return CreateLiteral(value, text); + return ConstantExpressionHelper.CreateLiteral(value, text); } else { @@ -1088,9 +796,10 @@ Expression ParseIntegerLiteral() text = text.Substring(3); } - long value; - if (!long.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, CultureInfo.CurrentCulture, out value)) + if (!long.TryParse(text, isHexadecimal ? NumberStyles.HexNumber : NumberStyles.Integer, CultureInfo.CurrentCulture, out long value)) + { throw ParseError(Res.InvalidIntegerLiteral, text); + } if (isHexadecimal) { @@ -1101,7 +810,7 @@ Expression ParseIntegerLiteral() if (!string.IsNullOrEmpty(qualifier)) { if (qualifier == "L" || qualifier == "l") - return CreateLiteral(value, text); + return ConstantExpressionHelper.CreateLiteral(value, text); if (qualifier == "F" || qualifier == "f") return TryParseAsFloat(text, qualifier[0]); @@ -1115,9 +824,12 @@ Expression ParseIntegerLiteral() throw ParseError(Res.MinusCannotBeAppliedToUnsignedInteger); } - if (value <= int.MaxValue) return CreateLiteral((int)value, text); + if (value <= int.MaxValue) + { + return ConstantExpressionHelper.CreateLiteral((int)value, text); + } - return CreateLiteral(value, text); + return ConstantExpressionHelper.CreateLiteral(value, text); } } @@ -1136,10 +848,9 @@ Expression TryParseAsFloat(string text, char qualifier) { if (qualifier == 'F' || qualifier == 'f') { - float f; - if (float.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Float, CultureInfo.InvariantCulture, out f)) + if (float.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Float, CultureInfo.InvariantCulture, out float f)) { - return CreateLiteral(f, text); + return ConstantExpressionHelper.CreateLiteral(f, text); } } @@ -1151,10 +862,9 @@ Expression TryParseAsDecimal(string text, char qualifier) { if (qualifier == 'M' || qualifier == 'm') { - decimal d; - if (decimal.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Number, CultureInfo.InvariantCulture, out d)) + if (decimal.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Number, CultureInfo.InvariantCulture, out decimal d)) { - return CreateLiteral(d, text); + return ConstantExpressionHelper.CreateLiteral(d, text); } } @@ -1169,25 +879,18 @@ Expression TryParseAsDouble(string text, char qualifier) { if (double.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Number, CultureInfo.InvariantCulture, out d)) { - return CreateLiteral(d, text); + return ConstantExpressionHelper.CreateLiteral(d, text); } } if (double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out d)) { - return CreateLiteral(d, text); + return ConstantExpressionHelper.CreateLiteral(d, text); } throw ParseError(Res.InvalidRealLiteral, text); } - Expression CreateLiteral(object value, string text) - { - ConstantExpression expr = Expression.Constant(value); - _literals.Add(expr, text); - return expr; - } - Expression ParseParenExpression() { _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); @@ -1201,22 +904,24 @@ Expression ParseParenExpression() Expression ParseIdentifier() { _textParser.ValidateToken(TokenId.Identifier); - object value; - if (_keywords.TryGetValue(_textParser.CurrentToken.Text, out value)) + if (_keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out object value)) { var typeValue = value as Type; - if (typeValue != null) return ParseTypeAccess(typeValue); - - if (value == (object)KEYWORD_IT) return ParseIt(); - if (value == (object)KEYWORD_PARENT) return ParseParent(); - if (value == (object)KEYWORD_ROOT) return ParseRoot(); - if (value == (object)SYMBOL_IT) return ParseIt(); - if (value == (object)SYMBOL_PARENT) return ParseParent(); - if (value == (object)SYMBOL_ROOT) return ParseRoot(); - if (value == (object)KEYWORD_IIF) return ParseIif(); - if (value == (object)KEYWORD_NEW) return ParseNew(); - if (value == (object)KEYWORD_ISNULL) return ParseIsNull(); + if (typeValue != null) + { + return ParseTypeAccess(typeValue); + } + + if (value == (object)KeywordsHelper.KEYWORD_IT) return ParseIt(); + if (value == (object)KeywordsHelper.KEYWORD_PARENT) return ParseParent(); + if (value == (object)KeywordsHelper.KEYWORD_ROOT) return ParseRoot(); + if (value == (object)KeywordsHelper.SYMBOL_IT) return ParseIt(); + if (value == (object)KeywordsHelper.SYMBOL_PARENT) return ParseParent(); + if (value == (object)KeywordsHelper.SYMBOL_ROOT) return ParseRoot(); + if (value == (object)KeywordsHelper.KEYWORD_IIF) return ParseIif(); + if (value == (object)KeywordsHelper.KEYWORD_NEW) return ParseNew(); + if (value == (object)KeywordsHelper.KEYWORD_ISNULL) return ParseIsNull(); _textParser.NextToken(); @@ -1235,7 +940,10 @@ Expression ParseIdentifier() else { LambdaExpression lambda = expr as LambdaExpression; - if (lambda != null) return ParseLambdaInvocation(lambda); + if (lambda != null) + { + return ParseLambdaInvocation(lambda); + } } _textParser.NextToken(); @@ -1244,7 +952,9 @@ Expression ParseIdentifier() } if (_it != null) + { return ParseMemberAccess(null, _it); + } throw ParseError(Res.UnknownIdentifier, _textParser.CurrentToken.Text); } @@ -1252,7 +962,9 @@ Expression ParseIdentifier() Expression ParseIt() { if (_it == null) + { throw ParseError(Res.NoItInScope); + } _textParser.NextToken(); return _it; } @@ -1260,7 +972,9 @@ Expression ParseIt() Expression ParseParent() { if (_parent == null) + { throw ParseError(Res.NoParentInScope); + } _textParser.NextToken(); return _parent; } @@ -1268,7 +982,9 @@ Expression ParseParent() Expression ParseRoot() { if (_root == null) + { throw ParseError(Res.NoRootInScope); + } _textParser.NextToken(); return _root; } @@ -1279,7 +995,9 @@ Expression ParseIif() _textParser.NextToken(); Expression[] args = ParseArgumentList(); if (args.Length != 3) + { throw ParseError(errorPos, Res.IifRequiresThreeArgs); + } return GenerateConditional(args[0], args[1], args[2], errorPos); } @@ -1287,11 +1005,14 @@ Expression ParseIif() Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos) { if (test.Type != typeof(bool)) + { throw ParseError(errorPos, Res.FirstExprMustBeBool); + } + if (expr1.Type != expr2.Type) { - Expression expr1As2 = expr2 != NullLiteral ? PromoteExpression(expr1, expr2.Type, true, false) : null; - Expression expr2As1 = expr1 != NullLiteral ? PromoteExpression(expr2, expr1.Type, true, false) : null; + Expression expr1As2 = expr2 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr1, expr2.Type, true, false) : null; + Expression expr2As1 = expr1 != Constants.NullLiteral ? ExpressionPromoter.Promote(expr2, expr1.Type, true, false) : null; if (expr1As2 != null && expr2As1 == null) { expr1 = expr1As2; @@ -1302,10 +1023,12 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp } else { - string type1 = expr1 != NullLiteral ? expr1.Type.Name : "null"; - string type2 = expr2 != NullLiteral ? expr2.Type.Name : "null"; + string type1 = expr1 != Constants.NullLiteral ? expr1.Type.Name : "null"; + string type2 = expr2 != Constants.NullLiteral ? expr2.Type.Name : "null"; if (expr1As2 != null) + { throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2); + } throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2); } @@ -1321,7 +1044,9 @@ Expression ParseNew() _textParser.CurrentToken.Id != TokenId.OpenCurlyParen && _textParser.CurrentToken.Id != TokenId.OpenBracket && _textParser.CurrentToken.Id != TokenId.Identifier) + { throw ParseError(Res.OpenParenOrIdentifierExpected); + } Type newType = null; if (_textParser.CurrentToken.Id == TokenId.Identifier) @@ -1334,19 +1059,25 @@ Expression ParseNew() var sep = _textParser.CurrentToken.Text; _textParser.NextToken(); if (_textParser.CurrentToken.Id != TokenId.Identifier) + { throw ParseError(Res.IdentifierExpected); + } newTypeName += sep + _textParser.CurrentToken.Text; _textParser.NextToken(); } newType = FindType(newTypeName); if (newType == null) + { throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName); + } if (_textParser.CurrentToken.Id != TokenId.OpenParen && _textParser.CurrentToken.Id != TokenId.OpenBracket && _textParser.CurrentToken.Id != TokenId.OpenCurlyParen) + { throw ParseError(Res.OpenParenExpected); + } } bool arrayInitializer = false; @@ -1364,8 +1095,7 @@ Expression ParseNew() var properties = new List(); var expressions = new List(); - while (_textParser.CurrentToken.Id != TokenId.CloseParen - && _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) + while (_textParser.CurrentToken.Id != TokenId.CloseParen && _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) { int exprPos = _textParser.CurrentToken.Pos; Expression expr = ParseConditionalOperator(); @@ -1380,7 +1110,10 @@ Expression ParseNew() } else { - if (!TryGetMemberName(expr, out propName)) throw ParseError(exprPos, Res.MissingAsClause); + if (!TryGetMemberName(expr, out propName)) + { + throw ParseError(exprPos, Res.MissingAsClause); + } } properties.Add(new DynamicProperty(propName, expr.Type)); @@ -1389,14 +1122,17 @@ Expression ParseNew() expressions.Add(expr); if (_textParser.CurrentToken.Id != TokenId.Comma) + { break; + } _textParser.NextToken(); } - if (_textParser.CurrentToken.Id != TokenId.CloseParen && - _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) + if (_textParser.CurrentToken.Id != TokenId.CloseParen && _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) + { throw ParseError(Res.CloseParenOrCommaExpected); + } _textParser.NextToken(); if (arrayInitializer) @@ -1418,7 +1154,7 @@ private Expression CreateArrayInitializerExpression(List expressions { return Expression.NewArrayInit( newType, - expressions.Select(expression => PromoteExpression(expression, newType, true, true))); + expressions.Select(expression => ExpressionPromoter.Promote(expression, newType, true, true))); } return Expression.NewArrayInit( @@ -1434,8 +1170,7 @@ private Expression CreateNewExpression(List properties, List).MakeGenericType(type); _textParser.NextToken(); @@ -1530,26 +1268,27 @@ Expression ParseTypeAccess(Type type) { Type argType = args[0].Type; - if (type.GetTypeInfo().IsValueType && IsNullableType(type) && argType.GetTypeInfo().IsValueType) + if (type.GetTypeInfo().IsValueType && TypeHelper.IsNullableType(type) && argType.GetTypeInfo().IsValueType) { return Expression.Convert(args[0], type); } } - MethodBase method; - switch (FindBestMethod(type.GetConstructors(), args, out method)) + switch (MethodFinder.FindBestMethod(type.GetConstructors(), args, out MethodBase method)) { case 0: if (args.Length == 1) + { return GenerateConversion(args[0], type, errorPos); + } - throw ParseError(errorPos, Res.NoMatchingConstructor, GetTypeName(type)); + throw ParseError(errorPos, Res.NoMatchingConstructor, TypeHelper.GetTypeName(type)); case 1: return Expression.New((ConstructorInfo)method, args); default: - throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, GetTypeName(type)); + throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, TypeHelper.GetTypeName(type)); } } @@ -1569,12 +1308,12 @@ static Expression GenerateConversion(Expression expr, Type type, int errorPos) if (exprType.GetTypeInfo().IsValueType && type.GetTypeInfo().IsValueType) { - if ((IsNullableType(exprType) || IsNullableType(type)) && GetNonNullableType(exprType) == GetNonNullableType(type)) + if ((TypeHelper.IsNullableType(exprType) || TypeHelper.IsNullableType(type)) && TypeHelper.GetNonNullableType(exprType) == TypeHelper.GetNonNullableType(type)) { return Expression.Convert(expr, type); } - if ((IsNumericType(exprType) || IsEnumType(exprType)) && IsNumericType(type) || IsEnumType(type)) + if ((TypeHelper.IsNumericType(exprType) || TypeHelper.IsEnumType(exprType)) && TypeHelper.IsNumericType(type) || TypeHelper.IsEnumType(type)) { return Expression.ConvertChecked(expr, type); } @@ -1608,7 +1347,7 @@ static Expression GenerateConversion(Expression expr, Type type, int errorPos) } } - throw ParseError(errorPos, Res.CannotConvertValue, GetTypeName(exprType), GetTypeName(type)); + throw ParseError(errorPos, Res.CannotConvertValue, TypeHelper.GetTypeName(exprType), TypeHelper.GetTypeName(type)); } Expression ParseMemberAccess(Type type, Expression instance) @@ -1626,32 +1365,32 @@ Expression ParseMemberAccess(Type type, Expression instance) { if (instance != null && type != typeof(string)) { - Type enumerableType = FindGenericType(typeof(IEnumerable<>), type); + Type enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type); if (enumerableType != null) { Type elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0]; - return ParseAggregate(instance, elementType, id, errorPos, FindGenericType(typeof(IQueryable<>), type) != null); + return ParseAggregate(instance, elementType, id, errorPos, TypeHelper.FindGenericType(typeof(IQueryable<>), type) != null); } } Expression[] args = ParseArgumentList(); - switch (FindMethod(type, id, instance == null, args, out MethodBase mb)) + switch (MethodFinder.FindMethod(type, id, instance == null, args, out MethodBase mb)) { case 0: - throw ParseError(errorPos, Res.NoApplicableMethod, id, GetTypeName(type)); + throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type)); case 1: MethodInfo method = (MethodInfo)mb; - if (!IsPredefinedType(method.DeclaringType) && !(method.IsPublic && IsPredefinedType(method.ReturnType))) - throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType)); + if (!_predefinedTypesHelper.IsPredefinedType(method.DeclaringType) && !(method.IsPublic && _predefinedTypesHelper.IsPredefinedType(method.ReturnType))) + throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType)); if (method.ReturnType == typeof(void)) - throw ParseError(errorPos, Res.MethodIsVoid, id, GetTypeName(method.DeclaringType)); + throw ParseError(errorPos, Res.MethodIsVoid, id, TypeHelper.GetTypeName(method.DeclaringType)); return Expression.Call(instance, method, args); default: - throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, GetTypeName(type)); + throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type)); } } @@ -1680,7 +1419,7 @@ Expression ParseMemberAccess(Type type, Expression instance) return ParseConditionalOperator(); } - throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type)); + throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type)); } var property = member as PropertyInfo; @@ -1692,46 +1431,40 @@ Expression ParseMemberAccess(Type type, Expression instance) return Expression.Field(instance, (FieldInfo)member); } - static Type FindGenericType(Type generic, Type type) - { - while (type != null && type != typeof(object)) - { - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == generic) - return type; - - if (generic.GetTypeInfo().IsInterface) - { - foreach (Type intfType in type.GetInterfaces()) - { - Type found = FindGenericType(generic, intfType); - if (found != null) return found; - } - } - - type = type.GetTypeInfo().BaseType; - } - - return null; - } - Type FindType(string name) { - _keywords.TryGetValue(name, out object type); + _keywordsHelper.TryGetValue(name, out object type); + var result = type as Type; if (result != null) + { return result; + } if (_it != null && _it.Type.Name == name) + { return _it.Type; + } if (_parent != null && _parent.Type.Name == name) + { return _parent.Type; + } if (_root != null && _root.Type.Name == name) + { return _root.Type; + } if (_it != null && _it.Type.Namespace + "." + _it.Type.Name == name) + { return _it.Type; + } if (_parent != null && _parent.Type.Namespace + "." + _parent.Type.Name == name) + { return _parent.Type; + } if (_root != null && _root.Type.Namespace + "." + _root.Type.Name == name) + { return _root.Type; + } + return null; } @@ -1759,13 +1492,13 @@ Expression ParseAggregate(Expression instance, Type elementType, string methodNa _it = outerIt; _parent = oldParent; - if (!ContainsMethod(typeof(IEnumerableSignatures), methodName, false, args)) + if (!MethodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, args)) { throw ParseError(errorPos, Res.NoApplicableAggregate, methodName); } Type callType = typeof(Enumerable); - if (isQueryable && ContainsMethod(typeof(IQueryableSignatures), methodName, false, args)) + if (isQueryable && MethodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, args)) { callType = typeof(Queryable); } @@ -1833,13 +1566,15 @@ Expression[] ParseArgumentList() Expression[] ParseArguments() { - List argList = new List(); + var argList = new List(); while (true) { argList.Add(ParseConditionalOperator()); if (_textParser.CurrentToken.Id != TokenId.Comma) + { break; + } _textParser.NextToken(); } @@ -1860,79 +1595,42 @@ Expression ParseElementAccess(Expression expr) if (expr.Type.IsArray) { if (expr.Type.GetArrayRank() != 1 || args.Length != 1) + { throw ParseError(errorPos, Res.CannotIndexMultiDimArray); - Expression index = PromoteExpression(args[0], typeof(int), true, false); + } + Expression index = ExpressionPromoter.Promote(args[0], typeof(int), true, false); + if (index == null) + { throw ParseError(errorPos, Res.InvalidIndex); + } + return Expression.ArrayIndex(expr, index); } - switch (FindIndexer(expr.Type, args, out var mb)) + switch (MethodFinder.FindIndexer(expr.Type, args, out var mb)) { case 0: throw ParseError(errorPos, Res.NoApplicableIndexer, - GetTypeName(expr.Type)); + TypeHelper.GetTypeName(expr.Type)); case 1: return Expression.Call(expr, (MethodInfo)mb, args); + default: - throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, - GetTypeName(expr.Type)); + throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, TypeHelper.GetTypeName(expr.Type)); } } - static bool IsPredefinedType(Type type) - { - if (_predefinedTypes.ContainsKey(type)) return true; - - if (GlobalConfig.CustomTypeProvider != null && GlobalConfig.CustomTypeProvider.GetCustomTypes().Contains(type)) return true; - - return false; - } - - public static bool IsNullableType(Type type) - { - Check.NotNull(type, nameof(type)); - - return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } - public static Type ToNullableType(Type type) { Check.NotNull(type, nameof(type)); - if (!type.GetTypeInfo().IsValueType || IsNullableType(type)) - throw ParseError(-1, Res.TypeHasNoNullableForm, GetTypeName(type)); - - return typeof(Nullable<>).MakeGenericType(type); - } - - public static Type GetNonNullableType(Type type) - { - Check.NotNull(type, nameof(type)); - - return IsNullableType(type) ? type.GetTypeInfo().GetGenericTypeArguments()[0] : type; - } - - public static Type GetUnderlyingType(Type type) - { - Check.NotNull(type, nameof(type)); - - var genericTypeArguments = type.GetGenericArguments(); - if (genericTypeArguments.Any()) + if (!type.GetTypeInfo().IsValueType || TypeHelper.IsNullableType(type)) { - var outerType = GetUnderlyingType(genericTypeArguments.LastOrDefault()); - return Nullable.GetUnderlyingType(type) == outerType ? type : outerType; + throw ParseError(-1, Res.TypeHasNoNullableForm, TypeHelper.GetTypeName(type)); } - return type; - } - - static string GetTypeName(Type type) - { - Type baseType = GetNonNullableType(type); - string s = baseType.Name; - if (type != baseType) s += '?'; - return s; + return typeof(Nullable<>).MakeGenericType(type); } static bool TryGetMemberName(Expression expression, out string memberName) @@ -1961,70 +1659,11 @@ static bool TryGetMemberName(Expression expression, out string memberName) return false; } - static bool IsNumericType(Type type) - { - return GetNumericTypeKind(type) != 0; - } - - static bool IsSignedIntegralType(Type type) - { - return GetNumericTypeKind(type) == 2; - } - - static bool IsUnsignedIntegralType(Type type) - { - return GetNumericTypeKind(type) == 3; - } - - static int GetNumericTypeKind(Type type) - { - type = GetNonNullableType(type); -#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - if (type.GetTypeInfo().IsEnum) return 0; - - switch (Type.GetTypeCode(type)) - { - case TypeCode.Char: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return 1; - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - return 2; - case TypeCode.Byte: - case TypeCode.UInt16: - case TypeCode.UInt32: - case TypeCode.UInt64: - return 3; - default: - return 0; - } -#else - if (type.GetTypeInfo().IsEnum) return 0; - - if (type == typeof(Char) || type == typeof(Single) || type == typeof(Double) || type == typeof(Decimal)) - return 1; - if (type == typeof(SByte) || type == typeof(Int16) || type == typeof(Int32) || type == typeof(Int64)) - return 2; - if (type == typeof(Byte) || type == typeof(UInt16) || type == typeof(UInt32) || type == typeof(UInt64)) - return 3; - return 0; -#endif - } - - static bool IsEnumType(Type type) - { - return GetNonNullableType(type).GetTypeInfo().IsEnum; - } - void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) { Expression[] args = { expr }; - if (!ContainsMethod(signatures, "F", false, args)) + if (!MethodFinder.ContainsMethod(signatures, "F", false, args)) { throw IncompatibleOperandError(opName, expr, errorPos); } @@ -2036,8 +1675,7 @@ void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left { Expression[] args = { left, right }; - MethodBase method; - if (FindMethod(signatures, "F", false, args, out method) != 1) + if (!MethodFinder.ContainsMethod(signatures, "F", false, args)) { throw IncompatibleOperandsError(opName, left, right, errorPos); } @@ -2048,37 +1686,43 @@ void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left static Exception IncompatibleOperandError(string opName, Expression expr, int errorPos) { - return ParseError(errorPos, Res.IncompatibleOperand, opName, GetTypeName(expr.Type)); + return ParseError(errorPos, Res.IncompatibleOperand, opName, TypeHelper.GetTypeName(expr.Type)); } static Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int errorPos) { - return ParseError(errorPos, Res.IncompatibleOperands, opName, GetTypeName(left.Type), GetTypeName(right.Type)); + return ParseError(errorPos, Res.IncompatibleOperands, opName, TypeHelper.GetTypeName(left.Type), TypeHelper.GetTypeName(right.Type)); } static MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) { #if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); - foreach (Type t in SelfAndBaseTypes(type)) + foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) { MemberInfo[] members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, flags, Type.FilterNameIgnoreCase, memberName); if (members.Length != 0) + { return members[0]; + } } return null; #else - foreach (Type t in SelfAndBaseTypes(type)) + foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) { // Try to find a property with the specified memberName MemberInfo member = t.GetTypeInfo().DeclaredProperties.FirstOrDefault(x => x.Name.ToLowerInvariant() == memberName.ToLowerInvariant()); if (member != null) + { return member; + } // If no property is found, try to get a field with the specified memberName member = t.GetTypeInfo().DeclaredFields.FirstOrDefault(x => (x.IsStatic || !staticAccess) && x.Name.ToLowerInvariant() == memberName.ToLowerInvariant()); if (member != null) + { return member; + } // No property or field is found, try the base type. } @@ -2086,753 +1730,20 @@ static MemberInfo FindPropertyOrField(Type type, string memberName, bool staticA #endif } - bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args) + bool TokenIdentifierIs(string id) { - return FindMethod(type, methodName, staticAccess, args, out var _) == 1; + return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase); } - int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) + string GetIdentifier() { -#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); - foreach (Type t in SelfAndBaseTypes(type)) + _textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected); + string id = _textParser.CurrentToken.Text; + if (id.Length > 1 && id[0] == '@') { - MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName); - int count = FindBestMethod(members.Cast(), args, out method); - if (count != 0) - { - return count; - } + id = id.Substring(1); } -#else - foreach (Type t in SelfAndBaseTypes(type)) - { - MethodInfo[] methods = t.GetTypeInfo().DeclaredMethods.Where(x => (x.IsStatic || !staticAccess) && x.Name.ToLowerInvariant() == methodName.ToLowerInvariant()).ToArray(); - int count = FindBestMethod(methods, args, out method); - if (count != 0) - { - return count; - } - } -#endif - method = null; - return 0; - } - - int FindIndexer(Type type, Expression[] args, out MethodBase method) - { - foreach (Type t in SelfAndBaseTypes(type)) - { - MemberInfo[] members = t.GetDefaultMembers(); - if (members.Length != 0) - { - IEnumerable methods = members.OfType(). -#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - Select(p => (MethodBase)p.GetGetMethod()). - Where(m => m != null); -#else - Select(p => (MethodBase)p.GetMethod); -#endif - int count = FindBestMethod(methods, args, out method); - if (count != 0) return count; - } - } - - method = null; - return 0; - } - - static IEnumerable SelfAndBaseTypes(Type type) - { - if (type.GetTypeInfo().IsInterface) - { - List types = new List(); - AddInterface(types, type); - return types; - } - return SelfAndBaseClasses(type); - } - - static IEnumerable SelfAndBaseClasses(Type type) - { - while (type != null) - { - yield return type; - type = type.GetTypeInfo().BaseType; - } - } - - static void AddInterface(List types, Type type) - { - if (!types.Contains(type)) - { - types.Add(type); - foreach (Type t in type.GetInterfaces()) AddInterface(types, t); - } - } - - class MethodData - { - public MethodBase MethodBase; - public ParameterInfo[] Parameters; - public Expression[] Args; - } - - int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method) - { - MethodData[] applicable = methods. - Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }). - Where(m => IsApplicable(m, args)). - ToArray(); - - if (applicable.Length > 1) - { - applicable = applicable.Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))).ToArray(); - } - - if (args.Length == 2 && applicable.Length > 1 && (args[0].Type == typeof(Guid?) || args[1].Type == typeof(Guid?))) - { - applicable = applicable.Take(1).ToArray(); - } - - if (applicable.Length == 1) - { - MethodData md = applicable[0]; - for (int i = 0; i < args.Length; i++) args[i] = md.Args[i]; - method = md.MethodBase; - } - else - { - method = null; - } - - return applicable.Length; - } - - bool IsApplicable(MethodData method, Expression[] args) - { - if (method.Parameters.Length != args.Length) return false; - Expression[] promotedArgs = new Expression[args.Length]; - for (int i = 0; i < args.Length; i++) - { - ParameterInfo pi = method.Parameters[i]; - if (pi.IsOut) return false; - Expression promoted = PromoteExpression(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures)); - if (promoted == null) return false; - promotedArgs[i] = promoted; - } - method.Args = promotedArgs; - return true; - } - - Expression PromoteExpression(Expression expr, Type type, bool exact, bool convertExpr) - { - if (expr.Type == type) - return expr; - - var ce = expr as ConstantExpression; - - if (ce != null) - { - if (ce == NullLiteral || ce.Value == null) - { - if (!type.GetTypeInfo().IsValueType || IsNullableType(type)) - return Expression.Constant(null, type); - } - else - { - if (_literals.TryGetValue(ce, out string text)) - { - Type target = GetNonNullableType(type); - object value = null; - -#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - switch (Type.GetTypeCode(ce.Type)) - { - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - value = ParseNumber(text, target); - - // Make sure an enum value stays an enum value - if (target.IsEnum) - value = Enum.ToObject(target, value); - break; - - case TypeCode.Double: - if (target == typeof(decimal)) value = ParseNumber(text, target); - break; - - case TypeCode.String: - value = ParseEnum(text, target); - break; - } -#else - if (ce.Type == typeof(Int32) || ce.Type == typeof(UInt32) || ce.Type == typeof(Int64) || ce.Type == typeof(UInt64)) - { - value = ParseNumber(text, target); - - // Make sure an enum value stays an enum value - if (target.GetTypeInfo().IsEnum) - value = Enum.ToObject(target, value); - } - else if (ce.Type == typeof(Double)) - { - if (target == typeof(decimal)) value = ParseNumber(text, target); - } - else if (ce.Type == typeof(String)) - { - value = ParseEnum(text, target); - } -#endif - if (value != null) - return Expression.Constant(value, type); - } - } - } - - if (IsCompatibleWith(expr.Type, type)) - { - if (type.GetTypeInfo().IsValueType || exact || expr.Type.GetTypeInfo().IsValueType && convertExpr) - { - return Expression.Convert(expr, type); - } - - return expr; - } - - return null; - } - - static object ParseNumber(string text, Type type) - { -#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - switch (Type.GetTypeCode(GetNonNullableType(type))) - { - case TypeCode.SByte: - sbyte sb; - if (sbyte.TryParse(text, out sb)) return sb; - break; - case TypeCode.Byte: - byte b; - if (byte.TryParse(text, out b)) return b; - break; - case TypeCode.Int16: - short s; - if (short.TryParse(text, out s)) return s; - break; - case TypeCode.UInt16: - ushort us; - if (ushort.TryParse(text, out us)) return us; - break; - case TypeCode.Int32: - int i; - if (int.TryParse(text, out i)) return i; - break; - case TypeCode.UInt32: - uint ui; - if (uint.TryParse(text, out ui)) return ui; - break; - case TypeCode.Int64: - long l; - if (long.TryParse(text, out l)) return l; - break; - case TypeCode.UInt64: - ulong ul; - if (ulong.TryParse(text, out ul)) return ul; - break; - case TypeCode.Single: - float f; - if (float.TryParse(text, out f)) return f; - break; - case TypeCode.Double: - double d; - if (double.TryParse(text, out d)) return d; - break; - case TypeCode.Decimal: - decimal e; - if (decimal.TryParse(text, out e)) return e; - break; - } -#else - var tp = GetNonNullableType(type); - if (tp == typeof(SByte)) - { - sbyte sb; - if (sbyte.TryParse(text, out sb)) return sb; - } - else if (tp == typeof(Byte)) - { - byte b; - if (byte.TryParse(text, out b)) return b; - } - else if (tp == typeof(Int16)) - { - short s; - if (short.TryParse(text, out s)) return s; - } - else if (tp == typeof(UInt16)) - { - ushort us; - if (ushort.TryParse(text, out us)) return us; - } - else if (tp == typeof(Int32)) - { - int i; - if (int.TryParse(text, out i)) return i; - } - else if (tp == typeof(UInt32)) - { - uint ui; - if (uint.TryParse(text, out ui)) return ui; - } - else if (tp == typeof(Int64)) - { - long l; - if (long.TryParse(text, out l)) return l; - } - else if (tp == typeof(UInt64)) - { - ulong ul; - if (ulong.TryParse(text, out ul)) return ul; - } - else if (tp == typeof(Single)) - { - float f; - if (float.TryParse(text, out f)) return f; - } - else if (tp == typeof(Double)) - { - double d; - if (double.TryParse(text, out d)) return d; - } - else if (tp == typeof(Decimal)) - { - decimal e; - if (decimal.TryParse(text, out e)) return e; - } -#endif - return null; - } - static object ParseEnum(string value, Type type) - { -#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - if (type.IsEnum) - { - MemberInfo[] memberInfos = type.FindMembers(MemberTypes.Field, - BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static, - Type.FilterNameIgnoreCase, value); - - if (memberInfos.Length != 0) - return ((FieldInfo)memberInfos[0]).GetValue(null); - } -#else - if (type.GetTypeInfo().IsEnum) - { - return Enum.Parse(type, value, true); - } -#endif - return null; - } - - static bool IsCompatibleWith(Type source, Type target) - { -#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) - if (source == target) return true; - if (!target.IsValueType) return target.IsAssignableFrom(source); - Type st = GetNonNullableType(source); - Type tt = GetNonNullableType(target); - if (st != source && tt == target) return false; - TypeCode sc = st.GetTypeInfo().IsEnum ? TypeCode.Object : Type.GetTypeCode(st); - TypeCode tc = tt.GetTypeInfo().IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); - switch (sc) - { - case TypeCode.SByte: - switch (tc) - { - case TypeCode.SByte: - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Byte: - switch (tc) - { - case TypeCode.Byte: - case TypeCode.Int16: - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int16: - switch (tc) - { - case TypeCode.Int16: - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt16: - switch (tc) - { - case TypeCode.UInt16: - case TypeCode.Int32: - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int32: - switch (tc) - { - case TypeCode.Int32: - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt32: - switch (tc) - { - case TypeCode.UInt32: - case TypeCode.Int64: - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Int64: - switch (tc) - { - case TypeCode.Int64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.UInt64: - switch (tc) - { - case TypeCode.UInt64: - case TypeCode.Single: - case TypeCode.Double: - case TypeCode.Decimal: - return true; - } - break; - case TypeCode.Single: - switch (tc) - { - case TypeCode.Single: - case TypeCode.Double: - return true; - } - break; - default: - if (st == tt) return true; - break; - } - return false; -#else - if (source == target) return true; - if (!target.GetTypeInfo().IsValueType) return target.IsAssignableFrom(source); - Type st = GetNonNullableType(source); - Type tt = GetNonNullableType(target); - if (st != source && tt == target) return false; - Type sc = st.GetTypeInfo().IsEnum ? typeof(Object) : st; - Type tc = tt.GetTypeInfo().IsEnum ? typeof(Object) : tt; - - if (sc == typeof(SByte)) - { - if (tc == typeof(SByte) || tc == typeof(Int16) || tc == typeof(Int32) || tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(Byte)) - { - if (tc == typeof(Byte) || tc == typeof(Int16) || tc == typeof(UInt16) || tc == typeof(Int32) || tc == typeof(UInt32) || tc == typeof(Int64) || tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(Int16)) - { - if (tc == typeof(Int16) || tc == typeof(Int32) || tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(UInt16)) - { - if (tc == typeof(UInt16) || tc == typeof(Int32) || tc == typeof(UInt32) || tc == typeof(Int64) || tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(Int32)) - { - if (tc == typeof(Int32) || tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(UInt32)) - { - if (tc == typeof(UInt32) || tc == typeof(Int64) || tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(Int64)) - { - if (tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(UInt64)) - { - if (tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) - return true; - } - else if (sc == typeof(Single)) - { - if (tc == typeof(Single) || tc == typeof(Double)) - return true; - } - - if (st == tt) - return true; - return false; -#endif - } - - static bool IsBetterThan(Expression[] args, MethodData first, MethodData second) - { - bool better = false; - for (int i = 0; i < args.Length; i++) - { - CompareConversionType result = CompareConversions(args[i].Type, first.Parameters[i].ParameterType, second.Parameters[i].ParameterType); - - // If second is better, return false - if (result == CompareConversionType.Second) - return false; - - // If first is better, return true - if (result == CompareConversionType.First) - return true; - - // If both are same, just set better to true and continue - if (result == CompareConversionType.Both) - better = true; - } - - return better; - } - - enum CompareConversionType - { - Both = 0, - First = 1, - Second = -1 - } - - // Return "First" if s -> t1 is a better conversion than s -> t2 - // Return "Second" if s -> t2 is a better conversion than s -> t1 - // Return "Both" if neither conversion is better - static CompareConversionType CompareConversions(Type source, Type first, Type second) - { - if (first == second) return CompareConversionType.Both; - if (source == first) return CompareConversionType.First; - if (source == second) return CompareConversionType.Second; - - bool firstIsCompatibleWithSecond = IsCompatibleWith(first, second); - bool secondIsCompatibleWithFirst = IsCompatibleWith(second, first); - - if (firstIsCompatibleWithSecond && !secondIsCompatibleWithFirst) return CompareConversionType.First; - if (secondIsCompatibleWithFirst && !firstIsCompatibleWithSecond) return CompareConversionType.Second; - - if (IsSignedIntegralType(first) && IsUnsignedIntegralType(second)) return CompareConversionType.First; - if (IsSignedIntegralType(second) && IsUnsignedIntegralType(first)) return CompareConversionType.Second; - - return CompareConversionType.Both; - } - - static Expression GenerateEqual(Expression left, Expression right) - { - OptimizeForEqualityIfPossible(ref left, ref right); - return Expression.Equal(left, right); - } - - static Expression GenerateNotEqual(Expression left, Expression right) - { - OptimizeForEqualityIfPossible(ref left, ref right); - return Expression.NotEqual(left, right); - } - - static Expression GenerateGreaterThan(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.GreaterThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } - - if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) - { - var leftPart = left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left; - var rightPart = right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right; - return Expression.GreaterThan(leftPart, rightPart); - } - - return Expression.GreaterThan(left, right); - } - - static Expression GenerateGreaterThanEqual(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.GreaterThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } - - if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) - { - return Expression.GreaterThanOrEqual(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left, - right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right); - } - - return Expression.GreaterThanOrEqual(left, right); - } - - static Expression GenerateLessThan(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.LessThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } - - if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) - { - return Expression.LessThan(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left, - right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right); - } - - return Expression.LessThan(left, right); - } - - static Expression GenerateLessThanEqual(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.LessThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } - - if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) - { - return Expression.LessThanOrEqual(left.Type.GetTypeInfo().IsEnum ? Expression.Convert(left, Enum.GetUnderlyingType(left.Type)) : left, - right.Type.GetTypeInfo().IsEnum ? Expression.Convert(right, Enum.GetUnderlyingType(right.Type)) : right); - } - - return Expression.LessThanOrEqual(left, right); - } - - static Expression GenerateAdd(Expression left, Expression right) - { - if (left.Type == typeof(string) && right.Type == typeof(string)) - { - return GenerateStaticMethodCall("Concat", left, right); - } - return Expression.Add(left, right); - } - - static Expression GenerateSubtract(Expression left, Expression right) - { - return Expression.Subtract(left, right); - } - - static Expression GenerateStringConcat(Expression left, Expression right) - { - // Allow concat String with something else - return Expression.Call(null, typeof(string).GetMethod("Concat", new[] { left.Type, right.Type }), new[] { left, right }); - } - - static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) - { - return left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); - } - - static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) - { - return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); - } - - static void OptimizeForEqualityIfPossible(ref Expression left, ref Expression right) - { - // The goal here is to provide the way to convert some types from the string form in a way that is compatible with Linq to Entities. - // - // The Expression.Call(typeof(Guid).GetMethod("Parse"), right); does the job only for Linq to Object but Linq to Entities. - // - Type leftType = left.Type; - Type rightType = right.Type; - - if (rightType == typeof(string) && right.NodeType == ExpressionType.Constant) - { - right = OptimizeStringForEqualityIfPossible((string)((ConstantExpression)right).Value, leftType) ?? right; - } - - if (leftType == typeof(string) && left.NodeType == ExpressionType.Constant) - { - left = OptimizeStringForEqualityIfPossible((string)((ConstantExpression)left).Value, rightType) ?? left; - } - } - - static Expression OptimizeStringForEqualityIfPossible(string text, Type type) - { - DateTime dateTime; - - if (type == typeof(DateTime) && - DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) - return Expression.Constant(dateTime, typeof(DateTime)); -#if !NET35 - Guid guid; - if (type == typeof(Guid) && Guid.TryParse(text, out guid)) - return Expression.Constant(guid, typeof(Guid)); -#else - try - { - return Expression.Constant(new Guid(text)); - } - catch - { - //Doing it in old fashion way when no TryParse interface was provided by .NET - } -#endif - return null; - } - - bool TokenIdentifierIs(string id) - { - return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase); - } - - string GetIdentifier() - { - _textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected); - string id = _textParser.CurrentToken.Text; - if (id.Length > 1 && id[0] == '@') id = id.Substring(1); return id; } @@ -2845,96 +1756,5 @@ static Exception ParseError(int pos, string format, params object[] args) { return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos); } - - static Dictionary CreateKeywords() - { - var d = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "true", TrueLiteral }, - { "false", FalseLiteral }, - { "null", NullLiteral } - }; - - if (GlobalConfig.AreContextKeywordsEnabled) - { - d.Add(KEYWORD_IT, KEYWORD_IT); - d.Add(KEYWORD_PARENT, KEYWORD_PARENT); - d.Add(KEYWORD_ROOT, KEYWORD_ROOT); - } - - d.Add(SYMBOL_IT, SYMBOL_IT); - d.Add(SYMBOL_PARENT, SYMBOL_PARENT); - d.Add(SYMBOL_ROOT, SYMBOL_ROOT); - d.Add(KEYWORD_IIF, KEYWORD_IIF); - d.Add(KEYWORD_NEW, KEYWORD_NEW); - d.Add(KEYWORD_ISNULL, KEYWORD_ISNULL); - - foreach (Type type in _predefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key)) - { - d[type.FullName] = type; - d[type.Name] = type; - } - - foreach (KeyValuePair pair in _predefinedTypesShorthands) - d.Add(pair.Key, pair.Value); - - if (GlobalConfig.CustomTypeProvider != null) - { - foreach (Type type in GlobalConfig.CustomTypeProvider.GetCustomTypes()) - { - d[type.FullName] = type; - d[type.Name] = type; - } - } - - return d; - } - - internal static void ResetDynamicLinqTypes() - { - _keywords = null; - } - - static void ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref Expression left, ref Expression right) - { - if (left.Type == right.Type) - return; - - if (left.Type == typeof(UInt64) || right.Type == typeof(UInt64)) - { - right = right.Type != typeof(UInt64) ? Expression.Convert(right, typeof(UInt64)) : right; - left = left.Type != typeof(UInt64) ? Expression.Convert(left, typeof(UInt64)) : left; - } - else if (left.Type == typeof(Int64) || right.Type == typeof(Int64)) - { - right = right.Type != typeof(Int64) ? Expression.Convert(right, typeof(Int64)) : right; - left = left.Type != typeof(Int64) ? Expression.Convert(left, typeof(Int64)) : left; - } - else if (left.Type == typeof(UInt32) || right.Type == typeof(UInt32)) - { - right = right.Type != typeof(UInt32) ? Expression.Convert(right, typeof(UInt32)) : right; - left = left.Type != typeof(UInt32) ? Expression.Convert(left, typeof(UInt32)) : left; - } - else if (left.Type == typeof(Int32) || right.Type == typeof(Int32)) - { - right = right.Type != typeof(Int32) ? Expression.Convert(right, typeof(Int32)) : right; - left = left.Type != typeof(Int32) ? Expression.Convert(left, typeof(Int32)) : left; - } - else if (left.Type == typeof(UInt16) || right.Type == typeof(UInt16)) - { - right = right.Type != typeof(UInt16) ? Expression.Convert(right, typeof(UInt16)) : right; - left = left.Type != typeof(UInt16) ? Expression.Convert(left, typeof(UInt16)) : left; - } - else if (left.Type == typeof(Int16) || right.Type == typeof(Int16)) - { - right = right.Type != typeof(Int16) ? Expression.Convert(right, typeof(Int16)) : right; - left = left.Type != typeof(Int16) ? Expression.Convert(left, typeof(Int16)) : left; - } - else if (left.Type == typeof(Byte) || right.Type == typeof(Byte)) - { - right = right.Type != typeof(Byte) ? Expression.Convert(right, typeof(Byte)) : right; - left = left.Type != typeof(Byte) ? Expression.Convert(left, typeof(Byte)) : left; - } - } } } diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs new file mode 100644 index 00000000..afbe68d4 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs @@ -0,0 +1,101 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Linq.Dynamic.Core.Parser +{ + internal static class ExpressionPromoter + { + public static Expression Promote(Expression expr, Type type, bool exact, bool convertExpr) + { + if (expr.Type == type) + { + return expr; + } + + var ce = expr as ConstantExpression; + + if (ce != null) + { + if (ce == Constants.NullLiteral || ce.Value == null) + { + if (!type.GetTypeInfo().IsValueType || TypeHelper.IsNullableType(type)) + { + return Expression.Constant(null, type); + } + } + else + { + if (ConstantExpressionHelper.TryGetText(ce, out string text)) + { + Type target = TypeHelper.GetNonNullableType(type); + object value = null; + +#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) + switch (Type.GetTypeCode(ce.Type)) + { + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + value = TypeHelper.ParseNumber(text, target); + + // Make sure an enum value stays an enum value + if (target.IsEnum) + { + value = Enum.ToObject(target, value); + } + break; + + case TypeCode.Double: + if (target == typeof(decimal)) value = TypeHelper.ParseNumber(text, target); + break; + + case TypeCode.String: + value = TypeHelper.ParseEnum(text, target); + break; + } +#else + if (ce.Type == typeof(Int32) || ce.Type == typeof(UInt32) || ce.Type == typeof(Int64) || ce.Type == typeof(UInt64)) + { + value = TypeHelper.ParseNumber(text, target); + + // Make sure an enum value stays an enum value + if (target.GetTypeInfo().IsEnum) + { + value = Enum.ToObject(target, value); + } + } + else if (ce.Type == typeof(Double)) + { + if (target == typeof(decimal)) + { + value = TypeHelper.ParseNumber(text, target); + } + } + else if (ce.Type == typeof(String)) + { + value = TypeHelper.ParseEnum(text, target); + } +#endif + if (value != null) + { + return Expression.Constant(value, type); + } + } + } + } + + if (TypeHelper.IsCompatibleWith(expr.Type, type)) + { + if (type.GetTypeInfo().IsValueType || exact || expr.Type.GetTypeInfo().IsValueType && convertExpr) + { + return Expression.Convert(expr, type); + } + + return expr; + } + + return null; + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs new file mode 100644 index 00000000..40214036 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace System.Linq.Dynamic.Core.Parser +{ + internal class KeywordsHelper + { + 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 KEYWORD_IIF = "iif"; + public const string KEYWORD_NEW = "new"; + public const string KEYWORD_ISNULL = "isnull"; + + private readonly IDictionary _keywords = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "true", Constants.TrueLiteral }, + { "false", Constants.FalseLiteral }, + { "null", Constants.NullLiteral } + }; + + public KeywordsHelper(ParsingConfig config, PredefinedTypesHelper predefinedTypesHelper) + { + if (config.AreContextKeywordsEnabled) + { + _keywords.Add(KEYWORD_IT, KEYWORD_IT); + _keywords.Add(KEYWORD_PARENT, KEYWORD_PARENT); + _keywords.Add(KEYWORD_ROOT, KEYWORD_ROOT); + } + + _keywords.Add(SYMBOL_IT, SYMBOL_IT); + _keywords.Add(SYMBOL_PARENT, SYMBOL_PARENT); + _keywords.Add(SYMBOL_ROOT, SYMBOL_ROOT); + _keywords.Add(KEYWORD_IIF, KEYWORD_IIF); + _keywords.Add(KEYWORD_NEW, KEYWORD_NEW); + _keywords.Add(KEYWORD_ISNULL, KEYWORD_ISNULL); + + foreach (Type type in predefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key)) + { + _keywords[type.FullName] = type; + _keywords[type.Name] = type; + } + + foreach (KeyValuePair pair in predefinedTypesHelper.PredefinedTypesShorthands) + { + _keywords.Add(pair.Key, pair.Value); + } + + if (config.CustomTypeProvider != null) + { + foreach (Type type in config.CustomTypeProvider.GetCustomTypes()) + { + _keywords[type.FullName] = type; + _keywords[type.Name] = type; + } + } + } + + public bool TryGetValue(string name, out object type) + { + return _keywords.TryGetValue(name, out type); + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs new file mode 100644 index 00000000..ffa736ec --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs @@ -0,0 +1,84 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace System.Linq.Dynamic.Core.Parser +{ + internal class PredefinedTypesHelper + { + private readonly ParsingConfig _config; + + // These shorthands have different name than actual type and therefore not recognized by default from the _predefinedTypes + public readonly IDictionary PredefinedTypesShorthands = new Dictionary + { + { "int", typeof(int) }, + { "uint", typeof(uint) }, + { "short", typeof(short) }, + { "ushort", typeof(ushort) }, + { "long", typeof(long) }, + { "ulong", typeof(ulong) }, + { "bool", typeof(bool) }, + { "float", typeof(float) }, + }; + + public readonly IDictionary PredefinedTypes = new ConcurrentDictionary(new Dictionary { + { typeof(object), 0 }, + { typeof(bool), 0 }, + { typeof(char), 0 }, + { typeof(string), 0 }, + { typeof(sbyte), 0 }, + { typeof(byte), 0 }, + { typeof(short), 0 }, + { typeof(ushort), 0 }, + { typeof(int), 0 }, + { typeof(uint), 0 }, + { typeof(long), 0 }, + { typeof(ulong), 0 }, + { typeof(float), 0 }, + { typeof(double), 0 }, + { typeof(decimal), 0 }, + { typeof(DateTime), 0 }, + { typeof(DateTimeOffset), 0 }, + { typeof(TimeSpan), 0 }, + { typeof(Guid), 0 }, + { typeof(Math), 0 }, + { typeof(Convert), 0 }, + { typeof(Uri), 0 } + }); + + public PredefinedTypesHelper(ParsingConfig config) + { + _config = config; + } + + public void TryAdd(string typeName, int x) + { + try + { + Type efType = Type.GetType(typeName); + if (efType != null) + { + PredefinedTypes.Add(efType, x); + } + } + catch + { + // in case of exception, do not add + } + } + + public bool IsPredefinedType(Type type) + { + if (PredefinedTypes.ContainsKey(type)) + { + return true; + } + + if (_config.CustomTypeProvider != null && _config.CustomTypeProvider.GetCustomTypes().Contains(type)) + { + return true; + } + + return false; + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/CompareConversionType.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/CompareConversionType.cs new file mode 100644 index 00000000..655b87e5 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/CompareConversionType.cs @@ -0,0 +1,10 @@ + +namespace System.Linq.Dynamic.Core.Parser.SupportedMethods +{ + internal enum CompareConversionType + { + Both = 0, + First = 1, + Second = -1 + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IEnumerableSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IEnumerableSignatures.cs new file mode 100644 index 00000000..0af0de06 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IEnumerableSignatures.cs @@ -0,0 +1,66 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedMethods +{ + internal interface IEnumerableSignatures + { + void All(bool predicate); + void Any(); + void Any(bool predicate); + void Average(decimal? selector); + void Average(decimal selector); + void Average(double? selector); + void Average(double selector); + void Average(float? selector); + void Average(float selector); + void Average(int? selector); + void Average(int selector); + void Average(long? selector); + void Average(long selector); + void Contains(object selector); + void Count(); + void Count(bool predicate); + void DefaultIfEmpty(); + void DefaultIfEmpty(object defaultValue); + void Distinct(); + void First(bool predicate); + void FirstOrDefault(bool predicate); + void GroupBy(object keySelector); + void GroupBy(object keySelector, object elementSelector); + void Last(bool predicate); + void LastOrDefault(bool predicate); + void Max(object selector); + void Min(object selector); + void OrderBy(object selector); + void OrderByDescending(object selector); + void Select(object selector); + void SelectMany(object selector); + void Single(bool predicate); + void SingleOrDefault(bool predicate); + void Skip(int count); + void SkipWhile(bool predicate); + void Sum(decimal? selector); + void Sum(decimal selector); + void Sum(double? selector); + void Sum(double selector); + void Sum(float? selector); + void Sum(float selector); + void Sum(int? selector); + void Sum(int selector); + void Sum(long? selector); + void Sum(long selector); + void Take(int count); + void TakeWhile(bool predicate); + void ThenBy(object selector); + void ThenByDescending(object selector); + void Where(bool predicate); + + // Executors + void First(); + void FirstOrDefault(); + void Last(); + void LastOrDefault(); + void Single(); + void SingleOrDefault(); + void ToArray(); + void ToList(); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IQueryableSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IQueryableSignatures.cs new file mode 100644 index 00000000..97d810fb --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/IQueryableSignatures.cs @@ -0,0 +1,63 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedMethods +{ + internal interface IQueryableSignatures + { + void All(bool predicate); + void Any(); + void Any(bool predicate); + void Average(decimal? selector); + void Average(decimal selector); + void Average(double? selector); + void Average(double selector); + void Average(float? selector); + void Average(float selector); + void Average(int? selector); + void Average(int selector); + void Average(long? selector); + void Average(long selector); + void Count(); + void Count(bool predicate); + void DefaultIfEmpty(); + void DefaultIfEmpty(object defaultValue); + void Distinct(); + void First(bool predicate); + void FirstOrDefault(bool predicate); + void GroupBy(object keySelector); + void GroupBy(object keySelector, object elementSelector); + void Last(bool predicate); + void LastOrDefault(bool predicate); + void Max(object selector); + void Min(object selector); + void OrderBy(object selector); + void OrderByDescending(object selector); + void Select(object selector); + void SelectMany(object selector); + void Single(bool predicate); + void SingleOrDefault(bool predicate); + void Skip(int count); + void SkipWhile(bool predicate); + void Sum(decimal? selector); + void Sum(decimal selector); + void Sum(double? selector); + void Sum(double selector); + void Sum(float? selector); + void Sum(float selector); + void Sum(int? selector); + void Sum(int selector); + void Sum(long? selector); + void Sum(long selector); + void Take(int count); + void TakeWhile(bool predicate); + void ThenBy(object selector); + void ThenByDescending(object selector); + void Where(bool predicate); + + // Executors + void First(); + void FirstOrDefault(); + void Last(); + void LastOrDefault(); + void Single(); + void SingleOrDefault(); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodData.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodData.cs new file mode 100644 index 00000000..b73f8666 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodData.cs @@ -0,0 +1,12 @@ +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Linq.Dynamic.Core.Parser.SupportedMethods +{ + internal class MethodData + { + public MethodBase MethodBase { get; set; } + public ParameterInfo[] Parameters { get; set; } + public Expression[] Args { get; set; } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs new file mode 100644 index 00000000..c1a51610 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -0,0 +1,229 @@ +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace System.Linq.Dynamic.Core.Parser.SupportedMethods +{ + internal static class MethodFinder + { + public static bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression[] args) + { + return FindMethod(type, methodName, staticAccess, args, out var _) == 1; + } + + public static int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) + { +#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) + BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | (staticAccess ? BindingFlags.Static : BindingFlags.Instance); + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.FindMembers(MemberTypes.Method, flags, Type.FilterNameIgnoreCase, methodName); + int count = FindBestMethod(members.Cast(), args, out method); + if (count != 0) + { + return count; + } + } +#else + foreach (Type t in SelfAndBaseTypes(type)) + { + MethodInfo[] methods = t.GetTypeInfo().DeclaredMethods.Where(x => (x.IsStatic || !staticAccess) && x.Name.ToLowerInvariant() == methodName.ToLowerInvariant()).ToArray(); + int count = FindBestMethod(methods, args, out method); + if (count != 0) + { + return count; + } + } +#endif + method = null; + return 0; + } + + public static int FindBestMethod(IEnumerable methods, Expression[] args, out MethodBase method) + { + MethodData[] applicable = methods. + Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }). + Where(m => IsApplicable(m, args)). + ToArray(); + + if (applicable.Length > 1) + { + applicable = applicable.Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))).ToArray(); + } + + if (args.Length == 2 && applicable.Length > 1 && (args[0].Type == typeof(Guid?) || args[1].Type == typeof(Guid?))) + { + applicable = applicable.Take(1).ToArray(); + } + + if (applicable.Length == 1) + { + MethodData md = applicable[0]; + for (int i = 0; i < args.Length; i++) + { + args[i] = md.Args[i]; + } + method = md.MethodBase; + } + else + { + method = null; + } + + return applicable.Length; + } + + public static int FindIndexer(Type type, Expression[] args, out MethodBase method) + { + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.GetDefaultMembers(); + if (members.Length != 0) + { + IEnumerable methods = members.OfType(). +#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) + Select(p => (MethodBase)p.GetGetMethod()). + Where(m => m != null); +#else + Select(p => (MethodBase)p.GetMethod); +#endif + int count = FindBestMethod(methods, args, out method); + if (count != 0) + { + return count; + } + } + } + + method = null; + return 0; + } + + static bool IsApplicable(MethodData method, Expression[] args) + { + if (method.Parameters.Length != args.Length) + { + return false; + } + + Expression[] promotedArgs = new Expression[args.Length]; + for (int i = 0; i < args.Length; i++) + { + ParameterInfo pi = method.Parameters[i]; + if (pi.IsOut) + { + return false; + } + + Expression promoted = ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures)); + if (promoted == null) + { + return false; + } + promotedArgs[i] = promoted; + } + method.Args = promotedArgs; + return true; + } + + static bool IsBetterThan(Expression[] args, MethodData first, MethodData second) + { + bool better = false; + for (int i = 0; i < args.Length; i++) + { + CompareConversionType result = CompareConversions(args[i].Type, first.Parameters[i].ParameterType, second.Parameters[i].ParameterType); + + // If second is better, return false + if (result == CompareConversionType.Second) + { + return false; + } + + // If first is better, return true + if (result == CompareConversionType.First) + { + return true; + } + + // If both are same, just set better to true and continue + if (result == CompareConversionType.Both) + { + better = true; + } + } + + return better; + } + + // Return "First" if s -> t1 is a better conversion than s -> t2 + // Return "Second" if s -> t2 is a better conversion than s -> t1 + // Return "Both" if neither conversion is better + static CompareConversionType CompareConversions(Type source, Type first, Type second) + { + if (first == second) + { + return CompareConversionType.Both; + } + if (source == first) + { + return CompareConversionType.First; + } + if (source == second) + { + return CompareConversionType.Second; + } + + bool firstIsCompatibleWithSecond = TypeHelper.IsCompatibleWith(first, second); + bool secondIsCompatibleWithFirst = TypeHelper.IsCompatibleWith(second, first); + + if (firstIsCompatibleWithSecond && !secondIsCompatibleWithFirst) + { + return CompareConversionType.First; + } + if (secondIsCompatibleWithFirst && !firstIsCompatibleWithSecond) + { + return CompareConversionType.Second; + } + + if (TypeHelper.IsSignedIntegralType(first) && TypeHelper.IsUnsignedIntegralType(second)) + { + return CompareConversionType.First; + } + if (TypeHelper.IsSignedIntegralType(second) && TypeHelper.IsUnsignedIntegralType(first)) + { + return CompareConversionType.Second; + } + + return CompareConversionType.Both; + } + + static IEnumerable SelfAndBaseTypes(Type type) + { + if (type.GetTypeInfo().IsInterface) + { + var types = new List(); + AddInterface(types, type); + return types; + } + return SelfAndBaseClasses(type); + } + + static IEnumerable SelfAndBaseClasses(Type type) + { + while (type != null) + { + yield return type; + type = type.GetTypeInfo().BaseType; + } + } + + static void AddInterface(List types, Type type) + { + if (!types.Contains(type)) + { + types.Add(type); + foreach (Type t in type.GetInterfaces()) AddInterface(types, t); + } + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs new file mode 100644 index 00000000..b8b78ae1 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs @@ -0,0 +1,10 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface IAddSignatures : IArithmeticSignatures + { + void F(DateTime x, TimeSpan y); + void F(TimeSpan x, TimeSpan y); + void F(DateTime? x, TimeSpan? y); + void F(TimeSpan? x, TimeSpan? y); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IArithmeticSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IArithmeticSignatures.cs new file mode 100644 index 00000000..602face9 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IArithmeticSignatures.cs @@ -0,0 +1,20 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface IArithmeticSignatures + { + void F(int x, int y); + void F(uint x, uint y); + void F(long x, long y); + void F(ulong x, ulong y); + void F(float x, float y); + void F(double x, double y); + void F(decimal x, decimal y); + void F(int? x, int? y); + void F(uint? x, uint? y); + void F(long? x, long? y); + void F(ulong? x, ulong? y); + void F(float? x, float? y); + void F(double? x, double? y); + void F(decimal? x, decimal? y); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IEqualitySignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IEqualitySignatures.cs new file mode 100644 index 00000000..afb8d394 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IEqualitySignatures.cs @@ -0,0 +1,21 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface IEqualitySignatures : IRelationalSignatures + { + void F(bool x, bool y); + void F(bool? x, bool? y); + + // Disabled 4 lines below because of : https://github.com/StefH/System.Linq.Dynamic.Core/issues/19 + //void F(DateTime x, string y); + //void F(DateTime? x, string y); + //void F(string x, DateTime y); + //void F(string x, DateTime? y); + + void F(Guid x, Guid y); + void F(Guid? x, Guid? y); + void F(Guid x, string y); + void F(Guid? x, string y); + void F(string x, Guid y); + void F(string x, Guid? y); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ILogicalSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ILogicalSignatures.cs new file mode 100644 index 00000000..b3724b39 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ILogicalSignatures.cs @@ -0,0 +1,8 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface ILogicalSignatures + { + void F(bool x, bool y); + void F(bool? x, bool? y); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/INegationSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/INegationSignatures.cs new file mode 100644 index 00000000..6d9c97d5 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/INegationSignatures.cs @@ -0,0 +1,16 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface INegationSignatures + { + void F(int x); + void F(long x); + void F(float x); + void F(double x); + void F(decimal x); + void F(int? x); + void F(long? x); + void F(float? x); + void F(double? x); + void F(decimal? x); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/INotSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/INotSignatures.cs new file mode 100644 index 00000000..2c53f724 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/INotSignatures.cs @@ -0,0 +1,8 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface INotSignatures + { + void F(bool x); + void F(bool? x); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IRelationalSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IRelationalSignatures.cs new file mode 100644 index 00000000..b91855ed --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IRelationalSignatures.cs @@ -0,0 +1,15 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface IRelationalSignatures : IArithmeticSignatures + { + void F(string x, string y); + void F(char x, char y); + void F(DateTime x, DateTime y); + void F(DateTimeOffset x, DateTimeOffset y); + void F(TimeSpan x, TimeSpan y); + void F(char? x, char? y); + void F(DateTime? x, DateTime? y); + void F(DateTimeOffset? x, DateTimeOffset? y); + void F(TimeSpan? x, TimeSpan? y); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IShiftSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IShiftSignatures.cs new file mode 100644 index 00000000..f9e68e8b --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IShiftSignatures.cs @@ -0,0 +1,22 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface IShiftSignatures + { + void F(int x, int y); + void F(uint x, int y); + void F(long x, int y); + void F(ulong x, int y); + void F(int? x, int y); + void F(uint? x, int y); + void F(long? x, int y); + void F(ulong? x, int y); + void F(int x, int? y); + void F(uint x, int? y); + void F(long x, int? y); + void F(ulong x, int? y); + void F(int? x, int? y); + void F(uint? x, int? y); + void F(long? x, int? y); + void F(ulong? x, int? y); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs new file mode 100644 index 00000000..19eec926 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs @@ -0,0 +1,8 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +{ + internal interface ISubtractSignatures : IAddSignatures + { + void F(DateTime x, DateTime y); + void F(DateTime? x, DateTime? y); + } +} diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs new file mode 100644 index 00000000..369ccb2f --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs @@ -0,0 +1,506 @@ +using System.Collections.Generic; +using System.Linq.Dynamic.Core.Validation; +using System.Reflection; + +namespace System.Linq.Dynamic.Core.Parser +{ + internal static class TypeHelper + { + public static Type FindGenericType(Type generic, Type type) + { + while (type != null && type != typeof(object)) + { + if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == generic) + { + return type; + } + + if (generic.GetTypeInfo().IsInterface) + { + foreach (Type intfType in type.GetInterfaces()) + { + Type found = FindGenericType(generic, intfType); + if (found != null) return found; + } + } + + type = type.GetTypeInfo().BaseType; + } + + return null; + } + + public static bool IsCompatibleWith(Type source, Type target) + { +#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) + if (source == target) + { + return true; + } + + if (!target.IsValueType) + { + return target.IsAssignableFrom(source); + } + + Type st = GetNonNullableType(source); + Type tt = GetNonNullableType(target); + + if (st != source && tt == target) + { + return false; + } + + TypeCode sc = st.GetTypeInfo().IsEnum ? TypeCode.Object : Type.GetTypeCode(st); + TypeCode tc = tt.GetTypeInfo().IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); + switch (sc) + { + case TypeCode.SByte: + switch (tc) + { + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Byte: + switch (tc) + { + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int16: + switch (tc) + { + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt16: + switch (tc) + { + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int32: + switch (tc) + { + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt32: + switch (tc) + { + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int64: + switch (tc) + { + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt64: + switch (tc) + { + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Single: + switch (tc) + { + case TypeCode.Single: + case TypeCode.Double: + return true; + } + break; + default: + if (st == tt) + { + return true; + } + break; + } + return false; +#else + if (source == target) + { + return true; + } + if (!target.GetTypeInfo().IsValueType) + { + return target.IsAssignableFrom(source); + } + Type st = GetNonNullableType(source); + Type tt = GetNonNullableType(target); + + if (st != source && tt == target) + { + return false; + } + Type sc = st.GetTypeInfo().IsEnum ? typeof(Object) : st; + Type tc = tt.GetTypeInfo().IsEnum ? typeof(Object) : tt; + + if (sc == typeof(SByte)) + { + if (tc == typeof(SByte) || tc == typeof(Int16) || tc == typeof(Int32) || tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(Byte)) + { + if (tc == typeof(Byte) || tc == typeof(Int16) || tc == typeof(UInt16) || tc == typeof(Int32) || tc == typeof(UInt32) || tc == typeof(Int64) || tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(Int16)) + { + if (tc == typeof(Int16) || tc == typeof(Int32) || tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(UInt16)) + { + if (tc == typeof(UInt16) || tc == typeof(Int32) || tc == typeof(UInt32) || tc == typeof(Int64) || tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(Int32)) + { + if (tc == typeof(Int32) || tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(UInt32)) + { + if (tc == typeof(UInt32) || tc == typeof(Int64) || tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(Int64)) + { + if (tc == typeof(Int64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(UInt64)) + { + if (tc == typeof(UInt64) || tc == typeof(Single) || tc == typeof(Double) || tc == typeof(Decimal)) + return true; + } + else if (sc == typeof(Single)) + { + if (tc == typeof(Single) || tc == typeof(Double)) + return true; + } + + if (st == tt) + { + return true; + } + + return false; +#endif + } + + public static bool IsEnumType(Type type) + { + return GetNonNullableType(type).GetTypeInfo().IsEnum; + } + + public static bool IsNumericType(Type type) + { + return GetNumericTypeKind(type) != 0; + } + + public static bool IsNullableType(Type type) + { + Check.NotNull(type, nameof(type)); + + return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + public static bool IsSignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 2; + } + + public static bool IsUnsignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 3; + } + + private static int GetNumericTypeKind(Type type) + { + type = GetNonNullableType(type); + +#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) + if (type.GetTypeInfo().IsEnum) + { + return 0; + } + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Char: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return 1; + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + return 2; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return 3; + default: + return 0; + } +#else + if (type.GetTypeInfo().IsEnum) + { + return 0; + } + + if (type == typeof(Char) || type == typeof(Single) || type == typeof(Double) || type == typeof(Decimal)) + return 1; + if (type == typeof(SByte) || type == typeof(Int16) || type == typeof(Int32) || type == typeof(Int64)) + return 2; + if (type == typeof(Byte) || type == typeof(UInt16) || type == typeof(UInt32) || type == typeof(UInt64)) + return 3; + + return 0; +#endif + } + + public static string GetTypeName(Type type) + { + Type baseType = GetNonNullableType(type); + + string s = baseType.Name; + if (type != baseType) + { + s += '?'; + } + return s; + } + + public static Type GetNonNullableType(Type type) + { + Check.NotNull(type, nameof(type)); + + return IsNullableType(type) ? type.GetTypeInfo().GetGenericTypeArguments()[0] : type; + } + + public static Type GetUnderlyingType(Type type) + { + Check.NotNull(type, nameof(type)); + + Type[] genericTypeArguments = type.GetGenericArguments(); + if (genericTypeArguments.Any()) + { + var outerType = GetUnderlyingType(genericTypeArguments.LastOrDefault()); + return Nullable.GetUnderlyingType(type) == outerType ? type : outerType; + } + + return type; + } + + public static IEnumerable GetSelfAndBaseTypes(Type type) + { + if (type.GetTypeInfo().IsInterface) + { + var types = new List(); + AddInterface(types, type); + return types; + } + return GetSelfAndBaseClasses(type); + } + + private static IEnumerable GetSelfAndBaseClasses(Type type) + { + while (type != null) + { + yield return type; + type = type.GetTypeInfo().BaseType; + } + } + + private static void AddInterface(List types, Type type) + { + if (!types.Contains(type)) + { + types.Add(type); + foreach (Type t in type.GetInterfaces()) + { + AddInterface(types, t); + } + } + } + + public static object ParseNumber(string text, Type type) + { +#if !(NETFX_CORE || WINDOWS_APP || DOTNET5_1 || UAP10_0 || NETSTANDARD) + switch (Type.GetTypeCode(GetNonNullableType(type))) + { + case TypeCode.SByte: + sbyte sb; + if (SByte.TryParse(text, out sb)) return sb; + break; + case TypeCode.Byte: + byte b; + if (Byte.TryParse(text, out b)) return b; + break; + case TypeCode.Int16: + short s; + if (Int16.TryParse(text, out s)) return s; + break; + case TypeCode.UInt16: + ushort us; + if (UInt16.TryParse(text, out us)) return us; + break; + case TypeCode.Int32: + int i; + if (Int32.TryParse(text, out i)) return i; + break; + case TypeCode.UInt32: + uint ui; + if (UInt32.TryParse(text, out ui)) return ui; + break; + case TypeCode.Int64: + long l; + if (Int64.TryParse(text, out l)) return l; + break; + case TypeCode.UInt64: + ulong ul; + if (UInt64.TryParse(text, out ul)) return ul; + break; + case TypeCode.Single: + float f; + if (Single.TryParse(text, out f)) return f; + break; + case TypeCode.Double: + double d; + if (Double.TryParse(text, out d)) return d; + break; + case TypeCode.Decimal: + decimal e; + if (Decimal.TryParse(text, out e)) return e; + break; + } +#else + var tp = GetNonNullableType(type); + if (tp == typeof(SByte)) + { + sbyte sb; + if (sbyte.TryParse(text, out sb)) return sb; + } + else if (tp == typeof(Byte)) + { + byte b; + if (byte.TryParse(text, out b)) return b; + } + else if (tp == typeof(Int16)) + { + short s; + if (short.TryParse(text, out s)) return s; + } + else if (tp == typeof(UInt16)) + { + ushort us; + if (ushort.TryParse(text, out us)) return us; + } + else if (tp == typeof(Int32)) + { + int i; + if (int.TryParse(text, out i)) return i; + } + else if (tp == typeof(UInt32)) + { + uint ui; + if (uint.TryParse(text, out ui)) return ui; + } + else if (tp == typeof(Int64)) + { + long l; + if (long.TryParse(text, out l)) return l; + } + else if (tp == typeof(UInt64)) + { + ulong ul; + if (ulong.TryParse(text, out ul)) return ul; + } + else if (tp == typeof(Single)) + { + float f; + if (float.TryParse(text, out f)) return f; + } + else if (tp == typeof(Double)) + { + double d; + if (double.TryParse(text, out d)) return d; + } + else if (tp == typeof(Decimal)) + { + decimal e; + if (decimal.TryParse(text, out e)) return e; + } +#endif + return null; + } + + public static object ParseEnum(string value, Type type) + { + if (type.GetTypeInfo().IsEnum && Enum.IsDefined(type, value)) + { + return Enum.Parse(type, value, true); + } + + return null; + } + } +} diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 255dfc14..01a13c88 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -1,4 +1,6 @@ -namespace System.Linq.Dynamic.Core +using System.Linq.Dynamic.Core.CustomTypeProviders; + +namespace System.Linq.Dynamic.Core { /// /// Configuration class for Dynamic Linq. @@ -7,6 +9,38 @@ public class ParsingConfig { internal static ParsingConfig Default { get; } = new ParsingConfig(); + private IDynamicLinkCustomTypeProvider _customTypeProvider; + + /// + /// Gets or sets the . + /// + public IDynamicLinkCustomTypeProvider CustomTypeProvider + { + get + { +#if !(DOTNET5_1 || WINDOWS_APP || UAP10_0 || NETSTANDARD) + // only use DefaultDynamicLinqCustomTypeProvider if not WINDOWS_APP || UAP10_0 || NETSTANDARD + return _customTypeProvider ?? (_customTypeProvider = new DefaultDynamicLinqCustomTypeProvider()); +#else + return _customTypeProvider; +#endif + } + + set + { + if (_customTypeProvider != value) + { + _customTypeProvider = value; + } + } + } + + /// + /// Determines if the context keywords (it, parent, and root) are valid and usable inside a Dynamic Linq string expression. + /// Does not affect the usability of the equivalent context symbols ($, ^ and ~). + /// + public bool AreContextKeywordsEnabled { get; set; } = true; + /// /// Gets or sets a value indicating whether to use dynamic object class for anonymous types. /// diff --git a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj index a42f62aa..a96d0ac6 100644 --- a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj +++ b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj @@ -2,7 +2,7 @@ This is a .NETStandard/ .NET Core port of the the Microsoft assembly for the .Net 4.0 Dynamic language functionality. System.Linq.Dynamic.Core - 1.0.7.13 + 1.0.8.0 Microsoft;Scott Guthrie;King Wilder;Nathan Arnott;Stef Heyenrath net35;net40;net45;net46;uap10.0;netstandard1.3;netstandard2.0 true diff --git a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs index 2b2d38c5..e3ec2303 100644 --- a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs +++ b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs @@ -55,7 +55,9 @@ private void NextChar() public void NextToken() { while (char.IsWhiteSpace(_ch)) + { NextChar(); + } TokenId tokenId = TokenId.Unknown; int tokenPos = _textPos; @@ -372,27 +374,42 @@ public void NextToken() public void ValidateToken(TokenId t, string errorMessage) { - if (CurrentToken.Id != t) throw ParseError(errorMessage); + if (CurrentToken.Id != t) + { + throw ParseError(errorMessage); + } } public void ValidateToken(TokenId t) { - if (CurrentToken.Id != t) throw ParseError(Res.SyntaxError); + if (CurrentToken.Id != t) + { + throw ParseError(Res.SyntaxError); + } } private void ValidateExpression() { - if (char.IsLetterOrDigit(_ch)) throw ParseError(_textPos, Res.ExpressionExpected); + if (char.IsLetterOrDigit(_ch)) + { + throw ParseError(_textPos, Res.ExpressionExpected); + } } private void ValidateDigit() { - if (!char.IsDigit(_ch)) throw ParseError(_textPos, Res.DigitExpected); + if (!char.IsDigit(_ch)) + { + throw ParseError(_textPos, Res.DigitExpected); + } } private void ValidateHexChar() { - if (!IsHexChar(_ch)) throw ParseError(_textPos, Res.HexCharExpected); + if (!IsHexChar(_ch)) + { + throw ParseError(_textPos, Res.HexCharExpected); + } } private Exception ParseError(string format, params object[] args) @@ -427,4 +444,4 @@ private static bool IsHexChar(char c) return false; } } -} \ No newline at end of file +} diff --git a/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs b/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs index 102ab00c..32f63e68 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs @@ -21,13 +21,13 @@ public void OuterIt_StackOverFlow_Question_43272152() var claim1 = new Claim { Balance = 100, Tags = new List { "Blah", "Blah Blah" } }; var claim2 = new Claim { Balance = 500, Tags = new List { "Dummy Tag", "Dummy tag 1", "New" } }; - var claims = new List() { claim1, claim2 }; + var claims = new List { claim1, claim2 }; var tags = new List { "New", "Blah" }; var parameters = new List { tags }; var query = claims.AsQueryable().Where("Tags.Any(@0.Contains(it)) AND Balance > 100", parameters.ToArray()).ToArray(); - Check.That(query).ContainsExactly(new [] { claim2 }); + Check.That(query).ContainsExactly(claim2); } /// @@ -119,4 +119,4 @@ public void GroupByAndSelect_TestDynamicSelectMember() #endif } } -} \ No newline at end of file +} diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicEnumerableExtensionsTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicEnumerableExtensionsTests.cs new file mode 100644 index 00000000..531a1a57 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicEnumerableExtensionsTests.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using NFluent; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public class DynamicEnumerableExtensionsTests + { + [Fact] + public void DynamicEnumerableExtensions_ToDynamicArray_int_to_int() + { + // Act + var result = new List { 0, 1 }.ToDynamicList(typeof(int)); + + // Assert + Check.That(result).ContainsExactly(0, 1); + } + + [Fact] + public void DynamicEnumerableExtensions_ToDynamicArray_dynamic_to_int() + { + // Act + var result = new List { 0, 1 }.ToDynamicList(typeof(int)); + + // Assert + Check.That(result).ContainsExactly(0, 1); + } + + [Fact] + public void DynamicEnumerableExtensions_ToDynamicArray_object_to_int() + { + // Act + var result = new List { 0, 1 }.ToDynamicList(typeof(int)); + + // Assert + Check.That(result).ContainsExactly(0, 1); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 8e59ddbf..da600982 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -39,11 +39,11 @@ private class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, ID public virtual HashSet GetCustomTypes() { if (_customTypes != null) + { return _customTypes; + } - _customTypes = - new HashSet( - FindTypesMarkedWithDynamicLinqTypeAttribute(new[] {this.GetType().GetTypeInfo().Assembly})); + _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly })); return _customTypes; } } @@ -119,7 +119,10 @@ public void ParseLambda_Complex_2() [Fact] public void ParseLambda_Complex_3() { - GlobalConfig.CustomTypeProvider = new TestCustomTypeProvider(); + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; // Arrange var testList = User.GenerateSampleModels(51); @@ -127,23 +130,23 @@ public void ParseLambda_Complex_3() var externals = new Dictionary { - { "Users", qry } + {"Users", qry} }; // Act - string query = "Users.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age).Select(j => new System.Linq.Dynamic.Core.Tests.DynamicExpressionParserTests+ComplexParseLambda3Result{j.Key.Age, j.Sum(k => k.Income) As TotalIncome})"; - LambdaExpression expression = DynamicExpressionParser.ParseLambda(null, query, externals); + string stringExpression = "Users.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age).Select(j => new System.Linq.Dynamic.Core.Tests.DynamicExpressionParserTests+ComplexParseLambda3Result{j.Key.Age, j.Sum(k => k.Income) As TotalIncome})"; + LambdaExpression expression = DynamicExpressionParser.ParseLambda(config, null, stringExpression, externals); Delegate del = expression.Compile(); IEnumerable result = del.DynamicInvoke() as IEnumerable; - var expected = qry.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age).Select(j => new ComplexParseLambda3Result { Age = j.Key.Age, TotalIncome = j.Sum(k => k.Income) }).Cast().ToArray(); + var expected = qry.GroupBy(x => new { x.Profile.Age }).OrderBy(gg => gg.Key.Age) + .Select(j => new ComplexParseLambda3Result { Age = j.Key.Age, TotalIncome = j.Sum(k => k.Income) }) + .Cast().ToArray(); // Assert Check.That(result).IsNotNull(); Check.That(result).HasSize(expected.Length); Check.That(result.ToArray()[0]).Equals(expected[0]); - - GlobalConfig.CustomTypeProvider = null; } [Fact] @@ -330,6 +333,14 @@ public void ParseLambda_StringLiteralEmpty_ReturnsBooleanLambdaExpression() Assert.Equal(typeof(Boolean), expression.Body.Type); } + [Fact] + public void ParseLambda_Config_StringLiteralEmpty_ReturnsBooleanLambdaExpression() + { + var config = new ParsingConfig(); + var expression = DynamicExpressionParser.ParseLambda(new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(Boolean), "Property1 == \"\""); + Assert.Equal(typeof(Boolean), expression.Body.Type); + } + [Fact] public void ParseLambda_StringLiteralEmbeddedQuote_ReturnsBooleanLambdaExpression() { diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionHelperTests.cs new file mode 100644 index 00000000..897f1be8 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionHelperTests.cs @@ -0,0 +1,37 @@ +using System.Linq.Dynamic.Core.Parser; +using System.Linq.Expressions; +using NFluent; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public class ExpressionHelperTests + { + [Fact] + public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid() + { + // Assign + string guidAsString = Guid.NewGuid().ToString(); + + // Act + Expression result = ExpressionHelper.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); + + // Assert + Check.That(result).IsInstanceOf(); + Check.That(result.ToString()).Equals(guidAsString); + } + + [Fact] + public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid_Invalid() + { + // Assign + string guidAsString = "x"; + + // Act + Expression result = ExpressionHelper.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); + + // Assert + Check.That(result).IsNull(); + } + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs new file mode 100644 index 00000000..f381d679 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionPromoterTests.cs @@ -0,0 +1,6 @@ +namespace System.Linq.Dynamic.Core.Tests +{ + class ExpressionPromoterTests + { + } +} diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index a1cc7334..4bc7bf0a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -341,52 +341,19 @@ public void ExpressionTests_ContainsGuid() Assert.False(notFound2.Any()); } - [Fact] - public void ExpressionTests_ContextKeywordsAndSymbols() - { - try - { - //Arrange - int[] values = { 1, 2, 3, 4, 5 }; - - //Act - GlobalConfig.AreContextKeywordsEnabled = false; - Assert.Throws(() => values.AsQueryable().Where("it = 2")); - Assert.Throws(() => values.AsQueryable().Where("root = 2")); - values.AsQueryable().Where("$ = 2"); - values.AsQueryable().Where("~ = 2"); - GlobalConfig.AreContextKeywordsEnabled = true; - - var qry1 = values.AsQueryable().Where("it = 2"); - var qry2 = values.AsQueryable().Where("$ = 2"); - - //Assert - Assert.Equal(2, qry1.Single()); - Assert.Equal(2, qry2.Single()); - } - finally - { - GlobalConfig.AreContextKeywordsEnabled = true; - } - } - [Fact] public void ExpressionTests_DateTimeString() { -#if NETSTANDARD - GlobalConfig.CustomTypeProvider = new NetStandardCustomTypeProvider(); -#endif - - //Arrange + // Arrange var lst = new List { DateTime.Today, DateTime.Today.AddDays(1), DateTime.Today.AddDays(2) }; var qry = lst.AsQueryable(); - //Act + // Act var testValue = lst[0].ToString(CultureInfo.InvariantCulture); var result1 = qry.Where("it = @0", testValue); var result2 = qry.Where("@0 = it", testValue); - //Assert + // Assert Assert.Equal(lst[0], result1.Single()); Assert.Equal(lst[0], result2.Single()); } @@ -450,9 +417,9 @@ public void ExpressionTests_DistinctBy() var qry3 = p.Where("@0.Any($ == ~.Item3)", qry); //Assert - Assert.Equal(qry1.Count(), 2); - Assert.Equal(qry2.Count(), 2); - Assert.Equal(qry3.Count(), 2); + Assert.Equal(2, qry1.Count()); + Assert.Equal(2, qry2.Count()); + Assert.Equal(2, qry3.Count()); } [Fact] @@ -556,8 +523,9 @@ public void ExpressionTests_DoubleQualifiers_Negative() [Fact] public void ExpressionTests_Enum() { -#if NETSTANDARD - GlobalConfig.CustomTypeProvider = new NetStandardCustomTypeProvider(); + var config = new ParsingConfig(); +#if NETSTANDARD + config.CustomTypeProvider = new NetStandardCustomTypeProvider(); #endif // Arrange @@ -565,26 +533,26 @@ public void ExpressionTests_Enum() var qry = lst.AsQueryable(); // Act - var resultLessThan = qry.Where("it < TestEnum.Var4"); - var resultLessThanEqual = qry.Where("it <= TestEnum.Var4"); + var resultLessThan = qry.Where(config, "it < TestEnum.Var4"); + var resultLessThanEqual = qry.Where(config, "it <= TestEnum.Var4"); - var resultGreaterThan = qry.Where("TestEnum.Var4 > it"); - var resultGreaterThanEqual = qry.Where("TestEnum.Var4 >= it"); + var resultGreaterThan = qry.Where(config, "TestEnum.Var4 > it"); + var resultGreaterThanEqual = qry.Where(config, "TestEnum.Var4 >= it"); - var resultEqualItLeft = qry.Where("it = Var5"); - var resultEqualItRight = qry.Where("Var5 = it"); + var resultEqualItLeft = qry.Where(config, "it = Var5"); + var resultEqualItRight = qry.Where(config, "Var5 = it"); - var resultEqualEnumParamLeft = qry.Where("@0 = it", TestEnum.Var5); - var resultEqualEnumParamRight = qry.Where("it = @0", TestEnum.Var5); + var resultEqualEnumParamLeft = qry.Where(config, "@0 = it", TestEnum.Var5); + var resultEqualEnumParamRight = qry.Where(config, "it = @0", TestEnum.Var5); var resultEqualIntParamLeft = qry.Where("@0 = it", 8); var resultEqualIntParamRight = qry.Where("it = @0", 8); - var resultEqualStringParamRight = qry.Where("it = @0", "Var5"); - var resultEqualStringParamLeft = qry.Where("@0 = it", "Var5"); + var resultEqualStringParamRight = qry.Where(config, "it = @0", "Var5"); + var resultEqualStringParamLeft = qry.Where(config, "@0 = it", "Var5"); - var resultEqualStringMixedCaseParamLeft = qry.Where("@0 = it", "vAR5"); - var resultEqualStringMixedCaseParamRight = qry.Where("it = @0", "vAR5"); + var resultEqualStringMixedCaseParamLeft = qry.Where(config, "@0 = it", "vAR5"); + var resultEqualStringMixedCaseParamRight = qry.Where(config, "it = @0", "vAR5"); // Assert @@ -612,22 +580,23 @@ public void ExpressionTests_Enum() [Fact] public void ExpressionTests_Enum_Nullable() { + var config = new ParsingConfig(); #if NETSTANDARD - GlobalConfig.CustomTypeProvider = new NetStandardCustomTypeProvider(); + config.CustomTypeProvider = new NetStandardCustomTypeProvider(); #endif //Act - var result1a = new[] { TestEnum.Var1 }.AsQueryable().Where("it = @0", (TestEnum?)TestEnum.Var1); - var result1b = new[] { TestEnum.Var1 }.AsQueryable().Where("@0 = it", (TestEnum?)TestEnum.Var1); - var result2a = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where("it = @0", TestEnum.Var1); - var result2b = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where("@0 = it", TestEnum.Var1); - var result3a = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where("it = @0", (TestEnum?)TestEnum.Var1); - var result3b = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where("@0 = it", (TestEnum?)TestEnum.Var1); - - var result10a = new[] { TestEnum.Var1 }.AsQueryable().Where("it = @0", "Var1"); - var result10b = new[] { TestEnum.Var1 }.AsQueryable().Where("@0 = it", "Var1"); - var result11a = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where("it = @0", "Var1"); - var result11b = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where("@0 = it", "Var1"); + var result1a = new[] { TestEnum.Var1 }.AsQueryable().Where(config, "it = @0", (TestEnum?)TestEnum.Var1); + var result1b = new[] { TestEnum.Var1 }.AsQueryable().Where(config, "@0 = it", (TestEnum?)TestEnum.Var1); + var result2a = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where(config, "it = @0", TestEnum.Var1); + var result2b = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where(config, "@0 = it", TestEnum.Var1); + var result3a = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where(config, "it = @0", (TestEnum?)TestEnum.Var1); + var result3b = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where(config, "@0 = it", (TestEnum?)TestEnum.Var1); + + var result10a = new[] { TestEnum.Var1 }.AsQueryable().Where(config, "it = @0", "Var1"); + var result10b = new[] { TestEnum.Var1 }.AsQueryable().Where(config, "@0 = it", "Var1"); + var result11a = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where(config, "it = @0", "Var1"); + var result11b = new[] { (TestEnum?)TestEnum.Var1, null }.AsQueryable().Where(config, "@0 = it", "Var1"); //Assert Assert.Equal(TestEnum.Var1, result1a.Single()); @@ -700,8 +669,9 @@ public void ExpressionTests_FloatQualifiers_Negative() [Fact] public void ExpressionTests_Guid_CompareTo_String() { + var config = new ParsingConfig(); #if NETSTANDARD - GlobalConfig.CustomTypeProvider = new NetStandardCustomTypeProvider(); + config.CustomTypeProvider = new NetStandardCustomTypeProvider(); #endif //Arrange @@ -710,11 +680,11 @@ public void ExpressionTests_Guid_CompareTo_String() //Act string testValue = lst[0].ToString(); - var result1a = qry.Where("it = \"0A191E77-E32D-4DE1-8F1C-A144C2B0424D\""); - var result1b = qry.Where("it = @0", testValue); + var result1a = qry.Where(config, "it = \"0A191E77-E32D-4DE1-8F1C-A144C2B0424D\""); + var result1b = qry.Where(config, "it = @0", testValue); - var result2a = qry.Where("\"0A191E77-E32D-4DE1-8F1C-A144C2B0424D\" = it"); - var result2b = qry.Where("@0 = it", testValue); + var result2a = qry.Where(config, "\"0A191E77-E32D-4DE1-8F1C-A144C2B0424D\" = it"); + var result2b = qry.Where(config, "@0 = it", testValue); //Assert Assert.Equal(lst[0], result1a.Single()); @@ -823,8 +793,9 @@ public void ExpressionTests_HexadecimalInteger() [Fact] public void ExpressionTests_In_Enum() { + var config = new ParsingConfig(); #if NETSTANDARD - GlobalConfig.CustomTypeProvider = new NetStandardCustomTypeProvider(); + config.CustomTypeProvider = new NetStandardCustomTypeProvider(); #endif // Arrange var model1 = new ModelWithEnum { TestEnum = TestEnum.Var1 }; @@ -834,8 +805,8 @@ public void ExpressionTests_In_Enum() // Act var expected = qry.Where(x => new[] { TestEnum.Var1, TestEnum.Var2 }.Contains(x.TestEnum)).ToArray(); - var result1 = qry.Where("it.TestEnum in (\"Var1\", \"Var2\")").ToArray(); - var result2 = qry.Where("it.TestEnum in (0, 1)").ToArray(); + var result1 = qry.Where("it.TestEnum in (\"Var1\", \"Var2\")", config).ToArray(); + var result2 = qry.Where("it.TestEnum in (0, 1)", config).ToArray(); // Assert Check.That(result1).ContainsExactly(expected); @@ -849,7 +820,7 @@ public void ExpressionTests_In_Short() var qry = new short[] { 1, 2, 3, 4, 5, 6, 7 }.AsQueryable(); // Act - var result = qry.Where("it in (1,2)"); + var result = qry.Where("it in (1, 2)"); var resultExpected = qry.Where(x => x == 1 || x == 2); // Assert diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupByMany.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupByMany.cs index 2c51c579..d5920a67 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupByMany.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.GroupByMany.cs @@ -23,7 +23,7 @@ public void GroupByMany_Dynamic_LambdaExpressions() var sel = lst.AsQueryable().GroupByMany(x => x.Item1, x => x.Item2); Assert.Equal(2, sel.Count()); - Assert.Equal(1, sel.First().Subgroups.Count()); + Assert.Single(sel.First().Subgroups); Assert.Equal(2, sel.Skip(1).First().Subgroups.Count()); } diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs index 328ac5ea..aac46b7c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -61,6 +61,32 @@ public void Select_Dynamic() #endif } + [Fact] + public void Select_Dynamic_Add_Integers() + { + // Arrange + var range = new List { 1, 2 }; + + // Act + IEnumerable rangeResult = range.AsQueryable().Select("it + 1"); + + // Assert + Assert.Equal(range.Select(x => x + 1).ToArray(), rangeResult.Cast().ToArray()); + } + + [Fact] + public void Select_Dynamic_Add_Strings() + { + // Arrange + var range = new List { "a", "b" }; + + // Act + IEnumerable rangeResult = range.AsQueryable().Select("it + \"c\""); + + // Assert + Assert.Equal(range.Select(x => x + "c").ToArray(), rangeResult.Cast().ToArray()); + } + [Fact] public void Select_Dynamic_WithIncludes() { diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs index 0982c2a8..9bd6f093 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs @@ -58,7 +58,7 @@ public void Where_Dynamic_StringQuoted() var expected = qry.Where(x => x.UserName == @"This \""is\"" a test.").ToArray(); //Assert - Assert.Equal(1, expected.Length); + Assert.Single(expected); Assert.Equal(expected, result1a); Assert.Equal(expected, result1b); Assert.Equal(expected, result2); diff --git a/test/System.Linq.Dynamic.Core.Tests/TypeHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/TypeHelperTests.cs new file mode 100644 index 00000000..df2b921f --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TypeHelperTests.cs @@ -0,0 +1,64 @@ +using NFluent; +using System.Linq.Dynamic.Core.Parser; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests +{ + public class TypeHelperTests + { + enum TestEnum + { + x = 1 + } + + [Fact] + public void TypeHelper_ParseEnum_Valid() + { + // Assign + Act + var result = TypeHelper.ParseEnum("x", typeof(TestEnum)); + + // Assert + Check.That(result).Equals(TestEnum.x); + } + + [Fact] + public void TypeHelper_ParseEnum_Invalid() + { + // Assign + Act + var result = TypeHelper.ParseEnum("test", typeof(TestEnum)); + + // Assert + Check.That(result).IsNull(); + } + + [Fact] + public void TypeHelper_IsCompatibleWith_SameTypes_True() + { + // Assign + Act + var result = TypeHelper.IsCompatibleWith(typeof(int), typeof(int)); + + // Assert + Check.That(result).IsTrue(); + } + + [Fact] + public void TypeHelper_IsCompatibleWith_True() + { + // Assign + Act + var result = TypeHelper.IsCompatibleWith(typeof(int), typeof(long)); + + // Assert + Check.That(result).IsTrue(); + } + + [Fact] + public void TypeHelper_IsCompatibleWith_False() + { + // Assign + Act + var result = TypeHelper.IsCompatibleWith(typeof(long), typeof(int)); + + // Assert + Check.That(result).IsFalse(); + } + } +}