Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix matching generic calls with AnyType when the generic argument is also a generic argument in return type, out or ref parameter #862

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/NSubstitute/Core/CallSpecification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ internal static bool TypesAreAllEquivalent(Type[] aArgs, Type[] bArgs)
var first = aArgs[i];
var second = bArgs[i];

// Get the element types for ref and out parameters, important for matching calls when Arg.AnyType
// is used in combination with ref or out parameters.
if (first.HasElementType && !first.IsArray
&& second.HasElementType && !second.IsArray)
{
first = first.GetElementType()!;
second = second.GetElementType()!;
}

if (first.IsGenericType && second.IsGenericType
&& first.GetGenericTypeDefinition() == second.GetGenericTypeDefinition())
{
Expand All @@ -93,7 +102,7 @@ internal static bool TypesAreAllEquivalent(Type[] aArgs, Type[] bArgs)
private static bool AreEquivalentDefinitions(MethodInfo a, MethodInfo b)
{
return a.IsGenericMethod == b.IsGenericMethod
&& a.ReturnType == b.ReturnType
&& TypesAreAllEquivalent([a.ReturnType], [b.ReturnType])
&& a.Name.Equals(b.Name, StringComparison.Ordinal);
}

Expand Down
48 changes: 47 additions & 1 deletion tests/NSubstitute.Acceptance.Specs/GenericArguments.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Globalization;
using NUnit.Framework;

Expand All @@ -11,6 +11,9 @@ public interface ISomethingWithGenerics
{
void SomeAction<TState>(int level, TState state);
string SomeFunction<TState>(int level, TState state);
ICollection<TState> SomeFunction<TState>(TState state);
bool SomeFunctionWithOut<TState>(out IEnumerable<TState> state);
bool SomeFunctionWithRef<TState>(ref IEnumerable<TState> state);
void SomeActionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
string SomeFunctionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
}
Expand Down Expand Up @@ -131,4 +134,47 @@ public void Is_matcher_works_with_AnyType_and_constraints()

Assert.That(result, Is.EqualTo("matched"));
}

[Test]
public void Returns_works_with_AnyType_for_result_with_AnyType_generic_argument()
{
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
something
.SomeFunction(Arg.Any<Arg.AnyType>())
.Returns(x =>
{
return default!;
});

ICollection<int> result = something.SomeFunction(7);

Assert.That(result, Is.Null);
}

[Test]
public void Returns_works_with_AnyType_for_out_parameter_with_AnyType_generic_argument()
{
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
something
.SomeFunctionWithOut(out Arg.Any<IEnumerable<Arg.AnyType>>())
.Returns(true);

bool result = something.SomeFunctionWithOut(out IEnumerable<int> value);

Assert.That(result, Is.True);
}

[Test]
public void Returns_works_with_AnyType_for_ref_parameter_with_AnyType_generic_argument()
{
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
something
.SomeFunctionWithRef(ref Arg.Any<IEnumerable<Arg.AnyType>>())
.Returns(true);

IEnumerable<int> refParameter = null;
bool result = something.SomeFunctionWithRef(ref refParameter);

Assert.That(result, Is.True);
}
}