diff --git a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs index f89c971e2a..c8b23bf4f6 100644 --- a/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs +++ b/src/Adapter/MSTest.TestAdapter/Extensions/MethodInfoExtensions.cs @@ -197,6 +197,42 @@ internal static void InvokeAsSynchronousTask(this MethodInfo methodInfo, object? } } + private static void InferGenerics(Type parameterType, Type argumentType, List<(Type ParameterType, Type Substitution)> result) + { + if (parameterType.IsGenericMethodParameter()) + { + // We found a generic parameter. The argument type should be the substitution for it. + result.Add((parameterType, argumentType)); + return; + } + + if (!parameterType.ContainsGenericParameters) + { + // We don't have any generics. + return; + } + + if (parameterType.GetElementType() is { } parameterTypeElementType && + argumentType.GetElementType() is { } argumentTypeElementType) + { + // If we have arrays, we need to infer the generic types for the element types. + // For example, if parameterType is `T[]` and argumentType is `string[]`, we need to infer that `T` is `string`. + // So, we call InferGenerics with `T` and `string`. + InferGenerics(parameterTypeElementType, argumentTypeElementType, result); + return; + } + else if (parameterType.GenericTypeArguments.Length == argumentType.GenericTypeArguments.Length) + { + for (int i = 0; i < parameterType.GenericTypeArguments.Length; i++) + { + if (parameterType.GenericTypeArguments[i].ContainsGenericParameters) + { + InferGenerics(parameterType.GenericTypeArguments[i], argumentType.GenericTypeArguments[i], result); + } + } + } + } + // Scenarios to test: // // [DataRow(null, "Hello")] @@ -228,26 +264,30 @@ private static MethodInfo ConstructGenericMethod(MethodInfo methodInfo, object?[ for (int i = 0; i < parameters.Length; i++) { Type parameterType = parameters[i].ParameterType; - if (!parameterType.IsGenericMethodParameter() || arguments[i] is null) + if (!parameterType.ContainsGenericParameters || arguments[i] is null) { continue; } - Type substitution = arguments[i]!/*Very strange nullability warning*/.GetType(); - int mapIndexForParameter = GetMapIndexForParameterType(parameterType, map); - Type? existingSubstitution = map[mapIndexForParameter].Substitution; - - if (existingSubstitution is null || substitution.IsAssignableFrom(existingSubstitution)) - { - map[mapIndexForParameter] = (parameterType, substitution); - } - else if (existingSubstitution.IsAssignableFrom(substitution)) - { - // Do nothing. We already have a good existing substitution. - } - else + var result = new List<(Type ParameterType, Type Substitution)>(); + InferGenerics(parameterType, arguments[i]!/*Very strange nullability warning*/.GetType(), result); + foreach ((Type genericParameterType, Type substitution) in result) { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.GenericParameterConflict, parameterType.Name, existingSubstitution, substitution)); + int mapIndexForParameter = GetMapIndexForParameterType(genericParameterType, map); + Type? existingSubstitution = map[mapIndexForParameter].Substitution; + + if (existingSubstitution is null || substitution.IsAssignableFrom(existingSubstitution)) + { + map[mapIndexForParameter] = (genericParameterType, substitution); + } + else if (existingSubstitution.IsAssignableFrom(substitution)) + { + // Do nothing. We already have a good existing substitution. + } + else + { + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Resource.GenericParameterConflict, parameterType.Name, existingSubstitution, substitution)); + } } }