Skip to content

Commit fed12c4

Browse files
authored
Merge pull request #862 from JMolenkamp/fix-generic-any-type-matching
Fix matching generic calls with AnyType when the generic argument is also a generic argument in return type, out or ref parameter
2 parents d24adb5 + ef71a37 commit fed12c4

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

src/NSubstitute/Core/CallSpecification.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ internal static bool TypesAreAllEquivalent(Type[] aArgs, Type[] bArgs)
6767
var first = aArgs[i];
6868
var second = bArgs[i];
6969

70+
// Get the element types for ref and out parameters, important for matching calls when Arg.AnyType
71+
// is used in combination with ref or out parameters.
72+
if (first.HasElementType && !first.IsArray
73+
&& second.HasElementType && !second.IsArray)
74+
{
75+
first = first.GetElementType()!;
76+
second = second.GetElementType()!;
77+
}
78+
7079
if (first.IsGenericType && second.IsGenericType
7180
&& first.GetGenericTypeDefinition() == second.GetGenericTypeDefinition())
7281
{
@@ -93,7 +102,7 @@ internal static bool TypesAreAllEquivalent(Type[] aArgs, Type[] bArgs)
93102
private static bool AreEquivalentDefinitions(MethodInfo a, MethodInfo b)
94103
{
95104
return a.IsGenericMethod == b.IsGenericMethod
96-
&& a.ReturnType == b.ReturnType
105+
&& TypesAreAllEquivalent([a.ReturnType], [b.ReturnType])
97106
&& a.Name.Equals(b.Name, StringComparison.Ordinal);
98107
}
99108

tests/NSubstitute.Acceptance.Specs/GenericArguments.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections;
1+
using System.Collections;
22
using System.Globalization;
33
using NUnit.Framework;
44

@@ -11,6 +11,9 @@ public interface ISomethingWithGenerics
1111
{
1212
void SomeAction<TState>(int level, TState state);
1313
string SomeFunction<TState>(int level, TState state);
14+
ICollection<TState> SomeFunction<TState>(TState state);
15+
bool SomeFunctionWithOut<TState>(out IEnumerable<TState> state);
16+
bool SomeFunctionWithRef<TState>(ref IEnumerable<TState> state);
1417
void SomeActionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
1518
string SomeFunctionWithGenericConstraints<TState>(int level, TState state) where TState : IEnumerable<int>;
1619
}
@@ -131,4 +134,47 @@ public void Is_matcher_works_with_AnyType_and_constraints()
131134

132135
Assert.That(result, Is.EqualTo("matched"));
133136
}
137+
138+
[Test]
139+
public void Returns_works_with_AnyType_for_result_with_AnyType_generic_argument()
140+
{
141+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
142+
something
143+
.SomeFunction(Arg.Any<Arg.AnyType>())
144+
.Returns(x =>
145+
{
146+
return default!;
147+
});
148+
149+
ICollection<int> result = something.SomeFunction(7);
150+
151+
Assert.That(result, Is.Null);
152+
}
153+
154+
[Test]
155+
public void Returns_works_with_AnyType_for_out_parameter_with_AnyType_generic_argument()
156+
{
157+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
158+
something
159+
.SomeFunctionWithOut(out Arg.Any<IEnumerable<Arg.AnyType>>())
160+
.Returns(true);
161+
162+
bool result = something.SomeFunctionWithOut(out IEnumerable<int> value);
163+
164+
Assert.That(result, Is.True);
165+
}
166+
167+
[Test]
168+
public void Returns_works_with_AnyType_for_ref_parameter_with_AnyType_generic_argument()
169+
{
170+
ISomethingWithGenerics something = Substitute.For<ISomethingWithGenerics>();
171+
something
172+
.SomeFunctionWithRef(ref Arg.Any<IEnumerable<Arg.AnyType>>())
173+
.Returns(true);
174+
175+
IEnumerable<int> refParameter = null;
176+
bool result = something.SomeFunctionWithRef(ref refParameter);
177+
178+
Assert.That(result, Is.True);
179+
}
134180
}

0 commit comments

Comments
 (0)