From 2eff2a162e938d9c2d18830679ecb8d9fabdb4d4 Mon Sep 17 00:00:00 2001 From: Stef Date: Sat, 25 Oct 2025 10:44:45 +0200 Subject: [PATCH] Fixed adding Enum and integer --- .../Parser/ExpressionPromoter.cs | 2 +- .../Parser/TypeHelper.cs | 37 +++++++------- .../Parser/TypeHelperTests.cs | 48 ++++++++++++++++++- .../QueryableTests.Select.cs | 26 ++++++++++ 4 files changed, 94 insertions(+), 19 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs index 088b755e..49731b24 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs @@ -122,7 +122,7 @@ public ExpressionPromoter(ParsingConfig config) if (TypeHelper.IsCompatibleWith(returnType, type)) { - if (type == typeof(decimal) && TypeHelper.IsEnumType(sourceExpression.Type)) + if (TypeHelper.TypesAreEqual(type, typeof(decimal)) && TypeHelper.IsEnumType(sourceExpression.Type)) { return Expression.Convert(Expression.Convert(sourceExpression, Enum.GetUnderlyingType(sourceExpression.Type)), type); } diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs index 19002c4f..f4401b63 100644 --- a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs @@ -83,20 +83,20 @@ public static bool IsCompatibleWith(Type source, Type target) return target.IsAssignableFrom(source); } - Type st = GetNonNullableType(source); - Type tt = GetNonNullableType(target); + var sourceType = GetNonNullableType(source); + var targetType = GetNonNullableType(target); - if (st != source && tt == target) + if (sourceType != source && targetType == target) { return false; } - TypeCode sc = st.GetTypeInfo().IsEnum ? TypeCode.Int64 : Type.GetTypeCode(st); - TypeCode tc = tt.GetTypeInfo().IsEnum ? TypeCode.Int64 : Type.GetTypeCode(tt); - switch (sc) + var sourceTypeCode = sourceType.GetTypeInfo().IsEnum ? TypeCode.Int32 : Type.GetTypeCode(sourceType); + var targetTypeCode = targetType.GetTypeInfo().IsEnum ? TypeCode.Int32 : Type.GetTypeCode(targetType); + switch (sourceTypeCode) { case TypeCode.SByte: - switch (tc) + switch (targetTypeCode) { case TypeCode.SByte: case TypeCode.Int16: @@ -110,7 +110,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.Byte: - switch (tc) + switch (targetTypeCode) { case TypeCode.Byte: case TypeCode.Int16: @@ -127,7 +127,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.Int16: - switch (tc) + switch (targetTypeCode) { case TypeCode.Int16: case TypeCode.Int32: @@ -140,7 +140,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.UInt16: - switch (tc) + switch (targetTypeCode) { case TypeCode.UInt16: case TypeCode.Int32: @@ -155,7 +155,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.Int32: - switch (tc) + switch (targetTypeCode) { case TypeCode.Int32: case TypeCode.Int64: @@ -167,7 +167,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.UInt32: - switch (tc) + switch (targetTypeCode) { case TypeCode.UInt32: case TypeCode.Int64: @@ -180,7 +180,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.Int64: - switch (tc) + switch (targetTypeCode) { case TypeCode.Int64: case TypeCode.Single: @@ -191,7 +191,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.UInt64: - switch (tc) + switch (targetTypeCode) { case TypeCode.UInt64: case TypeCode.Single: @@ -202,7 +202,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; case TypeCode.Single: - switch (tc) + switch (targetTypeCode) { case TypeCode.Single: case TypeCode.Double: @@ -211,7 +211,7 @@ public static bool IsCompatibleWith(Type source, Type target) break; default: - if (st == tt) + if (sourceType == targetType) { return true; } @@ -471,6 +471,11 @@ public static Type GetUnderlyingType(Type type) return type; } + public static bool TypesAreEqual(Type type, Type typeToCheck) + { + return GetNullableType(type) == GetNullableType(typeToCheck); + } + public static IList GetSelfAndBaseTypes(Type type, bool excludeObject = false) { if (type.GetTypeInfo().IsInterface) diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/TypeHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/TypeHelperTests.cs index c7a534ba..e6286834 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/TypeHelperTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/TypeHelperTests.cs @@ -45,7 +45,7 @@ public void TypeHelper_IsCompatibleWith_SameTypes_True() } [Fact] - public void TypeHelper_IsCompatibleWith_True() + public void TypeHelper_IsCompatibleWith_Int_And_Long_Returns_True() { // Assign + Act var result = TypeHelper.IsCompatibleWith(typeof(int), typeof(long)); @@ -54,8 +54,52 @@ public void TypeHelper_IsCompatibleWith_True() Check.That(result).IsTrue(); } + [Theory] + + // True (enum underlying Int32 compatible targets) + [InlineData(typeof(DayOfWeek), true)] + [InlineData(typeof(DayOfWeek?), true)] + [InlineData(typeof(int), true)] + [InlineData(typeof(int?), true)] + [InlineData(typeof(long), true)] + [InlineData(typeof(long?), true)] + [InlineData(typeof(float), true)] + [InlineData(typeof(float?), true)] + [InlineData(typeof(double), true)] + [InlineData(typeof(double?), true)] + [InlineData(typeof(decimal), true)] + [InlineData(typeof(decimal?), true)] + [InlineData(typeof(object), true)] + + // False (not compatible with enum's Int32 widening rules or reference types) + [InlineData(typeof(char), false)] + [InlineData(typeof(char?), false)] + [InlineData(typeof(short), false)] + [InlineData(typeof(short?), false)] + [InlineData(typeof(byte), false)] + [InlineData(typeof(byte?), false)] + [InlineData(typeof(sbyte), false)] + [InlineData(typeof(sbyte?), false)] + [InlineData(typeof(ushort), false)] + [InlineData(typeof(ushort?), false)] + [InlineData(typeof(uint), false)] + [InlineData(typeof(uint?), false)] + [InlineData(typeof(ulong), false)] + [InlineData(typeof(ulong?), false)] + [InlineData(typeof(bool), false)] + [InlineData(typeof(bool?), false)] + [InlineData(typeof(string), false)] + public void TypeHelper_IsCompatibleWith_Enum(Type targetType, bool expected) + { + // Assign + Act + var result = TypeHelper.IsCompatibleWith(typeof(DayOfWeek), targetType); + + // Assert + result.Should().Be(expected); + } + [Fact] - public void TypeHelper_IsCompatibleWith_False() + public void TypeHelper_IsCompatibleWith_Long_And_Int_Returns_False() { // Assign + Act var result = TypeHelper.IsCompatibleWith(typeof(long), typeof(int)); diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs index 364419a3..83d17e9c 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Select.cs @@ -202,6 +202,32 @@ public void Select_Dynamic_Add_Strings() Assert.Equal(range.Select(x => x + "c").ToArray(), rangeResult.Cast().ToArray()); } + [Fact] + public void Select_Dynamic_Add_DayOfWeekEnum_And_Integer() + { + // Arrange + var range = new DayOfWeek[] { DayOfWeek.Monday }; + + // Act + var rangeResult = range.AsQueryable().Select("it + 1"); + + // Assert + Assert.Equal(range.Select(x => x + 1).ToArray(), rangeResult.Cast().ToArray()); + } + + [Fact] + public void Select_Dynamic_Add_Integer_And_DayOfWeekEnum() + { + // Arrange + var range = new int[] { 1 }; + + // Act + var rangeResult = range.AsQueryable().Select("it + DayOfWeek.Monday"); + + // Assert + Assert.Equal(range.Select(x => x + DayOfWeek.Monday).Cast().ToArray(), rangeResult.Cast().ToArray()); + } + [Fact] public void Select_Dynamic_WithIncludes() {