|
4 | 4 | using TUnit.Mocks.Setup.Behaviors; |
5 | 5 | using TUnit.Mocks.Verification; |
6 | 6 | using System.Collections.Concurrent; |
| 7 | +using System.Collections.Immutable; |
7 | 8 | using System.Runtime.CompilerServices; |
8 | 9 | using System.Threading; |
9 | 10 | using System.ComponentModel; |
@@ -224,24 +225,24 @@ private void EnsureSetupArrayCapacity(int memberId) |
224 | 225 | ICallVerification IMockEngineAccess.CreateVerification(int memberId, string memberName, IArgumentMatcher[] matchers) |
225 | 226 | => new CallVerificationBuilder<T>(this, memberId, memberName, matchers); |
226 | 227 |
|
227 | | - ICallVerification ITypeArgumentVerificationFactory.CreateVerification(int memberId, string memberName, IArgumentMatcher[] matchers, Type[]? typeArguments) |
| 228 | + ICallVerification ITypeArgumentVerificationFactory.CreateVerification(int memberId, string memberName, IArgumentMatcher[] matchers, ImmutableArray<Type> typeArguments) |
228 | 229 | => new CallVerificationBuilder<T>(this, memberId, memberName, matchers, typeArguments); |
229 | 230 |
|
230 | 231 | /// <summary> |
231 | 232 | /// Handles a void method call. Records the call and executes matching setup behavior. |
232 | 233 | /// </summary> |
233 | 234 | public void HandleCall(int memberId, string memberName, object?[] args) |
234 | | - => HandleCallCore(memberId, memberName, args, null); |
| 235 | + => HandleCallCore(memberId, memberName, args, default); |
235 | 236 |
|
236 | 237 | /// <summary> |
237 | 238 | /// Handles a void generic-method call. <paramref name="typeArguments"/> are the concrete closed |
238 | 239 | /// type arguments, used to discriminate setups and recorded calls by type argument. |
239 | 240 | /// </summary> |
240 | 241 | [EditorBrowsable(EditorBrowsableState.Never)] |
241 | | - public void HandleCall(int memberId, string memberName, object?[] args, Type[] typeArguments) |
| 242 | + public void HandleCall(int memberId, string memberName, object?[] args, ImmutableArray<Type> typeArguments) |
242 | 243 | => HandleCallCore(memberId, memberName, args, typeArguments); |
243 | 244 |
|
244 | | - private void HandleCallCore(int memberId, string memberName, object?[] args, Type[]? typeArguments) |
| 245 | + private void HandleCallCore(int memberId, string memberName, object?[] args, ImmutableArray<Type> typeArguments) |
245 | 246 | { |
246 | 247 | RawReturnContext.Clear(); |
247 | 248 | var callRecord = RecordCall(memberId, memberName, args, typeArguments); |
@@ -296,29 +297,29 @@ private void HandleCallCore(int memberId, string memberName, object?[] args, Typ |
296 | 297 | /// or returns default/throws for strict mode. |
297 | 298 | /// </summary> |
298 | 299 | public TReturn HandleCallWithReturn<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue) |
299 | | - => HandleCallWithReturnCore(memberId, memberName, args, defaultValue, null, null); |
| 300 | + => HandleCallWithReturnCore(memberId, memberName, args, defaultValue, null, default); |
300 | 301 |
|
301 | 302 | [EditorBrowsable(EditorBrowsableState.Never)] |
302 | 303 | public TReturn HandleCallWithReturn<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue, Func<MockBehavior, IMock>? autoMockFactory) |
303 | | - => HandleCallWithReturnCore(memberId, memberName, args, defaultValue, autoMockFactory, null); |
| 304 | + => HandleCallWithReturnCore(memberId, memberName, args, defaultValue, autoMockFactory, default); |
304 | 305 |
|
305 | 306 | /// <summary> |
306 | 307 | /// Handles a generic-method call with a return value. <paramref name="typeArguments"/> are the |
307 | 308 | /// concrete closed type arguments, used to discriminate setups and recorded calls by type argument. |
308 | 309 | /// </summary> |
309 | 310 | [EditorBrowsable(EditorBrowsableState.Never)] |
310 | | - public TReturn HandleCallWithReturn<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue, Type[] typeArguments) |
| 311 | + public TReturn HandleCallWithReturn<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue, ImmutableArray<Type> typeArguments) |
311 | 312 | => HandleCallWithReturnCore(memberId, memberName, args, defaultValue, null, typeArguments); |
312 | 313 |
|
313 | 314 | /// <summary> |
314 | 315 | /// Generic-method call with a return value that may also auto-mock its return type. Combines the |
315 | 316 | /// <paramref name="autoMockFactory"/> and <paramref name="typeArguments"/> paths. |
316 | 317 | /// </summary> |
317 | 318 | [EditorBrowsable(EditorBrowsableState.Never)] |
318 | | - public TReturn HandleCallWithReturn<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue, Func<MockBehavior, IMock>? autoMockFactory, Type[] typeArguments) |
| 319 | + public TReturn HandleCallWithReturn<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue, Func<MockBehavior, IMock>? autoMockFactory, ImmutableArray<Type> typeArguments) |
319 | 320 | => HandleCallWithReturnCore(memberId, memberName, args, defaultValue, autoMockFactory, typeArguments); |
320 | 321 |
|
321 | | - private TReturn HandleCallWithReturnCore<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue, Func<MockBehavior, IMock>? autoMockFactory, Type[]? typeArguments) |
| 322 | + private TReturn HandleCallWithReturnCore<TReturn>(int memberId, string memberName, object?[] args, TReturn defaultValue, Func<MockBehavior, IMock>? autoMockFactory, ImmutableArray<Type> typeArguments) |
322 | 323 | { |
323 | 324 | RawReturnContext.Clear(); |
324 | 325 | var callRecord = RecordCall(memberId, memberName, args, typeArguments); |
@@ -785,7 +786,7 @@ private void CollectCallRecords(List<CallRecord> target, Func<CallRecord, bool>? |
785 | 786 | private CallRecord RecordCall(int memberId, string memberName, object?[] args) |
786 | 787 | => StoreCallRecord(new CallRecord(memberId, memberName, args, MockCallSequence.Next())); |
787 | 788 |
|
788 | | - private CallRecord RecordCall(int memberId, string memberName, object?[] args, Type[]? typeArguments) |
| 789 | + private CallRecord RecordCall(int memberId, string memberName, object?[] args, ImmutableArray<Type> typeArguments) |
789 | 790 | => StoreCallRecord(new CallRecord(memberId, memberName, args, MockCallSequence.Next(), typeArguments)); |
790 | 791 |
|
791 | 792 | private CallRecord RecordCall(int memberId, string memberName, IArgumentStore store) |
@@ -916,9 +917,9 @@ private void RebuildStaleSnapshots() |
916 | 917 | // Adding a defaulted third parameter here would let the generic overload bind instead, |
917 | 918 | // silently treating the args array as a single typed argument. |
918 | 919 | private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, object?[] args) |
919 | | - => FindMatchingSetup(memberId, args, (Type[]?)null); |
| 920 | + => FindMatchingSetup(memberId, args, default(ImmutableArray<Type>)); |
920 | 921 |
|
921 | | - private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, object?[] args, Type[]? typeArguments) |
| 922 | + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, object?[] args, ImmutableArray<Type> typeArguments) |
922 | 923 | { |
923 | 924 | // Rebuild snapshots if setup phase just ended (batches all ToArray work into one pass) |
924 | 925 | if (_hasStaleSetups) |
@@ -963,7 +964,7 @@ private void RebuildStaleSnapshots() |
963 | 964 |
|
964 | 965 | // FindMatchingSetupLocked has no generic sibling, so a defaulted parameter is safe here |
965 | 966 | // (unlike FindMatchingSetup, which competes with FindMatchingSetup<T1>). |
966 | | - private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetupLocked(int memberId, object?[] args, Type[]? typeArguments = null) |
| 967 | + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetupLocked(int memberId, object?[] args, ImmutableArray<Type> typeArguments = default) |
967 | 968 | { |
968 | 969 | lock (Lock) |
969 | 970 | { |
@@ -999,6 +1000,14 @@ private void RebuildStaleSnapshots() |
999 | 1000 | return (false, null, null); |
1000 | 1001 | } |
1001 | 1002 |
|
| 1003 | + // The typed FindMatchingSetup<T1..T8> family deliberately omits a TypeArgumentsMatch check. |
| 1004 | + // Typed dispatch never carries call-side type arguments, and per TypeArgumentMatching.Matches a |
| 1005 | + // default call side matches any setup — so the check would be a constant `true`. Two paths land |
| 1006 | + // here: non-generic methods (their setups never carry type arguments), and virtual/partial/wrap |
| 1007 | + // generic methods, which use the documented graceful-degradation path where setups are matched by |
| 1008 | + // arguments only, regardless of configured type arguments. Interface generic methods never use |
| 1009 | + // typed dispatch (see MockMembersBuilder.ShouldGenerateTypedWrapper) and are fully discriminated |
| 1010 | + // via the object?[] overloads above. |
1002 | 1011 | private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup<T1>(int memberId, T1 arg1) |
1003 | 1012 | { |
1004 | 1013 | if (_hasStaleSetups) RebuildStaleSnapshots(); |
|
0 commit comments