From 145b4fcae5d38f0d1d521788c6d1bf0a0b4df12f Mon Sep 17 00:00:00 2001 From: Louis Zanella Date: Mon, 10 Jan 2022 00:30:21 -0500 Subject: [PATCH] PerformDefinedConversions handles multiple conflicting conversions (#2429) TypeUtility.PerformDefinedConversions can now handle the case where there is more than 1 "op_Implicit" or "op_Explicit" method --- src/xunit.v3.common/Utility/TypeUtility.cs | 50 ++++++++++--------- .../Acceptance/Xunit3TheoryAcceptanceTests.cs | 23 +++++++++ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/xunit.v3.common/Utility/TypeUtility.cs b/src/xunit.v3.common/Utility/TypeUtility.cs index 5addd2744..556a8e403 100644 --- a/src/xunit.v3.common/Utility/TypeUtility.cs +++ b/src/xunit.v3.common/Utility/TypeUtility.cs @@ -148,42 +148,46 @@ public static string ConvertToSimpleTypeName(_ITypeInfo type) if (argumentValue == null) return null; - var argumentValueType = argumentValue.GetType(); - - // We don't need to check if we're passing null to a value type here, as MethodInfo.Invoke does this - if (argumentValueType == null) - return argumentValue; - // No need to perform conversion - if (parameterType.IsAssignableFrom(argumentValueType)) + if (parameterType.IsAssignableFrom(argumentValue.GetType())) return argumentValue; - // Implicit & explicit conversions to/from a type can be declared on either side of the relationship - // We need to check both possibilities. - return PerformDefinedConversions(argumentValue, parameterType) - ?? PerformDefinedConversions(argumentValue, argumentValueType) - ?? argumentValue; + return PerformDefinedConversions(argumentValue, parameterType) ?? argumentValue; } static object? PerformDefinedConversions( object argumentValue, - Type conversionDeclaringType) + Type parameterType) { // argumentValue is known to not be null when we're called from TryConvertObject var argumentValueType = argumentValue.GetType(); - - var methodTypes = new Type[] { argumentValueType }; var methodArguments = new object[] { argumentValue }; - // Check if we can implicitly convert the argument type to the parameter type - var implicitMethod = conversionDeclaringType.GetRuntimeMethod("op_Implicit", methodTypes); - if (implicitMethod != null && implicitMethod.IsStatic && !IsByRefLikeType(implicitMethod.ReturnType)) - return implicitMethod.Invoke(null, methodArguments); + bool isMatchingOperator( + MethodInfo m, + string name) => + m.Name.Equals(name) && + m.IsSpecialName && // Filter out non-operator methods that might bear this reserved name + m.IsStatic && + !IsByRefLikeType(m.ReturnType) && + m.GetParameters().Length == 1 && + m.GetParameters()[0].ParameterType == argumentValueType && + parameterType.IsAssignableFrom(m.ReturnType); + + // Implicit & explicit conversions to/from a type can be declared on either side of the relationship. + // We need to check both possibilities. + foreach (var conversionDeclaringType in new[] { parameterType, argumentValueType }) + { + var runtimeMethods = conversionDeclaringType.GetRuntimeMethods(); - // Check if we can explicitly convert the argument type to the parameter type - var explicitMethod = conversionDeclaringType.GetRuntimeMethod("op_Explicit", methodTypes); - if (explicitMethod != null && explicitMethod.IsStatic && !IsByRefLikeType(explicitMethod.ReturnType)) - return explicitMethod.Invoke(null, methodArguments); + var implicitMethod = runtimeMethods.FirstOrDefault(m => isMatchingOperator(m, "op_Implicit")); + if (implicitMethod != null) + return implicitMethod.Invoke(null, methodArguments); + + var explicitMethod = runtimeMethods.FirstOrDefault(m => isMatchingOperator(m, "op_Explicit")); + if (explicitMethod != null) + return explicitMethod.Invoke(null, methodArguments); + } return null; } diff --git a/src/xunit.v3.core.tests/Acceptance/Xunit3TheoryAcceptanceTests.cs b/src/xunit.v3.core.tests/Acceptance/Xunit3TheoryAcceptanceTests.cs index 818ecc983..cd1b8632f 100644 --- a/src/xunit.v3.core.tests/Acceptance/Xunit3TheoryAcceptanceTests.cs +++ b/src/xunit.v3.core.tests/Acceptance/Xunit3TheoryAcceptanceTests.cs @@ -266,6 +266,8 @@ public async void ImplicitExplicitConversions() results.OfType<_TestPassed>().Select(passed => results.OfType<_TestStarting>().Where(ts => ts.TestUniqueID == passed.TestUniqueID).Single().TestDisplayName).OrderBy(x => x), displayName => Assert.Equal(@"Xunit3TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ArgumentDeclaredExplicitConversion(value: ""abc"")", displayName), displayName => Assert.Equal(@"Xunit3TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ArgumentDeclaredImplicitConversion(value: ""abc"")", displayName), + displayName => Assert.Equal(@"Xunit3TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.DecimalToInt(value: 43)", displayName), + displayName => Assert.Equal(@"Xunit3TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.IntToDecimal(value: 43)", displayName), displayName => Assert.Equal(@"Xunit3TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.IntToLong(i: 1)", displayName), displayName => Assert.Equal(@"Xunit3TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ParameterDeclaredExplicitConversion(e: Explicit { Value = ""abc"" })", displayName), displayName => Assert.Equal(@"Xunit3TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ParameterDeclaredImplicitConversion(i: Implicit { Value = ""abc"" })", displayName), @@ -329,6 +331,27 @@ public void UIntToULong(ulong i) Assert.Equal(1UL, i); } + public static IEnumerable DecimalArgument() + { + yield return new object[] { 43M }; + } + + // Decimal type offers multiple explicit conversions + [Theory] + [MemberData(nameof(DecimalArgument))] + public void DecimalToInt(int value) + { + Assert.Equal(43, value); + } + + // Decimal type offers multiple implicit conversions + [Theory] + [InlineData(43)] + public void IntToDecimal(decimal value) + { + Assert.Equal(43M, value); + } + public class Explicit { public string? Value { get; set; }