diff --git a/src/System.Linq.Expressions/src/System/Dynamic/Utils/TypeUtils.cs b/src/System.Linq.Expressions/src/System/Dynamic/Utils/TypeUtils.cs index 45995c04e870..05585b28ce27 100644 --- a/src/System.Linq.Expressions/src/System/Dynamic/Utils/TypeUtils.cs +++ b/src/System.Linq.Expressions/src/System/Dynamic/Utils/TypeUtils.cs @@ -246,6 +246,70 @@ public static bool HasIdentityPrimitiveOrNullableConversionTo(this Type source, return false; } + public static bool IsValueTupleType(this Type type) => + type.IsConstructedGenericType && IsValueTupleType(type, type.GetGenericArguments().Length); + + private static bool IsValueTupleType(Type type, int argumentCount) + { + Debug.Assert(type.IsConstructedGenericType); + type = type.GetGenericTypeDefinition(); + switch (argumentCount) + { + case 1: + return type == typeof(ValueTuple<>); + case 2: + return type == typeof(ValueTuple<,>); + case 3: + return type == typeof(ValueTuple<,,>); + case 4: + return type == typeof(ValueTuple<,,,>); + case 5: + return type == typeof(ValueTuple<,,,,>); + case 6: + return type == typeof(ValueTuple<,,,,,>); + case 7: + return type == typeof(ValueTuple<,,,,,,>); + case 8: + return type == typeof(ValueTuple<,,,,,,,>); + } + + return false; + } + + public static bool HasTupleConversionTo(this Type source, Type dest) + { + source = source.GetNonNullableType(); + dest = dest.GetNonNullableType(); + if (source.IsConstructedGenericType && dest.IsConstructedGenericType) + { + Type[] sourceArgs = source.GetGenericArguments(); + int argLen = sourceArgs.Length; + if (IsValueTupleType(source, argLen)) + { + Type[] destArgs = dest.GetGenericArguments(); + if (destArgs.Length == argLen && IsValueTupleType(dest, argLen)) + { + for (int i = 0; i < sourceArgs.Length; ++i) + { + Type sourceArg = sourceArgs[i]; + Type destArg = destArgs[i]; + if (!sourceArg.HasIdentityPrimitiveOrNullableConversionTo(destArg) + && !sourceArg.HasReferenceConversionTo(destArg) + && !sourceArg.HasTupleConversionTo(destArg) + && GetUserDefinedCoercionMethod(sourceArg, destArg) == null) + { + return false; + } + } + + return true; + } + } + } + + return false; + } + public static bool HasReferenceConversionTo(this Type source, Type dest) { Debug.Assert(source != null && dest != null); diff --git a/src/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Unary.cs b/src/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Unary.cs index b9ed49e22c85..66bbc9404ce8 100644 --- a/src/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Unary.cs +++ b/src/System.Linq.Expressions/src/System/Linq/Expressions/Compiler/LambdaCompiler.Unary.cs @@ -352,18 +352,19 @@ private void EmitConvert(UnaryExpression node, CompilationFlags flags) { EmitExpressionAsVoid(node.Operand, flags); } + else if (TypeUtils.AreEquivalent(node.Operand.Type, node.Type)) + { + EmitExpression(node.Operand, flags); + } + else if (node.IsTupleConversion) + { + EmitExpression(node.ReduceTupleConversion(), flags); + } else { - if (TypeUtils.AreEquivalent(node.Operand.Type, node.Type)) - { - EmitExpression(node.Operand, flags); - } - else - { - // A conversion is emitted after emitting the operand, no tail call is emitted - EmitExpression(node.Operand); - _ilg.EmitConvertToType(node.Operand.Type, node.Type, node.NodeType == ExpressionType.ConvertChecked, this); - } + // A conversion is emitted after emitting the operand, no tail call is emitted + EmitExpression(node.Operand); + _ilg.EmitConvertToType(node.Operand.Type, node.Type, node.NodeType == ExpressionType.ConvertChecked, this); } } diff --git a/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs b/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs index 8a0a5ba88dc9..d035e55e5db4 100644 --- a/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs +++ b/src/System.Linq.Expressions/src/System/Linq/Expressions/Interpreter/LightCompiler.cs @@ -1072,6 +1072,10 @@ private void CompileConvertUnaryExpression(Expression expr) { CompileAsVoid(node.Operand); } + else if (node.IsTupleConversion) + { + Compile(node.ReduceTupleConversion()); + } else { Compile(node.Operand); diff --git a/src/System.Linq.Expressions/src/System/Linq/Expressions/UnaryExpression.cs b/src/System.Linq.Expressions/src/System/Linq/Expressions/UnaryExpression.cs index b309cd30e102..4833905ac190 100644 --- a/src/System.Linq.Expressions/src/System/Linq/Expressions/UnaryExpression.cs +++ b/src/System.Linq.Expressions/src/System/Linq/Expressions/UnaryExpression.cs @@ -287,6 +287,87 @@ public UnaryExpression Update(Expression operand) } return Expression.MakeUnary(NodeType, operand, Type, Method); } + + internal bool IsTupleConversion + { + get + { + Debug.Assert(NodeType == ExpressionType.Convert || NodeType == ExpressionType.ConvertChecked); + if (Method != null) + { + return false; + } + + Type type = Type.GetNonNullableType(); + Type opType = Operand.Type.GetNonNullableType(); + return type != opType && type.IsValueTupleType() && opType.IsValueTupleType(); + } + } + + internal Expression ReduceTupleConversion() + { + Debug.Assert(IsTupleConversion); + + Expression operand = Operand; + ParameterExpression nullableOperand = null; + if (operand.Type.IsNullableType()) + { + // Handle lifting of this type of conversion within the reduction + if (Type.IsNullableType()) + { + // We'll use nullableOperand to test and possibly skip the rest of the conversion + nullableOperand = Variable(operand.Type); + // Which means if the conversion is happening, it's guaranteed not to be null. + operand = Call(nullableOperand, operand.Type.GetMethod("GetValueOrDefault", Type.EmptyTypes)); + } + else + { + // Convert to non-nullable first, throwing if null. + operand = Convert(operand, operand.Type.GetNonNullableType()); + } + } + + ParameterExpression tuple = Variable(operand.Type); + Type type = Type.GetNonNullableType(); + Type[] destTypes = type.GetGenericArguments(); + Debug.Assert(destTypes.Length <= 8); + Expression[] conversions = new Expression[destTypes.Length]; + for (int i = 0; i < destTypes.Length; ++i) + { + Type itemType = destTypes[i]; + Expression field = Field(tuple, i == 7 ? "Rest" : $"Item{i + 1}"); + + if (field.Type == itemType) + { + // No conversion needed for this element. + conversions[i] = field; + } + else + { + UnaryExpression convItem = MakeUnary(NodeType, field, itemType); + conversions[i] = convItem.IsTupleConversion ? convItem.ReduceTupleConversion() : convItem; + } + } + + Expression conversion = Block(new[] {tuple}, Assign(tuple, operand), New(type.GetConstructors()[0], conversions)); + if (!ReferenceEquals(Type, type)) + { + // We've a conversion to non-nullable, add a further conversion to the nullable form. + conversion = Convert(conversion, Type); + + if (nullableOperand != null) + { + // If both source and destination types are nullable then if the operand is + // null we want to skip the conversion, and return null of the appropriate + // nullable type. + conversion = Block( + new[] {nullableOperand}, Assign(nullableOperand, Operand), + Condition(NotEqual(nullableOperand, Utils.Null), conversion, Default(Type))); + } + } + + return conversion; + } } public partial class Expression @@ -792,7 +873,8 @@ public static UnaryExpression Convert(Expression expression, Type type, MethodIn if (method == null) { if (expression.Type.HasIdentityPrimitiveOrNullableConversionTo(type) || - expression.Type.HasReferenceConversionTo(type)) + expression.Type.HasReferenceConversionTo(type) || + expression.Type.HasTupleConversionTo(type)) { return new UnaryExpression(ExpressionType.Convert, expression, type, null); } @@ -841,16 +923,25 @@ public static UnaryExpression ConvertChecked(Expression expression, Type type, M if (method == null) { - if (expression.Type.HasIdentityPrimitiveOrNullableConversionTo(type)) + Type opType = expression.Type; + if (opType.HasIdentityPrimitiveOrNullableConversionTo(type)) { return new UnaryExpression(ExpressionType.ConvertChecked, expression, type, null); } - if (expression.Type.HasReferenceConversionTo(type)) + + if (opType.HasReferenceConversionTo(type)) { return new UnaryExpression(ExpressionType.Convert, expression, type, null); } + + if (opType.HasTupleConversionTo(type)) + { + return new UnaryExpression(ExpressionType.ConvertChecked, expression, type, null); + } + return GetUserDefinedCoercionOrThrow(ExpressionType.ConvertChecked, expression, type); } + return GetMethodBasedCoercionOperator(ExpressionType.ConvertChecked, expression, type, method); } diff --git a/src/System.Linq.Expressions/tests/Convert/ConvertCheckedTests.cs b/src/System.Linq.Expressions/tests/Convert/ConvertCheckedTests.cs index 6831698ef578..bbfc8096ef38 100644 --- a/src/System.Linq.Expressions/tests/Convert/ConvertCheckedTests.cs +++ b/src/System.Linq.Expressions/tests/Convert/ConvertCheckedTests.cs @@ -6923,6 +6923,101 @@ public static void ConvertCheckedNullableUShortToNullableUShortTest(bool useInte } } + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedIntIntTupleToLongLongTest(bool useInterpreter) + { + foreach ((int, int) value in new[] { (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue) }) + { + VerifyCheckedIntIntTupleToLongLongTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedLongLongTupleToIntIntTest(bool useInterpreter) + { + foreach ((long, long) value in new[] { (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyCheckedLongLongTupleToIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedLongLongTupleToNullableIntIntTest(bool useInterpreter) + { + foreach ((long, long) value in new[] { (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyCheckedLongLongTupleToNullableIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedNullableLongLongTupleToNullableIntIntTest(bool useInterpreter) + { + foreach ((long, long)? value in new(long, long)?[] { null, (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyCheckedNullableLongLongTupleToNullableIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedNullableLongLongTupleToIntIntTest(bool useInterpreter) + { + foreach ((long, long)? value in new(long, long)?[] { null, (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyCheckedNullableLongLongTupleToIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedLongStringTupleToIntObjectTest(bool useInterpreter) + { + foreach ((long, string) value in new[] { (0, "hello"), (1, null), (0, "world"), (int.MinValue, "abc"), (long.MinValue, "123") }) + { + VerifyCheckedLongStringTupleToIntObjectTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedIntObjectToLongStringTest(bool useInterpreter) + { + foreach ((int, object) value in new(int, object)[] { (0, "hello"), (1, null), (0, "world"), (int.MinValue, "abc"), (int.MinValue, new Uri("http://example.net/")) }) + { + VerifyCheckedIntObjectToLongStringTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedHighArityLongToInt(bool useInterpreter) + { + (long, long, long, long, long, long, long, long, long, long, long, long)[] values = new(long, long, long, long, long, long, long, long, long, long, long, long)[] + { + (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), + (long.MinValue, int.MaxValue, 0, long.MaxValue, int.MinValue, int.MaxValue, 0, long.MaxValue, int.MinValue, int.MaxValue, 0, long.MaxValue), + (0, 2, 4, 8, 10, 12, 14, 16, 18, 20, 22, 24) + }; + + foreach ((long, long, long, long, long, long, long, long, long, long, long, long) value in values) + { + VerifyCheckedHighArityLongToInt(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertCheckedDeepTupleLongToInt(bool useInterpreter) + { + (((((((((((long, long), long), long), long), long), long), long), long), long), long), long)[] values = new(((((((((((long, long), long), long), long), long), long), long), long), long), long), long)[] + { + (((((((((((1, 2), 3), 4), 5), 6), 7), 8), 9), 10), 11), 12), + (((((((((((long.MinValue, int.MaxValue), 0), long.MaxValue), int.MinValue), int.MaxValue), 0), long.MaxValue), int.MinValue), int.MaxValue), 0), long.MaxValue), + (((((((((((0, 2), 4), 8), 10), 12), 14), 16), 18), 20), 22), 24) + }; + + foreach ((((((((((((long, long), long), long), long), long), long), long), long), long), long), long) value in values) + { + VerifyCheckedDeepTupleLongToInt(value, useInterpreter); + } + } + #endregion #region Test verifiers @@ -18397,6 +18492,134 @@ private static void VerifyCheckedNullableUShortToNullableUShort(ushort? value, b Assert.Equal(value, f()); } + + private static void VerifyCheckedIntIntTupleToLongLongTest((int, int) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value), typeof((long, long))), + Enumerable.Empty()); + Func<(long, long)> f = e.Compile(useInterpreter); + + Assert.Equal(value, f()); + } + + private static void VerifyCheckedLongLongTupleToIntIntTest((long, long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value), typeof((int, int))), + Enumerable.Empty()); + Func<(int, int)> f = e.Compile(useInterpreter); + + if (value.Item1 > int.MaxValue || value.Item1 < int.MinValue || value.Item2 > int.MaxValue || value.Item2 < int.MinValue) + Assert.Throws(() => f()); + else + Assert.Equal(((int, int))value, f()); + } + + private static void VerifyCheckedLongLongTupleToNullableIntIntTest((long, long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value), typeof((int, int)?)), + Enumerable.Empty()); + Func<(int, int)?> f = e.Compile(useInterpreter); + + if (value.Item1 > int.MaxValue || value.Item1 < int.MinValue || value.Item2 > int.MaxValue || value.Item2 < int.MinValue) + Assert.Throws(() => f()); + else + Assert.Equal(((int, int))value, f()); + } + + private static void VerifyCheckedNullableLongLongTupleToNullableIntIntTest((long, long)? value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value, typeof((long, long)?)), typeof((int, int)?)), + Enumerable.Empty()); + Func<(int, int)?> f = e.Compile(useInterpreter); + + if (value.HasValue && (value.Value.Item1 > int.MaxValue || value.Value.Item1 < int.MinValue || value.Value.Item2 > int.MaxValue || value.Value.Item2 < int.MinValue)) + Assert.Throws(() => f()); + else + Assert.Equal(((int, int)?)value, f()); + } + + private static void VerifyCheckedNullableLongLongTupleToIntIntTest((long, long)? value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value, typeof((long, long)?)), typeof((int, int))), + Enumerable.Empty()); + Func<(int, int)> f = e.Compile(useInterpreter); + + if (!value.HasValue) + Assert.Throws(() => f()); + else if (value.Value.Item1 > int.MaxValue || value.Value.Item1 < int.MinValue || value.Value.Item2 > int.MaxValue || value.Value.Item2 < int.MinValue) + Assert.Throws(() => f()); + else + Assert.Equal(((int, int))value, f()); + } + + private static void VerifyCheckedLongStringTupleToIntObjectTest((long, string) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value), typeof((int, object))), + Enumerable.Empty()); + Func<(int, object)> f = e.Compile(useInterpreter); + + if (value.Item1 > int.MaxValue || value.Item1 < int.MinValue) + Assert.Throws(() => f()); + else + Assert.Equal(((int, object))value, f()); + } + + private static void VerifyCheckedIntObjectToLongStringTest((int, object) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value), typeof((long, string))), + Enumerable.Empty()); + Func<(long, string)> f = e.Compile(useInterpreter); + + if (value.Item2 == null || value.Item2 is string) + Assert.Equal(((long, string))value, f()); + else + Assert.Throws(() => f()); + } + + private static void VerifyCheckedHighArityLongToInt( + (long, long, long, long, long, long, long, long, long, long, long, long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value), typeof((int, int, int, int, int, int, int, int, int, int, int, int))), + Enumerable.Empty()); + Func<(int, int, int, int, int, int, int, int, int, int, int, int)> f = e.Compile(useInterpreter); + + if (value.Item1 > int.MaxValue || value.Item1 < int.MinValue) + Assert.Throws(() => f()); + else + Assert.Equal(((int, int, int, int, int, int, int, int, int, int, int, int))value, f()); + } + + private static void VerifyCheckedDeepTupleLongToInt( + (((((((((((long, long), long), long), long), long), long), long), long), long), long), long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.ConvertChecked(Expression.Constant(value), typeof((((((((((((int, int), int), int), int), int), int), int), int), int), int), int))), + Enumerable.Empty()); + Func<(((((((((((int, int), int), int), int), int), int), int), int), int), int), int)> f = e.Compile(useInterpreter); + + if (value.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1 > int.MaxValue || value.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1.Item1 < int.MinValue) + Assert.Throws(() => f()); + else + Assert.Equal(((((((((((((int, int), int), int), int), int), int), int), int), int), int), int))value, f()); + } + #endregion [Fact] @@ -18442,5 +18665,13 @@ public static void ConvertCheckedMakeUnary(object source, object result, bool us Delegate del = lambda.Compile(useInterpreter); Assert.Equal(result, del.DynamicInvoke()); } + + [Fact] + public static void CantConvertTupleIfCantConvertElement() + { + ConstantExpression operand = Expression.Constant((0, "", 0, 0, 0, 0)); + Assert.Throws( + () => Expression.ConvertChecked(operand, typeof((long, long, long, long, long, long)))); + } } } diff --git a/src/System.Linq.Expressions/tests/Convert/ConvertTests.cs b/src/System.Linq.Expressions/tests/Convert/ConvertTests.cs index 0ac21cdd7582..324e21332cc7 100644 --- a/src/System.Linq.Expressions/tests/Convert/ConvertTests.cs +++ b/src/System.Linq.Expressions/tests/Convert/ConvertTests.cs @@ -6924,6 +6924,101 @@ public static void ConvertNullableUShortToNullableUShortTest(bool useInterpreter } } + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertIntIntTupleToLongLongTest(bool useInterpreter) + { + foreach ((int, int) value in new [] {(0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue)}) + { + VerifyIntIntTupleToLongLongTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertLongLongTupleToIntIntTest(bool useInterpreter) + { + foreach ((long, long) value in new[] { (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyLongLongTupleToIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertLongLongTupleToNullableIntIntTest(bool useInterpreter) + { + foreach ((long, long) value in new[] { (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyLongLongTupleToNullableIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertNullableLongLongTupleToNullableIntIntTest(bool useInterpreter) + { + foreach ((long, long)? value in new(long, long)?[] { null, (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyNullableLongLongTupleToNullableIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertNullableLongLongTupleToIntIntTest(bool useInterpreter) + { + foreach ((long, long)? value in new(long, long)?[] { null, (0, 0), (1, 0), (0, 1), (int.MinValue, int.MaxValue), (long.MinValue, long.MaxValue) }) + { + VerifyNullableLongLongTupleToIntIntTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertLongStringTupleToIntObjectTest(bool useInterpreter) + { + foreach ((long, string) value in new [] { (0, "hello"), (1, null), (0, "world"), (int.MinValue, "abc"), (long.MinValue, "123") }) + { + VerifyLongStringTupleToIntObjectTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertIntObjectToLongStringTest(bool useInterpreter) + { + foreach ((int, object) value in new (int, object)[] { (0, "hello"), (1, null), (0, "world"), (int.MinValue, "abc"), (int.MinValue, new Uri("http://example.net/")) }) + { + VerifyIntObjectToLongStringTest(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertHighArityLongToInt(bool useInterpreter) + { + (long, long, long, long, long, long, long, long, long, long, long, long)[] values = new (long, long, long, long, long, long, long, long, long, long, long, long)[] + { + (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), + (int.MinValue, int.MaxValue, 0, long.MaxValue, int.MinValue, int.MaxValue, 0, long.MaxValue, int.MinValue, int.MaxValue, 0, long.MaxValue), + (0, 2, 4, 8, 10, 12, 14, 16, 18, 20, 22, 24) + }; + + foreach ((long, long, long, long, long, long, long, long, long, long, long, long) value in values) + { + VerifyHighArityLongToInt(value, useInterpreter); + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void ConvertDeepTupleLongToInt(bool useInterpreter) + { + (((((((((((long, long), long), long), long), long), long), long), long), long), long), long)[] values = new(((((((((((long, long), long), long), long), long), long), long), long), long), long), long)[] + { + (((((((((((1, 2), 3), 4), 5), 6), 7), 8), 9), 10), 11), 12), + (((((((((((int.MinValue, int.MaxValue), 0), long.MaxValue), int.MinValue), int.MaxValue), 0), long.MaxValue), int.MinValue), int.MaxValue), 0), long.MaxValue), + (((((((((((0, 2), 4), 8), 10), 12), 14), 16), 18), 20), 22), 24) + }; + + foreach ((((((((((((long, long), long), long), long), long), long), long), long), long), long), long) value in values) + { + VerifyDeepTupleLongToInt(value, useInterpreter); + } + } + #endregion #region Test verifiers @@ -16458,6 +16553,113 @@ private static void VerifyNullableUShortToNullableUShort(ushort? value, bool use Assert.Equal(value, f()); } + private static void VerifyIntIntTupleToLongLongTest((int, int) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value), typeof((long, long))), + Enumerable.Empty()); + Func<(long, long)> f = e.Compile(useInterpreter); + + Assert.Equal(value, f()); + } + + private static void VerifyLongLongTupleToIntIntTest((long, long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value), typeof((int, int))), + Enumerable.Empty()); + Func<(int, int)> f = e.Compile(useInterpreter); + + Assert.Equal(((int, int))value, f()); + } + + private static void VerifyLongLongTupleToNullableIntIntTest((long, long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value), typeof((int, int)?)), + Enumerable.Empty()); + Func<(int, int)?> f = e.Compile(useInterpreter); + + Assert.Equal(((int, int))value, f()); + } + + private static void VerifyNullableLongLongTupleToNullableIntIntTest((long, long)? value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value, typeof((long, long)?)), typeof((int, int)?)), + Enumerable.Empty()); + Func<(int, int)?> f = e.Compile(useInterpreter); + + Assert.Equal(((int, int)?)value, f()); + } + + private static void VerifyNullableLongLongTupleToIntIntTest((long, long)? value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value, typeof((long, long)?)), typeof((int, int))), + Enumerable.Empty()); + Func<(int, int)> f = e.Compile(useInterpreter); + + if (value.HasValue) + Assert.Equal(((int, int))value, f()); + else + Assert.Throws(() => f()); + } + + private static void VerifyLongStringTupleToIntObjectTest((long, string) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value), typeof((int, object))), + Enumerable.Empty()); + Func<(int, object)> f = e.Compile(useInterpreter); + + Assert.Equal(((int, object))value, f()); + } + + private static void VerifyIntObjectToLongStringTest((int, object) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value), typeof((long, string))), + Enumerable.Empty()); + Func<(long, string)> f = e.Compile(useInterpreter); + + if (value.Item2 == null || value.Item2 is string) + Assert.Equal(((long, string))value, f()); + else + Assert.Throws(() => f()); + } + + private static void VerifyHighArityLongToInt( + (long, long, long, long, long, long, long, long, long, long, long, long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value), typeof((int, int, int, int, int, int, int, int, int, int, int, int))), + Enumerable.Empty()); + Func<(int, int, int, int, int, int, int, int, int, int, int, int)> f = e.Compile(useInterpreter); + + Assert.Equal(((int, int, int, int, int, int, int, int, int, int, int, int))value, f()); + } + + private static void VerifyDeepTupleLongToInt( + (((((((((((long, long), long), long), long), long), long), long), long), long), long), long) value, bool useInterpreter) + { + Expression> e = + Expression.Lambda>( + Expression.Convert(Expression.Constant(value), typeof((((((((((((int, int), int), int), int), int), int), int), int), int), int), int))), + Enumerable.Empty()); + Func<(((((((((((int, int), int), int), int), int), int), int), int), int), int), int)> f = e.Compile(useInterpreter); + + Assert.Equal(((((((((((((int, int), int), int), int), int), int), int), int), int), int), int))value, f()); + } + #endregion private class PerverselyNamedMembers @@ -16575,6 +16777,21 @@ public static void ExplicitHalfLiftedConversion(bool useInterpreter) Assert.Null(f(null)); } + [Theory, ClassData(typeof(CompilationTypes))] + public static void ExplicitHalfLiftedConversionInTuple(bool useInterpreter) + { + ParameterExpression x = Expression.Parameter(typeof((ExplicitHalfLiftedFrom?, int))); + Expression> e = + Expression.Lambda>( + Expression.Convert(x, typeof((HalfLiftedTo?, long))), + x); + Func<(ExplicitHalfLiftedFrom?, int), (HalfLiftedTo?, long)> f = e.Compile(useInterpreter); + Assert.NotNull(f((new ExplicitHalfLiftedFrom(), 23)).Item1); + Assert.Null(f((new ExplicitHalfLiftedFrom { NullEquiv = true }, 14)).Item1); + Assert.Null(f((null, 9)).Item1); + } + + [Theory, ClassData(typeof(CompilationTypes))] public static void ImplicitHalfLiftedOverloadedConversion(bool useInterpreter) { @@ -16808,5 +17025,43 @@ public static void CustomConversionNotStandardNameToExcessiveArity() MethodInfo method = typeof(CustomConversions).GetMethod(nameof(CustomConversions.FromAddition)); Assert.Throws(() => Expression.Convert(operand, typeof(int), method)); } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void TupleConversion(bool useInterpreter) + { + Expression operand = Expression.Constant((1, 2)); + Expression convert = Expression.Convert(operand, typeof((long, long))); + Func<(long, long)> func = Expression.Lambda>(convert).Compile(useInterpreter); + (long x, long y) = func(); + Assert.Equal(1L, x); + Assert.Equal(2L, y); + } + + private class CountedAccess + { + public int Count { get; private set; } + + public (int, int ) TupleProperty + { + get + { + ++Count; + return (1, 2); + } + } + } + + [Theory, ClassData(typeof(CompilationTypes))] + public static void TupleConvserionEvaluationsOperandOnlyOnce(bool useInterpreter) + { + CountedAccess counter = new CountedAccess(); + Expression> lambda = Expression.Lambda>( + Expression.Convert( + Expression.Property(Expression.Constant(counter), nameof(CountedAccess.TupleProperty)), + typeof((long, long)))); + Func<(long, long)> func = lambda.Compile(useInterpreter); + Assert.Equal((1L, 2L), func()); + Assert.Equal(1, counter.Count); + } } }