From 6c7872d4e6c294b1e6dd6b5f4683af685b5e70ea Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 5 Feb 2023 14:32:12 +0100 Subject: [PATCH] Fixed ExpressionParser when WrappedValue-string is used for equals-operator (#666) * UT * ? * Fixed ExpressionParser when WrappedValue-string is used for equals-operator --- .../Nullable/NotNullWhenAttribute.cs | 56 + .../Parser/ConstantExpressionWrapper.cs | 384 +- .../Parser/ExpressionHelper.cs | 567 +-- .../Parser/ExpressionParser.cs | 3299 +++++++++-------- .../Parser/IConstantExpressionWrapper.cs | 16 +- .../Parser/IExpressionHelper.cs | 50 +- .../SupportedOperands/IEqualitySignatures.cs | 37 +- .../Parser/WrappedValue.cs | 17 +- ...ts.UseParameterizedNamesInDynamicQuery .cs | 85 + 9 files changed, 2343 insertions(+), 2168 deletions(-) create mode 100644 src/System.Linq.Dynamic.Core/Compatibility/Nullable/NotNullWhenAttribute.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/QueryableTests.UseParameterizedNamesInDynamicQuery .cs diff --git a/src/System.Linq.Dynamic.Core/Compatibility/Nullable/NotNullWhenAttribute.cs b/src/System.Linq.Dynamic.Core/Compatibility/Nullable/NotNullWhenAttribute.cs new file mode 100644 index 00000000..676ef898 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Compatibility/Nullable/NotNullWhenAttribute.cs @@ -0,0 +1,56 @@ +#region License +// MIT License +// +// Copyright (c) Manuel Römer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion + +#if NETSTANDARD1_3_OR_GREATER || NET35 || NET40 || NET45 || NET452 || NET46 || NETCOREAPP2_1 || UAP10_0 + +// ReSharper disable once CheckNamespace +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies that when a method returns , +/// the parameter will not be even if the corresponding type allows it. +/// +[AttributeUsage(AttributeTargets.Parameter)] +[DebuggerNonUserCode] +internal sealed class NotNullWhenAttribute : Attribute +{ + /// + /// Gets the return value condition. + /// If the method returns this value, the associated parameter will not be . + /// + public bool ReturnValue { get; } + + /// + /// Initializes the attribute with the specified return value condition. + /// + /// + /// The return value condition. + /// If the method returns this value, the associated parameter will not be . + /// + public NotNullWhenAttribute(bool returnValue) + { + ReturnValue = returnValue; + } +} +#endif \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs index 7b3e403b..201afbf6 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionWrapper.cs @@ -1,199 +1,213 @@ -using System.Linq.Expressions; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +#if UAP10_0 || NETSTANDARD1_3 using System.Reflection; +#endif -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +/// +/// Based on gblog by graeme-hill. https://github.com/graeme-hill/gblog/blob/master/source_content/articles/2014.139_entity-framework-dynamic-queries-and-parameterization.mkd +/// +internal class ConstantExpressionWrapper : IConstantExpressionWrapper { - /// - /// Based on gblog by graeme-hill. https://github.com/graeme-hill/gblog/blob/master/source_content/articles/2014.139_entity-framework-dynamic-queries-and-parameterization.mkd - /// - internal class ConstantExpressionWrapper : IConstantExpressionWrapper + public void Wrap(ref Expression expression) { - public void Wrap(ref Expression expression) + if (expression is ConstantExpression constantExpression) { - if (expression is ConstantExpression constantExpression) - { - if (constantExpression.Type == typeof(bool)) - { - expression = WrappedConstant((bool)constantExpression.Value); - } - else if (constantExpression.Type == typeof(bool?)) - { - expression = WrappedConstant((bool?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(char)) - { - expression = WrappedConstant((char)constantExpression.Value); - } - else if (constantExpression.Type == typeof(char?)) - { - expression = WrappedConstant((char?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(byte)) - { - expression = WrappedConstant((byte)constantExpression.Value); - } - else if (constantExpression.Type == typeof(byte?)) - { - expression = WrappedConstant((byte?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(sbyte)) - { - expression = WrappedConstant((sbyte)constantExpression.Value); - } - else if (constantExpression.Type == typeof(string)) - { - expression = WrappedConstant((string)constantExpression.Value); - } - else if (constantExpression.Type == typeof(float)) - { - expression = WrappedConstant((float)constantExpression.Value); - } - else if (constantExpression.Type == typeof(float?)) - { - expression = WrappedConstant((float?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(decimal)) - { - expression = WrappedConstant((decimal)constantExpression.Value); - } - else if (constantExpression.Type == typeof(decimal?)) - { - expression = WrappedConstant((decimal?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(double)) - { - expression = WrappedConstant((double)constantExpression.Value); - } - else if (constantExpression.Type == typeof(double?)) - { - expression = WrappedConstant((double?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(long)) - { - expression = WrappedConstant((long)constantExpression.Value); - } - else if (constantExpression.Type == typeof(long?)) - { - expression = WrappedConstant((long?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(ulong)) - { - expression = WrappedConstant((ulong)constantExpression.Value); - } - else if (constantExpression.Type == typeof(ulong?)) - { - expression = WrappedConstant((ulong?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(int)) - { - expression = WrappedConstant((int)constantExpression.Value); - } - else if (constantExpression.Type == typeof(int?)) - { - expression = WrappedConstant((int?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(uint)) - { - expression = WrappedConstant((uint)constantExpression.Value); - } - else if (constantExpression.Type == typeof(uint?)) - { - expression = WrappedConstant((uint?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(short)) - { - expression = WrappedConstant((short)constantExpression.Value); - } - else if (constantExpression.Type == typeof(short?)) - { - expression = WrappedConstant((short?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(ushort)) - { - expression = WrappedConstant((ushort)constantExpression.Value); - } - else if (constantExpression.Type == typeof(ushort?)) - { - expression = WrappedConstant((ushort?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(Guid)) - { - expression = WrappedConstant((Guid)constantExpression.Value); - } - else if (constantExpression.Type == typeof(Guid?)) - { - expression = WrappedConstant((Guid?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(DateTime)) - { - expression = WrappedConstant((DateTime)constantExpression.Value); - } - else if (constantExpression.Type == typeof(DateTime?)) - { - expression = WrappedConstant((DateTime?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(DateTimeOffset)) - { - expression = WrappedConstant((DateTimeOffset)constantExpression.Value); - } - else if (constantExpression.Type == typeof(DateTimeOffset?)) - { - expression = WrappedConstant((DateTimeOffset?)constantExpression.Value); - } - else if (constantExpression.Type == typeof(TimeSpan)) - { - expression = WrappedConstant((TimeSpan)constantExpression.Value); - } - else if (constantExpression.Type == typeof(TimeSpan?)) - { - expression = WrappedConstant((TimeSpan?)constantExpression.Value); - } - - return; + if (constantExpression.Type == typeof(bool)) + { + expression = Wrap((bool)constantExpression.Value); } - - if (expression is NewExpression newExpression) - { - if (newExpression.Type == typeof(Guid)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } - else if (newExpression.Type == typeof(Guid?)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } - else if (newExpression.Type == typeof(DateTime)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } - else if (newExpression.Type == typeof(DateTime?)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } - else if (newExpression.Type == typeof(DateTimeOffset)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } - else if (newExpression.Type == typeof(DateTimeOffset?)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } - else if (newExpression.Type == typeof(TimeSpan)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } - else if (newExpression.Type == typeof(TimeSpan?)) - { - expression = WrappedConstant(Expression.Lambda>(newExpression).Compile()()); - } + else if (constantExpression.Type == typeof(bool?)) + { + expression = Wrap((bool?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(char)) + { + expression = Wrap((char)constantExpression.Value); + } + else if (constantExpression.Type == typeof(char?)) + { + expression = Wrap((char?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(byte)) + { + expression = Wrap((byte)constantExpression.Value); + } + else if (constantExpression.Type == typeof(byte?)) + { + expression = Wrap((byte?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(sbyte)) + { + expression = Wrap((sbyte)constantExpression.Value); + } + else if (constantExpression.Type == typeof(string)) + { + expression = Wrap((string)constantExpression.Value); + } + else if (constantExpression.Type == typeof(float)) + { + expression = Wrap((float)constantExpression.Value); + } + else if (constantExpression.Type == typeof(float?)) + { + expression = Wrap((float?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(decimal)) + { + expression = Wrap((decimal)constantExpression.Value); + } + else if (constantExpression.Type == typeof(decimal?)) + { + expression = Wrap((decimal?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(double)) + { + expression = Wrap((double)constantExpression.Value); + } + else if (constantExpression.Type == typeof(double?)) + { + expression = Wrap((double?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(long)) + { + expression = Wrap((long)constantExpression.Value); + } + else if (constantExpression.Type == typeof(long?)) + { + expression = Wrap((long?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(ulong)) + { + expression = Wrap((ulong)constantExpression.Value); + } + else if (constantExpression.Type == typeof(ulong?)) + { + expression = Wrap((ulong?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(int)) + { + expression = Wrap((int)constantExpression.Value); + } + else if (constantExpression.Type == typeof(int?)) + { + expression = Wrap((int?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(uint)) + { + expression = Wrap((uint)constantExpression.Value); + } + else if (constantExpression.Type == typeof(uint?)) + { + expression = Wrap((uint?)constantExpression.Value); } + else if (constantExpression.Type == typeof(short)) + { + expression = Wrap((short)constantExpression.Value); + } + else if (constantExpression.Type == typeof(short?)) + { + expression = Wrap((short?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(ushort)) + { + expression = Wrap((ushort)constantExpression.Value); + } + else if (constantExpression.Type == typeof(ushort?)) + { + expression = Wrap((ushort?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(Guid)) + { + expression = Wrap((Guid)constantExpression.Value); + } + else if (constantExpression.Type == typeof(Guid?)) + { + expression = Wrap((Guid?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(DateTime)) + { + expression = Wrap((DateTime)constantExpression.Value); + } + else if (constantExpression.Type == typeof(DateTime?)) + { + expression = Wrap((DateTime?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(DateTimeOffset)) + { + expression = Wrap((DateTimeOffset)constantExpression.Value); + } + else if (constantExpression.Type == typeof(DateTimeOffset?)) + { + expression = Wrap((DateTimeOffset?)constantExpression.Value); + } + else if (constantExpression.Type == typeof(TimeSpan)) + { + expression = Wrap((TimeSpan)constantExpression.Value); + } + else if (constantExpression.Type == typeof(TimeSpan?)) + { + expression = Wrap((TimeSpan?)constantExpression.Value); + } + + return; } - private static MemberExpression WrappedConstant(TValue value) + if (expression is NewExpression newExpression) { - var wrapper = new WrappedValue(value); + if (newExpression.Type == typeof(Guid)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(Guid?)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(DateTime)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(DateTime?)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(DateTimeOffset)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(DateTimeOffset?)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(TimeSpan)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + else if (newExpression.Type == typeof(TimeSpan?)) + { + expression = Wrap(Expression.Lambda>(newExpression).Compile()()); + } + } + } - return Expression.Property(Expression.Constant(wrapper), typeof(WrappedValue).GetProperty("Value")!); + public bool TryUnwrap(MemberExpression? expression, [NotNullWhen(true)] out TValue? value) + { + if (expression?.Expression is ConstantExpression { Value: WrappedValue wrapper }) + { + value = wrapper.Value!; + return true; } + + value = default; + return false; + } + + private static MemberExpression Wrap(TValue value) + { + var wrapper = new WrappedValue(value); + + return Expression.Property(Expression.Constant(wrapper), typeof(WrappedValue).GetProperty("Value")!); } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs index 2a8dc419..5900e037 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs @@ -1,209 +1,221 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +internal class ExpressionHelper : IExpressionHelper { - internal class ExpressionHelper : IExpressionHelper + private readonly IConstantExpressionWrapper _constantExpressionWrapper = new ConstantExpressionWrapper(); + private readonly ParsingConfig _parsingConfig; + + internal ExpressionHelper(ParsingConfig parsingConfig) { - private readonly IConstantExpressionWrapper _constantExpressionWrapper = new ConstantExpressionWrapper(); - private readonly ParsingConfig _parsingConfig; + _parsingConfig = Check.NotNull(parsingConfig); + } - internal ExpressionHelper(ParsingConfig parsingConfig) + public void WrapConstantExpression(ref Expression argument) + { + if (_parsingConfig.UseParameterizedNamesInDynamicQuery) { - _parsingConfig = Check.NotNull(parsingConfig); + _constantExpressionWrapper.Wrap(ref argument); } + } - public void WrapConstantExpression(ref Expression argument) + public bool TryUnwrapConstantExpression(Expression? expression, [NotNullWhen(true)] out TValue? value) + { + if (_parsingConfig.UseParameterizedNamesInDynamicQuery && _constantExpressionWrapper.TryUnwrap(expression as MemberExpression, out value)) { - if (_parsingConfig.UseParameterizedNamesInDynamicQuery) - { - _constantExpressionWrapper.Wrap(ref argument); - } + return true; } - public void ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref Expression left, ref Expression right) - { - if (left.Type == right.Type) - { - return; - } + value = default; + return false; + } - if (left.Type == typeof(ulong) || right.Type == typeof(ulong)) - { - right = right.Type != typeof(ulong) ? Expression.Convert(right, typeof(ulong)) : right; - left = left.Type != typeof(ulong) ? Expression.Convert(left, typeof(ulong)) : left; - } - else if (left.Type == typeof(long) || right.Type == typeof(long)) - { - right = right.Type != typeof(long) ? Expression.Convert(right, typeof(long)) : right; - left = left.Type != typeof(long) ? Expression.Convert(left, typeof(long)) : left; - } - else if (left.Type == typeof(uint) || right.Type == typeof(uint)) - { - right = right.Type != typeof(uint) ? Expression.Convert(right, typeof(uint)) : right; - left = left.Type != typeof(uint) ? Expression.Convert(left, typeof(uint)) : left; - } - else if (left.Type == typeof(int) || right.Type == typeof(int)) - { - right = right.Type != typeof(int) ? Expression.Convert(right, typeof(int)) : right; - left = left.Type != typeof(int) ? Expression.Convert(left, typeof(int)) : left; - } - else if (left.Type == typeof(ushort) || right.Type == typeof(ushort)) - { - right = right.Type != typeof(ushort) ? Expression.Convert(right, typeof(ushort)) : right; - left = left.Type != typeof(ushort) ? Expression.Convert(left, typeof(ushort)) : left; - } - else if (left.Type == typeof(short) || right.Type == typeof(short)) - { - right = right.Type != typeof(short) ? Expression.Convert(right, typeof(short)) : right; - left = left.Type != typeof(short) ? Expression.Convert(left, typeof(short)) : 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 void ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref Expression left, ref Expression right) + { + if (left.Type == right.Type) + { + return; } - public Expression GenerateAdd(Expression left, Expression right) + if (left.Type == typeof(ulong) || right.Type == typeof(ulong)) { - return Expression.Add(left, right); + right = right.Type != typeof(ulong) ? Expression.Convert(right, typeof(ulong)) : right; + left = left.Type != typeof(ulong) ? Expression.Convert(left, typeof(ulong)) : left; } - - public Expression GenerateStringConcat(Expression left, Expression right) + else if (left.Type == typeof(long) || right.Type == typeof(long)) { - return GenerateStaticMethodCall("Concat", left, right); + right = right.Type != typeof(long) ? Expression.Convert(right, typeof(long)) : right; + left = left.Type != typeof(long) ? Expression.Convert(left, typeof(long)) : left; } - - public Expression GenerateSubtract(Expression left, Expression right) + else if (left.Type == typeof(uint) || right.Type == typeof(uint)) { - return Expression.Subtract(left, right); + right = right.Type != typeof(uint) ? Expression.Convert(right, typeof(uint)) : right; + left = left.Type != typeof(uint) ? Expression.Convert(left, typeof(uint)) : left; } - - public Expression GenerateEqual(Expression left, Expression right) + else if (left.Type == typeof(int) || right.Type == typeof(int)) + { + right = right.Type != typeof(int) ? Expression.Convert(right, typeof(int)) : right; + left = left.Type != typeof(int) ? Expression.Convert(left, typeof(int)) : left; + } + else if (left.Type == typeof(ushort) || right.Type == typeof(ushort)) + { + right = right.Type != typeof(ushort) ? Expression.Convert(right, typeof(ushort)) : right; + left = left.Type != typeof(ushort) ? Expression.Convert(left, typeof(ushort)) : left; + } + else if (left.Type == typeof(short) || right.Type == typeof(short)) { - OptimizeForEqualityIfPossible(ref left, ref right); + right = right.Type != typeof(short) ? Expression.Convert(right, typeof(short)) : right; + left = left.Type != typeof(short) ? Expression.Convert(left, typeof(short)) : 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; + } + } - WrapConstantExpressions(ref left, ref right); + public Expression GenerateAdd(Expression left, Expression right) + { + return Expression.Add(left, right); + } - return Expression.Equal(left, right); - } + public Expression GenerateStringConcat(Expression left, Expression right) + { + return GenerateStaticMethodCall("Concat", left, right); + } - public Expression GenerateNotEqual(Expression left, Expression right) - { - OptimizeForEqualityIfPossible(ref left, ref right); + public Expression GenerateSubtract(Expression left, Expression right) + { + return Expression.Subtract(left, right); + } - WrapConstantExpressions(ref left, ref right); + public Expression GenerateEqual(Expression left, Expression right) + { + OptimizeForEqualityIfPossible(ref left, ref right); - return Expression.NotEqual(left, right); - } + WrapConstantExpressions(ref left, ref right); - public Expression GenerateGreaterThan(Expression left, Expression right) - { - if (left.Type == typeof(string)) - { - return Expression.GreaterThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } + return Expression.Equal(left, right); + } - 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); - } + public Expression GenerateNotEqual(Expression left, Expression right) + { + OptimizeForEqualityIfPossible(ref left, ref right); + + WrapConstantExpressions(ref left, ref right); - WrapConstantExpressions(ref left, ref right); + return Expression.NotEqual(left, right); + } - return Expression.GreaterThan(left, right); + public Expression GenerateGreaterThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); } - public Expression GenerateGreaterThanEqual(Expression left, Expression right) + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) { - if (left.Type == typeof(string)) - { - return Expression.GreaterThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } + 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); + } - 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); - } + WrapConstantExpressions(ref left, ref right); - WrapConstantExpressions(ref left, ref right); + return Expression.GreaterThan(left, right); + } - return Expression.GreaterThanOrEqual(left, right); + public Expression GenerateGreaterThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); } - public Expression GenerateLessThan(Expression left, Expression right) + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) { - if (left.Type == typeof(string)) - { - return Expression.LessThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } + 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); + } - 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); - } + WrapConstantExpressions(ref left, ref right); - WrapConstantExpressions(ref left, ref right); + return Expression.GreaterThanOrEqual(left, right); + } - return Expression.LessThan(left, right); + public Expression GenerateLessThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThan(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); } - public Expression GenerateLessThanEqual(Expression left, Expression right) + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) { - if (left.Type == typeof(string)) - { - return Expression.LessThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); - } + 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); + } - 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); - } + WrapConstantExpressions(ref left, ref right); - WrapConstantExpressions(ref left, ref right); + return Expression.LessThan(left, right); + } - return Expression.LessThanOrEqual(left, right); + public Expression GenerateLessThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThanOrEqual(GenerateStaticMethodCall("Compare", left, right), Expression.Constant(0)); } - public void OptimizeForEqualityIfPossible(ref Expression left, ref Expression right) + if (left.Type.GetTypeInfo().IsEnum || right.Type.GetTypeInfo().IsEnum) { - // 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; + 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); + } - if (rightType == typeof(string) && right.NodeType == ExpressionType.Constant) - { - right = OptimizeStringForEqualityIfPossible((string?)((ConstantExpression)right).Value, leftType) ?? right; - } + WrapConstantExpressions(ref left, ref right); - if (leftType == typeof(string) && left.NodeType == ExpressionType.Constant) - { - left = OptimizeStringForEqualityIfPossible((string?)((ConstantExpression)left).Value, rightType) ?? left; - } + return Expression.LessThanOrEqual(left, right); + } + + public 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; } - public Expression? OptimizeStringForEqualityIfPossible(string? text, Type type) + if (leftType == typeof(string) && left.NodeType == ExpressionType.Constant) { - if (type == typeof(DateTime) && DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateTime)) - { - return Expression.Constant(dateTime, typeof(DateTime)); - } + left = OptimizeStringForEqualityIfPossible((string?)((ConstantExpression)left).Value, rightType) ?? left; + } + } + + public 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)); - } + if (type == typeof(Guid) && Guid.TryParse(text, out Guid guid)) + { + return Expression.Constant(guid, typeof(Guid)); + } #else try { @@ -214,191 +226,190 @@ public void OptimizeForEqualityIfPossible(ref Expression left, ref Expression ri // Doing it in old fashion way when no TryParse interface was provided by .NET } #endif - return null; - } + return null; + } - public bool MemberExpressionIsDynamic(Expression expression) - { + public bool MemberExpressionIsDynamic(Expression expression) + { #if NET35 return false; #else - return expression is MemberExpression memberExpression && memberExpression.Member.GetCustomAttribute() != null; + return expression is MemberExpression memberExpression && memberExpression.Member.GetCustomAttribute() != null; #endif - } + } - public Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName) - { + public Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName) + { #if !NET35 && !UAP10_0 && !NETSTANDARD1_3 - return Expression.Dynamic(new DynamicGetMemberBinder(propertyName, _parsingConfig), type, expression); + return Expression.Dynamic(new DynamicGetMemberBinder(propertyName, _parsingConfig), type, expression); #else throw new NotSupportedException(Res.DynamicExpandoObjectIsNotSupported); #endif - } + } - private MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) + private MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) + { + var methodInfo = left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); + if (methodInfo == null) { - var methodInfo = left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); - if (methodInfo == null) - { - methodInfo = right.Type.GetMethod(methodName, new[] { left.Type, right.Type })!; - } - - return methodInfo; + methodInfo = right.Type.GetMethod(methodName, new[] { left.Type, right.Type })!; } - private Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) - { - return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); - } + return methodInfo; + } - private void WrapConstantExpressions(ref Expression left, ref Expression right) - { - if (_parsingConfig.UseParameterizedNamesInDynamicQuery) - { - _constantExpressionWrapper.Wrap(ref left); - _constantExpressionWrapper.Wrap(ref right); - } - } + private Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) + { + return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); + } - public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, bool addSelf, out Expression generatedExpression) + private void WrapConstantExpressions(ref Expression left, ref Expression right) + { + if (_parsingConfig.UseParameterizedNamesInDynamicQuery) { - var expressions = CollectExpressions(addSelf, sourceExpression); - - if (expressions.Count == 1 && !(expressions[0] is MethodCallExpression)) - { - generatedExpression = sourceExpression; - return false; - } + _constantExpressionWrapper.Wrap(ref left); + _constantExpressionWrapper.Wrap(ref right); + } + } - // Reverse the list - expressions.Reverse(); + public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, bool addSelf, out Expression generatedExpression) + { + var expressions = CollectExpressions(addSelf, sourceExpression); - // Convert all expressions into '!= null' expressions (only if the type can be null) - var binaryExpressions = expressions - .Where(expression => TypeHelper.TypeCanBeNull(expression.Type)) - .Select(expression => Expression.NotEqual(expression, Expression.Constant(null))) - .ToArray(); + if (expressions.Count == 1 && !(expressions[0] is MethodCallExpression)) + { + generatedExpression = sourceExpression; + return false; + } - // Convert all binary expressions into `AndAlso(...)` - generatedExpression = binaryExpressions[0]; - for (int i = 1; i < binaryExpressions.Length; i++) - { - generatedExpression = Expression.AndAlso(generatedExpression, binaryExpressions[i]); - } + // Reverse the list + expressions.Reverse(); - return true; - } + // Convert all expressions into '!= null' expressions (only if the type can be null) + var binaryExpressions = expressions + .Where(expression => TypeHelper.TypeCanBeNull(expression.Type)) + .Select(expression => Expression.NotEqual(expression, Expression.Constant(null))) + .ToArray(); - public bool ExpressionQualifiesForNullPropagation(Expression? expression) + // Convert all binary expressions into `AndAlso(...)` + generatedExpression = binaryExpressions[0]; + for (int i = 1; i < binaryExpressions.Length; i++) { - return - expression is MemberExpression || - expression is ParameterExpression || - expression is MethodCallExpression || - expression is UnaryExpression; + generatedExpression = Expression.AndAlso(generatedExpression, binaryExpressions[i]); } - public Expression GenerateDefaultExpression(Type type) - { + return true; + } + + public bool ExpressionQualifiesForNullPropagation(Expression? expression) + { + return + expression is MemberExpression || + expression is ParameterExpression || + expression is MethodCallExpression || + expression is UnaryExpression; + } + + public Expression GenerateDefaultExpression(Type type) + { #if NET35 return Expression.Constant(Activator.CreateInstance(type)); #else - return Expression.Default(type); + return Expression.Default(type); #endif + } + + private Expression? GetMemberExpression(Expression? expression) + { + if (ExpressionQualifiesForNullPropagation(expression)) + { + return expression; } - private Expression? GetMemberExpression(Expression? expression) + if (expression is LambdaExpression lambdaExpression) { - if (ExpressionQualifiesForNullPropagation(expression)) + if (lambdaExpression.Body is MemberExpression bodyAsMemberExpression) { - return expression; + return bodyAsMemberExpression; } - if (expression is LambdaExpression lambdaExpression) + if (lambdaExpression.Body is UnaryExpression bodyAsUnaryExpression) { - if (lambdaExpression.Body is MemberExpression bodyAsMemberExpression) - { - return bodyAsMemberExpression; - } - - if (lambdaExpression.Body is UnaryExpression bodyAsUnaryExpression) - { - return bodyAsUnaryExpression.Operand; - } + return bodyAsUnaryExpression.Operand; } - - return null; } - private List CollectExpressions(bool addSelf, Expression sourceExpression) - { - Expression? expression = GetMemberExpression(sourceExpression); + return null; + } - var list = new List(); + private List CollectExpressions(bool addSelf, Expression sourceExpression) + { + Expression? expression = GetMemberExpression(sourceExpression); - if (addSelf) - { - switch (expression) - { - case MemberExpression _: - list.Add(sourceExpression); - break; - - // ReSharper disable once RedundantEmptySwitchSection - default: - break; - } - } + var list = new List(); - bool expressionRecognized; - do + if (addSelf) + { + switch (expression) { - switch (expression) - { - case MemberExpression memberExpression: - expression = GetMemberExpression(memberExpression.Expression); - expressionRecognized = expression != null; - break; - - case MethodCallExpression methodCallExpression: - expression = GetMethodCallExpression(methodCallExpression); - expressionRecognized = expression != null; - break; - - case UnaryExpression unaryExpression: - expression = GetUnaryExpression(unaryExpression); - expressionRecognized = expression != null; - break; - - default: - expressionRecognized = false; - break; - } - - if (expressionRecognized && ExpressionQualifiesForNullPropagation(expression)) - { - list.Add(expression!); - } - } while (expressionRecognized); - - return list; + case MemberExpression _: + list.Add(sourceExpression); + break; + + // ReSharper disable once RedundantEmptySwitchSection + default: + break; + } } - private static Expression? GetMethodCallExpression(MethodCallExpression methodCallExpression) + bool expressionRecognized; + do { - if (methodCallExpression.Object != null) + switch (expression) + { + case MemberExpression memberExpression: + expression = GetMemberExpression(memberExpression.Expression); + expressionRecognized = expression != null; + break; + + case MethodCallExpression methodCallExpression: + expression = GetMethodCallExpression(methodCallExpression); + expressionRecognized = expression != null; + break; + + case UnaryExpression unaryExpression: + expression = GetUnaryExpression(unaryExpression); + expressionRecognized = expression != null; + break; + + default: + expressionRecognized = false; + break; + } + + if (expressionRecognized && ExpressionQualifiesForNullPropagation(expression)) { - // Something like: "np(FooValue.Zero().Length)" - return methodCallExpression.Object; + list.Add(expression!); } + } while (expressionRecognized); - // Something like: "np(MyClasses.FirstOrDefault())" - return methodCallExpression.Arguments.FirstOrDefault(); - } + return list; + } - private static Expression? GetUnaryExpression(UnaryExpression? unaryExpression) + private static Expression? GetMethodCallExpression(MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Object != null) { - return unaryExpression?.Operand; + // Something like: "np(FooValue.Zero().Length)" + return methodCallExpression.Object; } + + // Something like: "np(MyClasses.FirstOrDefault())" + return methodCallExpression.Arguments.FirstOrDefault(); + } + + private static Expression? GetUnaryExpression(UnaryExpression? unaryExpression) + { + return unaryExpression?.Operand; } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index dad76ebd..cb840f6a 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -12,2202 +12,2209 @@ using System.Reflection; using AnyOfTypes; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +/// +/// ExpressionParser +/// +public class ExpressionParser { + private static readonly string methodOrderBy = nameof(Queryable.OrderBy); + private static readonly string methodOrderByDescending = nameof(Queryable.OrderByDescending); + private static readonly string methodThenBy = nameof(Queryable.ThenBy); + private static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending); + + private readonly ParsingConfig _parsingConfig; + private readonly MethodFinder _methodFinder; + private readonly IKeywordsHelper _keywordsHelper; + private readonly TextParser _textParser; + private readonly NumberParser _numberParser; + private readonly IExpressionHelper _expressionHelper; + private readonly ITypeFinder _typeFinder; + private readonly ITypeConverterFactory _typeConverterFactory; + private readonly Dictionary _internals = new(); + private readonly Dictionary _symbols; + + private IDictionary? _externals; + private ParameterExpression? _it; + private ParameterExpression? _parent; + private ParameterExpression? _root; + private Type? _resultType; + private bool _createParameterCtor; + /// - /// ExpressionParser + /// Gets name for the `it` field. By default this is set to the KeyWord value "it". /// - public class ExpressionParser - { - private static readonly string methodOrderBy = nameof(Queryable.OrderBy); - private static readonly string methodOrderByDescending = nameof(Queryable.OrderByDescending); - private static readonly string methodThenBy = nameof(Queryable.ThenBy); - private static readonly string methodThenByDescending = nameof(Queryable.ThenByDescending); + public string ItName { get; private set; } = KeywordsHelper.KEYWORD_IT; - private readonly ParsingConfig _parsingConfig; - private readonly MethodFinder _methodFinder; - private readonly IKeywordsHelper _keywordsHelper; - private readonly TextParser _textParser; - private readonly NumberParser _numberParser; - private readonly IExpressionHelper _expressionHelper; - private readonly ITypeFinder _typeFinder; - private readonly ITypeConverterFactory _typeConverterFactory; - private readonly Dictionary _internals = new(); - private readonly Dictionary _symbols; + /// + /// There was a problem when an expression contained multiple lambdas where + /// the ItName was not cleared and freed for the next lambda. This variable + /// stores the ItName of the last parsed lambda. + /// Not used internally by ExpressionParser, but used to preserve compatiblity of parsingConfig.RenameParameterExpression + /// which was designed to only work with mono-lambda expressions. + /// + public string LastLambdaItName { get; private set; } = KeywordsHelper.KEYWORD_IT; - private IDictionary? _externals; - private ParameterExpression? _it; - private ParameterExpression? _parent; - private ParameterExpression? _root; - private Type? _resultType; - private bool _createParameterCtor; + /// + /// Initializes a new instance of the class. + /// + /// The parameters. + /// The expression. + /// The values. + /// The parsing configuration. + public ExpressionParser(ParameterExpression[]? parameters, string expression, object?[]? values, ParsingConfig? parsingConfig) + { + Check.NotEmpty(expression, nameof(expression)); - /// - /// Gets name for the `it` field. By default this is set to the KeyWord value "it". - /// - public string ItName { get; private set; } = KeywordsHelper.KEYWORD_IT; + _symbols = new Dictionary(parsingConfig is { IsCaseSensitive: true } ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + _parsingConfig = parsingConfig ?? ParsingConfig.Default; - /// - /// There was a problem when an expression contained multiple lambdas where - /// the ItName was not cleared and freed for the next lambda. This variable - /// stores the ItName of the last parsed lambda. - /// Not used internally by ExpressionParser, but used to preserve compatiblity of parsingConfig.RenameParameterExpression - /// which was designed to only work with mono-lambda expressions. - /// - public string LastLambdaItName { get; private set; } = KeywordsHelper.KEYWORD_IT; + _keywordsHelper = new KeywordsHelper(_parsingConfig); + _textParser = new TextParser(_parsingConfig, expression); + _numberParser = new NumberParser(parsingConfig); + _methodFinder = new MethodFinder(_parsingConfig); + _expressionHelper = new ExpressionHelper(_parsingConfig); + _typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper); + _typeConverterFactory = new TypeConverterFactory(_parsingConfig); - /// - /// Initializes a new instance of the class. - /// - /// The parameters. - /// The expression. - /// The values. - /// The parsing configuration. - public ExpressionParser(ParameterExpression[]? parameters, string expression, object?[]? values, ParsingConfig? parsingConfig) + if (parameters != null) { - Check.NotEmpty(expression, nameof(expression)); + ProcessParameters(parameters); + } - _symbols = new Dictionary(parsingConfig is { IsCaseSensitive: true } ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); - _parsingConfig = parsingConfig ?? ParsingConfig.Default; + if (values != null) + { + ProcessValues(values); + } + } - _keywordsHelper = new KeywordsHelper(_parsingConfig); - _textParser = new TextParser(_parsingConfig, expression); - _numberParser = new NumberParser(parsingConfig); - _methodFinder = new MethodFinder(_parsingConfig); - _expressionHelper = new ExpressionHelper(_parsingConfig); - _typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper); - _typeConverterFactory = new TypeConverterFactory(_parsingConfig); + private void ProcessParameters(ParameterExpression[] parameters) + { + foreach (ParameterExpression pe in parameters.Where(p => !string.IsNullOrEmpty(p.Name))) + { + AddSymbol(pe.Name, pe); + } - if (parameters != null) - { - ProcessParameters(parameters); - } + // If there is only 1 ParameterExpression, do also allow access using 'it' + if (parameters.Length == 1) + { + _parent = _it; + _it = parameters[0]; - if (values != null) + if (_root == null) { - ProcessValues(values); + _root = _it; } } + } - private void ProcessParameters(ParameterExpression[] parameters) + private void ProcessValues(object?[] values) + { + for (int i = 0; i < values.Length; i++) { - foreach (ParameterExpression pe in parameters.Where(p => !string.IsNullOrEmpty(p.Name))) - { - AddSymbol(pe.Name, pe); - } + object? value = values[i]; + IDictionary? externals; - // If there is only 1 ParameterExpression, do also allow access using 'it' - if (parameters.Length == 1) + if (i == values.Length - 1 && (externals = value as IDictionary) != null) { - _parent = _it; - _it = parameters[0]; - - if (_root == null) - { - _root = _it; - } + _externals = externals; } - } - - private void ProcessValues(object?[] values) - { - for (int i = 0; i < values.Length; i++) + else { - object? value = values[i]; - IDictionary? externals; - - if (i == values.Length - 1 && (externals = value as IDictionary) != null) - { - _externals = externals; - } - else - { - AddSymbol("@" + i.ToString(CultureInfo.InvariantCulture), value); - } + AddSymbol("@" + i.ToString(CultureInfo.InvariantCulture), value); } } + } - private void AddSymbol(string name, object? value) + private void AddSymbol(string name, object? value) + { + if (_symbols.ContainsKey(name)) { - if (_symbols.ContainsKey(name)) - { - throw ParseError(Res.DuplicateIdentifier, name); - } - - _symbols.Add(name, value); + throw ParseError(Res.DuplicateIdentifier, name); } - /// - /// Uses the TextParser to parse the string into the specified result type. - /// - /// Type of the result. - /// if set to true [create parameter ctor]. - /// Expression - public Expression Parse(Type? resultType, bool createParameterCtor = true) - { - _resultType = resultType; - _createParameterCtor = createParameterCtor; + _symbols.Add(name, value); + } - int exprPos = _textParser.CurrentToken.Pos; - Expression? expr = ParseConditionalOperator(); + /// + /// Uses the TextParser to parse the string into the specified result type. + /// + /// Type of the result. + /// if set to true [create parameter ctor]. + /// Expression + public Expression Parse(Type? resultType, bool createParameterCtor = true) + { + _resultType = resultType; + _createParameterCtor = createParameterCtor; - if (resultType != null) + int exprPos = _textParser.CurrentToken.Pos; + Expression? expr = ParseConditionalOperator(); + + if (resultType != null) + { + if ((expr = _parsingConfig.ExpressionPromoter.Promote(expr, resultType, true, false)) == null) { - if ((expr = _parsingConfig.ExpressionPromoter.Promote(expr, resultType, true, false)) == null) - { - throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType)); - } + throw ParseError(exprPos, Res.ExpressionTypeMismatch, TypeHelper.GetTypeName(resultType)); } + } - _textParser.ValidateToken(TokenId.End, Res.SyntaxError); + _textParser.ValidateToken(TokenId.End, Res.SyntaxError); - return expr; - } + return expr; + } #pragma warning disable 0219 - internal IList ParseOrdering(bool forceThenBy = false) + internal IList ParseOrdering(bool forceThenBy = false) + { + var orderings = new List(); + while (true) { - var orderings = new List(); - while (true) + Expression expr = ParseConditionalOperator(); + bool ascending = true; + if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) { - Expression expr = ParseConditionalOperator(); - bool ascending = true; - if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) - { - _textParser.NextToken(); - } - else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) - { - _textParser.NextToken(); - ascending = false; - } - - string methodName; - if (forceThenBy || orderings.Count > 0) - { - methodName = ascending ? methodThenBy : methodThenByDescending; - } - else - { - methodName = ascending ? methodOrderBy : methodOrderByDescending; - } + _textParser.NextToken(); + } + else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) + { + _textParser.NextToken(); + ascending = false; + } - orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending, MethodName = methodName }); + string methodName; + if (forceThenBy || orderings.Count > 0) + { + methodName = ascending ? methodThenBy : methodThenByDescending; + } + else + { + methodName = ascending ? methodOrderBy : methodOrderByDescending; + } - if (_textParser.CurrentToken.Id != TokenId.Comma) - { - break; - } + orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending, MethodName = methodName }); - _textParser.NextToken(); + if (_textParser.CurrentToken.Id != TokenId.Comma) + { + break; } - _textParser.ValidateToken(TokenId.End, Res.SyntaxError); - return orderings; + _textParser.NextToken(); } + + _textParser.ValidateToken(TokenId.End, Res.SyntaxError); + return orderings; + } #pragma warning restore 0219 - // ?: operator - private Expression ParseConditionalOperator() + // ?: operator + private Expression ParseConditionalOperator() + { + int errorPos = _textParser.CurrentToken.Pos; + Expression expr = ParseNullCoalescingOperator(); + if (_textParser.CurrentToken.Id == TokenId.Question) { - int errorPos = _textParser.CurrentToken.Pos; - Expression expr = ParseNullCoalescingOperator(); - if (_textParser.CurrentToken.Id == TokenId.Question) - { - _textParser.NextToken(); - Expression expr1 = ParseConditionalOperator(); - _textParser.ValidateToken(TokenId.Colon, Res.ColonExpected); - _textParser.NextToken(); - Expression expr2 = ParseConditionalOperator(); - expr = GenerateConditional(expr, expr1, expr2, false, errorPos); - } - return expr; + _textParser.NextToken(); + Expression expr1 = ParseConditionalOperator(); + _textParser.ValidateToken(TokenId.Colon, Res.ColonExpected); + _textParser.NextToken(); + Expression expr2 = ParseConditionalOperator(); + expr = GenerateConditional(expr, expr1, expr2, false, errorPos); } + return expr; + } - // ?? (null-coalescing) operator - private Expression ParseNullCoalescingOperator() + // ?? (null-coalescing) operator + private Expression ParseNullCoalescingOperator() + { + Expression expr = ParseLambdaOperator(); + if (_textParser.CurrentToken.Id == TokenId.NullCoalescing) { - Expression expr = ParseLambdaOperator(); - if (_textParser.CurrentToken.Id == TokenId.NullCoalescing) - { - _textParser.NextToken(); - Expression right = ParseConditionalOperator(); - expr = Expression.Coalesce(expr, right); - } - return expr; + _textParser.NextToken(); + Expression right = ParseConditionalOperator(); + expr = Expression.Coalesce(expr, right); } + return expr; + } - // => operator - Added Support for projection operator - private Expression ParseLambdaOperator() + // => operator - Added Support for projection operator + private Expression ParseLambdaOperator() + { + Expression expr = ParseOrOperator(); + if (_textParser.CurrentToken.Id == TokenId.Lambda && _it?.Type == expr.Type) { - Expression expr = ParseOrOperator(); - if (_textParser.CurrentToken.Id == TokenId.Lambda && _it?.Type == expr.Type) + _textParser.NextToken(); + if (_textParser.CurrentToken.Id == TokenId.Identifier || _textParser.CurrentToken.Id == TokenId.OpenParen) { - _textParser.NextToken(); - if (_textParser.CurrentToken.Id == TokenId.Identifier || _textParser.CurrentToken.Id == TokenId.OpenParen) - { - var right = ParseConditionalOperator(); - return Expression.Lambda(right, new[] { (ParameterExpression)expr }); - } - _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + var right = ParseConditionalOperator(); + return Expression.Lambda(right, new[] { (ParameterExpression)expr }); } - return expr; + _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); } + return expr; + } - // Or operator - // - || - // - Or - // - OrElse - private Expression ParseOrOperator() + // Or operator + // - || + // - Or + // - OrElse + private Expression ParseOrOperator() + { + Expression left = ParseAndOperator(); + while (_textParser.CurrentToken.Id == TokenId.DoubleBar) { - Expression left = ParseAndOperator(); - while (_textParser.CurrentToken.Id == TokenId.DoubleBar) - { - Token op = _textParser.CurrentToken; - _textParser.NextToken(); - Expression right = ParseAndOperator(); - CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos); - left = Expression.OrElse(left, right); - } - return left; + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + Expression right = ParseAndOperator(); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + left = Expression.OrElse(left, right); } + return left; + } - // And operator - // - && - // - And - // - AndAlso - private Expression ParseAndOperator() + // And operator + // - && + // - And + // - AndAlso + private Expression ParseAndOperator() + { + Expression left = ParseIn(); + while (_textParser.CurrentToken.Id == TokenId.DoubleAmpersand) { - Expression left = ParseIn(); - while (_textParser.CurrentToken.Id == TokenId.DoubleAmpersand) - { - Token op = _textParser.CurrentToken; - _textParser.NextToken(); - Expression right = ParseIn(); - CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos); - left = Expression.AndAlso(left, right); - } - return left; + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + Expression right = ParseIn(); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + left = Expression.AndAlso(left, right); } + return left; + } + + // in operator for literals - example: "x in (1,2,3,4)" + // in operator to mimic contains - example: "x in @0", compare to @0.Contains(x) + // Adapted from ticket submitted by github user mlewis9548 + private Expression ParseIn() + { + Expression left = ParseLogicalAndOrOperator(); + Expression accumulate = left; - // in operator for literals - example: "x in (1,2,3,4)" - // in operator to mimic contains - example: "x in @0", compare to @0.Contains(x) - // Adapted from ticket submitted by github user mlewis9548 - private Expression ParseIn() + while (TokenIdentifierIs("in")) { - Expression left = ParseLogicalAndOrOperator(); - Expression accumulate = left; + var op = _textParser.CurrentToken; - while (TokenIdentifierIs("in")) + _textParser.NextToken(); + if (_textParser.CurrentToken.Id == TokenId.OpenParen) // literals (or other inline list) { - var op = _textParser.CurrentToken; - - _textParser.NextToken(); - if (_textParser.CurrentToken.Id == TokenId.OpenParen) // literals (or other inline list) + while (_textParser.CurrentToken.Id != TokenId.CloseParen) { - while (_textParser.CurrentToken.Id != TokenId.CloseParen) - { - _textParser.NextToken(); - - // we need to parse unary expressions because otherwise 'in' clause will fail in use cases like 'in (-1, -1)' or 'in (!true)' - Expression right = ParseUnary(); - - // if the identifier is an Enum, try to convert the right-side also to an Enum. - if (left.Type.GetTypeInfo().IsEnum && right is ConstantExpression constantExpression) - { - right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpression); - } - - // else, check for direct type match - else if (left.Type != right.Type) - { - CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, op.Pos); - } + _textParser.NextToken(); - if (accumulate.Type != typeof(bool)) - { - accumulate = _expressionHelper.GenerateEqual(left, right); - } - else - { - accumulate = Expression.OrElse(accumulate, _expressionHelper.GenerateEqual(left, right)); - } + // we need to parse unary expressions because otherwise 'in' clause will fail in use cases like 'in (-1, -1)' or 'in (!true)' + Expression right = ParseUnary(); - if (_textParser.CurrentToken.Id == TokenId.End) - { - throw ParseError(op.Pos, Res.CloseParenOrCommaExpected); - } + // if the identifier is an Enum, try to convert the right-side also to an Enum. + if (left.Type.GetTypeInfo().IsEnum && right is ConstantExpression constantExpression) + { + right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpression); } - // Since this started with an open paren, make sure to move off the close - _textParser.NextToken(); - } - else if (_textParser.CurrentToken.Id == TokenId.Identifier) // a single argument - { - Expression right = ParsePrimary(); - - if (!typeof(IEnumerable).IsAssignableFrom(right.Type)) + // else, check for direct type match + else if (left.Type != right.Type) { - throw ParseError(_textParser.CurrentToken.Pos, Res.IdentifierImplementingInterfaceExpected, typeof(IEnumerable)); + CheckAndPromoteOperands(typeof(IEqualitySignatures), TokenId.DoubleEqual, "==", ref left, ref right, op.Pos); } - var args = new[] { left }; - - Expression? nullExpressionReference = null; - if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, ref nullExpressionReference, ref args, out var containsSignature) != 1) + if (accumulate.Type != typeof(bool)) { - throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains), string.Join(",", args.Select(a => a.Type.Name).ToArray())); + accumulate = _expressionHelper.GenerateEqual(left, right); + } + else + { + accumulate = Expression.OrElse(accumulate, _expressionHelper.GenerateEqual(left, right)); } - var typeArgs = new[] { left.Type }; - - args = new[] { right, left }; - - accumulate = Expression.Call(typeof(Enumerable), containsSignature!.Name, typeArgs, args); - } - else - { - throw ParseError(op.Pos, Res.OpenParenOrIdentifierExpected); + if (_textParser.CurrentToken.Id == TokenId.End) + { + throw ParseError(op.Pos, Res.CloseParenOrCommaExpected); + } } - } - - return accumulate; - } - // &, | bitwise operators - private Expression ParseLogicalAndOrOperator() - { - Expression left = ParseComparisonOperator(); - while (_textParser.CurrentToken.Id == TokenId.Ampersand || _textParser.CurrentToken.Id == TokenId.Bar) - { - Token op = _textParser.CurrentToken; + // Since this started with an open paren, make sure to move off the close _textParser.NextToken(); - Expression right = ParseComparisonOperator(); + } + else if (_textParser.CurrentToken.Id == TokenId.Identifier) // a single argument + { + Expression right = ParsePrimary(); - if (left.Type.GetTypeInfo().IsEnum) + if (!typeof(IEnumerable).IsAssignableFrom(right.Type)) { - left = Expression.Convert(left, Enum.GetUnderlyingType(left.Type)); + throw ParseError(_textParser.CurrentToken.Pos, Res.IdentifierImplementingInterfaceExpected, typeof(IEnumerable)); } - if (right.Type.GetTypeInfo().IsEnum) + var args = new[] { left }; + + Expression? nullExpressionReference = null; + if (_methodFinder.FindMethod(typeof(IEnumerableSignatures), nameof(IEnumerableSignatures.Contains), false, ref nullExpressionReference, ref args, out var containsSignature) != 1) { - right = Expression.Convert(right, Enum.GetUnderlyingType(right.Type)); + throw ParseError(op.Pos, Res.NoApplicableAggregate, nameof(IEnumerableSignatures.Contains), string.Join(",", args.Select(a => a.Type.Name).ToArray())); } - switch (op.Id) - { - case TokenId.Ampersand: - if (left.Type == typeof(string) && left.NodeType == ExpressionType.Constant && int.TryParse((string)((ConstantExpression)left).Value, out var parseValue) && TypeHelper.IsNumericType(right.Type)) - { - left = Expression.Constant(parseValue); - } - else if (right.Type == typeof(string) && right.NodeType == ExpressionType.Constant && int.TryParse((string)((ConstantExpression)right).Value, out parseValue) && TypeHelper.IsNumericType(left.Type)) - { - right = Expression.Constant(parseValue); - } + var typeArgs = new[] { left.Type }; - // When at least one side of the operator is a string, consider it's a VB-style concatenation operator. - // 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 = _expressionHelper.GenerateStringConcat(left, right); - } - else - { - _expressionHelper.ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); - left = Expression.And(left, right); - } - break; + args = new[] { right, left }; - case TokenId.Bar: - _expressionHelper.ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); - left = Expression.Or(left, right); - break; - } + accumulate = Expression.Call(typeof(Enumerable), containsSignature!.Name, typeArgs, args); + } + else + { + throw ParseError(op.Pos, Res.OpenParenOrIdentifierExpected); } - return left; } - // =, ==, !=, <>, >, >=, <, <= operators - private Expression ParseComparisonOperator() + return accumulate; + } + + // &, | bitwise operators + private Expression ParseLogicalAndOrOperator() + { + Expression left = ParseComparisonOperator(); + while (_textParser.CurrentToken.Id == TokenId.Ampersand || _textParser.CurrentToken.Id == TokenId.Bar) { - Expression left = ParseShiftOperator(); - while (_textParser.CurrentToken.Id == TokenId.Equal || _textParser.CurrentToken.Id == TokenId.DoubleEqual || - _textParser.CurrentToken.Id == TokenId.ExclamationEqual || _textParser.CurrentToken.Id == TokenId.LessGreater || - _textParser.CurrentToken.Id == TokenId.GreaterThan || _textParser.CurrentToken.Id == TokenId.GreaterThanEqual || - _textParser.CurrentToken.Id == TokenId.LessThan || _textParser.CurrentToken.Id == TokenId.LessThanEqual) + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + Expression right = ParseComparisonOperator(); + + if (left.Type.GetTypeInfo().IsEnum) { - ConstantExpression? constantExpr; - TypeConverter typeConverter; - Token op = _textParser.CurrentToken; - _textParser.NextToken(); - Expression right = ParseShiftOperator(); - bool isEquality = op.Id == TokenId.Equal || op.Id == TokenId.DoubleEqual || op.Id == TokenId.ExclamationEqual || op.Id == TokenId.LessGreater; + left = Expression.Convert(left, Enum.GetUnderlyingType(left.Type)); + } - 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 (!(Constants.IsNull(left) || Constants.IsNull(right)) && left.Type != right.Type) + if (right.Type.GetTypeInfo().IsEnum) + { + right = Expression.Convert(right, Enum.GetUnderlyingType(right.Type)); + } + + switch (op.Id) + { + case TokenId.Ampersand: + if (left.Type == typeof(string) && left.NodeType == ExpressionType.Constant && int.TryParse((string)((ConstantExpression)left).Value, out var parseValue) && TypeHelper.IsNumericType(right.Type)) { - if (left.Type.IsAssignableFrom(right.Type) || HasImplicitConversion(right.Type, left.Type)) - { - right = Expression.Convert(right, left.Type); - } - else if (right.Type.IsAssignableFrom(left.Type) || HasImplicitConversion(left.Type, right.Type)) - { - left = Expression.Convert(left, right.Type); - } - else - { - throw IncompatibleOperandsError(op.Text, left, right, op.Pos); - } + left = Expression.Constant(parseValue); } - } - else if (TypeHelper.IsEnumType(left.Type) || TypeHelper.IsEnumType(right.Type)) + else if (right.Type == typeof(string) && right.NodeType == ExpressionType.Constant && int.TryParse((string)((ConstantExpression)right).Value, out parseValue) && TypeHelper.IsNumericType(left.Type)) + { + right = Expression.Constant(parseValue); + } + + // When at least one side of the operator is a string, consider it's a VB-style concatenation operator. + // 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 = _expressionHelper.GenerateStringConcat(left, right); + } + else + { + _expressionHelper.ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); + left = Expression.And(left, right); + } + break; + + case TokenId.Bar: + _expressionHelper.ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref left, ref right); + left = Expression.Or(left, right); + break; + } + } + return left; + } + + // =, ==, !=, <>, >, >=, <, <= operators + private Expression ParseComparisonOperator() + { + Expression left = ParseShiftOperator(); + while (_textParser.CurrentToken.Id == TokenId.Equal || _textParser.CurrentToken.Id == TokenId.DoubleEqual || + _textParser.CurrentToken.Id == TokenId.ExclamationEqual || _textParser.CurrentToken.Id == TokenId.LessGreater || + _textParser.CurrentToken.Id == TokenId.GreaterThan || _textParser.CurrentToken.Id == TokenId.GreaterThanEqual || + _textParser.CurrentToken.Id == TokenId.LessThan || _textParser.CurrentToken.Id == TokenId.LessThanEqual) + { + ConstantExpression? constantExpr; + TypeConverter typeConverter; + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + Expression right = ParseShiftOperator(); + bool isEquality = op.Id == TokenId.Equal || op.Id == TokenId.DoubleEqual || op.Id == TokenId.ExclamationEqual || op.Id == TokenId.LessGreater; + + 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 (!(Constants.IsNull(left) || Constants.IsNull(right)) && left.Type != right.Type) { - if (left.Type != right.Type) + if (left.Type.IsAssignableFrom(right.Type) || HasImplicitConversion(right.Type, left.Type)) { - Expression? e; - if ((e = _parsingConfig.ExpressionPromoter.Promote(right, left.Type, true, false)) != null) - { - right = e; - } - else if ((e = _parsingConfig.ExpressionPromoter.Promote(left, right.Type, true, false)) != null) - { - left = e; - } - else if (TypeHelper.IsEnumType(left.Type) && (constantExpr = right as ConstantExpression) != null) - { - right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpr); - } - else if (TypeHelper.IsEnumType(right.Type) && (constantExpr = left as ConstantExpression) != null) - { - left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExpr); - } - else - { - throw IncompatibleOperandsError(op.Text, left, right, op.Pos); - } + right = Expression.Convert(right, left.Type); + } + else if (right.Type.IsAssignableFrom(left.Type) || HasImplicitConversion(left.Type, right.Type)) + { + left = Expression.Convert(left, right.Type); + } + else + { + throw IncompatibleOperandsError(op.Text, left, right, op.Pos); } } - else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string stringValueR && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null) + } + else if (TypeHelper.IsEnumType(left.Type) || TypeHelper.IsEnumType(right.Type)) + { + if (left.Type != right.Type) { - right = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueR), left.Type); + Expression? e; + if ((e = _parsingConfig.ExpressionPromoter.Promote(right, left.Type, true, false)) != null) + { + right = e; + } + else if ((e = _parsingConfig.ExpressionPromoter.Promote(left, right.Type, true, false)) != null) + { + left = e; + } + else if (TypeHelper.IsEnumType(left.Type) && (constantExpr = right as ConstantExpression) != null) + { + right = ParseEnumToConstantExpression(op.Pos, left.Type, constantExpr); + } + else if (TypeHelper.IsEnumType(right.Type) && (constantExpr = left as ConstantExpression) != null) + { + left = ParseEnumToConstantExpression(op.Pos, right.Type, constantExpr); + } + else + { + throw IncompatibleOperandsError(op.Text, left, right, op.Pos); + } } - else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string stringValueL && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null) + } + else if ((constantExpr = right as ConstantExpression) != null && constantExpr.Value is string stringValueR && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null) + { + right = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueR), left.Type); + } + else if ((constantExpr = left as ConstantExpression) != null && constantExpr.Value is string stringValueL && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null) + { + left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type); + } + else if (_expressionHelper.TryUnwrapConstantExpression(right, out var unwrappedStringValueR) && (typeConverter = _typeConverterFactory.GetConverter(left.Type)) != null) + { + right = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueR), left.Type); + } + else if (_expressionHelper.TryUnwrapConstantExpression(left, out var unwrappedStringValueL) && (typeConverter = _typeConverterFactory.GetConverter(right.Type)) != null) + { + left = Expression.Constant(typeConverter.ConvertFromInvariantString(unwrappedStringValueL), right.Type); + } + else + { + bool typesAreSameAndImplementCorrectInterface = false; + if (left.Type == right.Type) { - left = Expression.Constant(typeConverter.ConvertFromInvariantString(stringValueL), right.Type); + var interfaces = left.Type.GetInterfaces().Where(x => x.GetTypeInfo().IsGenericType); + if (isEquality) + { + typesAreSameAndImplementCorrectInterface = interfaces.Any(x => x.GetGenericTypeDefinition() == typeof(IEquatable<>)); + } + else + { + typesAreSameAndImplementCorrectInterface = interfaces.Any(x => x.GetGenericTypeDefinition() == typeof(IComparable<>)); + } } - else + + if (!typesAreSameAndImplementCorrectInterface) { - bool typesAreSameAndImplementCorrectInterface = false; - if (left.Type == right.Type) + if ((TypeHelper.IsClass(left.Type) || TypeHelper.IsStruct(left.Type)) && right is ConstantExpression) { - var interfaces = left.Type.GetInterfaces().Where(x => x.GetTypeInfo().IsGenericType); - if (isEquality) + if (HasImplicitConversion(left.Type, right.Type)) { - typesAreSameAndImplementCorrectInterface = interfaces.Any(x => x.GetGenericTypeDefinition() == typeof(IEquatable<>)); + left = Expression.Convert(left, right.Type); } - else + else if (HasImplicitConversion(right.Type, left.Type)) { - typesAreSameAndImplementCorrectInterface = interfaces.Any(x => x.GetGenericTypeDefinition() == typeof(IComparable<>)); + right = Expression.Convert(right, left.Type); } } - - if (!typesAreSameAndImplementCorrectInterface) + else if ((TypeHelper.IsClass(right.Type) || TypeHelper.IsStruct(right.Type)) && left is ConstantExpression) { - if ((TypeHelper.IsClass(left.Type) || TypeHelper.IsStruct(left.Type)) && right is ConstantExpression) - { - if (HasImplicitConversion(left.Type, right.Type)) - { - left = Expression.Convert(left, right.Type); - } - else if (HasImplicitConversion(right.Type, left.Type)) - { - right = Expression.Convert(right, left.Type); - } - } - else if ((TypeHelper.IsClass(right.Type) || TypeHelper.IsStruct(right.Type)) && left is ConstantExpression) + if (HasImplicitConversion(right.Type, left.Type)) { - if (HasImplicitConversion(right.Type, left.Type)) - { - right = Expression.Convert(right, left.Type); - } - else if (HasImplicitConversion(left.Type, right.Type)) - { - left = Expression.Convert(left, right.Type); - } + right = Expression.Convert(right, left.Type); } - else + else if (HasImplicitConversion(left.Type, right.Type)) { - CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + left = Expression.Convert(left, right.Type); } } - } - - switch (op.Id) - { - case TokenId.Equal: - case TokenId.DoubleEqual: - left = _expressionHelper.GenerateEqual(left, right); - break; - case TokenId.ExclamationEqual: - case TokenId.LessGreater: - left = _expressionHelper.GenerateNotEqual(left, right); - break; - case TokenId.GreaterThan: - left = _expressionHelper.GenerateGreaterThan(left, right); - break; - case TokenId.GreaterThanEqual: - left = _expressionHelper.GenerateGreaterThanEqual(left, right); - break; - case TokenId.LessThan: - left = _expressionHelper.GenerateLessThan(left, right); - break; - case TokenId.LessThanEqual: - left = _expressionHelper.GenerateLessThanEqual(left, right); - break; + else + { + CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + } } } - return left; + switch (op.Id) + { + case TokenId.Equal: + case TokenId.DoubleEqual: + left = _expressionHelper.GenerateEqual(left, right); + break; + case TokenId.ExclamationEqual: + case TokenId.LessGreater: + left = _expressionHelper.GenerateNotEqual(left, right); + break; + case TokenId.GreaterThan: + left = _expressionHelper.GenerateGreaterThan(left, right); + break; + case TokenId.GreaterThanEqual: + left = _expressionHelper.GenerateGreaterThanEqual(left, right); + break; + case TokenId.LessThan: + left = _expressionHelper.GenerateLessThan(left, right); + break; + case TokenId.LessThanEqual: + left = _expressionHelper.GenerateLessThanEqual(left, right); + break; + } } - private bool HasImplicitConversion(Type baseType, Type targetType) + return left; + } + + private bool HasImplicitConversion(Type baseType, Type targetType) + { + var baseTypeHasConversion = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType) + .Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType); + + if (baseTypeHasConversion) { - var baseTypeHasConversion = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType) - .Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType); + return true; + } - if (baseTypeHasConversion) + return targetType.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType) + .Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType); + } + + private ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr) + { + return Expression.Constant(ParseConstantExpressionToEnum(pos, leftType, constantExpr), leftType); + } + + private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr) + { + try + { + if (constantExpr.Value is string stringValue) { - return true; + return Enum.Parse(TypeHelper.GetNonNullableType(leftType), stringValue, true); } - - return targetType.GetMethods(BindingFlags.Public | BindingFlags.Static) - .Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType) - .Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType); } - - private ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr) + catch { - return Expression.Constant(ParseConstantExpressionToEnum(pos, leftType, constantExpr), leftType); + throw ParseError(pos, Res.ExpressionTypeMismatch, leftType); } - private object ParseConstantExpressionToEnum(int pos, Type leftType, ConstantExpression constantExpr) + try { - try - { - if (constantExpr.Value is string stringValue) - { - return Enum.Parse(TypeHelper.GetNonNullableType(leftType), stringValue, true); - } - } - catch - { - throw ParseError(pos, Res.ExpressionTypeMismatch, leftType); - } + return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value); + } + catch + { + throw ParseError(pos, Res.ExpressionTypeMismatch, leftType); + } + } - try - { - return Enum.ToObject(TypeHelper.GetNonNullableType(leftType), constantExpr.Value); - } - catch + // <<, >> operators + private Expression ParseShiftOperator() + { + Expression left = ParseAdditive(); + while (_textParser.CurrentToken.Id == TokenId.DoubleLessThan || _textParser.CurrentToken.Id == TokenId.DoubleGreaterThan) + { + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + Expression right = ParseAdditive(); + switch (op.Id) { - throw ParseError(pos, Res.ExpressionTypeMismatch, leftType); + case TokenId.DoubleLessThan: + CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + left = Expression.LeftShift(left, right); + break; + case TokenId.DoubleGreaterThan: + CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + left = Expression.RightShift(left, right); + break; } } + return left; + } - // <<, >> operators - private Expression ParseShiftOperator() + // +, - operators + private Expression ParseAdditive() + { + Expression left = ParseMultiplicative(); + while (_textParser.CurrentToken.Id == TokenId.Plus || _textParser.CurrentToken.Id == TokenId.Minus) { - Expression left = ParseAdditive(); - while (_textParser.CurrentToken.Id == TokenId.DoubleLessThan || _textParser.CurrentToken.Id == TokenId.DoubleGreaterThan) + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + Expression right = ParseMultiplicative(); + switch (op.Id) { - Token op = _textParser.CurrentToken; - _textParser.NextToken(); - Expression right = ParseAdditive(); - switch (op.Id) - { - case TokenId.DoubleLessThan: - CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos); - left = Expression.LeftShift(left, right); - break; - case TokenId.DoubleGreaterThan: - CheckAndPromoteOperands(typeof(IShiftSignatures), op.Id, op.Text, ref left, ref right, op.Pos); - left = Expression.RightShift(left, right); - break; - } + case TokenId.Plus: + if (left.Type == typeof(string) || right.Type == typeof(string)) + { + left = _expressionHelper.GenerateStringConcat(left, right); + } + else + { + CheckAndPromoteOperands(typeof(IAddSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + left = _expressionHelper.GenerateAdd(left, right); + } + break; + case TokenId.Minus: + CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + left = _expressionHelper.GenerateSubtract(left, right); + break; } - return left; } + return left; + } - // +, - operators - private Expression ParseAdditive() + // *, /, %, mod operators + private Expression ParseMultiplicative() + { + Expression left = ParseUnary(); + while (_textParser.CurrentToken.Id == TokenId.Asterisk || _textParser.CurrentToken.Id == TokenId.Slash || + _textParser.CurrentToken.Id == TokenId.Percent || TokenIdentifierIs("mod")) { - Expression left = ParseMultiplicative(); - while (_textParser.CurrentToken.Id == TokenId.Plus || _textParser.CurrentToken.Id == TokenId.Minus) + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + Expression right = ParseUnary(); + CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + switch (op.Id) { - Token op = _textParser.CurrentToken; - _textParser.NextToken(); - Expression right = ParseMultiplicative(); - switch (op.Id) - { - case TokenId.Plus: - if (left.Type == typeof(string) || right.Type == typeof(string)) - { - left = _expressionHelper.GenerateStringConcat(left, right); - } - else - { - CheckAndPromoteOperands(typeof(IAddSignatures), op.Id, op.Text, ref left, ref right, op.Pos); - left = _expressionHelper.GenerateAdd(left, right); - } - break; - case TokenId.Minus: - CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos); - left = _expressionHelper.GenerateSubtract(left, right); - break; - } + case TokenId.Asterisk: + left = Expression.Multiply(left, right); + break; + case TokenId.Slash: + left = Expression.Divide(left, right); + break; + case TokenId.Percent: + case TokenId.Identifier: + left = Expression.Modulo(left, right); + break; } - return left; } + return left; + } - // *, /, %, mod operators - private Expression ParseMultiplicative() + // -, !, not unary operators + private Expression ParseUnary() + { + if (_textParser.CurrentToken.Id == TokenId.Minus || _textParser.CurrentToken.Id == TokenId.Exclamation || TokenIdentifierIs("not")) { - Expression left = ParseUnary(); - while (_textParser.CurrentToken.Id == TokenId.Asterisk || _textParser.CurrentToken.Id == TokenId.Slash || - _textParser.CurrentToken.Id == TokenId.Percent || TokenIdentifierIs("mod")) + Token op = _textParser.CurrentToken; + _textParser.NextToken(); + if (op.Id == TokenId.Minus && (_textParser.CurrentToken.Id == TokenId.IntegerLiteral || _textParser.CurrentToken.Id == TokenId.RealLiteral)) { - Token op = _textParser.CurrentToken; - _textParser.NextToken(); - Expression right = ParseUnary(); - CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.Id, op.Text, ref left, ref right, op.Pos); - switch (op.Id) - { - case TokenId.Asterisk: - left = Expression.Multiply(left, right); - break; - case TokenId.Slash: - left = Expression.Divide(left, right); - break; - case TokenId.Percent: - case TokenId.Identifier: - left = Expression.Modulo(left, right); - break; - } + _textParser.CurrentToken.Text = "-" + _textParser.CurrentToken.Text; + _textParser.CurrentToken.Pos = op.Pos; + return ParsePrimary(); } - return left; - } - // -, !, not unary operators - private Expression ParseUnary() - { - if (_textParser.CurrentToken.Id == TokenId.Minus || _textParser.CurrentToken.Id == TokenId.Exclamation || TokenIdentifierIs("not")) + Expression expr = ParseUnary(); + if (op.Id == TokenId.Minus) { - Token op = _textParser.CurrentToken; - _textParser.NextToken(); - if (op.Id == TokenId.Minus && (_textParser.CurrentToken.Id == TokenId.IntegerLiteral || _textParser.CurrentToken.Id == TokenId.RealLiteral)) - { - _textParser.CurrentToken.Text = "-" + _textParser.CurrentToken.Text; - _textParser.CurrentToken.Pos = op.Pos; - return ParsePrimary(); - } - - Expression expr = ParseUnary(); - if (op.Id == TokenId.Minus) - { - CheckAndPromoteOperand(typeof(INegationSignatures), op.Text, ref expr, op.Pos); - expr = Expression.Negate(expr); - } - else - { - CheckAndPromoteOperand(typeof(INotSignatures), op.Text, ref expr, op.Pos); - expr = Expression.Not(expr); - } - - return expr; + CheckAndPromoteOperand(typeof(INegationSignatures), op.Text, ref expr, op.Pos); + expr = Expression.Negate(expr); + } + else + { + CheckAndPromoteOperand(typeof(INotSignatures), op.Text, ref expr, op.Pos); + expr = Expression.Not(expr); } - return ParsePrimary(); + return expr; } - private Expression ParsePrimary() - { - var expr = ParsePrimaryStart(); - _expressionHelper.WrapConstantExpression(ref expr); + return ParsePrimary(); + } - while (true) + private Expression ParsePrimary() + { + var expr = ParsePrimaryStart(); + _expressionHelper.WrapConstantExpression(ref expr); + + while (true) + { + if (_textParser.CurrentToken.Id == TokenId.Dot) { - if (_textParser.CurrentToken.Id == TokenId.Dot) - { - _textParser.NextToken(); - expr = ParseMemberAccess(null, expr); - } - else if (_textParser.CurrentToken.Id == TokenId.NullPropagation) - { - throw new NotSupportedException("An expression tree lambda may not contain a null propagating operator. Use the 'np()' or 'np(...)' (null-propagation) function instead."); - } - else if (_textParser.CurrentToken.Id == TokenId.OpenBracket) - { - expr = ParseElementAccess(expr); - } - else - { - break; - } + _textParser.NextToken(); + expr = ParseMemberAccess(null, expr); + } + else if (_textParser.CurrentToken.Id == TokenId.NullPropagation) + { + throw new NotSupportedException("An expression tree lambda may not contain a null propagating operator. Use the 'np()' or 'np(...)' (null-propagation) function instead."); + } + else if (_textParser.CurrentToken.Id == TokenId.OpenBracket) + { + expr = ParseElementAccess(expr); + } + else + { + break; } - - return expr; } - private Expression ParsePrimaryStart() + return expr; + } + + private Expression ParsePrimaryStart() + { + switch (_textParser.CurrentToken.Id) { - switch (_textParser.CurrentToken.Id) - { - case TokenId.Identifier: - return ParseIdentifier(); + case TokenId.Identifier: + return ParseIdentifier(); - case TokenId.StringLiteral: - var expressionOrType = ParseStringLiteral(false); - return expressionOrType.IsFirst ? expressionOrType.First : ParseTypeAccess(expressionOrType.Second, false); + case TokenId.StringLiteral: + var expressionOrType = ParseStringLiteral(false); + return expressionOrType.IsFirst ? expressionOrType.First : ParseTypeAccess(expressionOrType.Second, false); - case TokenId.IntegerLiteral: - return ParseIntegerLiteral(); + case TokenId.IntegerLiteral: + return ParseIntegerLiteral(); - case TokenId.RealLiteral: - return ParseRealLiteral(); + case TokenId.RealLiteral: + return ParseRealLiteral(); - case TokenId.OpenParen: - return ParseParenExpression(); + case TokenId.OpenParen: + return ParseParenExpression(); - default: - throw ParseError(Res.ExpressionExpected); - } + default: + throw ParseError(Res.ExpressionExpected); } + } - private AnyOf ParseStringLiteral(bool forceParseAsString) - { - _textParser.ValidateToken(TokenId.StringLiteral); + private AnyOf ParseStringLiteral(bool forceParseAsString) + { + _textParser.ValidateToken(TokenId.StringLiteral); - var stringValue = StringParser.ParseString(_textParser.CurrentToken.Text); + var stringValue = StringParser.ParseString(_textParser.CurrentToken.Text); - if (_textParser.CurrentToken.Text[0] == '\'') + if (_textParser.CurrentToken.Text[0] == '\'') + { + if (stringValue.Length > 1) { - if (stringValue.Length > 1) - { - throw ParseError(Res.InvalidCharacterLiteral); - } - - _textParser.NextToken(); - return ConstantExpressionHelper.CreateLiteral(stringValue[0], stringValue); + throw ParseError(Res.InvalidCharacterLiteral); } _textParser.NextToken(); + return ConstantExpressionHelper.CreateLiteral(stringValue[0], stringValue); + } - if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && stringValue.Length > 2 && stringValue.Contains('.')) + _textParser.NextToken(); + + if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && stringValue.Length > 2 && stringValue.Contains('.')) + { + // Try to resolve this string as a type + var type = _typeFinder.FindTypeByName(stringValue, null, false); + if (type is { }) { - // Try to resolve this string as a type - var type = _typeFinder.FindTypeByName(stringValue, null, false); - if (type is { }) - { - return type; - } + return type; } - - return ConstantExpressionHelper.CreateLiteral(stringValue, stringValue); } - private Expression ParseIntegerLiteral() - { - _textParser.ValidateToken(TokenId.IntegerLiteral); + return ConstantExpressionHelper.CreateLiteral(stringValue, stringValue); + } - string text = _textParser.CurrentToken.Text; + private Expression ParseIntegerLiteral() + { + _textParser.ValidateToken(TokenId.IntegerLiteral); - var tokenPosition = _textParser.CurrentToken.Pos; + string text = _textParser.CurrentToken.Text; - var constantExpression = _numberParser.ParseIntegerLiteral(tokenPosition, text); - _textParser.NextToken(); - return constantExpression; - } + var tokenPosition = _textParser.CurrentToken.Pos; - private Expression ParseRealLiteral() - { - _textParser.ValidateToken(TokenId.RealLiteral); + var constantExpression = _numberParser.ParseIntegerLiteral(tokenPosition, text); + _textParser.NextToken(); + return constantExpression; + } - string text = _textParser.CurrentToken.Text; + private Expression ParseRealLiteral() + { + _textParser.ValidateToken(TokenId.RealLiteral); - _textParser.NextToken(); + string text = _textParser.CurrentToken.Text; - return _numberParser.ParseRealLiteral(text, text[text.Length - 1], true); - } + _textParser.NextToken(); - private Expression ParseParenExpression() - { - _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); - _textParser.NextToken(); - Expression e = ParseConditionalOperator(); - _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected); - _textParser.NextToken(); - return e; - } + return _numberParser.ParseRealLiteral(text, text[text.Length - 1], true); + } - private Expression ParseIdentifier() - { - _textParser.ValidateToken(TokenId.Identifier); + private Expression ParseParenExpression() + { + _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + _textParser.NextToken(); + Expression e = ParseConditionalOperator(); + _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected); + _textParser.NextToken(); + return e; + } - var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value); + private Expression ParseIdentifier() + { + _textParser.ValidateToken(TokenId.Identifier); + + var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value); - var extraCondition = !_parsingConfig.PrioritizePropertyOrFieldOverTheType || - (_parsingConfig.PrioritizePropertyOrFieldOverTheType && !(value is Type && _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null)); + var extraCondition = !_parsingConfig.PrioritizePropertyOrFieldOverTheType || + (_parsingConfig.PrioritizePropertyOrFieldOverTheType && !(value is Type && _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null)); - if (isValidKeyWord && extraCondition) + if (isValidKeyWord && extraCondition) + { + if (value is Type typeValue) { - if (value is Type typeValue) - { - return ParseTypeAccess(typeValue, true); - } + return ParseTypeAccess(typeValue, true); + } - switch (value) - { - case KeywordsHelper.KEYWORD_IT: - case KeywordsHelper.SYMBOL_IT: - return ParseIt(); + switch (value) + { + case KeywordsHelper.KEYWORD_IT: + case KeywordsHelper.SYMBOL_IT: + return ParseIt(); - case KeywordsHelper.KEYWORD_PARENT: - case KeywordsHelper.SYMBOL_PARENT: - return ParseParent(); + case KeywordsHelper.KEYWORD_PARENT: + case KeywordsHelper.SYMBOL_PARENT: + return ParseParent(); - case KeywordsHelper.KEYWORD_ROOT: - case KeywordsHelper.SYMBOL_ROOT: - return ParseRoot(); + case KeywordsHelper.KEYWORD_ROOT: + case KeywordsHelper.SYMBOL_ROOT: + return ParseRoot(); - case KeywordsHelper.FUNCTION_IIF: - return ParseFunctionIif(); + case KeywordsHelper.FUNCTION_IIF: + return ParseFunctionIif(); - case KeywordsHelper.FUNCTION_ISNULL: - return ParseFunctionIsNull(); + case KeywordsHelper.FUNCTION_ISNULL: + return ParseFunctionIsNull(); - case KeywordsHelper.FUNCTION_NEW: - return ParseNew(); + case KeywordsHelper.FUNCTION_NEW: + return ParseNew(); - case KeywordsHelper.FUNCTION_NULLPROPAGATION: - return ParseFunctionNullPropagation(); + case KeywordsHelper.FUNCTION_NULLPROPAGATION: + return ParseFunctionNullPropagation(); - case KeywordsHelper.FUNCTION_IS: - return ParseFunctionIs(); + case KeywordsHelper.FUNCTION_IS: + return ParseFunctionIs(); - case KeywordsHelper.FUNCTION_AS: - return ParseFunctionAs(); + case KeywordsHelper.FUNCTION_AS: + return ParseFunctionAs(); - case KeywordsHelper.FUNCTION_CAST: - return ParseFunctionCast(); - } + case KeywordsHelper.FUNCTION_CAST: + return ParseFunctionCast(); + } - _textParser.NextToken(); + _textParser.NextToken(); - return (Expression)value; - } + return (Expression)value; + } - if (_symbols.TryGetValue(_textParser.CurrentToken.Text, out value) || - _externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value) || - _internals.TryGetValue(_textParser.CurrentToken.Text, out value)) + if (_symbols.TryGetValue(_textParser.CurrentToken.Text, out value) || + _externals != null && _externals.TryGetValue(_textParser.CurrentToken.Text, out value) || + _internals.TryGetValue(_textParser.CurrentToken.Text, out value)) + { + var expr = value as Expression; + if (expr == null) { - var expr = value as Expression; - if (expr == null) - { - expr = Expression.Constant(value); - } - else + expr = Expression.Constant(value); + } + else + { + if (expr is LambdaExpression lambdaExpression) { - if (expr is LambdaExpression lambdaExpression) - { - return ParseLambdaInvocation(lambdaExpression); - } + return ParseLambdaInvocation(lambdaExpression); } - - _textParser.NextToken(); - - return expr; } - if (_it != null) - { - return ParseMemberAccess(null, _it); - } + _textParser.NextToken(); - throw ParseError(Res.UnknownIdentifier, _textParser.CurrentToken.Text); + return expr; } - private Expression ParseIt() + if (_it != null) { - if (_it == null) - { - throw ParseError(Res.NoItInScope); - } - _textParser.NextToken(); - return _it; + return ParseMemberAccess(null, _it); } - private Expression ParseParent() + throw ParseError(Res.UnknownIdentifier, _textParser.CurrentToken.Text); + } + + private Expression ParseIt() + { + if (_it == null) { - if (_parent == null) - { - throw ParseError(Res.NoParentInScope); - } - _textParser.NextToken(); - return _parent; + throw ParseError(Res.NoItInScope); } + _textParser.NextToken(); + return _it; + } - private Expression ParseRoot() + private Expression ParseParent() + { + if (_parent == null) { - if (_root == null) - { - throw ParseError(Res.NoRootInScope); - } - _textParser.NextToken(); - return _root; + throw ParseError(Res.NoParentInScope); } + _textParser.NextToken(); + return _parent; + } - // isnull(a,b) function - private Expression ParseFunctionIsNull() + private Expression ParseRoot() + { + if (_root == null) { - int errorPos = _textParser.CurrentToken.Pos; - _textParser.NextToken(); - Expression[] args = ParseArgumentList(); - if (args.Length != 2) - { - throw ParseError(errorPos, Res.IsNullRequiresTwoArgs); - } - - return Expression.Coalesce(args[0], args[1]); + throw ParseError(Res.NoRootInScope); } + _textParser.NextToken(); + return _root; + } - // iif(test, ifTrue, ifFalse) function - private Expression ParseFunctionIif() + // isnull(a,b) function + private Expression ParseFunctionIsNull() + { + int errorPos = _textParser.CurrentToken.Pos; + _textParser.NextToken(); + Expression[] args = ParseArgumentList(); + if (args.Length != 2) { - int errorPos = _textParser.CurrentToken.Pos; - _textParser.NextToken(); + throw ParseError(errorPos, Res.IsNullRequiresTwoArgs); + } - Expression[] args = ParseArgumentList(); - if (args.Length != 3) - { - throw ParseError(errorPos, Res.IifRequiresThreeArgs); - } + return Expression.Coalesce(args[0], args[1]); + } - return GenerateConditional(args[0], args[1], args[2], false, errorPos); + // iif(test, ifTrue, ifFalse) function + private Expression ParseFunctionIif() + { + int errorPos = _textParser.CurrentToken.Pos; + _textParser.NextToken(); + + Expression[] args = ParseArgumentList(); + if (args.Length != 3) + { + throw ParseError(errorPos, Res.IifRequiresThreeArgs); } - // np(...) function - private Expression ParseFunctionNullPropagation() + return GenerateConditional(args[0], args[1], args[2], false, errorPos); + } + + // np(...) function + private Expression ParseFunctionNullPropagation() + { + int errorPos = _textParser.CurrentToken.Pos; + _textParser.NextToken(); + + Expression[] args = ParseArgumentList(); + + if (args.Length != 1 && args.Length != 2) { - int errorPos = _textParser.CurrentToken.Pos; - _textParser.NextToken(); + throw ParseError(errorPos, Res.NullPropagationRequiresCorrectArgs); + } - Expression[] args = ParseArgumentList(); + if (_expressionHelper.ExpressionQualifiesForNullPropagation(args[0])) + { + bool hasDefaultParameter = args.Length == 2; + Expression expressionIfFalse = hasDefaultParameter ? args[1] : Expression.Constant(null); - if (args.Length != 1 && args.Length != 2) + if (_expressionHelper.TryGenerateAndAlsoNotNullExpression(args[0], true, out Expression generatedExpression)) { - throw ParseError(errorPos, Res.NullPropagationRequiresCorrectArgs); + return GenerateConditional(generatedExpression, args[0], expressionIfFalse, true, errorPos); } - if (_expressionHelper.ExpressionQualifiesForNullPropagation(args[0])) - { - bool hasDefaultParameter = args.Length == 2; - Expression expressionIfFalse = hasDefaultParameter ? args[1] : Expression.Constant(null); + return args[0]; + } - if (_expressionHelper.TryGenerateAndAlsoNotNullExpression(args[0], true, out Expression generatedExpression)) - { - return GenerateConditional(generatedExpression, args[0], expressionIfFalse, true, errorPos); - } + throw ParseError(errorPos, Res.NullPropagationRequiresValidExpression); + } - return args[0]; - } + // Is(...) function + private Expression ParseFunctionIs() + { + int errorPos = _textParser.CurrentToken.Pos; + string functionName = _textParser.CurrentToken.Text; + _textParser.NextToken(); + + Expression[] args = ParseArgumentList(); - throw ParseError(errorPos, Res.NullPropagationRequiresValidExpression); + if (args.Length != 1 && args.Length != 2) + { + throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName); } - // Is(...) function - private Expression ParseFunctionIs() + Expression typeArgument; + Expression it; + if (args.Length == 1) { - int errorPos = _textParser.CurrentToken.Pos; - string functionName = _textParser.CurrentToken.Text; - _textParser.NextToken(); + typeArgument = args[0]; + it = _it!; + } + else + { + typeArgument = args[1]; + it = args[0]; + } - Expression[] args = ParseArgumentList(); + return Expression.TypeIs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length)); + } - if (args.Length != 1 && args.Length != 2) - { - throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName); - } + // As(...) function + private Expression ParseFunctionAs() + { + int errorPos = _textParser.CurrentToken.Pos; + string functionName = _textParser.CurrentToken.Text; + _textParser.NextToken(); - Expression typeArgument; - Expression it; - if (args.Length == 1) - { - typeArgument = args[0]; - it = _it!; - } - else - { - typeArgument = args[1]; - it = args[0]; - } + Expression[] args = ParseArgumentList(); - return Expression.TypeIs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length)); + if (args.Length != 1 && args.Length != 2) + { + throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName); } - // As(...) function - private Expression ParseFunctionAs() + Expression typeArgument; + Expression it; + if (args.Length == 1) { - int errorPos = _textParser.CurrentToken.Pos; - string functionName = _textParser.CurrentToken.Text; - _textParser.NextToken(); + typeArgument = args[0]; + it = _it!; + } + else + { + typeArgument = args[1]; + it = args[0]; + } - Expression[] args = ParseArgumentList(); + return Expression.TypeAs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length)); + } - if (args.Length != 1 && args.Length != 2) - { - throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName); - } + // Cast(...) function + private Expression ParseFunctionCast() + { + int errorPos = _textParser.CurrentToken.Pos; + string functionName = _textParser.CurrentToken.Text; + _textParser.NextToken(); - Expression typeArgument; - Expression it; - if (args.Length == 1) - { - typeArgument = args[0]; - it = _it!; - } - else - { - typeArgument = args[1]; - it = args[0]; - } + Expression[] args = ParseArgumentList(); - return Expression.TypeAs(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length)); + if (args.Length != 1 && args.Length != 2) + { + throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName); } - // Cast(...) function - private Expression ParseFunctionCast() + Expression typeArgument; + Expression it; + if (args.Length == 1) { - int errorPos = _textParser.CurrentToken.Pos; - string functionName = _textParser.CurrentToken.Text; - _textParser.NextToken(); - - Expression[] args = ParseArgumentList(); - - if (args.Length != 1 && args.Length != 2) - { - throw ParseError(errorPos, Res.FunctionRequiresOneOrTwoArgs, functionName); - } + typeArgument = args[0]; + it = _it!; + } + else + { + typeArgument = args[1]; + it = args[0]; + } - Expression typeArgument; - Expression it; - if (args.Length == 1) - { - typeArgument = args[0]; - it = _it!; - } - else - { - typeArgument = args[1]; - it = args[0]; - } + return Expression.ConvertChecked(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length)); + } - return Expression.ConvertChecked(it, ResolveTypeFromArgumentExpression(functionName, typeArgument, args.Length)); + private Expression GenerateConditional(Expression test, Expression expressionIfTrue, Expression expressionIfFalse, bool nullPropagating, int errorPos) + { + if (test.Type != typeof(bool)) + { + throw ParseError(errorPos, Res.FirstExprMustBeBool); } - private Expression GenerateConditional(Expression test, Expression expressionIfTrue, Expression expressionIfFalse, bool nullPropagating, int errorPos) + if (expressionIfTrue.Type != expressionIfFalse.Type) { - if (test.Type != typeof(bool)) - { - throw ParseError(errorPos, Res.FirstExprMustBeBool); - } - - if (expressionIfTrue.Type != expressionIfFalse.Type) + // If expressionIfTrue is a null constant and expressionIfFalse is ValueType: + if (Constants.IsNull(expressionIfTrue) && expressionIfFalse.Type.GetTypeInfo().IsValueType) { - // If expressionIfTrue is a null constant and expressionIfFalse is ValueType: - if (Constants.IsNull(expressionIfTrue) && expressionIfFalse.Type.GetTypeInfo().IsValueType) + if (nullPropagating && _parsingConfig.NullPropagatingUseDefaultValueForNonNullableValueTypes) { - if (nullPropagating && _parsingConfig.NullPropagatingUseDefaultValueForNonNullableValueTypes) - { - // If expressionIfFalse is a non-nullable type: - // generate default expression from the expressionIfFalse-type for expressionIfTrue - // Else - // create nullable constant from expressionIfTrue with type from expressionIfFalse + // If expressionIfFalse is a non-nullable type: + // generate default expression from the expressionIfFalse-type for expressionIfTrue + // Else + // create nullable constant from expressionIfTrue with type from expressionIfFalse - if (!TypeHelper.IsNullableType(expressionIfFalse.Type)) - { - expressionIfTrue = _expressionHelper.GenerateDefaultExpression(expressionIfFalse.Type); - } - else - { - expressionIfTrue = Expression.Constant(null, expressionIfFalse.Type); - } + if (!TypeHelper.IsNullableType(expressionIfFalse.Type)) + { + expressionIfTrue = _expressionHelper.GenerateDefaultExpression(expressionIfFalse.Type); } else { - // - create nullable constant from expressionIfTrue with type from expressionIfFalse - // - convert expressionIfFalse to nullable (unless it's already nullable) - var nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type); - expressionIfTrue = Expression.Constant(null, nullableType); - - if (!TypeHelper.IsNullableType(expressionIfFalse.Type)) - { - expressionIfFalse = Expression.Convert(expressionIfFalse, nullableType); - } + expressionIfTrue = Expression.Constant(null, expressionIfFalse.Type); } - - return Expression.Condition(test, expressionIfTrue, expressionIfFalse); } - - // If expressionIfFalse is a null constant and expressionIfTrue is a ValueType: - if (Constants.IsNull(expressionIfFalse) && expressionIfTrue.Type.GetTypeInfo().IsValueType) + else { - if (nullPropagating && _parsingConfig.NullPropagatingUseDefaultValueForNonNullableValueTypes) - { - // If expressionIfTrue is a non-nullable type: - // generate default expression from the expressionIfFalse-type for expressionIfFalse - // Else - // create nullable constant from expressionIfFalse with type from expressionIfTrue + // - create nullable constant from expressionIfTrue with type from expressionIfFalse + // - convert expressionIfFalse to nullable (unless it's already nullable) + var nullableType = TypeHelper.ToNullableType(expressionIfFalse.Type); + expressionIfTrue = Expression.Constant(null, nullableType); - if (!TypeHelper.IsNullableType(expressionIfTrue.Type)) - { - expressionIfFalse = _expressionHelper.GenerateDefaultExpression(expressionIfTrue.Type); - } - else - { - expressionIfFalse = Expression.Constant(null, expressionIfTrue.Type); - } - } - else + if (!TypeHelper.IsNullableType(expressionIfFalse.Type)) { - // - create nullable constant from expressionIfFalse with type from expressionIfTrue - // - convert expressionIfTrue to nullable (unless it's already nullable) - - Type nullableType = TypeHelper.ToNullableType(expressionIfTrue.Type); - expressionIfFalse = Expression.Constant(null, nullableType); - if (!TypeHelper.IsNullableType(expressionIfTrue.Type)) - { - expressionIfTrue = Expression.Convert(expressionIfTrue, nullableType); - } + expressionIfFalse = Expression.Convert(expressionIfFalse, nullableType); } - - return Expression.Condition(test, expressionIfTrue, expressionIfFalse); } - var expr1As2 = !Constants.IsNull(expressionIfFalse) ? _parsingConfig.ExpressionPromoter.Promote(expressionIfTrue, expressionIfFalse.Type, true, false) : null; - var expr2As1 = !Constants.IsNull(expressionIfTrue) ? _parsingConfig.ExpressionPromoter.Promote(expressionIfFalse, expressionIfTrue.Type, true, false) : null; - if (expr1As2 != null && expr2As1 == null) - { - expressionIfTrue = expr1As2; - } - else if (expr2As1 != null && expr1As2 == null) + return Expression.Condition(test, expressionIfTrue, expressionIfFalse); + } + + // If expressionIfFalse is a null constant and expressionIfTrue is a ValueType: + if (Constants.IsNull(expressionIfFalse) && expressionIfTrue.Type.GetTypeInfo().IsValueType) + { + if (nullPropagating && _parsingConfig.NullPropagatingUseDefaultValueForNonNullableValueTypes) { - expressionIfFalse = expr2As1; + // If expressionIfTrue is a non-nullable type: + // generate default expression from the expressionIfFalse-type for expressionIfFalse + // Else + // create nullable constant from expressionIfFalse with type from expressionIfTrue + + if (!TypeHelper.IsNullableType(expressionIfTrue.Type)) + { + expressionIfFalse = _expressionHelper.GenerateDefaultExpression(expressionIfTrue.Type); + } + else + { + expressionIfFalse = Expression.Constant(null, expressionIfTrue.Type); + } } else { - string type1 = !Constants.IsNull(expressionIfTrue) ? expressionIfTrue.Type.Name : "null"; - string type2 = !Constants.IsNull(expressionIfFalse) ? expressionIfFalse.Type.Name : "null"; - if (expr1As2 != null) + // - create nullable constant from expressionIfFalse with type from expressionIfTrue + // - convert expressionIfTrue to nullable (unless it's already nullable) + + Type nullableType = TypeHelper.ToNullableType(expressionIfTrue.Type); + expressionIfFalse = Expression.Constant(null, nullableType); + if (!TypeHelper.IsNullableType(expressionIfTrue.Type)) { - throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2); + expressionIfTrue = Expression.Convert(expressionIfTrue, nullableType); } + } - throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2); + return Expression.Condition(test, expressionIfTrue, expressionIfFalse); + } + + var expr1As2 = !Constants.IsNull(expressionIfFalse) ? _parsingConfig.ExpressionPromoter.Promote(expressionIfTrue, expressionIfFalse.Type, true, false) : null; + var expr2As1 = !Constants.IsNull(expressionIfTrue) ? _parsingConfig.ExpressionPromoter.Promote(expressionIfFalse, expressionIfTrue.Type, true, false) : null; + if (expr1As2 != null && expr2As1 == null) + { + expressionIfTrue = expr1As2; + } + else if (expr2As1 != null && expr1As2 == null) + { + expressionIfFalse = expr2As1; + } + else + { + string type1 = !Constants.IsNull(expressionIfTrue) ? expressionIfTrue.Type.Name : "null"; + string type2 = !Constants.IsNull(expressionIfFalse) ? expressionIfFalse.Type.Name : "null"; + if (expr1As2 != null) + { + throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2); } + + throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2); } + } + + return Expression.Condition(test, expressionIfTrue, expressionIfFalse); + } - return Expression.Condition(test, expressionIfTrue, expressionIfFalse); + // new (...) function + private Expression ParseNew() + { + _textParser.NextToken(); + if (_textParser.CurrentToken.Id != TokenId.OpenParen && + _textParser.CurrentToken.Id != TokenId.OpenCurlyParen && + _textParser.CurrentToken.Id != TokenId.OpenBracket && + _textParser.CurrentToken.Id != TokenId.Identifier) + { + throw ParseError(Res.OpenParenOrIdentifierExpected); } - // new (...) function - private Expression ParseNew() + Type? newType = null; + if (_textParser.CurrentToken.Id == TokenId.Identifier) { + var newTypeName = _textParser.CurrentToken.Text; _textParser.NextToken(); - if (_textParser.CurrentToken.Id != TokenId.OpenParen && - _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) + while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) { - var newTypeName = _textParser.CurrentToken.Text; + var sep = _textParser.CurrentToken.Text; _textParser.NextToken(); - - while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) - { - 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 = _typeFinder.FindTypeByName(newTypeName, new[] { _it, _parent, _root }, false); - if (newType == null) + if (_textParser.CurrentToken.Id != TokenId.Identifier) { - throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName); + throw ParseError(Res.IdentifierExpected); } + newTypeName += sep + _textParser.CurrentToken.Text; + _textParser.NextToken(); + } - if (_textParser.CurrentToken.Id != TokenId.OpenParen && - _textParser.CurrentToken.Id != TokenId.OpenBracket && - _textParser.CurrentToken.Id != TokenId.OpenCurlyParen) - { - throw ParseError(Res.OpenParenExpected); - } + newType = _typeFinder.FindTypeByName(newTypeName, new[] { _it, _parent, _root }, false); + if (newType == null) + { + throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, newTypeName); } - bool arrayInitializer = false; - if (_textParser.CurrentToken.Id == TokenId.OpenBracket) + if (_textParser.CurrentToken.Id != TokenId.OpenParen && + _textParser.CurrentToken.Id != TokenId.OpenBracket && + _textParser.CurrentToken.Id != TokenId.OpenCurlyParen) { - _textParser.NextToken(); - _textParser.ValidateToken(TokenId.CloseBracket, Res.CloseBracketExpected); - _textParser.NextToken(); - _textParser.ValidateToken(TokenId.OpenCurlyParen, Res.OpenCurlyParenExpected); - arrayInitializer = true; + throw ParseError(Res.OpenParenExpected); } + } + bool arrayInitializer = false; + if (_textParser.CurrentToken.Id == TokenId.OpenBracket) + { _textParser.NextToken(); + _textParser.ValidateToken(TokenId.CloseBracket, Res.CloseBracketExpected); + _textParser.NextToken(); + _textParser.ValidateToken(TokenId.OpenCurlyParen, Res.OpenCurlyParenExpected); + arrayInitializer = true; + } - var properties = new List(); - var expressions = new List(); + _textParser.NextToken(); - while (_textParser.CurrentToken.Id != TokenId.CloseParen && _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) + var properties = new List(); + var expressions = new List(); + + while (_textParser.CurrentToken.Id != TokenId.CloseParen && _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) + { + int exprPos = _textParser.CurrentToken.Pos; + Expression expr = ParseConditionalOperator(); + if (!arrayInitializer) { - int exprPos = _textParser.CurrentToken.Pos; - Expression expr = ParseConditionalOperator(); - if (!arrayInitializer) + string? propName; + if (TokenIdentifierIs("as")) { - string? propName; - if (TokenIdentifierIs("as")) - { - _textParser.NextToken(); - propName = GetIdentifier(); - _textParser.NextToken(); - } - else + _textParser.NextToken(); + propName = GetIdentifier(); + _textParser.NextToken(); + } + else + { + if (!TryGetMemberName(expr, out propName)) // TODO : investigate this { - if (!TryGetMemberName(expr, out propName)) // TODO : investigate this + if (expr is MethodCallExpression methodCallExpression + && methodCallExpression.Arguments.Count == 1 + && methodCallExpression.Arguments[0] is ConstantExpression methodCallExpressionArgument + && methodCallExpressionArgument.Type == typeof(string) + && properties.All(x => x.Name != (string)methodCallExpressionArgument.Value)) { - if (expr is MethodCallExpression methodCallExpression - && methodCallExpression.Arguments.Count == 1 - && methodCallExpression.Arguments[0] is ConstantExpression methodCallExpressionArgument - && methodCallExpressionArgument.Type == typeof(string) - && properties.All(x => x.Name != (string)methodCallExpressionArgument.Value)) - { - propName = (string)methodCallExpressionArgument.Value; - } - else - { - throw ParseError(exprPos, Res.MissingAsClause); - } + propName = (string)methodCallExpressionArgument.Value; + } + else + { + throw ParseError(exprPos, Res.MissingAsClause); } - } - - if (!string.IsNullOrEmpty(propName)) - { - properties.Add(new DynamicProperty(propName!, expr.Type)); } } - expressions.Add(expr); - - if (_textParser.CurrentToken.Id != TokenId.Comma) + if (!string.IsNullOrEmpty(propName)) { - break; + properties.Add(new DynamicProperty(propName!, expr.Type)); } - - _textParser.NextToken(); } - if (_textParser.CurrentToken.Id != TokenId.CloseParen && _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) - { - throw ParseError(Res.CloseParenOrCommaExpected); - } - _textParser.NextToken(); + expressions.Add(expr); - if (arrayInitializer) + if (_textParser.CurrentToken.Id != TokenId.Comma) { - return CreateArrayInitializerExpression(expressions, newType); + break; } - return CreateNewExpression(properties, expressions, newType); + _textParser.NextToken(); } - private Expression CreateArrayInitializerExpression(List expressions, Type? newType) + if (_textParser.CurrentToken.Id != TokenId.CloseParen && _textParser.CurrentToken.Id != TokenId.CloseCurlyParen) { - if (expressions.Count == 0) - { - return Expression.NewArrayInit(newType ?? typeof(object)); - } + throw ParseError(Res.CloseParenOrCommaExpected); + } + _textParser.NextToken(); - if (newType != null) - { - return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true))); - } + if (arrayInitializer) + { + return CreateArrayInitializerExpression(expressions, newType); + } + + return CreateNewExpression(properties, expressions, newType); + } - return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions); + private Expression CreateArrayInitializerExpression(List expressions, Type? newType) + { + if (expressions.Count == 0) + { + return Expression.NewArrayInit(newType ?? typeof(object)); } - private Expression CreateNewExpression(List properties, List expressions, Type? newType) + if (newType != null) { - // http://solutionizing.net/category/linq/ - Type? type = newType ?? _resultType; + return Expression.NewArrayInit(newType, expressions.Select(expression => _parsingConfig.ExpressionPromoter.Promote(expression, newType, true, true))); + } - if (type == null) - { + return Expression.NewArrayInit(expressions.All(expression => expression.Type == expressions[0].Type) ? expressions[0].Type : typeof(object), expressions); + } + + private Expression CreateNewExpression(List properties, List expressions, Type? newType) + { + // http://solutionizing.net/category/linq/ + Type? type = newType ?? _resultType; + + if (type == null) + { #if UAP10_0 - type = typeof(DynamicClass); - Type typeForKeyValuePair = typeof(KeyValuePair); + type = typeof(DynamicClass); + Type typeForKeyValuePair = typeof(KeyValuePair); - ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); + ConstructorInfo constructorForKeyValuePair = typeForKeyValuePair.GetTypeInfo().DeclaredConstructors.First(); - var arrayIndexParams = new List(); - for (int i = 0; i < expressions.Count; i++) - { - // Just convert the expression always to an object expression. - UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); - NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression); + var arrayIndexParams = new List(); + for (int i = 0; i < expressions.Count; i++) + { + // Just convert the expression always to an object expression. + UnaryExpression boxingExpression = Expression.Convert(expressions[i], typeof(object)); + NewExpression parameter = Expression.New(constructorForKeyValuePair, (Expression)Expression.Constant(properties[i].Name), boxingExpression); - arrayIndexParams.Add(parameter); - } + arrayIndexParams.Add(parameter); + } - // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. - NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); + // Create an expression tree that represents creating and initializing a one-dimensional array of type KeyValuePair. + NewArrayExpression newArrayExpression = Expression.NewArrayInit(typeof(KeyValuePair), arrayIndexParams); - // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor - ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); + // Get the "public DynamicClass(KeyValuePair[] propertylist)" constructor + ConstructorInfo constructor = type.GetTypeInfo().DeclaredConstructors.First(); - return Expression.New(constructor, newArrayExpression); + return Expression.New(constructor, newArrayExpression); #else - type = DynamicClassFactory.CreateType(properties, _createParameterCtor); + type = DynamicClassFactory.CreateType(properties, _createParameterCtor); #endif - } + } - // Option 1. Try to bind via properties (TODO : investigate if this code block is 100% correct and is needed) - var propertyInfos = type.GetProperties(); - if (type.GetTypeInfo().BaseType == typeof(DynamicClass)) - { - propertyInfos = propertyInfos.Where(x => x.Name != "Item").ToArray(); - } - var propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray(); - var ctor = type.GetConstructor(propertyTypes); - if (ctor != null) + // Option 1. Try to bind via properties (TODO : investigate if this code block is 100% correct and is needed) + var propertyInfos = type.GetProperties(); + if (type.GetTypeInfo().BaseType == typeof(DynamicClass)) + { + propertyInfos = propertyInfos.Where(x => x.Name != "Item").ToArray(); + } + var propertyTypes = propertyInfos.Select(p => p.PropertyType).ToArray(); + var ctor = type.GetConstructor(propertyTypes); + if (ctor != null) + { + var constructorParameters = ctor.GetParameters(); + if (constructorParameters.Length == expressions.Count) { - var constructorParameters = ctor.GetParameters(); - if (constructorParameters.Length == expressions.Count) - { - bool bindParametersSequentially = !properties.All(p => constructorParameters - .Any(cp => cp.Name == p.Name && (cp.ParameterType == p.Type || p.Type == Nullable.GetUnderlyingType(cp.ParameterType)))); - var expressionsPromoted = new List(); + bool bindParametersSequentially = !properties.All(p => constructorParameters + .Any(cp => cp.Name == p.Name && (cp.ParameterType == p.Type || p.Type == Nullable.GetUnderlyingType(cp.ParameterType)))); + var expressionsPromoted = new List(); - // Loop all expressions and promote if needed - for (int i = 0; i < constructorParameters.Length; i++) + // Loop all expressions and promote if needed + for (int i = 0; i < constructorParameters.Length; i++) + { + if (bindParametersSequentially) { - if (bindParametersSequentially) - { - expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyTypes[i], true, true)); - } - else - { - Type propertyType = constructorParameters[i].ParameterType; - string cParameterName = constructorParameters[i].Name; - var propertyAndIndex = properties.Select((p, index) => new { p, index }) - .First(p => p.p.Name == cParameterName && (p.p.Type == propertyType || p.p.Type == Nullable.GetUnderlyingType(propertyType))); - // Promote from Type to Nullable Type if needed - expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[propertyAndIndex.index], propertyType, true, true)); - } + expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyTypes[i], true, true)); + } + else + { + Type propertyType = constructorParameters[i].ParameterType; + string cParameterName = constructorParameters[i].Name; + var propertyAndIndex = properties.Select((p, index) => new { p, index }) + .First(p => p.p.Name == cParameterName && (p.p.Type == propertyType || p.p.Type == Nullable.GetUnderlyingType(propertyType))); + // Promote from Type to Nullable Type if needed + expressionsPromoted.Add(_parsingConfig.ExpressionPromoter.Promote(expressions[propertyAndIndex.index], propertyType, true, true)); } - - return Expression.New(ctor, expressionsPromoted, (IEnumerable)propertyInfos); } + + return Expression.New(ctor, expressionsPromoted, (IEnumerable)propertyInfos); } + } - // Option 2. Try to find a constructor with the exact argument-types and exact same order - var constructorArgumentTypes = properties.Select(p => p.Type).ToArray(); - var exactConstructor = type.GetConstructor(constructorArgumentTypes); - if (exactConstructor != null) - { - // Promote from Type to Nullable Type if needed - var expressionsPromoted = exactConstructor.GetParameters() - .Select((t, i) => _parsingConfig.ExpressionPromoter.Promote(expressions[i], t.ParameterType, true, true)) - .ToArray(); + // Option 2. Try to find a constructor with the exact argument-types and exact same order + var constructorArgumentTypes = properties.Select(p => p.Type).ToArray(); + var exactConstructor = type.GetConstructor(constructorArgumentTypes); + if (exactConstructor != null) + { + // Promote from Type to Nullable Type if needed + var expressionsPromoted = exactConstructor.GetParameters() + .Select((t, i) => _parsingConfig.ExpressionPromoter.Promote(expressions[i], t.ParameterType, true, true)) + .ToArray(); - return Expression.New(exactConstructor, expressionsPromoted); - } + return Expression.New(exactConstructor, expressionsPromoted); + } - // Option 2. Call the default (empty) constructor and set the members - var memberBindings = new MemberBinding[properties.Count]; - for (int i = 0; i < memberBindings.Length; i++) + // Option 2. Call the default (empty) constructor and set the members + var memberBindings = new MemberBinding[properties.Count]; + for (int i = 0; i < memberBindings.Length; i++) + { + string propertyOrFieldName = properties[i].Name; + Type propertyOrFieldType; + MemberInfo memberInfo; + var propertyInfo = type.GetProperty(propertyOrFieldName); + if (propertyInfo != null) + { + memberInfo = propertyInfo; + propertyOrFieldType = propertyInfo.PropertyType; + } + else { - string propertyOrFieldName = properties[i].Name; - Type propertyOrFieldType; - MemberInfo memberInfo; - var propertyInfo = type.GetProperty(propertyOrFieldName); - if (propertyInfo != null) + var fieldInfo = type.GetField(propertyOrFieldName); + if (fieldInfo == null) { - memberInfo = propertyInfo; - propertyOrFieldType = propertyInfo.PropertyType; + throw ParseError(Res.UnknownPropertyOrField, propertyOrFieldName, TypeHelper.GetTypeName(type)); } - else - { - var fieldInfo = type.GetField(propertyOrFieldName); - if (fieldInfo == null) - { - throw ParseError(Res.UnknownPropertyOrField, propertyOrFieldName, TypeHelper.GetTypeName(type)); - } - memberInfo = fieldInfo; - propertyOrFieldType = fieldInfo.FieldType; - } + memberInfo = fieldInfo; + propertyOrFieldType = fieldInfo.FieldType; + } - // Promote from Type to Nullable Type if needed - var promoted = _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyOrFieldType, true, true); - if (promoted is null) - { - throw new NotSupportedException($"Unable to promote expression '{expressions[i]}'."); - } - memberBindings[i] = Expression.Bind(memberInfo, promoted); + // Promote from Type to Nullable Type if needed + var promoted = _parsingConfig.ExpressionPromoter.Promote(expressions[i], propertyOrFieldType, true, true); + if (promoted is null) + { + throw new NotSupportedException($"Unable to promote expression '{expressions[i]}'."); } + memberBindings[i] = Expression.Bind(memberInfo, promoted); + } + + return Expression.MemberInit(Expression.New(type), memberBindings); + } + + private Expression ParseLambdaInvocation(LambdaExpression lambda) + { + int errorPos = _textParser.CurrentToken.Pos; + _textParser.NextToken(); + Expression[] args = ParseArgumentList(); - return Expression.MemberInit(Expression.New(type), memberBindings); + Expression? nullExpressionReference = null; + if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, ref nullExpressionReference, ref args, out _) != 1) + { + throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda); } - private Expression ParseLambdaInvocation(LambdaExpression lambda) + return Expression.Invoke(lambda, args); + } + + private Expression ParseTypeAccess(Type type, bool getNext) + { + int errorPos = _textParser.CurrentToken.Pos; + if (getNext) { - int errorPos = _textParser.CurrentToken.Pos; _textParser.NextToken(); - Expression[] args = ParseArgumentList(); + } - Expression? nullExpressionReference = null; - if (_methodFinder.FindMethod(lambda.Type, nameof(Expression.Invoke), false, ref nullExpressionReference, ref args, out _) != 1) + if (_textParser.CurrentToken.Id == TokenId.Question) + { + if (!type.GetTypeInfo().IsValueType || TypeHelper.IsNullableType(type)) { - throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda); + throw ParseError(errorPos, Res.TypeHasNoNullableForm, TypeHelper.GetTypeName(type)); } - return Expression.Invoke(lambda, args); + type = typeof(Nullable<>).MakeGenericType(type); + _textParser.NextToken(); } - private Expression ParseTypeAccess(Type type, bool getNext) + // This is a shorthand for explicitly converting a string to something + bool shorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral; + if (_textParser.CurrentToken.Id == TokenId.OpenParen || shorthand) { - int errorPos = _textParser.CurrentToken.Pos; - if (getNext) + Expression[] args; + if (shorthand) { - _textParser.NextToken(); + var expressionOrType = ParseStringLiteral(true); + args = new[] { expressionOrType.First }; } - - if (_textParser.CurrentToken.Id == TokenId.Question) + else { - if (!type.GetTypeInfo().IsValueType || TypeHelper.IsNullableType(type)) - { - throw ParseError(errorPos, Res.TypeHasNoNullableForm, TypeHelper.GetTypeName(type)); - } - - type = typeof(Nullable<>).MakeGenericType(type); - _textParser.NextToken(); + args = ParseArgumentList(); } - // This is a shorthand for explicitly converting a string to something - bool shorthand = _textParser.CurrentToken.Id == TokenId.StringLiteral; - if (_textParser.CurrentToken.Id == TokenId.OpenParen || shorthand) + // If only 1 argument and + // - the arg is ConstantExpression, return the conversion + // OR + // - the arg is null, return the conversion (Can't use constructor) + // + // Then try to GenerateConversion + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (args.Length == 1 && (args[0] == null || args[0] is ConstantExpression) && TryGenerateConversion(args[0], type, out var generatedExpression)) { - Expression[] args; - if (shorthand) - { - var expressionOrType = ParseStringLiteral(true); - args = new[] { expressionOrType.First }; - } - else - { - args = ParseArgumentList(); - } + return generatedExpression!; + } - // If only 1 argument and - // - the arg is ConstantExpression, return the conversion - // OR - // - the arg is null, return the conversion (Can't use constructor) - // - // Then try to GenerateConversion + // If only 1 argument, and if the type is a ValueType and argType is also a ValueType, just Convert + if (args.Length == 1) + { + Type argType = args[0].Type; - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (args.Length == 1 && (args[0] == null || args[0] is ConstantExpression) && TryGenerateConversion(args[0], type, out var generatedExpression)) + if (type.GetTypeInfo().IsValueType && TypeHelper.IsNullableType(type) && argType.GetTypeInfo().IsValueType) { - return generatedExpression!; + return Expression.Convert(args[0], type); } + } - // If only 1 argument, and if the type is a ValueType and argType is also a ValueType, just Convert - if (args.Length == 1) - { - Type argType = args[0].Type; - - if (type.GetTypeInfo().IsValueType && TypeHelper.IsNullableType(type) && argType.GetTypeInfo().IsValueType) + var constructorsWithOutPointerArguments = type.GetConstructors() + .Where(c => !c.GetParameters().Any(p => p.ParameterType.GetTypeInfo().IsPointer)) + .ToArray(); + switch (_methodFinder.FindBestMethodBasedOnArguments(constructorsWithOutPointerArguments, ref args, out var method)) + { + case 0: + if (args.Length == 1 && TryGenerateConversion(args[0], type, out generatedExpression)) { - return Expression.Convert(args[0], type); + return generatedExpression!; } - } - - var constructorsWithOutPointerArguments = type.GetConstructors() - .Where(c => !c.GetParameters().Any(p => p.ParameterType.GetTypeInfo().IsPointer)) - .ToArray(); - switch (_methodFinder.FindBestMethodBasedOnArguments(constructorsWithOutPointerArguments, ref args, out var method)) - { - case 0: - if (args.Length == 1 && TryGenerateConversion(args[0], type, out generatedExpression)) - { - return generatedExpression!; - } - throw ParseError(errorPos, Res.NoMatchingConstructor, TypeHelper.GetTypeName(type)); + throw ParseError(errorPos, Res.NoMatchingConstructor, TypeHelper.GetTypeName(type)); - case 1: - return Expression.New((ConstructorInfo)method!, args); + case 1: + return Expression.New((ConstructorInfo)method!, args); - default: - throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, TypeHelper.GetTypeName(type)); - } + default: + throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, TypeHelper.GetTypeName(type)); } + } - _textParser.ValidateToken(TokenId.Dot, Res.DotOrOpenParenOrStringLiteralExpected); - _textParser.NextToken(); + _textParser.ValidateToken(TokenId.Dot, Res.DotOrOpenParenOrStringLiteralExpected); + _textParser.NextToken(); - return ParseMemberAccess(type, null); + return ParseMemberAccess(type, null); + } + + private bool TryGenerateConversion(Expression sourceExpression, Type type, out Expression? expression) + { + Type exprType = sourceExpression.Type; + if (exprType == type) + { + expression = sourceExpression; + return true; } - private bool TryGenerateConversion(Expression sourceExpression, Type type, out Expression? expression) + if (exprType.GetTypeInfo().IsValueType && type.GetTypeInfo().IsValueType) { - Type exprType = sourceExpression.Type; - if (exprType == type) + if ((TypeHelper.IsNullableType(exprType) || TypeHelper.IsNullableType(type)) && TypeHelper.GetNonNullableType(exprType) == TypeHelper.GetNonNullableType(type)) { - expression = sourceExpression; + expression = Expression.Convert(sourceExpression, type); return true; } - if (exprType.GetTypeInfo().IsValueType && type.GetTypeInfo().IsValueType) - { - if ((TypeHelper.IsNullableType(exprType) || TypeHelper.IsNullableType(type)) && TypeHelper.GetNonNullableType(exprType) == TypeHelper.GetNonNullableType(type)) - { - expression = Expression.Convert(sourceExpression, type); - return true; - } - - if ((TypeHelper.IsNumericType(exprType) || TypeHelper.IsEnumType(exprType)) && TypeHelper.IsNumericType(type) || TypeHelper.IsEnumType(type)) - { - expression = Expression.ConvertChecked(sourceExpression, type); - return true; - } - } - - if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) || exprType.GetTypeInfo().IsInterface || type.GetTypeInfo().IsInterface) + if ((TypeHelper.IsNumericType(exprType) || TypeHelper.IsEnumType(exprType)) && TypeHelper.IsNumericType(type) || TypeHelper.IsEnumType(type)) { - expression = Expression.Convert(sourceExpression, type); + expression = Expression.ConvertChecked(sourceExpression, type); return true; } + } - // Try to Parse the string rather than just generate the convert statement - if (sourceExpression.NodeType == ExpressionType.Constant && exprType == typeof(string)) - { - string text = (string)((ConstantExpression)sourceExpression).Value; + if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) || exprType.GetTypeInfo().IsInterface || type.GetTypeInfo().IsInterface) + { + expression = Expression.Convert(sourceExpression, type); + return true; + } - var typeConvertor = _typeConverterFactory.GetConverter(type); - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (typeConvertor != null && typeConvertor.CanConvertFrom(typeof(string))) - { - var value = typeConvertor.ConvertFromInvariantString(text); - expression = Expression.Constant(value, type); - return true; - } - } + // Try to Parse the string rather than just generate the convert statement + if (sourceExpression.NodeType == ExpressionType.Constant && exprType == typeof(string)) + { + string text = (string)((ConstantExpression)sourceExpression).Value; - // Check if there are any explicit conversion operators on the source type which fit the requirement (cast to the return type). - bool explicitOperatorAvailable = exprType.GetTypeInfo().GetDeclaredMethods("op_Explicit").Any(m => m.ReturnType == type); - if (explicitOperatorAvailable) + var typeConvertor = _typeConverterFactory.GetConverter(type); + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (typeConvertor != null && typeConvertor.CanConvertFrom(typeof(string))) { - expression = Expression.Convert(sourceExpression, type); + var value = typeConvertor.ConvertFromInvariantString(text); + expression = Expression.Constant(value, type); return true; } + } - expression = null; - return false; + // Check if there are any explicit conversion operators on the source type which fit the requirement (cast to the return type). + bool explicitOperatorAvailable = exprType.GetTypeInfo().GetDeclaredMethods("op_Explicit").Any(m => m.ReturnType == type); + if (explicitOperatorAvailable) + { + expression = Expression.Convert(sourceExpression, type); + return true; } - private Expression ParseMemberAccess(Type? type, Expression? expression) + expression = null; + return false; + } + + private Expression ParseMemberAccess(Type? type, Expression? expression) + { + if (expression != null) { - if (expression != null) - { - type = expression.Type; - } + type = expression.Type; + } - int errorPos = _textParser.CurrentToken.Pos; - string id = GetIdentifier(); - _textParser.NextToken(); + int errorPos = _textParser.CurrentToken.Pos; + string id = GetIdentifier(); + _textParser.NextToken(); - if (_textParser.CurrentToken.Id == TokenId.OpenParen) + if (_textParser.CurrentToken.Id == TokenId.OpenParen) + { + if (expression != null && type != typeof(string)) { - if (expression != null && type != typeof(string)) - { - var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type); - if (enumerableType != null) - { - Type elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0]; - return ParseEnumerable(expression, elementType, id, errorPos, type); - } - } - - Expression[] args = ParseArgumentList(); - switch (_methodFinder.FindMethod(type, id, expression == null, ref expression, ref args, out var mb)) + var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type); + if (enumerableType != null) { - case 0: - throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type)); - - case 1: - MethodInfo method = (MethodInfo)mb!; - if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!) && !(method.IsPublic && PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.ReturnType))) - { - throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!)); - } - - if (method.IsGenericMethod) - { - var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter); - var typeArguments = genericParameters.Select(a => args[a.Position].Type); - var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray()); - return Expression.Call(expression, constructedMethod, args); - } - - return Expression.Call(expression, method, args); - - default: - throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type)); + Type elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0]; + return ParseEnumerable(expression, elementType, id, errorPos, type); } } - var @enum = TypeHelper.ParseEnum(id, type); - if (@enum != null) + Expression[] args = ParseArgumentList(); + switch (_methodFinder.FindMethod(type, id, expression == null, ref expression, ref args, out var mb)) { - return Expression.Constant(@enum); - } + case 0: + throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type)); -#if UAP10_0 || NETSTANDARD1_3 - if (type == typeof(DynamicClass)) - { - return Expression.MakeIndex(expression, typeof(DynamicClass).GetProperty("Item"), new[] { Expression.Constant(id) }); - } -#endif - MemberInfo? member = FindPropertyOrField(type!, id, expression == null); - if (member is PropertyInfo property) - { - return Expression.Property(expression, property); - } + case 1: + MethodInfo method = (MethodInfo)mb!; + if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!) && !(method.IsPublic && PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.ReturnType))) + { + throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!)); + } - if (member is FieldInfo field) - { - return Expression.Field(expression, field); - } + if (method.IsGenericMethod) + { + var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter); + var typeArguments = genericParameters.Select(a => args[a.Position].Type); + var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray()); + return Expression.Call(expression, constructedMethod, args); + } - // #357 #662 - var extraCheck = !_parsingConfig.PrioritizePropertyOrFieldOverTheType || - _parsingConfig.PrioritizePropertyOrFieldOverTheType && expression != null; + return Expression.Call(expression, method, args); - if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck) - { - var indexerMethod = expression?.Type.GetMethod("get_Item", new[] { typeof(string) }); - if (indexerMethod != null) - { - return Expression.Call(expression, indexerMethod, Expression.Constant(id)); - } + default: + throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type)); } + } -#if !NET35 && !UAP10_0 && !NETSTANDARD1_3 - if (type == typeof(object)) - { - // The member is a dynamic or ExpandoObject, so convert this - return _expressionHelper.ConvertToExpandoObjectAndCreateDynamicExpression(expression, type, id); - } + var @enum = TypeHelper.ParseEnum(id, type); + if (@enum != null) + { + return Expression.Constant(@enum); + } + +#if UAP10_0 || NETSTANDARD1_3 + if (type == typeof(DynamicClass)) + { + return Expression.MakeIndex(expression, typeof(DynamicClass).GetProperty("Item"), new[] { Expression.Constant(id) }); + } #endif - // Parse as Lambda - if (_textParser.CurrentToken.Id == TokenId.Lambda && _it?.Type == type) - { - return ParseAsLambda(id); - } + MemberInfo? member = FindPropertyOrField(type!, id, expression == null); + if (member is PropertyInfo property) + { + return Expression.Property(expression, property); + } - // This could be enum like "A.B.C.MyEnum.Value1" or "A.B.C+MyEnum.Value1" - if (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) + if (member is FieldInfo field) + { + return Expression.Field(expression, field); + } + + // #357 #662 + var extraCheck = !_parsingConfig.PrioritizePropertyOrFieldOverTheType || + _parsingConfig.PrioritizePropertyOrFieldOverTheType && expression != null; + + if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck) + { + var indexerMethod = expression?.Type.GetMethod("get_Item", new[] { typeof(string) }); + if (indexerMethod != null) { - return ParseAsEnum(id); + return Expression.Call(expression, indexerMethod, Expression.Constant(id)); } + } - throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type)); +#if !NET35 && !UAP10_0 && !NETSTANDARD1_3 + if (type == typeof(object)) + { + // The member is a dynamic or ExpandoObject, so convert this + return _expressionHelper.ConvertToExpandoObjectAndCreateDynamicExpression(expression, type, id); + } +#endif + // Parse as Lambda + if (_textParser.CurrentToken.Id == TokenId.Lambda && _it?.Type == type) + { + return ParseAsLambda(id); } - private Expression ParseAsLambda(string id) + // This could be enum like "A.B.C.MyEnum.Value1" or "A.B.C+MyEnum.Value1" + if (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) { - // This might be an internal variable for use within a lambda expression, so store it as such - _internals.Add(id, _it!); - string previousItName = ItName; + return ParseAsEnum(id); + } - // Also store ItName (only once) - if (string.Equals(ItName, KeywordsHelper.KEYWORD_IT)) - { - ItName = id; - } + throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type)); + } - // next - _textParser.NextToken(); + private Expression ParseAsLambda(string id) + { + // This might be an internal variable for use within a lambda expression, so store it as such + _internals.Add(id, _it!); + string previousItName = ItName; - LastLambdaItName = ItName; - var exp = ParseConditionalOperator(); + // Also store ItName (only once) + if (string.Equals(ItName, KeywordsHelper.KEYWORD_IT)) + { + ItName = id; + } - // Restore previous context and clear internals - _internals.Remove(id); - ItName = previousItName; + // next + _textParser.NextToken(); - return exp; - } + LastLambdaItName = ItName; + var exp = ParseConditionalOperator(); - private Expression ParseAsEnum(string id) - { - var parts = new List { id }; + // Restore previous context and clear internals + _internals.Remove(id); + ItName = previousItName; - while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) - { - if (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) - { - parts.Add(_textParser.CurrentToken.Text); - _textParser.NextToken(); - } + return exp; + } - if (_textParser.CurrentToken.Id == TokenId.Identifier) - { - parts.Add(_textParser.CurrentToken.Text); - _textParser.NextToken(); - } - } + private Expression ParseAsEnum(string id) + { + var parts = new List { id }; - var enumTypeAsString = string.Concat(parts.Take(parts.Count - 2).ToArray()); - var enumType = _typeFinder.FindTypeByName(enumTypeAsString, null, true); - if (enumType == null) + while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) + { + if (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) { - throw ParseError(_textParser.CurrentToken.Pos, Res.EnumTypeNotFound, enumTypeAsString); + parts.Add(_textParser.CurrentToken.Text); + _textParser.NextToken(); } - var enumValueAsString = parts.LastOrDefault(); - if (enumValueAsString == null) + if (_textParser.CurrentToken.Id == TokenId.Identifier) { - throw ParseError(_textParser.CurrentToken.Pos, Res.EnumValueExpected); + parts.Add(_textParser.CurrentToken.Text); + _textParser.NextToken(); } + } - var enumValue = TypeHelper.ParseEnum(enumValueAsString, enumType); - if (enumValue == null) - { - throw ParseError(_textParser.CurrentToken.Pos, Res.EnumValueNotDefined, enumValueAsString, enumTypeAsString); - } + var enumTypeAsString = string.Concat(parts.Take(parts.Count - 2).ToArray()); + var enumType = _typeFinder.FindTypeByName(enumTypeAsString, null, true); + if (enumType == null) + { + throw ParseError(_textParser.CurrentToken.Pos, Res.EnumTypeNotFound, enumTypeAsString); + } - return Expression.Constant(enumValue); + var enumValueAsString = parts.LastOrDefault(); + if (enumValueAsString == null) + { + throw ParseError(_textParser.CurrentToken.Pos, Res.EnumValueExpected); } - private Expression ParseEnumerable(Expression instance, Type elementType, string methodName, int errorPos, Type? type) + var enumValue = TypeHelper.ParseEnum(enumValueAsString, enumType); + if (enumValue == null) { - bool isQueryable = TypeHelper.FindGenericType(typeof(IQueryable<>), type) != null; - bool isDictionary = TypeHelper.IsDictionary(type); + throw ParseError(_textParser.CurrentToken.Pos, Res.EnumValueNotDefined, enumValueAsString, enumTypeAsString); + } - var oldParent = _parent; + return Expression.Constant(enumValue); + } - ParameterExpression? outerIt = _it; - ParameterExpression innerIt = ParameterExpressionHelper.CreateParameterExpression(elementType, string.Empty, _parsingConfig.RenameEmptyParameterExpressionNames); + private Expression ParseEnumerable(Expression instance, Type elementType, string methodName, int errorPos, Type? type) + { + bool isQueryable = TypeHelper.FindGenericType(typeof(IQueryable<>), type) != null; + bool isDictionary = TypeHelper.IsDictionary(type); - _parent = _it; + var oldParent = _parent; - if (methodName == "Contains" || methodName == "ContainsKey" || methodName == "Skip" || methodName == "Take") - { - // for any method that acts on the parent element type, we need to specify the outerIt as scope. - _it = outerIt; - } - else - { - _it = innerIt; - } + ParameterExpression? outerIt = _it; + ParameterExpression innerIt = ParameterExpressionHelper.CreateParameterExpression(elementType, string.Empty, _parsingConfig.RenameEmptyParameterExpressionNames); - Expression[] args = ParseArgumentList(); + _parent = _it; + if (methodName == "Contains" || methodName == "ContainsKey" || methodName == "Skip" || methodName == "Take") + { + // for any method that acts on the parent element type, we need to specify the outerIt as scope. _it = outerIt; - _parent = oldParent; + } + else + { + _it = innerIt; + } - if (isDictionary && _methodFinder.ContainsMethod(typeof(IDictionarySignatures), methodName, false, null, ref args)) - { - var method = type!.GetMethod(methodName)!; - return Expression.Call(instance, method, args); - } + Expression[] args = ParseArgumentList(); - if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, null, ref args)) - { - throw ParseError(errorPos, Res.NoApplicableAggregate, methodName, string.Join(",", args.Select(a => a.Type.Name).ToArray())); - } + _it = outerIt; + _parent = oldParent; - Type callType = typeof(Enumerable); - if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, null, ref args)) - { - callType = typeof(Queryable); - } + if (isDictionary && _methodFinder.ContainsMethod(typeof(IDictionarySignatures), methodName, false, null, ref args)) + { + var method = type!.GetMethod(methodName)!; + return Expression.Call(instance, method, args); + } - Type[] typeArgs; - if (new[] { "OfType", "Cast" }.Contains(methodName)) - { - if (args.Length != 1) - { - throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneArg, methodName); - } + if (!_methodFinder.ContainsMethod(typeof(IEnumerableSignatures), methodName, false, null, ref args)) + { + throw ParseError(errorPos, Res.NoApplicableAggregate, methodName, string.Join(",", args.Select(a => a.Type.Name).ToArray())); + } - typeArgs = new[] { ResolveTypeFromArgumentExpression(methodName, args[0]) }; - args = new Expression[0]; - } - else if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(methodName)) + Type callType = typeof(Enumerable); + if (isQueryable && _methodFinder.ContainsMethod(typeof(IQueryableSignatures), methodName, false, null, ref args)) + { + callType = typeof(Queryable); + } + + Type[] typeArgs; + if (new[] { "OfType", "Cast" }.Contains(methodName)) + { + if (args.Length != 1) { - if (args.Length == 2) - { - typeArgs = new[] { elementType, args[0].Type, args[1].Type }; - } - else - { - typeArgs = new[] { elementType, args[0].Type }; - } + throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresOneArg, methodName); } - else if (methodName == "SelectMany") + + typeArgs = new[] { ResolveTypeFromArgumentExpression(methodName, args[0]) }; + args = new Expression[0]; + } + else if (new[] { "Min", "Max", "Select", "OrderBy", "OrderByDescending", "ThenBy", "ThenByDescending", "GroupBy" }.Contains(methodName)) + { + if (args.Length == 2) { - var bodyType = Expression.Lambda(args[0], innerIt).Body.Type; - var interfaces = bodyType.GetInterfaces().Union(new[] { bodyType }); - Type interfaceType = interfaces.Single(i => i.Name == typeof(IEnumerable<>).Name); - Type resultType = interfaceType.GetTypeInfo().GetGenericTypeArguments()[0]; - typeArgs = new[] { elementType, resultType }; + typeArgs = new[] { elementType, args[0].Type, args[1].Type }; } else { - typeArgs = new[] { elementType }; + typeArgs = new[] { elementType, args[0].Type }; } + } + else if (methodName == "SelectMany") + { + var bodyType = Expression.Lambda(args[0], innerIt).Body.Type; + var interfaces = bodyType.GetInterfaces().Union(new[] { bodyType }); + Type interfaceType = interfaces.Single(i => i.Name == typeof(IEnumerable<>).Name); + Type resultType = interfaceType.GetTypeInfo().GetGenericTypeArguments()[0]; + typeArgs = new[] { elementType, resultType }; + } + else + { + typeArgs = new[] { elementType }; + } - if (args.Length == 0) + if (args.Length == 0) + { + args = new[] { instance }; + } + else + { + if (new[] { "Concat", "Contains", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union" }.Contains(methodName)) { - args = new[] { instance }; + args = new[] { instance, args[0] }; } else { - if (new[] { "Concat", "Contains", "DefaultIfEmpty", "Except", "Intersect", "Skip", "Take", "Union" }.Contains(methodName)) + if (args.Length == 2) { - args = new[] { instance, args[0] }; + args = new[] { instance, Expression.Lambda(args[0], innerIt), Expression.Lambda(args[1], innerIt) }; } else { - if (args.Length == 2) - { - args = new[] { instance, Expression.Lambda(args[0], innerIt), Expression.Lambda(args[1], innerIt) }; - } - else - { - args = new[] { instance, Expression.Lambda(args[0], innerIt) }; - } + args = new[] { instance, Expression.Lambda(args[0], innerIt) }; } } - - return Expression.Call(callType, methodName, typeArgs, args); } - private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null) + return Expression.Call(callType, methodName, typeArgs, args); + } + + private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null) + { + string argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second "; + switch (argumentExpression) { - string argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second "; - switch (argumentExpression) - { - case ConstantExpression constantExpression: - switch (constantExpression.Value) - { - case string typeName: - return ResolveTypeStringFromArgument(typeName); + case ConstantExpression constantExpression: + switch (constantExpression.Value) + { + case string typeName: + return ResolveTypeStringFromArgument(typeName); - case Type type: - return type; + case Type type: + return type; - default: - throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "string or System.Type"); - } + default: + throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "string or System.Type"); + } - default: - throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "ConstantExpression"); - } + default: + throw ParseError(_textParser.CurrentToken.Pos, Res.FunctionRequiresNotNullArgOfType, functionName, argument, "ConstantExpression"); } + } - private Type ResolveTypeStringFromArgument(string typeName) + private Type ResolveTypeStringFromArgument(string typeName) + { + bool typeIsNullable = false; + if (typeName.EndsWith("?")) { - bool typeIsNullable = false; - if (typeName.EndsWith("?")) - { - typeName = typeName.TrimEnd('?'); - typeIsNullable = true; - } - - var resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true); - if (resultType == null) - { - throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeName); - } - - return typeIsNullable ? TypeHelper.ToNullableType(resultType) : resultType; + typeName = typeName.TrimEnd('?'); + typeIsNullable = true; } - private Expression[] ParseArgumentList() + var resultType = _typeFinder.FindTypeByName(typeName, new[] { _it, _parent, _root }, true); + if (resultType == null) { - _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); - _textParser.NextToken(); - Expression[] args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; - _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); - _textParser.NextToken(); - return args; + throw ParseError(_textParser.CurrentToken.Pos, Res.TypeNotFound, typeName); } - private Expression[] ParseArguments() - { - var argList = new List(); - while (true) - { - var argumentExpression = ParseConditionalOperator(); + return typeIsNullable ? TypeHelper.ToNullableType(resultType) : resultType; + } + + private Expression[] ParseArgumentList() + { + _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + _textParser.NextToken(); + Expression[] args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); + _textParser.NextToken(); + return args; + } - _expressionHelper.WrapConstantExpression(ref argumentExpression); + private Expression[] ParseArguments() + { + var argList = new List(); + while (true) + { + var argumentExpression = ParseConditionalOperator(); - argList.Add(argumentExpression); + _expressionHelper.WrapConstantExpression(ref argumentExpression); - if (_textParser.CurrentToken.Id != TokenId.Comma) - { - break; - } + argList.Add(argumentExpression); - _textParser.NextToken(); + if (_textParser.CurrentToken.Id != TokenId.Comma) + { + break; } - return argList.ToArray(); - } - - private Expression ParseElementAccess(Expression expr) - { - int errorPos = _textParser.CurrentToken.Pos; - _textParser.ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected); _textParser.NextToken(); + } - Expression[] args = ParseArguments(); - _textParser.ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected); - _textParser.NextToken(); + return argList.ToArray(); + } - if (expr.Type.IsArray) - { - if (expr.Type.GetArrayRank() != 1 || args.Length != 1) - { - throw ParseError(errorPos, Res.CannotIndexMultiDimArray); - } + private Expression ParseElementAccess(Expression expr) + { + int errorPos = _textParser.CurrentToken.Pos; + _textParser.ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected); + _textParser.NextToken(); - var indexExpression = _parsingConfig.ExpressionPromoter.Promote(args[0], typeof(int), true, false); - if (indexExpression == null) - { - throw ParseError(errorPos, Res.InvalidIndex); - } + Expression[] args = ParseArguments(); + _textParser.ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected); + _textParser.NextToken(); - return Expression.ArrayIndex(expr, indexExpression); + if (expr.Type.IsArray) + { + if (expr.Type.GetArrayRank() != 1 || args.Length != 1) + { + throw ParseError(errorPos, Res.CannotIndexMultiDimArray); } - switch (_methodFinder.FindIndexer(expr.Type, args, out var mb)) + var indexExpression = _parsingConfig.ExpressionPromoter.Promote(args[0], typeof(int), true, false); + if (indexExpression == null) { - case 0: - throw ParseError(errorPos, Res.NoApplicableIndexer, - TypeHelper.GetTypeName(expr.Type)); - - case 1: - var indexMethod = (MethodInfo)mb!; - var indexParameterType = indexMethod.GetParameters().First().ParameterType; - - var indexArgumentExpression = args[0]; // Indexer only has 1 parameter, so we can use args[0] here - if (indexParameterType != indexArgumentExpression.Type) - { - indexArgumentExpression = Expression.Convert(indexArgumentExpression, indexParameterType); - } - - return Expression.Call(expr, indexMethod, indexArgumentExpression); - - default: - throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, TypeHelper.GetTypeName(expr.Type)); + throw ParseError(errorPos, Res.InvalidIndex); } + + return Expression.ArrayIndex(expr, indexExpression); } - internal static Type ToNullableType(Type type) + switch (_methodFinder.FindIndexer(expr.Type, args, out var mb)) { - Check.NotNull(type, nameof(type)); + case 0: + throw ParseError(errorPos, Res.NoApplicableIndexer, + TypeHelper.GetTypeName(expr.Type)); - if (!type.GetTypeInfo().IsValueType || TypeHelper.IsNullableType(type)) - { - throw ParseError(-1, Res.TypeHasNoNullableForm, TypeHelper.GetTypeName(type)); - } + case 1: + var indexMethod = (MethodInfo)mb!; + var indexParameterType = indexMethod.GetParameters().First().ParameterType; + + var indexArgumentExpression = args[0]; // Indexer only has 1 parameter, so we can use args[0] here + if (indexParameterType != indexArgumentExpression.Type) + { + indexArgumentExpression = Expression.Convert(indexArgumentExpression, indexParameterType); + } - return typeof(Nullable<>).MakeGenericType(type); + return Expression.Call(expr, indexMethod, indexArgumentExpression); + + default: + throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, TypeHelper.GetTypeName(expr.Type)); } + } - private static bool TryGetMemberName(Expression expression, out string? memberName) + internal static Type ToNullableType(Type type) + { + Check.NotNull(type, nameof(type)); + + if (!type.GetTypeInfo().IsValueType || TypeHelper.IsNullableType(type)) { - var memberExpression = expression as MemberExpression; - if (memberExpression == null && expression.NodeType == ExpressionType.Coalesce) - { - memberExpression = (expression as BinaryExpression)?.Left as MemberExpression; - } + throw ParseError(-1, Res.TypeHasNoNullableForm, TypeHelper.GetTypeName(type)); + } - if (memberExpression != null) - { - memberName = memberExpression.Member.Name; - return true; - } + return typeof(Nullable<>).MakeGenericType(type); + } -#if NETFX_CORE - var indexExpression = expression as IndexExpression; - if (indexExpression != null && indexExpression.Indexer.DeclaringType == typeof(DynamicObjectClass)) - { - memberName = ((ConstantExpression)indexExpression.Arguments.First()).Value as string; - return true; - } -#endif - memberName = null; - return false; + private static bool TryGetMemberName(Expression expression, out string? memberName) + { + var memberExpression = expression as MemberExpression; + if (memberExpression == null && expression.NodeType == ExpressionType.Coalesce) + { + memberExpression = (expression as BinaryExpression)?.Left as MemberExpression; } - private void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) + if (memberExpression != null) { - Expression[] args = { expr }; - - if (!_methodFinder.ContainsMethod(signatures, "F", false, null, ref args)) - { - throw IncompatibleOperandError(opName, expr, errorPos); - } + memberName = memberExpression.Member.Name; + return true; + } - expr = args[0]; +#if NETFX_CORE + var indexExpression = expression as IndexExpression; + if (indexExpression != null && indexExpression.Indexer.DeclaringType == typeof(DynamicObjectClass)) + { + memberName = ((ConstantExpression)indexExpression.Arguments.First()).Value as string; + return true; } +#endif + memberName = null; + return false; + } - private static string? GetOverloadedOperationName(TokenId tokenId) + private void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) + { + Expression[] args = { expr }; + + if (!_methodFinder.ContainsMethod(signatures, "F", false, null, ref args)) { - switch (tokenId) - { - case TokenId.DoubleEqual: - case TokenId.Equal: - return "op_Equality"; - case TokenId.ExclamationEqual: - return "op_Inequality"; - default: - return null; - } + throw IncompatibleOperandError(opName, expr, errorPos); } - private void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref Expression left, ref Expression right, int errorPos) + expr = args[0]; + } + + private static string? GetOverloadedOperationName(TokenId tokenId) + { + switch (tokenId) { - Expression[] args = { left, right }; + case TokenId.DoubleEqual: + case TokenId.Equal: + return "op_Equality"; + case TokenId.ExclamationEqual: + return "op_Inequality"; + default: + return null; + } + } - // support operator overloading - var nativeOperation = GetOverloadedOperationName(opId); - bool found = false; + private void CheckAndPromoteOperands(Type signatures, TokenId opId, string opName, ref Expression left, ref Expression right, int errorPos) + { + Expression[] args = { left, right }; - if (nativeOperation != null) - { - // first try left operand's equality operators - found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, null, ref args); - if (!found) - { - found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, null, ref args); - } - } + // support operator overloading + var nativeOperation = GetOverloadedOperationName(opId); + bool found = false; - if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, null, ref args)) + if (nativeOperation != null) + { + // first try left operand's equality operators + found = _methodFinder.ContainsMethod(left.Type, nativeOperation, true, null, ref args); + if (!found) { - throw IncompatibleOperandsError(opName, left, right, errorPos); + found = _methodFinder.ContainsMethod(right.Type, nativeOperation, true, null, ref args); } - - left = args[0]; - right = args[1]; } - private static Exception IncompatibleOperandError(string opName, Expression expr, int errorPos) + if (!found && !_methodFinder.ContainsMethod(signatures, "F", false, null, ref args)) { - return ParseError(errorPos, Res.IncompatibleOperand, opName, TypeHelper.GetTypeName(expr.Type)); + throw IncompatibleOperandsError(opName, left, right, errorPos); } - private static Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int errorPos) - { - return ParseError(errorPos, Res.IncompatibleOperands, opName, TypeHelper.GetTypeName(left.Type), TypeHelper.GetTypeName(right.Type)); - } + left = args[0]; + right = args[1]; + } - private MemberInfo? FindPropertyOrField(Type type, string memberName, bool staticAccess) - { -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) - var extraBindingFlag = _parsingConfig.PrioritizePropertyOrFieldOverTheType && staticAccess ? BindingFlags.Static : BindingFlags.Instance; - var bindingFlags = BindingFlags.Public | BindingFlags.DeclaredOnly | extraBindingFlag; - foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) - { - var findMembersType = _parsingConfig?.IsCaseSensitive == true ? Type.FilterName : Type.FilterNameIgnoreCase; - var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, bindingFlags, findMembersType, memberName); + private static Exception IncompatibleOperandError(string opName, Expression expr, int errorPos) + { + return ParseError(errorPos, Res.IncompatibleOperand, opName, TypeHelper.GetTypeName(expr.Type)); + } - if (members.Length != 0) - { - return members[0]; - } - } - return null; -#else - var isCaseSensitive = _parsingConfig?.IsCaseSensitive == true; - foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) - { - // Try to find a property with the specified memberName - MemberInfo? member = t.GetTypeInfo().DeclaredProperties.FirstOrDefault(x => (!staticAccess || x.GetAccessors(true)[0].IsStatic) && ((x.Name == memberName) || (!isCaseSensitive && x.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)))); - if (member != null) - { - return member; - } + private static Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int errorPos) + { + return ParseError(errorPos, Res.IncompatibleOperands, opName, TypeHelper.GetTypeName(left.Type), TypeHelper.GetTypeName(right.Type)); + } - // If no property is found, try to get a field with the specified memberName - member = t.GetTypeInfo().DeclaredFields.FirstOrDefault(x => (!staticAccess || x.IsStatic) && ((x.Name == memberName) || (!isCaseSensitive && x.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)))); - if (member != null) - { - return member; - } + private MemberInfo? FindPropertyOrField(Type type, string memberName, bool staticAccess) + { +#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) + var extraBindingFlag = _parsingConfig.PrioritizePropertyOrFieldOverTheType && staticAccess ? BindingFlags.Static : BindingFlags.Instance; + var bindingFlags = BindingFlags.Public | BindingFlags.DeclaredOnly | extraBindingFlag; + foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) + { + var findMembersType = _parsingConfig?.IsCaseSensitive == true ? Type.FilterName : Type.FilterNameIgnoreCase; + var members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, bindingFlags, findMembersType, memberName); - // No property or field is found, try the base type. + if (members.Length != 0) + { + return members[0]; } - return null; -#endif } - - private bool TokenIdentifierIs(string id) + return null; +#else + var isCaseSensitive = _parsingConfig?.IsCaseSensitive == true; + foreach (Type t in TypeHelper.GetSelfAndBaseTypes(type)) { - return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase); - } + // Try to find a property with the specified memberName + MemberInfo? member = t.GetTypeInfo().DeclaredProperties.FirstOrDefault(x => (!staticAccess || x.GetAccessors(true)[0].IsStatic) && ((x.Name == memberName) || (!isCaseSensitive && x.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)))); + if (member != null) + { + return member; + } - private string GetIdentifier() - { - _textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected); - string id = _textParser.CurrentToken.Text; - if (id.Length > 1 && id[0] == '@') + // If no property is found, try to get a field with the specified memberName + member = t.GetTypeInfo().DeclaredFields.FirstOrDefault(x => (!staticAccess || x.IsStatic) && ((x.Name == memberName) || (!isCaseSensitive && x.Name.Equals(memberName, StringComparison.OrdinalIgnoreCase)))); + if (member != null) { - id = id.Substring(1); + return member; } - return id; + // No property or field is found, try the base type. } + return null; +#endif + } - private Exception ParseError(string format, params object[] args) - { - return ParseError(_textParser.CurrentToken.Pos, format, args); - } + private bool TokenIdentifierIs(string id) + { + return _textParser.CurrentToken.Id == TokenId.Identifier && string.Equals(id, _textParser.CurrentToken.Text, StringComparison.OrdinalIgnoreCase); + } - private static Exception ParseError(int pos, string format, params object[] args) + private string GetIdentifier() + { + _textParser.ValidateToken(TokenId.Identifier, Res.IdentifierExpected); + string id = _textParser.CurrentToken.Text; + if (id.Length > 1 && id[0] == '@') { - return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos); + id = id.Substring(1); } + + return id; + } + + private Exception ParseError(string format, params object[] args) + { + return ParseError(_textParser.CurrentToken.Pos, format, args); + } + + private static Exception ParseError(int pos, string format, params object[] args) + { + return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos); } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/IConstantExpressionWrapper.cs b/src/System.Linq.Dynamic.Core/Parser/IConstantExpressionWrapper.cs index 42acec1d..e2cb4904 100644 --- a/src/System.Linq.Dynamic.Core/Parser/IConstantExpressionWrapper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/IConstantExpressionWrapper.cs @@ -1,9 +1,11 @@ -using System.Linq.Expressions; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +internal interface IConstantExpressionWrapper { - internal interface IConstantExpressionWrapper - { - void Wrap(ref Expression expression); - } -} + void Wrap(ref Expression expression); + + bool TryUnwrap(MemberExpression? expression, [NotNullWhen(true)] out TValue? value); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs index 8fb14e71..b276a712 100644 --- a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs @@ -1,43 +1,45 @@ -using System.Linq.Expressions; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +internal interface IExpressionHelper { - internal interface IExpressionHelper - { - void ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref Expression left, ref Expression right); + void ConvertNumericTypeToBiggestCommonTypeForBinaryOperator(ref Expression left, ref Expression right); + + Expression GenerateAdd(Expression left, Expression right); - Expression GenerateAdd(Expression left, Expression right); + Expression GenerateEqual(Expression left, Expression right); - Expression GenerateEqual(Expression left, Expression right); + Expression GenerateGreaterThan(Expression left, Expression right); - Expression GenerateGreaterThan(Expression left, Expression right); + Expression GenerateGreaterThanEqual(Expression left, Expression right); - Expression GenerateGreaterThanEqual(Expression left, Expression right); + Expression GenerateLessThan(Expression left, Expression right); - Expression GenerateLessThan(Expression left, Expression right); + Expression GenerateLessThanEqual(Expression left, Expression right); - Expression GenerateLessThanEqual(Expression left, Expression right); + Expression GenerateNotEqual(Expression left, Expression right); - Expression GenerateNotEqual(Expression left, Expression right); + Expression GenerateStringConcat(Expression left, Expression right); - Expression GenerateStringConcat(Expression left, Expression right); + Expression GenerateSubtract(Expression left, Expression right); - Expression GenerateSubtract(Expression left, Expression right); + void OptimizeForEqualityIfPossible(ref Expression left, ref Expression right); - void OptimizeForEqualityIfPossible(ref Expression left, ref Expression right); + Expression? OptimizeStringForEqualityIfPossible(string text, Type type); - Expression? OptimizeStringForEqualityIfPossible(string text, Type type); + bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, bool addSelf, out Expression generatedExpression); - bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, bool addSelf, out Expression generatedExpression); + bool ExpressionQualifiesForNullPropagation(Expression expression); - bool ExpressionQualifiesForNullPropagation(Expression expression); + void WrapConstantExpression(ref Expression argument); - void WrapConstantExpression(ref Expression argument); + bool TryUnwrapConstantExpression(Expression? expression, [NotNullWhen(true)] out TValue? value); - bool MemberExpressionIsDynamic(Expression expression); + bool MemberExpressionIsDynamic(Expression expression); - Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName); + Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName); - Expression GenerateDefaultExpression(Type type); - } -} + Expression GenerateDefaultExpression(Type type); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IEqualitySignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IEqualitySignatures.cs index 8b379b53..e7c79c31 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IEqualitySignatures.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IEqualitySignatures.cs @@ -1,23 +1,22 @@ -namespace System.Linq.Dynamic.Core.Parser.SupportedOperands +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands; + +internal interface IEqualitySignatures : IRelationalSignatures { - internal interface IEqualitySignatures : IRelationalSignatures - { - void F(bool x, bool y); - void F(bool? x, bool? y); + 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); + // 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, Guid y); + void F(Guid? x, Guid? y); - // Disabled 4 lines below because of : https://github.com/StefH/System.Linq.Dynamic.Core/pull/200 - //void F(Guid x, string y); - //void F(Guid? x, string y); - //void F(string x, Guid y); - //void F(string x, Guid? y); - } -} + // Disabled 4 lines below because of : https://github.com/StefH/System.Linq.Dynamic.Core/pull/200 + //void F(Guid x, string y); + //void F(Guid? x, string y); + //void F(string x, Guid y); + //void F(string x, Guid? y); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/WrappedValue.cs b/src/System.Linq.Dynamic.Core/Parser/WrappedValue.cs index 34a923e5..3121f4af 100644 --- a/src/System.Linq.Dynamic.Core/Parser/WrappedValue.cs +++ b/src/System.Linq.Dynamic.Core/Parser/WrappedValue.cs @@ -1,12 +1,11 @@ -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +internal class WrappedValue { - internal class WrappedValue - { - public TValue Value { get; private set; } + public TValue Value { get; } - public WrappedValue(TValue value) - { - Value = value; - } + public WrappedValue(TValue value) + { + Value = value; } -} +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.UseParameterizedNamesInDynamicQuery .cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.UseParameterizedNamesInDynamicQuery .cs new file mode 100644 index 00000000..698e63fe --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.UseParameterizedNamesInDynamicQuery .cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using FluentAssertions; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests; + +public partial class QueryableTests +{ + /// + /// Issue #645 + /// + [Fact] + public void When_UseParameterizedNamesInDynamicQuery_IsTrue_WrappedStringValue_Should_Be_Unwrapped() + { + // Arrange + var list = new List + { + new() + { + Name = "Terri Lee Duffy", + CompanyName = "ABC", + City = "Paris", + Phone = "333-444444", + Location = new Location { Name = "test" }, + LastContact = DateTimeOffset.Parse("2022-11-14") + }, + new() + { + Name = "Garry Moore", + CompanyName = "ABC", + City = "Paris", + Phone = "54654-444444", + Location = new Location { Name = "other test", UpdateAt = DateTimeOffset.Parse("2022-11-16") }, + LastContact = DateTimeOffset.Parse("2022-11-16") + } + }; + + var config = new ParsingConfig + { + UseParameterizedNamesInDynamicQuery = true + }; + + // Act 1A + var result1A = list.AsQueryable().Where(config, "LastContact = \"2022-11-16\"").ToArray(); + + // Assert 1A + result1A.Should().HaveCount(1); + + // Act 1B + var result1B = list.AsQueryable().Where(config, "\"2022-11-16\" == LastContact").ToArray(); + + // Assert 1B + result1B.Should().HaveCount(1); + + // Act 2A + var result2A = list.AsQueryable().Where("Location.UpdateAt = \"2022-11-16\"").ToArray(); + + // Assert 2A + result2A.Should().HaveCount(1); + + // Act 2B + var result2B = list.AsQueryable().Where("\"2022-11-16\" == Location.UpdateAt").ToArray(); + + // Assert 2B + result2B.Should().HaveCount(1); + } +} + +public class Customer +{ + public int CustomerID { get; set; } + public string Name { get; set; } + public string CompanyName { get; set; } + public string City { get; set; } + public string Phone { get; set; } + public Location Location { get; set; } + public DateTimeOffset? LastContact { get; set; } +} + +public class Location +{ + public int LocationID { get; set; } + public string Name { get; set; } + public DateTimeOffset UpdateAt { get; set; } +} \ No newline at end of file