diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_Extension_Discovery.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_Extension_Discovery.verified.txt index 74df5e37c6..2fc30a83fe 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_Extension_Discovery.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_Extension_Discovery.verified.txt @@ -166,7 +166,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IRepository_string__GetById_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -236,7 +236,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IRepository_string__Save_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Class_Type_Argument.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Class_Type_Argument.verified.txt index e216d9570d..df55149d05 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Class_Type_Argument.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Class_Type_Argument.verified.txt @@ -149,7 +149,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public Sandbox_IFoo_Sandbox_Bar__Process_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((global::Sandbox.Bar)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Multiple_Non_Builtin_Type_Arguments.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Multiple_Non_Builtin_Type_Arguments.verified.txt index 2766fa1f4d..3f17e163f4 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Multiple_Non_Builtin_Type_Arguments.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Multiple_Non_Builtin_Type_Arguments.verified.txt @@ -143,7 +143,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public Sandbox_IMapper_Sandbox_Entity_Sandbox_Status__Map_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((global::Sandbox.Entity)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Nested_Namespace_Type_Argument.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Nested_Namespace_Type_Argument.verified.txt index 199200a7fd..1d1f0b1cee 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Nested_Namespace_Type_Argument.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Generic_Interface_With_Nested_Namespace_Type_Argument.verified.txt @@ -149,7 +149,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public Sandbox_IService_Outer_Inner_Config__Apply_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((global::Outer.Inner.Config)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt index 4804dd3118..2bcae59aa1 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_Inheriting_Multiple_Interfaces.verified.txt @@ -165,7 +165,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IReadWriter_Write_M2_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt index 5d526cb8d7..ee1bc297d7 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Async_Methods.verified.txt @@ -260,7 +260,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IAsyncService_GetValueAsync_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -415,7 +415,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IAsyncService_ComputeAsync_M2_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -492,7 +492,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IAsyncService_InitializeAsync_M3_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((global::System.Threading.CancellationToken)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt index c33be9fb39..ac17e57599 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt @@ -195,7 +195,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public INotifier_Notify_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt index edf7852f03..887078a01a 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Keyword_Parameter_Names.verified.txt @@ -171,7 +171,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public ITest_Test_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -251,7 +251,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public ITest_Get_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!, (string)args[1]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt index 3929abc96c..9e5754d76c 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Mixed_Members.verified.txt @@ -274,7 +274,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IService_GetAsync_M3_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -347,7 +347,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IService_Process_M4_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt index 7ec88b80ea..251905ed22 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Nullable_Reference_Type_Parameters.verified.txt @@ -288,7 +288,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFoo_Bar_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((object?)args[0])); + EnsureSetup().Callback(callback); return this; } @@ -368,7 +368,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFoo_GetValue_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string?)args[0], (int)args[1]!)); + EnsureSetup().Callback(callback); return this; } @@ -438,7 +438,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFoo_Process_M2_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!, (string?)args[1], (object?)args[2])); + EnsureSetup().Callback(callback); return this; } @@ -530,7 +530,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFoo_GetAsync_M3_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string?)args[0])); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt index ad3c04e3d2..ac808c4c62 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Out_Ref_Parameters.verified.txt @@ -172,7 +172,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IDictionary_TryGetValue_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -245,7 +245,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IDictionary_Swap_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!, (int)args[1]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt index 60705ed74e..1fe1e22119 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Overloaded_Methods.verified.txt @@ -265,7 +265,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFormatter_Format_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -345,7 +345,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFormatter_Format_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!)); + EnsureSetup().Callback(callback); return this; } @@ -425,7 +425,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFormatter_Format_M2_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!, (string)args[1]!)); + EnsureSetup().Callback(callback); return this; } @@ -505,7 +505,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IFormatter_Format_M3_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!, (string)args[1]!, (string)args[2]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt index f38f0b6214..42efb69368 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Static_Abstract_Transitive_Return_Type.verified.txt @@ -162,7 +162,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IMyService_GetValue_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt index 1aed69f470..e5aa3f1404 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt @@ -209,7 +209,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public ICalculator_Add_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!, (int)args[1]!)); + EnsureSetup().Callback(callback); return this; } @@ -289,7 +289,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public ICalculator_Subtract_M1_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((int)args[0]!, (int)args[1]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt index 8221261294..d138493695 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Internal_Virtual_Members_From_External_Assembly.verified.txt @@ -168,7 +168,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public ExternalLib_ExternalClient_PublicMethod_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt index fb6287604a..d9ee40cc69 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_Filters_Members_With_Internal_Signature_Types.verified.txt @@ -128,7 +128,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public ExternalLib_ServiceClient_GetValue_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt index d8ba2a836a..8ee3000f09 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Simple_Interface_With_One_Method.verified.txt @@ -143,7 +143,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public IGreeter_Greet_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Static_Extension_Discovery_Without_Mock_Of.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Static_Extension_Discovery_Without_Mock_Of.verified.txt index 9da98fb638..f4b9505f3f 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Static_Extension_Discovery_Without_Mock_Of.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Static_Extension_Discovery_Without_Mock_Of.verified.txt @@ -149,7 +149,7 @@ namespace TUnit.Mocks.Generated /// Execute a typed callback using the actual method parameters. public INotifier_Notify_M0_MockCall Callback(global::System.Action callback) { - EnsureSetup().Callback(args => callback((string)args[0]!)); + EnsureSetup().Callback(callback); return this; } diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs index cd378696e9..465a211023 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockMembersBuilder.cs @@ -432,12 +432,22 @@ private static void GenerateTypedCallbackOverload(CodeWriter writer, List p.FullyQualifiedType)); var actionType = $"global::System.Action<{typeList}>"; - var castArgs = BuildCastArgs(nonOutParams, allNonOutParams); writer.AppendLine("/// Execute a typed callback using the actual method parameters."); using (writer.Block($"public {wrapperName} Callback({actionType} callback)")) { - writer.AppendLine($"EnsureSetup().Callback(args => callback({castArgs}));"); + // allNonOutParams is null when this is the primary overload (no out/ref struct subset remapping). + // In that case the callback's parameter types match the typed Callback overload directly, + // so we can register it without a wrapping closure — avoiding the object?[] allocation. + if (allNonOutParams is null && nonOutParams.Count <= MaxTypedParams) + { + writer.AppendLine("EnsureSetup().Callback(callback);"); + } + else + { + var castArgs = BuildCastArgs(nonOutParams, allNonOutParams); + writer.AppendLine($"EnsureSetup().Callback(args => callback({castArgs}));"); + } writer.AppendLine("return this;"); } } diff --git a/TUnit.Mocks/MockEngine.Typed.cs b/TUnit.Mocks/MockEngine.Typed.cs index b12307eac6..3e775fced5 100644 --- a/TUnit.Mocks/MockEngine.Typed.cs +++ b/TUnit.Mocks/MockEngine.Typed.cs @@ -3,11 +3,52 @@ using TUnit.Mocks.Setup; using TUnit.Mocks.Setup.Behaviors; using System.ComponentModel; +using System.Runtime.CompilerServices; namespace TUnit.Mocks; public sealed partial class MockEngine where T : class { + // ── ARITY COUPLING (1–8) ────────────────────────────────────────────── + // Behavior execution helpers — check IArgumentFreeBehavior, then + // ITypedBehavior, then fall back to store.ToArray(). + // If you add an arity (e.g. T9), you MUST also update: + // - ITypedBehavior and TypedCallbackBehavior in TypedCallbackBehavior.cs + // - Callback in MethodSetupBuilder.cs and VoidMethodSetupBuilder.cs + // - MaxTypedParams in MockMembersBuilder.cs (source generator) + // ────────────────────────────────────────────────────────────────────── + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1) : b.Execute(store.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1, T2 a2) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1, a2) : b.Execute(store.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1, T2 a2, T3 a3) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1, a2, a3) : b.Execute(store.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1, T2 a2, T3 a3, T4 a4) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1, a2, a3, a4) : b.Execute(store.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1, a2, a3, a4, a5) : b.Execute(store.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1, a2, a3, a4, a5, a6) : b.Execute(store.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6, T7 a7) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1, a2, a3, a4, a5, a6, a7) : b.Execute(store.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object? ExecuteBehavior(IBehavior b, in ArgumentStore store, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6, T7 a7, T8 a8) + => b is IArgumentFreeBehavior af ? af.Execute() : b is ITypedBehavior tb ? tb.Execute(a1, a2, a3, a4, a5, a6, a7, a8) : b.Execute(store.ToArray()); // ────────────────────────────────────────────────────────────────────── // Arity 1 // ────────────────────────────────────────────────────────────────────── @@ -28,7 +69,7 @@ public void HandleCall(int memberId, string memberName, T1 arg1) if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -56,7 +97,7 @@ public TReturn HandleCallWithReturn(int memberId, string memberName if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -120,7 +161,7 @@ public bool TryHandleCall(int memberId, string memberName, T1 arg1) if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -147,7 +188,7 @@ public bool TryHandleCallWithReturn(int memberId, string memberName if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -186,7 +227,7 @@ public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2 if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -214,7 +255,7 @@ public TReturn HandleCallWithReturn(int memberId, string member if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1, arg2); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -278,7 +319,7 @@ public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 a if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -305,7 +346,7 @@ public bool TryHandleCallWithReturn(int memberId, string member if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -344,7 +385,7 @@ public void HandleCall(int memberId, string memberName, T1 arg1, T2 if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -372,7 +413,7 @@ public TReturn HandleCallWithReturn(int memberId, string me if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1, arg2, arg3); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -436,7 +477,7 @@ public bool TryHandleCall(int memberId, string memberName, T1 arg1, if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -463,7 +504,7 @@ public bool TryHandleCallWithReturn(int memberId, string me if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -502,7 +543,7 @@ public void HandleCall(int memberId, string memberName, T1 arg1, if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -530,7 +571,7 @@ public TReturn HandleCallWithReturn(int memberId, strin if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -594,7 +635,7 @@ public bool TryHandleCall(int memberId, string memberName, T1 ar if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -621,7 +662,7 @@ public bool TryHandleCallWithReturn(int memberId, strin if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -660,7 +701,7 @@ public void HandleCall(int memberId, string memberName, T1 a if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -688,7 +729,7 @@ public TReturn HandleCallWithReturn(int memberId, s if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -752,7 +793,7 @@ public bool TryHandleCall(int memberId, string memberName, T if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -779,7 +820,7 @@ public bool TryHandleCallWithReturn(int memberId, s if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -818,7 +859,7 @@ public void HandleCall(int memberId, string memberName, if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -846,7 +887,7 @@ public TReturn HandleCallWithReturn(int memberI if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -910,7 +951,7 @@ public bool TryHandleCall(int memberId, string memberNam if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -937,7 +978,7 @@ public bool TryHandleCallWithReturn(int memberI if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -976,7 +1017,7 @@ public void HandleCall(int memberId, string memberNa if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -1004,7 +1045,7 @@ public TReturn HandleCallWithReturn(int mem if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -1068,7 +1109,7 @@ public bool TryHandleCall(int memberId, string membe if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -1095,7 +1136,7 @@ public bool TryHandleCallWithReturn(int mem if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -1134,7 +1175,7 @@ public void HandleCall(int memberId, string memb if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -1162,7 +1203,7 @@ public TReturn HandleCallWithReturn(int if (behavior is not null) { - var result = behavior.Execute(store.ToArray()); + var result = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); if (result is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -1226,7 +1267,7 @@ public bool TryHandleCall(int memberId, string m if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } @@ -1253,7 +1294,7 @@ public bool TryHandleCallWithReturn(int if (behavior is not null) { - var behaviorResult = behavior.Execute(store.ToArray()); + var behaviorResult = ExecuteBehavior(behavior, store, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); try { ApplyMatchedSetup(matchedSetup); } catch { RawReturnContext.Clear(); throw; } diff --git a/TUnit.Mocks/MockEngine.cs b/TUnit.Mocks/MockEngine.cs index dae5f94c9c..54cee4b4ec 100644 --- a/TUnit.Mocks/MockEngine.cs +++ b/TUnit.Mocks/MockEngine.cs @@ -202,7 +202,7 @@ public void HandleCall(int memberId, string memberName, object?[] args) if (behavior is not null) { - var behaviorResult = behavior.Execute(args); + var behaviorResult = behavior is IArgumentFreeBehavior argFree ? argFree.Execute() : behavior.Execute(args); if (behaviorResult is RawReturn raw) { RawReturnContext.Set(raw); @@ -250,7 +250,7 @@ public TReturn HandleCallWithReturn(int memberId, string memberName, ob if (behavior is not null) { - var result = behavior.Execute(args); + var result = behavior is IArgumentFreeBehavior argFree ? argFree.Execute() : behavior.Execute(args); if (result is RawReturn raw) { RawReturnContext.Set(raw); diff --git a/TUnit.Mocks/Setup/Behaviors/CallbackBehavior.cs b/TUnit.Mocks/Setup/Behaviors/CallbackBehavior.cs index 7cb7d63aa8..98ceac185e 100644 --- a/TUnit.Mocks/Setup/Behaviors/CallbackBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/CallbackBehavior.cs @@ -1,12 +1,14 @@ namespace TUnit.Mocks.Setup.Behaviors; -internal sealed class CallbackBehavior : IBehavior +internal sealed class CallbackBehavior : IBehavior, IArgumentFreeBehavior { private readonly Action _callback; public CallbackBehavior(Action callback) => _callback = callback; - public object? Execute(object?[] arguments) + public object? Execute(object?[] arguments) => Execute(); + + public object? Execute() { _callback(); return null; diff --git a/TUnit.Mocks/Setup/Behaviors/CallbackWithArgsBehavior.cs b/TUnit.Mocks/Setup/Behaviors/CallbackWithArgsBehavior.cs index 3759656696..11ddd683c4 100644 --- a/TUnit.Mocks/Setup/Behaviors/CallbackWithArgsBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/CallbackWithArgsBehavior.cs @@ -6,6 +6,9 @@ namespace TUnit.Mocks.Setup.Behaviors; /// Behavior that invokes a callback with the method arguments. /// Public for generated code access. Not intended for direct use. /// +/// +/// Future optimization: implement ITypedBehavior<T...> to avoid store.ToArray() when args are needed. +/// [EditorBrowsable(EditorBrowsableState.Never)] public sealed class CallbackWithArgsBehavior : IBehavior { diff --git a/TUnit.Mocks/Setup/Behaviors/ComputedReturnBehavior.cs b/TUnit.Mocks/Setup/Behaviors/ComputedReturnBehavior.cs index a6faa10864..11d720d1db 100644 --- a/TUnit.Mocks/Setup/Behaviors/ComputedReturnBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/ComputedReturnBehavior.cs @@ -1,10 +1,12 @@ namespace TUnit.Mocks.Setup.Behaviors; -internal sealed class ComputedReturnBehavior : IBehavior +internal sealed class ComputedReturnBehavior : IBehavior, IArgumentFreeBehavior { private readonly Func _factory; public ComputedReturnBehavior(Func factory) => _factory = factory; - public object? Execute(object?[] arguments) => _factory(); + public object? Execute(object?[] arguments) => Execute(); + + public object? Execute() => _factory(); } diff --git a/TUnit.Mocks/Setup/Behaviors/ComputedReturnWithArgsBehavior.cs b/TUnit.Mocks/Setup/Behaviors/ComputedReturnWithArgsBehavior.cs index 2d7060a870..f306245e08 100644 --- a/TUnit.Mocks/Setup/Behaviors/ComputedReturnWithArgsBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/ComputedReturnWithArgsBehavior.cs @@ -6,6 +6,9 @@ namespace TUnit.Mocks.Setup.Behaviors; /// Behavior that computes a return value from the method arguments. /// Public for generated code access. Not intended for direct use. /// +/// +/// Future optimization: implement ITypedBehavior<T...> to avoid store.ToArray() when args are needed. +/// [EditorBrowsable(EditorBrowsableState.Never)] public sealed class ComputedReturnWithArgsBehavior : IBehavior { diff --git a/TUnit.Mocks/Setup/Behaviors/ComputedThrowBehavior.cs b/TUnit.Mocks/Setup/Behaviors/ComputedThrowBehavior.cs index beb4e6bba3..af0786b65a 100644 --- a/TUnit.Mocks/Setup/Behaviors/ComputedThrowBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/ComputedThrowBehavior.cs @@ -6,6 +6,9 @@ namespace TUnit.Mocks.Setup.Behaviors; /// Behavior that computes an exception from the method arguments and throws it. /// Public for generated code access. Not intended for direct use. /// +/// +/// Future optimization: implement ITypedBehavior<T...> to avoid store.ToArray() when args are needed. +/// [EditorBrowsable(EditorBrowsableState.Never)] public sealed class ComputedThrowBehavior : IBehavior { diff --git a/TUnit.Mocks/Setup/Behaviors/IBehavior.cs b/TUnit.Mocks/Setup/Behaviors/IBehavior.cs index 05914ef70e..15552d50f0 100644 --- a/TUnit.Mocks/Setup/Behaviors/IBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/IBehavior.cs @@ -11,3 +11,15 @@ public interface IBehavior { object? Execute(object?[] arguments); } + +/// +/// Marker interface for behaviors that do not use the method arguments. +/// Implementing this avoids the allocation of the boxed argument array on the invocation hot path. +/// Custom implementations that ignore arguments can implement this +/// to participate in the fast path. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public interface IArgumentFreeBehavior +{ + object? Execute(); +} diff --git a/TUnit.Mocks/Setup/Behaviors/RawReturnBehavior.cs b/TUnit.Mocks/Setup/Behaviors/RawReturnBehavior.cs index a8fc4de305..cf8dba4ba6 100644 --- a/TUnit.Mocks/Setup/Behaviors/RawReturnBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/RawReturnBehavior.cs @@ -1,23 +1,28 @@ namespace TUnit.Mocks.Setup.Behaviors; -internal sealed class RawReturnBehavior : IBehavior +internal sealed class RawReturnBehavior : IBehavior, IArgumentFreeBehavior { private readonly RawReturn _wrapper; public RawReturnBehavior(object? rawValue) => _wrapper = new RawReturn(rawValue); - public object? Execute(object?[] arguments) => _wrapper; + public object? Execute(object?[] arguments) => Execute(); + + public object? Execute() => _wrapper; } -internal sealed class ComputedRawReturnBehavior : IBehavior +internal sealed class ComputedRawReturnBehavior : IBehavior, IArgumentFreeBehavior { private readonly Func _factory; public ComputedRawReturnBehavior(Func factory) => _factory = factory; - public object? Execute(object?[] arguments) => new RawReturn(_factory()); + public object? Execute(object?[] arguments) => Execute(); + + public object? Execute() => new RawReturn(_factory()); } +// Future optimization: implement ITypedBehavior to avoid store.ToArray() when args are needed. internal sealed class ComputedRawReturnWithArgsBehavior : IBehavior { private readonly Func _factory; diff --git a/TUnit.Mocks/Setup/Behaviors/ReturnBehavior.cs b/TUnit.Mocks/Setup/Behaviors/ReturnBehavior.cs index f51371182c..7320546b21 100644 --- a/TUnit.Mocks/Setup/Behaviors/ReturnBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/ReturnBehavior.cs @@ -1,10 +1,12 @@ namespace TUnit.Mocks.Setup.Behaviors; -internal sealed class ReturnBehavior : IBehavior +internal sealed class ReturnBehavior : IBehavior, IArgumentFreeBehavior { private readonly TReturn _value; public ReturnBehavior(TReturn value) => _value = value; - public object? Execute(object?[] arguments) => _value; + public object? Execute(object?[] arguments) => Execute(); + + public object? Execute() => _value; } diff --git a/TUnit.Mocks/Setup/Behaviors/ThrowBehavior.cs b/TUnit.Mocks/Setup/Behaviors/ThrowBehavior.cs index b401dcddf4..bf5030f1e9 100644 --- a/TUnit.Mocks/Setup/Behaviors/ThrowBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/ThrowBehavior.cs @@ -1,10 +1,12 @@ namespace TUnit.Mocks.Setup.Behaviors; -internal sealed class ThrowBehavior : IBehavior +internal sealed class ThrowBehavior : IBehavior, IArgumentFreeBehavior { private readonly Exception _exception; public ThrowBehavior(Exception exception) => _exception = exception; - public object? Execute(object?[] arguments) => throw _exception; + public object? Execute(object?[] arguments) => Execute(); + + public object? Execute() => throw _exception; } diff --git a/TUnit.Mocks/Setup/Behaviors/TypedCallbackBehavior.cs b/TUnit.Mocks/Setup/Behaviors/TypedCallbackBehavior.cs new file mode 100644 index 0000000000..e09c54e759 --- /dev/null +++ b/TUnit.Mocks/Setup/Behaviors/TypedCallbackBehavior.cs @@ -0,0 +1,156 @@ +namespace TUnit.Mocks.Setup.Behaviors; + +/// +/// Typed behavior dispatch interfaces. ExecuteBehavior checks for these after IArgumentFreeBehavior, +/// enabling behaviors to receive typed arguments without boxing into object?[]. +/// Currently implemented by TypedCallbackBehavior; extensible for future typed return behaviors +/// (e.g. a TypedComputedReturnBehavior that takes Func<T1, TReturn>). +/// +/// +/// Intentionally internal: the typed dispatch is tightly coupled to the source generator's +/// knowledge of parameter arity — only generated code knows the concrete types at compile time. +/// is public because any behavior can opt in without type knowledge. +/// +internal interface ITypedBehavior +{ + object? Execute(T1 arg1); +} + +internal interface ITypedBehavior +{ + object? Execute(T1 arg1, T2 arg2); +} + +internal interface ITypedBehavior +{ + object? Execute(T1 arg1, T2 arg2, T3 arg3); +} + +internal interface ITypedBehavior +{ + object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4); +} + +internal interface ITypedBehavior +{ + object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); +} + +internal interface ITypedBehavior +{ + object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); +} + +internal interface ITypedBehavior +{ + object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); +} + +internal interface ITypedBehavior +{ + object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); +} + +// ── ARITY COUPLING (1–8) ────────────────────────────────────────────── +// If you add an arity (e.g. T9), you MUST also update: +// - ITypedBehavior above +// - ExecuteBehavior in MockEngine.Typed.cs +// - Callback in MethodSetupBuilder.cs and VoidMethodSetupBuilder.cs +// - MaxTypedParams in MockMembersBuilder.cs (source generator) +// ────────────────────────────────────────────────────────────────────── + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 1) throw new ArgumentException($"Expected at least 1 argument, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!); + return null; + } + + public object? Execute(T1 arg1) { callback(arg1); return null; } +} + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 2) throw new ArgumentException($"Expected at least 2 arguments, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!, (T2)arguments[1]!); + return null; + } + + public object? Execute(T1 arg1, T2 arg2) { callback(arg1, arg2); return null; } +} + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 3) throw new ArgumentException($"Expected at least 3 arguments, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!, (T2)arguments[1]!, (T3)arguments[2]!); + return null; + } + + public object? Execute(T1 arg1, T2 arg2, T3 arg3) { callback(arg1, arg2, arg3); return null; } +} + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 4) throw new ArgumentException($"Expected at least 4 arguments, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!, (T2)arguments[1]!, (T3)arguments[2]!, (T4)arguments[3]!); + return null; + } + + public object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { callback(arg1, arg2, arg3, arg4); return null; } +} + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 5) throw new ArgumentException($"Expected at least 5 arguments, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!, (T2)arguments[1]!, (T3)arguments[2]!, (T4)arguments[3]!, (T5)arguments[4]!); + return null; + } + + public object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { callback(arg1, arg2, arg3, arg4, arg5); return null; } +} + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 6) throw new ArgumentException($"Expected at least 6 arguments, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!, (T2)arguments[1]!, (T3)arguments[2]!, (T4)arguments[3]!, (T5)arguments[4]!, (T6)arguments[5]!); + return null; + } + + public object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { callback(arg1, arg2, arg3, arg4, arg5, arg6); return null; } +} + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 7) throw new ArgumentException($"Expected at least 7 arguments, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!, (T2)arguments[1]!, (T3)arguments[2]!, (T4)arguments[3]!, (T5)arguments[4]!, (T6)arguments[5]!, (T7)arguments[6]!); + return null; + } + + public object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) { callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7); return null; } +} + +internal sealed class TypedCallbackBehavior(Action callback) : IBehavior, ITypedBehavior +{ + public object? Execute(object?[] arguments) + { + if (arguments.Length < 8) throw new ArgumentException($"Expected at least 8 arguments, got {arguments.Length}.", nameof(arguments)); + callback((T1)arguments[0]!, (T2)arguments[1]!, (T3)arguments[2]!, (T4)arguments[3]!, (T5)arguments[4]!, (T6)arguments[5]!, (T7)arguments[6]!, (T8)arguments[7]!); + return null; + } + + public object? Execute(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) { callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); return null; } +} diff --git a/TUnit.Mocks/Setup/Behaviors/VoidReturnBehavior.cs b/TUnit.Mocks/Setup/Behaviors/VoidReturnBehavior.cs index f97398055d..d9bee1200c 100644 --- a/TUnit.Mocks/Setup/Behaviors/VoidReturnBehavior.cs +++ b/TUnit.Mocks/Setup/Behaviors/VoidReturnBehavior.cs @@ -1,8 +1,10 @@ namespace TUnit.Mocks.Setup.Behaviors; -internal sealed class VoidReturnBehavior : IBehavior +internal sealed class VoidReturnBehavior : IBehavior, IArgumentFreeBehavior { public static VoidReturnBehavior Instance { get; } = new(); - public object? Execute(object?[] arguments) => null; + public object? Execute(object?[] arguments) => Execute(); + + public object? Execute() => null; } diff --git a/TUnit.Mocks/Setup/MethodSetupBuilder.cs b/TUnit.Mocks/Setup/MethodSetupBuilder.cs index c81a4d1145..17fe4bb4ab 100644 --- a/TUnit.Mocks/Setup/MethodSetupBuilder.cs +++ b/TUnit.Mocks/Setup/MethodSetupBuilder.cs @@ -64,6 +64,66 @@ public ISetupChain Callback(Action callback) return this; } + // ── ARITY COUPLING (1–8): keep in sync with VoidMethodSetupBuilder, + // TypedCallbackBehavior.cs, MockEngine.Typed.cs, and MaxTypedParams in MockMembersBuilder.cs + + /// Typed callback overload emitted by the source generator. Avoids boxing arguments into object?[]. + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public ISetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + [EditorBrowsable(EditorBrowsableState.Never)] public ISetupChain Returns(Func factory) { diff --git a/TUnit.Mocks/Setup/VoidMethodSetupBuilder.cs b/TUnit.Mocks/Setup/VoidMethodSetupBuilder.cs index cf602bed48..05ddc34169 100644 --- a/TUnit.Mocks/Setup/VoidMethodSetupBuilder.cs +++ b/TUnit.Mocks/Setup/VoidMethodSetupBuilder.cs @@ -48,6 +48,66 @@ public IVoidSetupChain Callback(Action callback) return this; } + // ── ARITY COUPLING (1–8): keep in sync with MethodSetupBuilder, + // TypedCallbackBehavior.cs, MockEngine.Typed.cs, and MaxTypedParams in MockMembersBuilder.cs + + /// Typed callback overload emitted by the source generator. Avoids boxing arguments into object?[]. + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public IVoidSetupChain Callback(Action callback) + { + _setup.AddBehavior(new TypedCallbackBehavior(callback)); + return this; + } + [EditorBrowsable(EditorBrowsableState.Never)] public IVoidSetupChain Throws(Func exceptionFactory) {