Skip to content

Commit

Permalink
PerformDefinedConversions handles multiple conflicting conversions
Browse files Browse the repository at this point in the history
TypeUtility.PerformDefinedConversions can now handle the case where there is more than 1 "op_Implicit" or "op_Explicit" method
  • Loading branch information
louis-z authored and bradwilson committed Jan 10, 2022
1 parent 59dea5b commit b88fee5
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 23 deletions.
51 changes: 28 additions & 23 deletions src/xunit.execution/Sdk/TypeUtility.cs
Expand Up @@ -130,48 +130,53 @@ public static object[] ResolveMethodArguments(this MethodBase testMethod, object

private static object TryConvertObject(object argumentValue, Type parameterType)
{
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;
if (argumentValue == null)
return null;

// 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;
}

private 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);

// 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);
// 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();

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;
}

private static bool IsByRefLikeType(Type type)
{
object val = type.GetType().GetRuntimeProperty("IsByRefLike")?.GetValue(type);
var val = type.GetType().GetRuntimeProperty("IsByRefLike")?.GetValue(type);
if (val is bool isByRefLike)
return isByRefLike;

Expand Down
Expand Up @@ -264,6 +264,8 @@ public void ImplicitExplicitConversions()
Assert.Collection(results.Cast<ITestPassed>().OrderBy(r => r.Test.DisplayName),
result => Assert.Equal(@"Xunit2TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ArgumentDeclaredExplicitConversion(value: ""abc"")", result.Test.DisplayName),
result => Assert.Equal(@"Xunit2TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ArgumentDeclaredImplicitConversion(value: ""abc"")", result.Test.DisplayName),
result => Assert.Equal(@"Xunit2TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.DecimalToInt(value: 43)", result.Test.DisplayName),
result => Assert.Equal(@"Xunit2TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.IntToDecimal(value: 43)", result.Test.DisplayName),
result => Assert.Equal(@"Xunit2TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.IntToLong(i: 1)", result.Test.DisplayName),
result => Assert.Equal(@"Xunit2TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ParameterDeclaredExplicitConversion(e: Explicit { Value = ""abc"" })", result.Test.DisplayName),
result => Assert.Equal(@"Xunit2TheoryAcceptanceTests+TheoryTests+ClassWithOperatorConversions.ParameterDeclaredImplicitConversion(i: Implicit { Value = ""abc"" })", result.Test.DisplayName),
Expand Down Expand Up @@ -325,6 +327,27 @@ public void UIntToULong(ulong i)
Assert.Equal(1UL, i);
}

public static IEnumerable<object[]> 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; }
Expand Down

0 comments on commit b88fee5

Please sign in to comment.