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 1b6eec6cad..74df5e37c6 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 @@ -40,12 +40,12 @@ namespace TUnit.Mocks.Generated public string GetById(int id) { - return _engine.HandleCallWithReturn(0, "GetById", new object?[] { id }, ""); + return _engine.HandleCallWithReturn(0, "GetById", id, ""); } public void Save(string entity) { - _engine.HandleCall(1, "Save", new object?[] { entity }); + _engine.HandleCall(1, "Save", entity); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 9cd676094c..4804dd3118 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 @@ -55,7 +55,7 @@ namespace TUnit.Mocks.Generated public void Write(string data) { - _engine.HandleCall(2, "Write", new object?[] { data }); + _engine.HandleCall(2, "Write", data); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 1f0dbff165..5d526cb8d7 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 @@ -43,7 +43,7 @@ namespace TUnit.Mocks.Generated { try { - var __result = _engine.HandleCallWithReturn(0, "GetValueAsync", new object?[] { key }, ""); + var __result = _engine.HandleCallWithReturn(0, "GetValueAsync", key, ""); if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) { if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; @@ -79,7 +79,7 @@ namespace TUnit.Mocks.Generated { try { - var __result = _engine.HandleCallWithReturn(2, "ComputeAsync", new object?[] { input }, default); + var __result = _engine.HandleCallWithReturn(2, "ComputeAsync", input, default); if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) { if (__rawAsync is global::System.Threading.Tasks.ValueTask __typedAsync) return __typedAsync; @@ -97,7 +97,7 @@ namespace TUnit.Mocks.Generated { try { - _engine.HandleCall(3, "InitializeAsync", new object?[] { ct }); + _engine.HandleCall(3, "InitializeAsync", ct); if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) { if (__rawAsync is global::System.Threading.Tasks.ValueTask __typedAsync) return __typedAsync; 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 71c2792229..c33be9fb39 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Events.verified.txt @@ -71,7 +71,7 @@ namespace TUnit.Mocks.Generated public void Notify(string message) { - _engine.HandleCall(0, "Notify", new object?[] { message }); + _engine.HandleCall(0, "Notify", message); } private global::System.EventHandler? _backing_ItemAdded; diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt index 06185cf045..fa4bd62369 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Interface_With_Generic_Methods.verified.txt @@ -42,17 +42,17 @@ namespace TUnit.Mocks.Generated public T GetById(int id) where T : class { - return _engine.HandleCallWithReturn(0, "GetById", new object?[] { id }, default!); + return _engine.HandleCallWithReturn(0, "GetById", id, default!); } public void Save(T entity) where T : class, new() { - _engine.HandleCall(1, "Save", new object?[] { entity }); + _engine.HandleCall(1, "Save", entity); } public TResult Transform(TInput input) where TInput : notnull where TResult : struct { - return _engine.HandleCallWithReturn(2, "Transform", new object?[] { input }, default); + return _engine.HandleCallWithReturn(2, "Transform", input, default); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 6022556ec1..edf7852f03 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 @@ -40,12 +40,12 @@ namespace TUnit.Mocks.Generated public void Test(string @event) { - _engine.HandleCall(0, "Test", new object?[] { @event }); + _engine.HandleCall(0, "Test", @event); } public string Get(int @class, string @return) { - return _engine.HandleCallWithReturn(1, "Get", new object?[] { @class, @return }, ""); + return _engine.HandleCallWithReturn(1, "Get", @class, @return, ""); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 fe3891fd6b..3929abc96c 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 @@ -79,7 +79,7 @@ namespace TUnit.Mocks.Generated { try { - var __result = _engine.HandleCallWithReturn(3, "GetAsync", new object?[] { id }, ""); + var __result = _engine.HandleCallWithReturn(3, "GetAsync", id, ""); if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) { if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; @@ -95,7 +95,7 @@ namespace TUnit.Mocks.Generated public void Process(string data) { - _engine.HandleCall(4, "Process", new object?[] { data }); + _engine.HandleCall(4, "Process", data); } public string Name 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 c0b3c48dd3..7ec88b80ea 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 @@ -49,24 +49,24 @@ namespace TUnit.Mocks.Generated public void Bar(object? baz) { - _engine.HandleCall(0, "Bar", new object?[] { baz }); + _engine.HandleCall(0, "Bar", baz); } public string? GetValue(string? key, int count) { - return _engine.HandleCallWithReturn(1, "GetValue", new object?[] { key, count }, default); + return _engine.HandleCallWithReturn(1, "GetValue", key, count, default); } public void Process(string nonNull, string? nullable, object? obj) { - _engine.HandleCall(2, "Process", new object?[] { nonNull, nullable, obj }); + _engine.HandleCall(2, "Process", nonNull, nullable, obj); } public global::System.Threading.Tasks.Task GetAsync(string? key) { try { - var __result = _engine.HandleCallWithReturn(3, "GetAsync", new object?[] { key }, default); + var __result = _engine.HandleCallWithReturn(3, "GetAsync", key, default); if (global::TUnit.Mocks.Setup.RawReturnContext.TryConsume(out var __rawAsync)) { if (__rawAsync is global::System.Threading.Tasks.Task __typedAsync) return __typedAsync; 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 afcc3331a7..ad3c04e3d2 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 @@ -41,7 +41,7 @@ namespace TUnit.Mocks.Generated public bool TryGetValue(string key, out string value) { value = default!; - var __result = _engine.HandleCallWithReturn(0, "TryGetValue", new object?[] { key }, default); + var __result = _engine.HandleCallWithReturn(0, "TryGetValue", key, default); var __outRef = global::TUnit.Mocks.Setup.OutRefContext.Consume(); if (__outRef is not null) { @@ -52,7 +52,7 @@ namespace TUnit.Mocks.Generated public void Swap(ref int a, ref int b) { - _engine.HandleCall(1, "Swap", new object?[] { a, b }); + _engine.HandleCall(1, "Swap", a, b); var __outRef = global::TUnit.Mocks.Setup.OutRefContext.Consume(); if (__outRef is not null) { 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 590c864d0a..60705ed74e 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 @@ -41,22 +41,22 @@ namespace TUnit.Mocks.Generated public string Format(string value) { - return _engine.HandleCallWithReturn(0, "Format", new object?[] { value }, ""); + return _engine.HandleCallWithReturn(0, "Format", value, ""); } public string Format(int value) { - return _engine.HandleCallWithReturn(1, "Format", new object?[] { value }, ""); + return _engine.HandleCallWithReturn(1, "Format", value, ""); } public string Format(string template, string arg1) { - return _engine.HandleCallWithReturn(2, "Format", new object?[] { template, arg1 }, ""); + return _engine.HandleCallWithReturn(2, "Format", template, arg1, ""); } public string Format(string template, string arg1, string arg2) { - return _engine.HandleCallWithReturn(3, "Format", new object?[] { template, arg1, arg2 }, ""); + return _engine.HandleCallWithReturn(3, "Format", template, arg1, arg2, ""); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 df4a7ab7f7..f38f0b6214 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 @@ -37,7 +37,7 @@ namespace TUnit.Mocks.Generated public string GetValue(string key) { - return _engine.HandleCallWithReturn(0, "GetValue", new object?[] { key }, ""); + return _engine.HandleCallWithReturn(0, "GetValue", key, ""); } public global::IConfigProvider GetConfigProvider() 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 de3108a4d6..1aed69f470 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Multi_Method_Interface.verified.txt @@ -42,12 +42,12 @@ namespace TUnit.Mocks.Generated public int Add(int a, int b) { - return _engine.HandleCallWithReturn(0, "Add", new object?[] { a, b }, default); + return _engine.HandleCallWithReturn(0, "Add", a, b, default); } public int Subtract(int a, int b) { - return _engine.HandleCallWithReturn(1, "Subtract", new object?[] { a, b }, default); + return _engine.HandleCallWithReturn(1, "Subtract", a, b, default); } public void Reset() 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 627bce21df..8221261294 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 @@ -17,7 +17,7 @@ namespace TUnit.Mocks.Generated.ExternalLib public override string PublicMethod(string input) { - if (_engine.TryHandleCallWithReturn(0, "PublicMethod", new object?[] { input }, "", out var __result)) + if (_engine.TryHandleCallWithReturn(0, "PublicMethod", input, "", out var __result)) { return __result; } 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 60890a5cae..fb6287604a 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 @@ -17,7 +17,7 @@ namespace TUnit.Mocks.Generated.ExternalLib public override string GetValue(string key) { - if (_engine.TryHandleCallWithReturn(0, "GetValue", new object?[] { key }, "", out var __result)) + if (_engine.TryHandleCallWithReturn(0, "GetValue", key, "", out var __result)) { return __result; } diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt index 52519c6564..23de6dc4a0 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Partial_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt @@ -17,7 +17,7 @@ namespace TUnit.Mocks.Generated public override T GetById(int id) { - if (_engine.TryHandleCallWithReturn(0, "GetById", new object?[] { id }, default!, out var __result)) + if (_engine.TryHandleCallWithReturn(0, "GetById", id, default!, out var __result)) { return __result; } @@ -26,7 +26,7 @@ namespace TUnit.Mocks.Generated public override void Save(T entity) { - if (_engine.TryHandleCall(1, "Save", new object?[] { entity })) + if (_engine.TryHandleCall(1, "Save", entity)) { return; } @@ -35,7 +35,7 @@ namespace TUnit.Mocks.Generated public override TResult Transform(TInput input) { - if (_engine.TryHandleCallWithReturn(2, "Transform", new object?[] { input }, default, out var __result)) + if (_engine.TryHandleCallWithReturn(2, "Transform", input, default, out var __result)) { return __result; } 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 94a8cfbaba..d8ba2a836a 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 @@ -35,7 +35,7 @@ namespace TUnit.Mocks.Generated public string Greet(string name) { - return _engine.HandleCallWithReturn(0, "Greet", new object?[] { name }, ""); + return _engine.HandleCallWithReturn(0, "Greet", name, ""); } [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] 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 a2f4245afe..9da98fb638 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 @@ -40,7 +40,7 @@ namespace TUnit.Mocks.Generated public void Notify(string message) { - _engine.HandleCall(0, "Notify", new object?[] { message }); + _engine.HandleCall(0, "Notify", message); } public string GetStatus() diff --git a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Wrap_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Wrap_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt index dcf36e227d..2edd07f51c 100644 --- a/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Wrap_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt +++ b/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Wrap_Mock_With_Generic_Constrained_Virtual_Methods.verified.txt @@ -19,7 +19,7 @@ namespace TUnit.Mocks.Generated public override T Get(int id) { - if (_engine.TryHandleCallWithReturn(0, "Get", new object?[] { id }, default!, out var __result)) + if (_engine.TryHandleCallWithReturn(0, "Get", id, default!, out var __result)) { return __result; } @@ -28,7 +28,7 @@ namespace TUnit.Mocks.Generated public override void Store(T item) { - if (_engine.TryHandleCall(1, "Store", new object?[] { item })) + if (_engine.TryHandleCall(1, "Store", item)) { return; } diff --git a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs index 158bec78f6..c4f6a59f22 100644 --- a/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs +++ b/TUnit.Mocks.SourceGenerator/Builders/MockImplBuilder.cs @@ -213,12 +213,14 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me } } - var argsArray = EmitArgsArrayVariable(writer, method); + var (isTyped, typeArgs, argsList) = GetTypedDispatchInfo(method); + var argsArray = isTyped ? null : EmitArgsArrayVariable(writer, method); + var argPassList = GetArgPassList(method); if (method.IsVoid && !method.IsAsync) { - writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))"); + writer.AppendLine($"if ({EmitTryHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)})"); writer.AppendLine("{"); writer.IncreaseIndent(); EmitOutRefReadback(writer, method); @@ -229,7 +231,7 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me } else if (method.IsVoid && method.IsAsync) { - writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))"); + writer.AppendLine($"if ({EmitTryHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)})"); writer.AppendLine("{"); writer.IncreaseIndent(); EmitOutRefReadback(writer, method); @@ -249,14 +251,14 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me { if (method.IsReturnTypeStaticAbstractInterface) { - writer.AppendLine($"if (_engine.TryHandleCallWithReturn({method.MemberId}, \"{method.Name}\", {argsArray}, null, out var __rawResult))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, "object?", method.MemberId, method.Name, "null", "__rawResult")})"); writer.AppendLine("{"); writer.IncreaseIndent(); writer.AppendLine($"var __result = ({method.UnwrappedReturnType})__rawResult!;"); } else { - writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{method.UnwrappedReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.UnwrappedSmartDefault}, out var __result))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, method.UnwrappedReturnType, method.MemberId, method.Name, method.UnwrappedSmartDefault, "__result")})"); writer.AppendLine("{"); writer.IncreaseIndent(); } @@ -275,7 +277,7 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me } else if (method.IsRefStructReturn) { - writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))"); + writer.AppendLine($"if ({EmitTryHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)})"); writer.AppendLine("{"); writer.IncreaseIndent(); if (method.SpanReturnElementType is not null) @@ -293,7 +295,7 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me } else if (method.IsReturnTypeStaticAbstractInterface) { - writer.AppendLine($"if (_engine.TryHandleCallWithReturn({method.MemberId}, \"{method.Name}\", {argsArray}, null, out var __rawResult))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, "object?", method.MemberId, method.Name, "null", "__rawResult")})"); writer.AppendLine("{"); writer.IncreaseIndent(); writer.AppendLine($"var __result = ({method.ReturnType})__rawResult!;"); @@ -305,7 +307,7 @@ private static void GenerateWrapMethodBody(CodeWriter writer, MockMemberModel me } else { - writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{method.ReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.SmartDefault}, out var __result))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, method.ReturnType, method.MemberId, method.Name, method.SmartDefault, "__result")})"); writer.AppendLine("{"); writer.IncreaseIndent(); EmitOutRefReadback(writer, method); @@ -552,13 +554,15 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel } } - var argsArray = EmitArgsArrayVariable(writer, method); + var (isTyped, typeArgs, argsList) = GetTypedDispatchInfo(method); + var argsArray = isTyped ? null : EmitArgsArrayVariable(writer, method); + var argPassList = GetArgPassList(method); if (method.IsVoid && !method.IsAsync) { // void virtual method - writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))"); + writer.AppendLine($"if ({EmitTryHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)})"); writer.AppendLine("{"); writer.IncreaseIndent(); EmitOutRefReadback(writer, method); @@ -570,7 +574,7 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel else if (method.IsVoid && method.IsAsync) { // async void virtual method (Task/ValueTask) - writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))"); + writer.AppendLine($"if ({EmitTryHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)})"); writer.AppendLine("{"); writer.IncreaseIndent(); EmitOutRefReadback(writer, method); @@ -592,14 +596,14 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel // async method with return (Task/ValueTask) if (method.IsReturnTypeStaticAbstractInterface) { - writer.AppendLine($"if (_engine.TryHandleCallWithReturn({method.MemberId}, \"{method.Name}\", {argsArray}, null, out var __rawResult))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, "object?", method.MemberId, method.Name, "null", "__rawResult")})"); writer.AppendLine("{"); writer.IncreaseIndent(); writer.AppendLine($"var __result = ({method.UnwrappedReturnType})__rawResult!;"); } else { - writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{method.UnwrappedReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.UnwrappedSmartDefault}, out var __result))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, method.UnwrappedReturnType, method.MemberId, method.Name, method.UnwrappedSmartDefault, "__result")})"); writer.AppendLine("{"); writer.IncreaseIndent(); } @@ -620,7 +624,7 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel else if (method.IsRefStructReturn) { // synchronous method returning ref struct — use void dispatch, fall back to base - writer.AppendLine($"if (_engine.TryHandleCall({method.MemberId}, \"{method.Name}\", {argsArray}))"); + writer.AppendLine($"if ({EmitTryHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)})"); writer.AppendLine("{"); writer.IncreaseIndent(); if (method.SpanReturnElementType is not null) @@ -638,7 +642,7 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel } else if (method.IsReturnTypeStaticAbstractInterface) { - writer.AppendLine($"if (_engine.TryHandleCallWithReturn({method.MemberId}, \"{method.Name}\", {argsArray}, null, out var __rawResult))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, "object?", method.MemberId, method.Name, "null", "__rawResult")})"); writer.AppendLine("{"); writer.IncreaseIndent(); writer.AppendLine($"var __result = ({method.ReturnType})__rawResult!;"); @@ -651,7 +655,7 @@ private static void GeneratePartialMethodBody(CodeWriter writer, MockMemberModel else { // synchronous method with return value - writer.AppendLine($"if (_engine.TryHandleCallWithReturn<{method.ReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.SmartDefault}, out var __result))"); + writer.AppendLine($"if ({EmitTryHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, method.ReturnType, method.MemberId, method.Name, method.SmartDefault, "__result")})"); writer.AppendLine("{"); writer.IncreaseIndent(); EmitOutRefReadback(writer, method); @@ -673,14 +677,15 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode } } - var argsArray = EmitArgsArrayVariable(writer, method); + var (isTyped, typeArgs, argsList) = GetTypedDispatchInfo(method); + var argsArray = isTyped ? null : EmitArgsArrayVariable(writer, method); var hasOutRef = HasOutRefParams(method); if (method.IsVoid && !method.IsAsync) { // Pure void method - writer.AppendLine($"_engine.HandleCall({method.MemberId}, \"{method.Name}\", {argsArray});"); + writer.AppendLine($"{EmitHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)};"); EmitOutRefReadback(writer, method); } else if (method.IsVoid && method.IsAsync) @@ -688,7 +693,7 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode // Async void method (Task or ValueTask with no generic arg) using (writer.Block("try")) { - writer.AppendLine($"_engine.HandleCall({method.MemberId}, \"{method.Name}\", {argsArray});"); + writer.AppendLine($"{EmitHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)};"); EmitOutRefReadback(writer, method); EmitRawReturnCheck(writer, method); if (method.IsValueTask) @@ -721,11 +726,11 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode { if (method.IsReturnTypeStaticAbstractInterface) { - writer.AppendLine($"var __result = ({method.UnwrappedReturnType})_engine.HandleCallWithReturn<{unwrappedArg}>({method.MemberId}, \"{method.Name}\", {argsArray}, {unwrappedDefault})!;"); + writer.AppendLine($"var __result = ({method.UnwrappedReturnType}){EmitHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, unwrappedArg, method.MemberId, method.Name, unwrappedDefault)}!;"); } else { - writer.AppendLine($"var __result = _engine.HandleCallWithReturn<{unwrappedArg}>({method.MemberId}, \"{method.Name}\", {argsArray}, {unwrappedDefault});"); + writer.AppendLine($"var __result = {EmitHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, unwrappedArg, method.MemberId, method.Name, unwrappedDefault)};"); } EmitOutRefReadback(writer, method); EmitRawReturnCheck(writer, method); @@ -755,7 +760,7 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode // Synchronous method returning a ref struct — can't use HandleCallWithReturn because // ref structs can't be generic type arguments. Use void dispatch for call tracking, // callbacks, and throws. - writer.AppendLine($"_engine.HandleCall({method.MemberId}, \"{method.Name}\", {argsArray});"); + writer.AppendLine($"{EmitHandleCall(isTyped, typeArgs, argsList, argsArray, method.MemberId, method.Name)};"); if (method.SpanReturnElementType is not null) { // Span return: read back out/ref params AND extract return value from OutRefContext index -1 @@ -773,13 +778,13 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode // as a generic type argument. Use object? and cast. if (hasOutRef) { - writer.AppendLine($"var __result = ({method.ReturnType})_engine.HandleCallWithReturn({method.MemberId}, \"{method.Name}\", {argsArray}, null)!;"); + writer.AppendLine($"var __result = ({method.ReturnType}){EmitHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, "object?", method.MemberId, method.Name, "null")}!;"); EmitOutRefReadback(writer, method); writer.AppendLine("return __result;"); } else { - writer.AppendLine($"return ({method.ReturnType})_engine.HandleCallWithReturn({method.MemberId}, \"{method.Name}\", {argsArray}, null)!;"); + writer.AppendLine($"return ({method.ReturnType}){EmitHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, "object?", method.MemberId, method.Name, "null")}!;"); } } else @@ -787,13 +792,13 @@ private static void GenerateEngineDispatchBody(CodeWriter writer, MockMemberMode // Synchronous method with return value — need to read back out/ref before returning if (hasOutRef) { - writer.AppendLine($"var __result = _engine.HandleCallWithReturn<{method.ReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.SmartDefault});"); + writer.AppendLine($"var __result = {EmitHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, method.ReturnType, method.MemberId, method.Name, method.SmartDefault)};"); EmitOutRefReadback(writer, method); writer.AppendLine("return __result;"); } else { - writer.AppendLine($"return _engine.HandleCallWithReturn<{method.ReturnType}>({method.MemberId}, \"{method.Name}\", {argsArray}, {method.SmartDefault});"); + writer.AppendLine($"return {EmitHandleCallWithReturn(isTyped, typeArgs, argsList, argsArray, method.ReturnType, method.MemberId, method.Name, method.SmartDefault)};"); } } } @@ -1119,6 +1124,43 @@ private static string FormatConstraintClauses(EquatableArray 0 ? " " + string.Join(' ', clauses) : ""; } + /// + /// Computes dispatch strategy for a method: typed (arity 1-8, no ref structs) or fallback (object?[]). + /// + private static (bool IsTyped, string? TypeArgs, string? ArgsList) GetTypedDispatchInfo(MockMemberModel method) + { + if (method.HasRefStructParams) return (false, null, null); + var nonOutParams = method.Parameters.Where(p => p.Direction != ParameterDirection.Out).ToList(); + if (nonOutParams.Count is < 1 or > 8) return (false, null, null); + var typeArgs = string.Join(", ", nonOutParams.Select(p => p.FullyQualifiedType)); + var argsList = string.Join(", ", nonOutParams.Select(p => p.Name)); + return (true, typeArgs, argsList); + } + + /// Emits a HandleCall or TryHandleCall invocation, choosing typed or fallback path. + private static string EmitHandleCall(bool isTyped, string? typeArgs, string? argsList, string? argsArray, int memberId, string memberName) + => isTyped + ? $"_engine.HandleCall<{typeArgs}>({memberId}, \"{memberName}\", {argsList})" + : $"_engine.HandleCall({memberId}, \"{memberName}\", {argsArray})"; + + /// Emits a HandleCallWithReturn invocation, choosing typed or fallback path. + private static string EmitHandleCallWithReturn(bool isTyped, string? typeArgs, string? argsList, string? argsArray, string returnTypeArg, int memberId, string memberName, string defaultValue) + => isTyped + ? $"_engine.HandleCallWithReturn<{returnTypeArg}, {typeArgs}>({memberId}, \"{memberName}\", {argsList}, {defaultValue})" + : $"_engine.HandleCallWithReturn<{returnTypeArg}>({memberId}, \"{memberName}\", {argsArray}, {defaultValue})"; + + /// Emits a TryHandleCall condition, choosing typed or fallback path. + private static string EmitTryHandleCall(bool isTyped, string? typeArgs, string? argsList, string? argsArray, int memberId, string memberName) + => isTyped + ? $"_engine.TryHandleCall<{typeArgs}>({memberId}, \"{memberName}\", {argsList})" + : $"_engine.TryHandleCall({memberId}, \"{memberName}\", {argsArray})"; + + /// Emits a TryHandleCallWithReturn condition, choosing typed or fallback path. + private static string EmitTryHandleCallWithReturn(bool isTyped, string? typeArgs, string? argsList, string? argsArray, string returnTypeArg, int memberId, string memberName, string defaultValue, string outVar) + => isTyped + ? $"_engine.TryHandleCallWithReturn<{returnTypeArg}, {typeArgs}>({memberId}, \"{memberName}\", {argsList}, {defaultValue}, out var {outVar})" + : $"_engine.TryHandleCallWithReturn<{returnTypeArg}>({memberId}, \"{memberName}\", {argsArray}, {defaultValue}, out var {outVar})"; + /// /// Returns true if the method has any out or ref parameters that need read-back. /// diff --git a/TUnit.Mocks/Arguments/ArgumentStore.cs b/TUnit.Mocks/Arguments/ArgumentStore.cs new file mode 100644 index 0000000000..52a116788f --- /dev/null +++ b/TUnit.Mocks/Arguments/ArgumentStore.cs @@ -0,0 +1,177 @@ +using System.ComponentModel; + +namespace TUnit.Mocks.Arguments; + +/// +/// Holds typed method arguments and defers boxing until is called. +/// Public for generated code access. Not intended for direct use. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public interface IArgumentStore +{ + /// Gets the number of arguments stored. + int Count { get; } + + /// Returns all arguments as a boxed array. Allocates on every call. + object?[] ToArray(); +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + /// + public int Count => 1; + + /// + public object?[] ToArray() => [Arg1]; +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1, T2 arg2) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + public readonly T2 Arg2 = arg2; + + /// + public int Count => 2; + + /// + public object?[] ToArray() => [Arg1, Arg2]; +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + public readonly T2 Arg2 = arg2; + + public readonly T3 Arg3 = arg3; + + /// + public int Count => 3; + + /// + public object?[] ToArray() => [Arg1, Arg2, Arg3]; +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + public readonly T2 Arg2 = arg2; + + public readonly T3 Arg3 = arg3; + + public readonly T4 Arg4 = arg4; + + /// + public int Count => 4; + + /// + public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4]; +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + public readonly T2 Arg2 = arg2; + + public readonly T3 Arg3 = arg3; + + public readonly T4 Arg4 = arg4; + + public readonly T5 Arg5 = arg5; + + /// + public int Count => 5; + + /// + public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5]; +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + public readonly T2 Arg2 = arg2; + + public readonly T3 Arg3 = arg3; + + public readonly T4 Arg4 = arg4; + + public readonly T5 Arg5 = arg5; + + public readonly T6 Arg6 = arg6; + + /// + public int Count => 6; + + /// + public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5, Arg6]; +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + public readonly T2 Arg2 = arg2; + + public readonly T3 Arg3 = arg3; + + public readonly T4 Arg4 = arg4; + + public readonly T5 Arg5 = arg5; + + public readonly T6 Arg6 = arg6; + + public readonly T7 Arg7 = arg7; + + /// + public int Count => 7; + + /// + public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7]; +} + +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public readonly struct ArgumentStore(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) : IArgumentStore +{ + public readonly T1 Arg1 = arg1; + + public readonly T2 Arg2 = arg2; + + public readonly T3 Arg3 = arg3; + + public readonly T4 Arg4 = arg4; + + public readonly T5 Arg5 = arg5; + + public readonly T6 Arg6 = arg6; + + public readonly T7 Arg7 = arg7; + + public readonly T8 Arg8 = arg8; + + /// + public int Count => 8; + + /// + public object?[] ToArray() => [Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8]; +} diff --git a/TUnit.Mocks/Arguments/CaptureMatcher.cs b/TUnit.Mocks/Arguments/CaptureMatcher.cs index 9648c39a60..ae99848d8f 100644 --- a/TUnit.Mocks/Arguments/CaptureMatcher.cs +++ b/TUnit.Mocks/Arguments/CaptureMatcher.cs @@ -11,12 +11,20 @@ internal interface ICapturingMatcher void ApplyCapture(object? value); } +/// +/// Generic interface for typed capture without boxing. +/// +internal interface ICapturingMatcher +{ + void ApplyCapture(T? value); +} + /// /// A decorator matcher that delegates to an inner matcher and captures /// argument values when the inner matcher returns . /// Every wraps its matcher in this decorator automatically. /// -internal sealed class CapturingMatcher : IArgumentMatcher, ICapturingMatcher +internal sealed class CapturingMatcher : IArgumentMatcher, ICapturingMatcher, ICapturingMatcher { private readonly IArgumentMatcher _inner; private ConcurrentQueue? _captured; @@ -87,5 +95,18 @@ void ICapturingMatcher.ApplyCapture(object? value) } } + /// + /// Typed capture path — avoids boxing for value types. + /// + void ICapturingMatcher.ApplyCapture(T? value) + { + if (_captured is null) + { + Interlocked.CompareExchange(ref _captured, new ConcurrentQueue(), null); + } + + _captured.Enqueue(value); + } + public string Describe() => _inner.Describe(); } diff --git a/TUnit.Mocks/MockEngine.Typed.cs b/TUnit.Mocks/MockEngine.Typed.cs new file mode 100644 index 0000000000..b12307eac6 --- /dev/null +++ b/TUnit.Mocks/MockEngine.Typed.cs @@ -0,0 +1,1279 @@ +using TUnit.Mocks.Arguments; +using TUnit.Mocks.Exceptions; +using TUnit.Mocks.Setup; +using TUnit.Mocks.Setup.Behaviors; +using System.ComponentModel; + +namespace TUnit.Mocks; + +public sealed partial class MockEngine where T : class +{ + // ────────────────────────────────────────────────────────────────────── + // Arity 1 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1); + var callRecord = RecordCall(memberId, memberName, store); + + if (AutoTrackProperties && memberName.StartsWith("set_", StringComparison.Ordinal)) + { + AutoTrackValues[memberName[4..]] = arg1; + } + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } + + // ────────────────────────────────────────────────────────────────────── + // Arity 2 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } + + // ────────────────────────────────────────────────────────────────────── + // Arity 3 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } + + // ────────────────────────────────────────────────────────────────────── + // Arity 4 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } + + // ────────────────────────────────────────────────────────────────────── + // Arity 5 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } + + // ────────────────────────────────────────────────────────────────────── + // Arity 6 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } + + // ────────────────────────────────────────────────────────────────────── + // Arity 7 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } + + // ────────────────────────────────────────────────────────────────────── + // Arity 8 + // ────────────────────────────────────────────────────────────────────── + + [EditorBrowsable(EditorBrowsableState.Never)] + public void HandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return; + + callRecord.IsUnmatched = true; + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public TReturn HandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, TReturn defaultValue) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + if (behavior is not null) + { + var result = behavior.Execute(store.ToArray()); + if (result is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (result is TReturn typed) return typed; + if (result is null) return default(TReturn)!; + if (result is RawReturn) return defaultValue; + throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {result.GetType().Name}."); + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) return defaultValue; + + callRecord.IsUnmatched = true; + + if (AutoTrackProperties && Volatile.Read(ref _autoTrackValues) is { } trackValues && memberName.StartsWith("get_", StringComparison.Ordinal)) + { + if (trackValues.TryGetValue(memberName[4..], out var trackedValue)) + { + if (trackedValue is TReturn t) return t; + if (trackedValue is null) return default(TReturn)!; + } + } + +#pragma warning disable IL3050, IL2026 + if (DefaultValueProvider is not null && DefaultValueProvider.CanProvide(typeof(TReturn))) + { + var customDefault = DefaultValueProvider.GetDefaultValue(typeof(TReturn)); + if (customDefault is TReturn typedCustom) return typedCustom; + if (customDefault is null) return default(TReturn)!; + } +#pragma warning restore IL3050, IL2026 + + if (Behavior == MockBehavior.Loose && typeof(TReturn).IsInterface) + { + var cacheKey = memberName + "|" + typeof(TReturn).FullName; + var autoMock = AutoMockCache.GetOrAdd(cacheKey, _ => + { + MockRegistry.TryCreateAutoMock(typeof(TReturn), Behavior, out var m); + return m; + }); + if (autoMock is not null) return (TReturn)autoMock.ObjectInstance; + } + + if (Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + + return defaultValue; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCall(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (!setupFound) callRecord.IsUnmatched = true; + if (!setupFound && IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + return setupFound; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool TryHandleCallWithReturn(int memberId, string memberName, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, TReturn defaultValue, out TReturn result) + { + RawReturnContext.Clear(); + var store = new ArgumentStore(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + var callRecord = RecordCall(memberId, memberName, store); + + var (setupFound, behavior, matchedSetup) = FindMatchingSetup(memberId, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + if (behavior is not null) + { + var behaviorResult = behavior.Execute(store.ToArray()); + if (behaviorResult is RawReturn raw) RawReturnContext.Set(raw); + try { ApplyMatchedSetup(matchedSetup); } + catch { RawReturnContext.Clear(); throw; } + if (behaviorResult is TReturn typed) result = typed; + else if (behaviorResult is null) result = default(TReturn)!; + else if (behaviorResult is RawReturn) result = defaultValue; + else throw new InvalidOperationException( + $"Setup for method returning {typeof(TReturn).Name} returned incompatible type {behaviorResult.GetType().Name}."); + return true; + } + + ApplyMatchedSetup(matchedSetup); + if (setupFound) { result = defaultValue; return true; } + + callRecord.IsUnmatched = true; + if (IsWrapMock && Behavior == MockBehavior.Strict) + { + throw new MockStrictBehaviorException(FormatCall(memberName, store)); + } + result = defaultValue; + return false; + } +} diff --git a/TUnit.Mocks/MockEngine.cs b/TUnit.Mocks/MockEngine.cs index bb9bebf91b..dae5f94c9c 100644 --- a/TUnit.Mocks/MockEngine.cs +++ b/TUnit.Mocks/MockEngine.cs @@ -21,7 +21,7 @@ internal static class MockCallSequence } [EditorBrowsable(EditorBrowsableState.Never)] -public sealed class MockEngine : IMockEngineAccess where T : class +public sealed partial class MockEngine : IMockEngineAccess where T : class { // Single lock for both setup and call mutations — reduces allocation by one Lock object. // Contention is acceptable since setup and call recording rarely overlap in typical usage. @@ -724,15 +724,19 @@ private void CollectCallRecords(List target, Predicate? } private CallRecord RecordCall(int memberId, string memberName, object?[] args) + => StoreCallRecord(new CallRecord(memberId, memberName, args, MockCallSequence.Next())); + + private CallRecord RecordCall(int memberId, string memberName, IArgumentStore store) + => StoreCallRecord(new CallRecord(memberId, memberName, store, MockCallSequence.Next())); + + private CallRecord StoreCallRecord(CallRecord record) { - var seq = MockCallSequence.Next(); - var record = new CallRecord(memberId, memberName, args, seq); lock (Lock) { - EnsureCallArrayCapacity(memberId); - var memberCalls = _callsByMemberId![memberId] ??= new(); + EnsureCallArrayCapacity(record.MemberId); + var memberCalls = _callsByMemberId![record.MemberId] ??= new(); memberCalls.Add(record); - _callCountByMemberId![memberId]++; + _callCountByMemberId![record.MemberId]++; } return record; } @@ -894,9 +898,207 @@ private void RebuildStaleSnapshots() return (false, null, null); } + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1, arg2]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1, arg2)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1, arg2); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1, arg2, arg3)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1, arg2, arg3); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1, arg2, arg3, arg4)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1, arg2, arg3, arg4); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1, arg2, arg3, arg4, arg5)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5, arg6]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1, arg2, arg3, arg4, arg5, arg6)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5, arg6); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5, arg6, arg7]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1, arg2, arg3, arg4, arg5, arg6, arg7)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + + private (bool SetupFound, IBehavior? Behavior, MethodSetup? Setup) FindMatchingSetup(int memberId, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + if (_hasStaleSetups) RebuildStaleSnapshots(); + if (_hasStatefulSetups) + return FindMatchingSetupLocked(memberId, [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]); + + var snapshot = _setupsByMemberId; + if (snapshot is null || (uint)memberId >= (uint)snapshot.Length) return (false, null, null); + var setups = snapshot[memberId]; + if (setups is null) return (false, null, null); + + for (int i = setups.Length - 1; i >= 0; i--) + { + var setup = setups[i]; + if (setup.Matches(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)) + { + setup.IncrementInvokeCount(); + setup.ApplyCaptures(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + return (true, setup.GetNextBehavior(), setup); + } + } + return (false, null, null); + } + private static string FormatCall(string memberName, object?[] args) { var formattedArgs = string.Join(", ", args.Select(a => a?.ToString() ?? "null")); return $"{memberName}({formattedArgs})"; } + + private static string FormatCall(string memberName, IArgumentStore store) + { + var formattedArgs = string.Join(", ", store.ToArray().Select(a => a?.ToString() ?? "null")); + return $"{memberName}({formattedArgs})"; + } } diff --git a/TUnit.Mocks/Setup/MethodSetup.cs b/TUnit.Mocks/Setup/MethodSetup.cs index 0f48291eca..7eb0d3a2bc 100644 --- a/TUnit.Mocks/Setup/MethodSetup.cs +++ b/TUnit.Mocks/Setup/MethodSetup.cs @@ -107,6 +107,155 @@ public bool Matches(object?[] actualArgs) return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool MatchSingle(IArgumentMatcher matcher, T value) + { + if (matcher is IArgumentMatcher typed) + return typed.Matches(value); + return matcher.Matches(value); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1) + { + if (_matchers.Length != 1) return false; + return MatchSingle(_matchers[0], arg1); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1, T2 arg2) + { + if (_matchers.Length != 2) return false; + return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1, T2 arg2, T3 arg3) + { + if (_matchers.Length != 3) return false; + return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + if (_matchers.Length != 4) return false; + return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + if (_matchers.Length != 5) return false; + return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + if (_matchers.Length != 6) return false; + return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5) && MatchSingle(_matchers[5], arg6); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + if (_matchers.Length != 7) return false; + return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5) && MatchSingle(_matchers[5], arg6) && MatchSingle(_matchers[6], arg7); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool Matches(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + if (_matchers.Length != 8) return false; + return MatchSingle(_matchers[0], arg1) && MatchSingle(_matchers[1], arg2) && MatchSingle(_matchers[2], arg3) && MatchSingle(_matchers[3], arg4) && MatchSingle(_matchers[4], arg5) && MatchSingle(_matchers[5], arg6) && MatchSingle(_matchers[6], arg7) && MatchSingle(_matchers[7], arg8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void CaptureSingle(IArgumentMatcher matcher, T value) + { + if (matcher is ICapturingMatcher typed) + typed.ApplyCapture(value); + else if (matcher is ICapturingMatcher untyped) + untyped.ApplyCapture(value); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1, T2 arg2) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2); + if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2); + if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3); + if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2); + if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3); + if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4); + if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2); + if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3); + if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4); + if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5); + if (_matchers.Length >= 6) CaptureSingle(_matchers[5], arg6); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2); + if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3); + if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4); + if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5); + if (_matchers.Length >= 6) CaptureSingle(_matchers[5], arg6); + if (_matchers.Length >= 7) CaptureSingle(_matchers[6], arg7); + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public void ApplyCaptures(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + if (_matchers.Length >= 1) CaptureSingle(_matchers[0], arg1); + if (_matchers.Length >= 2) CaptureSingle(_matchers[1], arg2); + if (_matchers.Length >= 3) CaptureSingle(_matchers[2], arg3); + if (_matchers.Length >= 4) CaptureSingle(_matchers[3], arg4); + if (_matchers.Length >= 5) CaptureSingle(_matchers[4], arg5); + if (_matchers.Length >= 6) CaptureSingle(_matchers[5], arg6); + if (_matchers.Length >= 7) CaptureSingle(_matchers[6], arg7); + if (_matchers.Length >= 8) CaptureSingle(_matchers[7], arg8); + } + public void AddEventRaise(EventRaiseInfo raiseInfo) { lock (BehaviorLock) diff --git a/TUnit.Mocks/Verification/CallRecord.cs b/TUnit.Mocks/Verification/CallRecord.cs index aa9963fdeb..3741aa43ad 100644 --- a/TUnit.Mocks/Verification/CallRecord.cs +++ b/TUnit.Mocks/Verification/CallRecord.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using TUnit.Mocks.Arguments; namespace TUnit.Mocks.Verification; @@ -6,13 +7,57 @@ namespace TUnit.Mocks.Verification; /// Records a single method invocation. Public for generated code and verification access. /// [EditorBrowsable(EditorBrowsableState.Never)] -public sealed record CallRecord( - int MemberId, - string MemberName, - object?[] Arguments, - long SequenceNumber -) +public sealed class CallRecord { + private readonly IArgumentStore? _store; + private object?[]? _arguments; + + /// + /// Creates a call record with pre-boxed arguments (fallback path). + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public CallRecord(int memberId, string memberName, object?[] arguments, long sequenceNumber) + { + MemberId = memberId; + MemberName = memberName; + _arguments = arguments; + SequenceNumber = sequenceNumber; + } + + /// + /// Creates a call record with a typed argument store for deferred boxing. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public CallRecord(int memberId, string memberName, IArgumentStore store, long sequenceNumber) + { + MemberId = memberId; + MemberName = memberName; + _store = store; + SequenceNumber = sequenceNumber; + } + + /// The unique identifier for the member that was called. + public int MemberId { get; } + + /// The name of the member that was called. + public string MemberName { get; } + + /// The global sequence number for cross-mock ordering. + public long SequenceNumber { get; } + + /// + /// The arguments passed to the call. Lazily materialized from the argument store if one was provided. + /// + public object?[] Arguments + { + get + { + if (_arguments is not null) return _arguments; + var arr = _store?.ToArray() ?? []; + return Interlocked.CompareExchange(ref _arguments, arr, null) ?? arr; + } + } + internal bool IsVerifiedField; ///