Skip to content

Commit

Permalink
xunit/xunit#3007: xUnit1039 fires on generic methods with generic arr…
Browse files Browse the repository at this point in the history
…ay or enumerable argument
  • Loading branch information
bradwilson committed Aug 25, 2024
1 parent 3932a3e commit c83af22
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,27 @@ public void TestMethod((int a, int b) x) { }
await Verify.VerifyAnalyzerV3(LanguageVersion.CSharp8, source);
}

// https://github.com/xunit/xunit/issues/3007
[Theory]
[InlineData("T[]")]
[InlineData("IEnumerable<T>")]
public async Task WhenPassingArrayToGenericTheory_TheoryData_DoesNotTrigger(string enumerable)
{
var source = string.Format(/* lang=c#-test */ """
using System.Collections.Generic;
using Xunit;

public class TestClass {{
public static TheoryData<object[]> TestData = new TheoryData<object[]>();

[MemberData(nameof(TestData))]
public void PuzzleOne<T>({0} _1) {{ }}
}}
""", enumerable);

await Verify.VerifyAnalyzer(source);
}

[Fact]
public async Task WithExtraValueNotCompatibleWithParamsArray_TheoryData_Triggers()
{
Expand Down
13 changes: 9 additions & 4 deletions src/xunit.analyzers/Utility/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,31 +154,36 @@ public static bool IsInstanceOf(

public static ITypeSymbol? UnwrapEnumerable(
this ITypeSymbol? type,
Compilation compilation)
Compilation compilation,
bool unwrapArray = false)
{
if (type is null)
return null;

var iEnumerableOfT = TypeSymbolFactory.IEnumerableOfT(compilation);
var result = UnwrapEnumerable(type, iEnumerableOfT);
var result = UnwrapEnumerable(type, iEnumerableOfT, unwrapArray);

if (result is null)
{
var iAsyncEnumerableOfT = TypeSymbolFactory.IAsyncEnumerableOfT(compilation);
if (iAsyncEnumerableOfT is not null)
result = UnwrapEnumerable(type, iAsyncEnumerableOfT);
result = UnwrapEnumerable(type, iAsyncEnumerableOfT, unwrapArray);
}

return result;
}

public static ITypeSymbol? UnwrapEnumerable(
this ITypeSymbol? type,
ITypeSymbol enumerableType)
ITypeSymbol enumerableType,
bool unwrapArray = false)
{
if (type is null)
return null;

if (unwrapArray && type is IArrayTypeSymbol arrayType)
return arrayType.ElementType;

IEnumerable<INamedTypeSymbol> interfaces = type.AllInterfaces;
if (type is INamedTypeSymbol namedType)
interfaces = interfaces.Concat([namedType]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,14 @@ static void VerifyGenericArgumentTypes(
if (typeArgument is null)
continue;

// We want to map T[] and IEnumerable<T> in generic methods so long as the parameter type is itself an array.
// This is a simplistic view, since at runtime multiple things competing for the same T may end up being
// incompatible, but we'll leave that as an edge case that can be found at runtime, so we're not forced
// to run the whole "generic resolver" in the context of an analyzer.
var enumerable = parameterType.UnwrapEnumerable(semanticModel.Compilation, unwrapArray: true);
if (enumerable is not null && enumerable.Kind == SymbolKind.TypeParameter && typeArgument is IArrayTypeSymbol)
continue;

if (parameterType.Kind != SymbolKind.TypeParameter && !parameterType.IsAssignableFrom(typeArgument))
{
bool report = true;
Expand Down

0 comments on commit c83af22

Please sign in to comment.