diff --git a/Source/Mockolate.Analyzers/MockabilityAnalyzer.cs b/Source/Mockolate.Analyzers/MockabilityAnalyzer.cs index 6f92f67c..5d3a4095 100644 --- a/Source/Mockolate.Analyzers/MockabilityAnalyzer.cs +++ b/Source/Mockolate.Analyzers/MockabilityAnalyzer.cs @@ -116,7 +116,7 @@ private static void ReportRefStructIssuesForType(SyntaxNodeAnalysisContext conte if (type.TypeKind == TypeKind.Delegate) { if (type is INamedTypeSymbol { DelegateInvokeMethod: { } invoke, } && - TryGetRefStructIssue(invoke, pipelineUnsupportedReason, out string? delegateIssue)) + TryGetRefStructIssue(invoke, pipelineUnsupportedReason, out string? delegateIssue, isDelegate: true)) { context.ReportDiagnostic(Diagnostic.Create( s_refStructRule, @@ -292,7 +292,7 @@ private static bool NeedsRefStructPipeline(ITypeSymbol type) } private static bool TryGetRefStructIssue(IMethodSymbol method, string? pipelineUnsupportedReason, - out string? issue) + out string? issue, bool isDelegate = false) { bool hasRefStructParam = false; foreach (IParameterSymbol p in method.Parameters) @@ -304,9 +304,14 @@ private static bool TryGetRefStructIssue(IMethodSymbol method, string? pipelineU hasRefStructParam = true; - if (p.RefKind is RefKind.Out or RefKind.Ref or RefKind.RefReadOnlyParameter) + // Delegates don't go through the ref-struct setup pipeline at all, so any ref-struct + // parameter (by-value or by-ref) is unsupported on delegate Invoke methods — the + // emitted VoidMethodSetup / ReturnMethodSetup lacks an 'allows ref struct' + // constraint. Interface/class methods route through the IOutRefStructParameter / + // IRefRefStructParameter pipeline. + if (isDelegate) { - issue = "out/ref ref-struct parameters are not supported"; + issue = "ref-struct parameters are not supported on delegate types"; return true; } } diff --git a/Source/Mockolate.SourceGenerators/Helpers.cs b/Source/Mockolate.SourceGenerators/Helpers.cs index cda85ac2..d157d52d 100644 --- a/Source/Mockolate.SourceGenerators/Helpers.cs +++ b/Source/Mockolate.SourceGenerators/Helpers.cs @@ -130,8 +130,25 @@ public static bool NeedsRefStructPipeline(this Type type) => type.IsRefStruct && type.SpecialGenericType is not (SpecialGenericType.Span or SpecialGenericType.ReadOnlySpan); + /// + /// Returns true if the parameter must flow through the ref-struct setup pipeline. The + /// overload excludes Span<T> and ReadOnlySpan<T> + /// because by-value, out, and ref positions use the + /// SpanWrapper<T> / ReadOnlySpanWrapper<T> non-ref-struct carve-out. + /// ref readonly Span/ROS, however, has no dedicated wrapper-based emit branch, so it + /// falls back to the generic ref-struct pipeline. + /// public static bool NeedsRefStructPipeline(this MethodParameter parameter) - => parameter.Type.NeedsRefStructPipeline(); + { + if (parameter.Type.NeedsRefStructPipeline()) + { + return true; + } + + return parameter.RefKind == RefKind.RefReadOnlyParameter + && parameter.Type.IsRefStruct + && parameter.Type.SpecialGenericType is (SpecialGenericType.Span or SpecialGenericType.ReadOnlySpan); + } extension(ITypeSymbol typeSymbol) { @@ -454,24 +471,47 @@ public string ToTypeOrWrapper() public string ToParameter() { - return (parameter.RefKind, parameter.Type.SpecialGenericType) switch + bool needsRefStructPipeline = parameter.NeedsRefStructPipeline(); + return (parameter.RefKind, parameter.Type.SpecialGenericType, needsRefStructPipeline) switch { - (RefKind.Ref, _) => $"global::Mockolate.Parameters.IRefParameter<{GetMethodParameterType(parameter)}>", - (RefKind.Out, _) => $"global::Mockolate.Parameters.IOutParameter<{GetMethodParameterType(parameter)}>", - (RefKind.RefReadOnlyParameter, _) => $"global::Mockolate.Parameters.IRefParameter<{GetMethodParameterType(parameter)}>", - (_, SpecialGenericType.Span) => $"global::Mockolate.Parameters.ISpanParameter<{GetMethodParameterType(parameter)}>", - (_, SpecialGenericType.ReadOnlySpan) => + (RefKind.Ref, _, true) => + $"global::Mockolate.Parameters.IRefRefStructParameter<{GetMethodParameterType(parameter)}>", + (RefKind.Out, _, true) => + $"global::Mockolate.Parameters.IOutRefStructParameter<{GetMethodParameterType(parameter)}>", + (RefKind.RefReadOnlyParameter, _, true) => + $"global::Mockolate.Parameters.IRefRefStructParameter<{GetMethodParameterType(parameter)}>", + (RefKind.Out, SpecialGenericType.Span, _) => + $"global::Mockolate.Parameters.IOutParameter>", + (RefKind.Out, SpecialGenericType.ReadOnlySpan, _) => + $"global::Mockolate.Parameters.IOutParameter>", + (RefKind.Ref, SpecialGenericType.Span, _) => + $"global::Mockolate.Parameters.IRefParameter>", + (RefKind.Ref, SpecialGenericType.ReadOnlySpan, _) => + $"global::Mockolate.Parameters.IRefParameter>", + (RefKind.Ref, _, _) => $"global::Mockolate.Parameters.IRefParameter<{GetMethodParameterType(parameter)}>", + (RefKind.Out, _, _) => $"global::Mockolate.Parameters.IOutParameter<{GetMethodParameterType(parameter)}>", + (RefKind.RefReadOnlyParameter, _, _) => + $"global::Mockolate.Parameters.IRefParameter<{GetMethodParameterType(parameter)}>", + (_, SpecialGenericType.Span, _) => + $"global::Mockolate.Parameters.ISpanParameter<{GetMethodParameterType(parameter)}>", + (_, SpecialGenericType.ReadOnlySpan, _) => $"global::Mockolate.Parameters.IReadOnlySpanParameter<{GetMethodParameterType(parameter)}>", - (_, _) => $"global::Mockolate.Parameters.IParameter<{GetMethodParameterType(parameter)}>", + (_, _, _) => $"global::Mockolate.Parameters.IParameter<{GetMethodParameterType(parameter)}>", }; static string GetMethodParameterType(MethodParameter parameter) { + // The non-ref-struct routes for Span/ROS use the SpanWrapper / ReadOnlySpanWrapper + // matcher, so the relevant generic argument is the element type. The ref-struct + // pipeline, by contrast, handles the bare Span / ReadOnlySpan directly, so we + // must preserve the full type there. + bool stripSpanToElement = !parameter.NeedsRefStructPipeline(); return (parameter.Type.SpecialGenericType, parameter.IsNullableAnnotated && !parameter.Type.Fullname.EndsWith("?")) switch { - (SpecialGenericType.Span, _) => parameter.Type.GenericTypeParameters!.Value.First().Fullname, - (SpecialGenericType.ReadOnlySpan, _) => + (SpecialGenericType.Span, _) when stripSpanToElement => + parameter.Type.GenericTypeParameters!.Value.First().Fullname, + (SpecialGenericType.ReadOnlySpan, _) when stripSpanToElement => parameter.Type.GenericTypeParameters!.Value.First().Fullname, (_, false) => parameter.Type.Fullname, (_, true) => $"{parameter.Type.Fullname}?", diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs index 027236aa..4383f3b4 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs @@ -2267,8 +2267,9 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, sb.Append("\t\t\tvar ").Append(paramRef).Append(" = ").Append(p.Name).Append(';').AppendLine(); sb2.Append(paramRef); } - else if (p.Type.SpecialGenericType == SpecialGenericType.Span || - p.Type.SpecialGenericType == SpecialGenericType.ReadOnlySpan) + else if (p.RefKind != RefKind.Out && + (p.Type.SpecialGenericType == SpecialGenericType.Span || + p.Type.SpecialGenericType == SpecialGenericType.ReadOnlySpan)) { string paramRef = Helpers.GetUniqueLocalVariableName($"ref_{p.Name}", method.Parameters); @@ -2440,6 +2441,7 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, { string outParamBase = Helpers.GetUniqueIndexedLocalVariableBase("outParam", method.Parameters); string refParamBase = Helpers.GetUniqueIndexedLocalVariableBase("refParam", method.Parameters); + string outTempBase = Helpers.GetUniqueIndexedLocalVariableBase("outTemp", method.Parameters); sb.Append("\t\t\t\tif (!").Append(hasWrappedResult).Append(" || ").Append(methodSetup).Append(" is ") .Append(methodSetupType) .Append(".WithParameterCollection)") @@ -2454,17 +2456,45 @@ private static void AppendMockSubject_ImplementClass_AddMethod(StringBuilder sb, parameterIndex++; if (parameter.RefKind == RefKind.Out) { - sb.Append("\t\t\t\t\t\tif (").Append(wpc).Append(".Parameter").Append(parameterIndex) - .Append(" is not global::Mockolate.Parameters.IOutParameter<") - .Append(parameter.Type.ToTypeOrWrapper()).Append("> ").Append(outParamBase) - .Append(parameterIndex) - .Append(" || !").Append(outParamBase).Append(parameterIndex).Append(".TryGetValue(out ") - .Append(parameter.Name).Append("))").AppendLine(); - sb.Append("\t\t\t\t\t\t{").AppendLine(); - sb.Append("\t\t\t\t\t\t\t").Append(parameter.Name).Append(" = ") - .AppendDefaultValueGeneratorFor(parameter.Type, $"{mockRegistry}.Behavior.DefaultValue") - .Append(';').AppendLine(); - sb.Append("\t\t\t\t\t\t}").AppendLine(); + bool isSpanOrReadOnlySpan = parameter.Type.SpecialGenericType + is SpecialGenericType.Span or SpecialGenericType.ReadOnlySpan; + if (isSpanOrReadOnlySpan) + { + // C# does not insert user-defined implicit conversions across `out`, so the + // wrapper-typed slot must be unpacked into a wrapper-typed temp local first, + // then assigned to the bare-typed parameter (which triggers the implicit op). + sb.Append("\t\t\t\t\t\tif (").Append(wpc).Append(".Parameter").Append(parameterIndex) + .Append(" is not global::Mockolate.Parameters.IOutParameter<") + .Append(parameter.Type.ToTypeOrWrapper()).Append("> ").Append(outParamBase) + .Append(parameterIndex) + .Append(" || !").Append(outParamBase).Append(parameterIndex) + .Append(".TryGetValue(out ").Append(parameter.Type.ToTypeOrWrapper()) + .Append(' ').Append(outTempBase).Append(parameterIndex).Append("))").AppendLine(); + sb.Append("\t\t\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t\t\t").Append(parameter.Name).Append(" = ") + .AppendDefaultValueGeneratorFor(parameter.Type, $"{mockRegistry}.Behavior.DefaultValue") + .Append(';').AppendLine(); + sb.Append("\t\t\t\t\t\t}").AppendLine(); + sb.Append("\t\t\t\t\t\telse").AppendLine(); + sb.Append("\t\t\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t\t\t").Append(parameter.Name).Append(" = ").Append(outTempBase) + .Append(parameterIndex).Append(';').AppendLine(); + sb.Append("\t\t\t\t\t\t}").AppendLine(); + } + else + { + sb.Append("\t\t\t\t\t\tif (").Append(wpc).Append(".Parameter").Append(parameterIndex) + .Append(" is not global::Mockolate.Parameters.IOutParameter<") + .Append(parameter.Type.ToTypeOrWrapper()).Append("> ").Append(outParamBase) + .Append(parameterIndex) + .Append(" || !").Append(outParamBase).Append(parameterIndex).Append(".TryGetValue(out ") + .Append(parameter.Name).Append("))").AppendLine(); + sb.Append("\t\t\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t\t\t").Append(parameter.Name).Append(" = ") + .AppendDefaultValueGeneratorFor(parameter.Type, $"{mockRegistry}.Behavior.DefaultValue") + .Append(';').AppendLine(); + sb.Append("\t\t\t\t\t\t}").AppendLine(); + } } else if (parameter.RefKind == RefKind.Ref) { @@ -2593,21 +2623,15 @@ private static void AppendMockSubject_ImplementClass_AddRefStructMethodBody( { sb.Append("#if NET9_0_OR_GREATER").AppendLine(); - bool hasUnsupportedParameter = - method.Parameters.Any(p => - (p.RefKind == RefKind.Out || p.RefKind == RefKind.Ref || - p.RefKind == RefKind.RefReadOnlyParameter) && p.NeedsRefStructPipeline()); bool returnsUnsupportedRefStruct = method.ReturnType.IsRefStruct && method.ReturnType.SpecialGenericType is not (SpecialGenericType.Span or SpecialGenericType.ReadOnlySpan); - if (hasUnsupportedParameter || returnsUnsupportedRefStruct) + if (returnsUnsupportedRefStruct) { - string reason = returnsUnsupportedRefStruct - ? "methods returning a non-span ref struct are not supported" - : "out/ref ref-struct parameters are not supported"; sb.Append("\t\t\tthrow new global::System.NotSupportedException(\"Mockolate: ") - .Append(reason).Append(". Method '").Append(method.ContainingType).Append('.') + .Append("methods returning a non-span ref struct are not supported") + .Append(". Method '").Append(method.ContainingType).Append('.') .Append(method.Name).Append("'.\");").AppendLine(); sb.Append("#else").AppendLine(); sb.Append( @@ -2634,6 +2658,15 @@ method.ReturnType.SpecialGenericType is not sb.Append("));").AppendLine(); + // Pre-default any `out` ref-struct slot. The MockBehavior.DefaultValue generator returns + // object? and cannot produce ref-struct values, so we assign default! directly. If a + // matching setup later supplies a value via IOutRefStructParameter.TryGetValue, that + // value overwrites this; otherwise the default! sticks. + foreach (MethodParameter outParameter in method.Parameters.Where(p => p.RefKind == RefKind.Out)) + { + sb.Append("\t\t\t").Append(outParameter.Name).Append(" = default!;").AppendLine(); + } + // Iterate setups in latest-registered-first order (scenario-scoped first, default-scope // after — GetMethodSetups preserves that ordering). Stop on the first matcher that // accepts every positional argument. The matching runs synchronously on the stack so @@ -2654,6 +2687,37 @@ method.ReturnType.SpecialGenericType is not sb.AppendLine(); sb.Append("\t\t\t\t").Append(matchedVar).Append(" = true;").AppendLine(); + // Per-slot write-back for out/ref ref-struct parameters. ref-readonly is read-only by + // definition and needs no write-back. + int refStructSlotIndex = 0; + foreach (MethodParameter parameter in method.Parameters) + { + refStructSlotIndex++; + if (parameter.RefKind == RefKind.Out) + { + string outVar = Helpers.GetUniqueLocalVariableName($"outParam{refStructSlotIndex}", method.Parameters); + sb.Append("\t\t\t\tif (").Append(setupVar).Append(".GetMatcher").Append(refStructSlotIndex) + .Append("() is global::Mockolate.Parameters.IOutRefStructParameter<") + .Append(parameter.Type.Fullname).Append("> ").Append(outVar).Append(" && ").Append(outVar) + .Append(".TryGetValue(out ").Append(parameter.Name).Append(")) { }").AppendLine(); + sb.Append("\t\t\t\telse").AppendLine(); + sb.Append("\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t").Append(parameter.Name).Append(" = default!;").AppendLine(); + sb.Append("\t\t\t\t}").AppendLine(); + } + else if (parameter.RefKind == RefKind.Ref) + { + string refVar = Helpers.GetUniqueLocalVariableName($"refParam{refStructSlotIndex}", method.Parameters); + sb.Append("\t\t\t\tif (").Append(setupVar).Append(".GetMatcher").Append(refStructSlotIndex) + .Append("() is global::Mockolate.Parameters.IRefRefStructParameter<") + .Append(parameter.Type.Fullname).Append("> ").Append(refVar).Append(")").AppendLine(); + sb.Append("\t\t\t\t{").AppendLine(); + sb.Append("\t\t\t\t\t").Append(parameter.Name).Append(" = ").Append(refVar).Append(".GetValue(") + .Append(parameter.Name).Append(");").AppendLine(); + sb.Append("\t\t\t\t}").AppendLine(); + } + } + if (method.ReturnType == Type.Void) { sb.Append("\t\t\t\t").Append(setupVar).Append(".Invoke(").Append(paramNames).Append(");").AppendLine(); @@ -3774,16 +3838,19 @@ private static void AppendMethodSetupImplementation(StringBuilder sb, Method met private static void AppendRefStructMethodSetupDefinition(StringBuilder sb, Method method, string? methodNameOverride) { + // Out/ref/ref-readonly slots that don't go through the ref-struct setup pipeline have no setup + // surface today. That includes regular non-ref-struct types and out/ref Span/ReadOnlySpan + // (which route through the SpanWrapper/ReadOnlySpanWrapper carve-out, not IParameterMatch). + // The ref-struct narrow setup uses IParameterMatch slots; mixing in a wrapper-based or + // non-ref-struct IOutParameter would require a wider pipeline. Continue to skip those. bool unsupported = method.Parameters.Any(p => - p.RefKind == RefKind.Out || p.RefKind == RefKind.Ref || - p.RefKind == RefKind.RefReadOnlyParameter) || + (p.RefKind == RefKind.Out || p.RefKind == RefKind.Ref || + p.RefKind == RefKind.RefReadOnlyParameter) + && !p.NeedsRefStructPipeline()) || (method.ReturnType.IsRefStruct && method.ReturnType.SpecialGenericType is not (SpecialGenericType.Span or SpecialGenericType.ReadOnlySpan)); if (unsupported) { - // No setup surface — the mock method body throws NotSupportedException at runtime and - // the analyzer flags the signature at build time. Skipping the declaration entirely - // keeps the setup interface clean. return; } @@ -3806,8 +3873,7 @@ private static void AppendRefStructMethodSetupDefinition(StringBuilder sb, Metho sb.Append(", "); } - sb.Append("global::Mockolate.Parameters.IParameter<").Append(parameter.Type.Fullname) - .Append(">? ").Append(parameter.Name); + sb.Append(parameter.ToParameter()).Append("? ").Append(parameter.Name); } sb.Append(");").AppendLine(); @@ -3828,8 +3894,9 @@ private static void AppendRefStructMethodSetupImplementation(StringBuilder sb, M #pragma warning restore S107 { bool unsupported = method.Parameters.Any(p => - p.RefKind == RefKind.Out || p.RefKind == RefKind.Ref || - p.RefKind == RefKind.RefReadOnlyParameter) || + (p.RefKind == RefKind.Out || p.RefKind == RefKind.Ref || + p.RefKind == RefKind.RefReadOnlyParameter) + && !p.NeedsRefStructPipeline()) || (method.ReturnType.IsRefStruct && method.ReturnType.SpecialGenericType is not (SpecialGenericType.Span or SpecialGenericType.ReadOnlySpan)); if (unsupported) @@ -3859,8 +3926,7 @@ private static void AppendRefStructMethodSetupImplementation(StringBuilder sb, M sb.Append(", "); } - sb.Append("global::Mockolate.Parameters.IParameter<").Append(parameter.Type.Fullname) - .Append(">? ").Append(parameter.Name); + sb.Append(parameter.ToParameter()).Append("? ").Append(parameter.Name); } sb.Append(")").AppendLine(); diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.RefStructMethodSetups.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.RefStructMethodSetups.cs index 16b35a21..084bcd62 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.RefStructMethodSetups.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.RefStructMethodSetups.cs @@ -179,6 +179,19 @@ private static void AppendRefStructVoidMethodSetup(StringBuilder sb, int numberO sb.Append(';').AppendLine(); sb.AppendLine(); + // Per-slot matcher accessors. Used by generated mock bodies to access + // IOutRefStructParameter/IRefRefStructParameter payloads on out/ref slots. + for (int i = 1; i <= numberOfParameters; i++) + { + sb.Append( + "\t\t[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]") + .AppendLine(); + sb.Append("\t\tpublic global::Mockolate.Parameters.IParameterMatch? GetMatcher").Append(i).Append("() => _matcher").Append(i).Append(';').AppendLine(); + } + + sb.AppendLine(); + // Invoke. sb.Append("\t\tpublic void Invoke("); for (int i = 1; i <= numberOfParameters; i++) @@ -386,6 +399,19 @@ private static void AppendRefStructReturnMethodSetup(StringBuilder sb, int numbe sb.Append(';').AppendLine(); sb.AppendLine(); + // Per-slot matcher accessors. Used by generated mock bodies to access + // IOutRefStructParameter/IRefRefStructParameter payloads on out/ref slots. + for (int i = 1; i <= numberOfParameters; i++) + { + sb.Append( + "\t\t[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]") + .AppendLine(); + sb.Append("\t\tpublic global::Mockolate.Parameters.IParameterMatch? GetMatcher").Append(i).Append("() => _matcher").Append(i).Append(';').AppendLine(); + } + + sb.AppendLine(); + // Invoke. sb.Append("\t\tpublic TReturn Invoke("); for (int i = 1; i <= numberOfParameters; i++) diff --git a/Source/Mockolate/It.IsOut.cs b/Source/Mockolate/It.IsOut.cs index 7ce4979c..f6cdde4f 100644 --- a/Source/Mockolate/It.IsOut.cs +++ b/Source/Mockolate/It.IsOut.cs @@ -57,6 +57,69 @@ public static IOutParameter IsOut(Func setter, public static IOutParameter IsAnyOut() => new AnyOutParameterMatch(); +#if NET8_0_OR_GREATER + /// + /// Matches any parameter in a Setup and uses + /// to produce the value assigned to the caller's variable when the + /// method is invoked. + /// + /// + /// is a ref struct, so the setup-side payload is the non-ref-struct + /// . The generated mock unpacks the wrapper + /// into a temp local and assigns it to the caller's via the wrapper's + /// implicit conversion operator. Pair with + /// to observe each produced wrapper. + /// + /// The element type of the out- parameter. + /// Factory that produces the wrapper to assign to the caller's out-variable. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IOutParameter> IsOutSpan( + Func> setter, + [CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") + => new OutParameterMatch>(setter, doNotPopulateThisValue); + + /// + /// Matches any parameter in a Setup and + /// assigns the default produced by the + /// mock's generator (which yields an empty + /// at the call site via the implicit conversion operator). + /// + /// The element type of the out- parameter. + /// An over . + public static IOutParameter> IsAnyOutSpan() + => new AnyOutParameterMatch>(); + + /// + /// Matches any parameter in a Setup + /// and uses to produce the value assigned to the caller's variable + /// when the method is invoked. + /// + /// + /// is a ref struct, so the setup-side payload is the non-ref-struct + /// . Pair with + /// to observe each produced wrapper. + /// + /// The element type of the out- parameter. + /// Factory that produces the wrapper to assign to the caller's out-variable. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IOutParameter> IsOutReadOnlySpan( + Func> setter, + [CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") + => new OutParameterMatch>(setter, doNotPopulateThisValue); + + /// + /// Matches any parameter in a Setup + /// and assigns the default produced + /// by the mock's generator. + /// + /// The element type of the out- parameter. + /// An over . + public static IOutParameter> IsAnyOutReadOnlySpan() + => new AnyOutParameterMatch>(); +#endif + /// /// Matches an parameter against an expectation. /// @@ -102,6 +165,9 @@ public override bool TryGetValue(out T value) [System.Diagnostics.DebuggerNonUserCode] #endif private sealed class InvokedOutParameterMatch : IVerifyOutParameter, IParameterMatch +#if NET9_0_OR_GREATER + where T : allows ref struct +#endif { /// public override string ToString() => $"It.IsOut<{typeof(T).FormatType()}>()"; @@ -151,6 +217,112 @@ public void InvokeCallbacks(T value) } } } + +#if NET9_0_OR_GREATER + /// + /// Matches any parameter of a ref struct type + /// in a Setup and uses to + /// produce the value assigned to the caller's variable when the method is invoked. + /// + /// + /// runs on every matching invocation, so you can return a fresh + /// value per call. The ref-struct-safe counterpart to + /// does not support + /// callbacks because + /// cannot carry the allows ref struct anti-constraint. + /// defers + /// to the overload when both are viable. + /// + /// The out-parameter's ref struct type. + /// Factory that produces the value to assign to the caller's out-variable. + /// Do not populate - captured automatically by the compiler. + /// An that produces a value via . + [OverloadResolutionPriority(-1)] + public static IOutRefStructParameter IsOut(RefStructFactory setter, + [CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") + where T : allows ref struct + => new OutRefStructParameterMatch(setter, doNotPopulateThisValue); + + /// + /// Matches any parameter of a ref struct type + /// in a Setup and assigns + /// () to the caller's variable. + /// + /// + /// Use this when the caller's out value is irrelevant to the test and you just need the + /// method call to return. Unlike the non-ref-struct overload, + /// the value is always () because + /// cannot produce ref-struct values. + /// + /// The out-parameter's ref struct type. + /// An that produces . + public static IOutRefStructParameter IsAnyOutRefStruct() + where T : allows ref struct + => new AnyOutRefStructParameterMatch(); + + /// + /// Matches an parameter of a ref struct type against an expectation. + /// +#if !DEBUG + [System.Diagnostics.DebuggerNonUserCode] +#endif + private sealed class OutRefStructParameterMatch(RefStructFactory setter, string setterExpression) + : IOutRefStructParameter, IParameterMatch + where T : allows ref struct + { + /// + public bool TryGetValue(out T value) + { + value = setter(); + return true; + } + + /// + public bool Matches(T value) + => true; + + /// + public void InvokeCallbacks(T value) + { + // No callbacks: Action cannot carry the 'allows ref struct' anti-constraint. + } + + /// + public override string ToString() => $"It.IsOut<{typeof(T).FormatType()}>({setterExpression})"; + } + + /// + /// Matches any parameter of a ref struct type and assigns + /// to the caller's variable. + /// +#if !DEBUG + [System.Diagnostics.DebuggerNonUserCode] +#endif + private sealed class AnyOutRefStructParameterMatch : IOutRefStructParameter, IParameterMatch + where T : allows ref struct + { + /// + public bool TryGetValue(out T value) + { + value = default!; + return false; + } + + /// + public bool Matches(T value) + => true; + + /// + public void InvokeCallbacks(T value) + { + // No callbacks: Action cannot carry the 'allows ref struct' anti-constraint. + } + + /// + public override string ToString() => $"It.IsAnyOutRefStruct<{typeof(T).FormatType()}>()"; + } + +#endif } #pragma warning restore S3218 // Inner class members should not shadow outer class "static" or type members #pragma warning restore S3453 // This class can't be instantiated; make its constructor 'public'. diff --git a/Source/Mockolate/It.IsRef.cs b/Source/Mockolate/It.IsRef.cs index 889d62dc..bc25739b 100644 --- a/Source/Mockolate/It.IsRef.cs +++ b/Source/Mockolate/It.IsRef.cs @@ -89,6 +89,122 @@ public static IVerifyRefParameter IsRef() public static IRefParameter IsAnyRef() => new AnyRefParameterMatch(); +#if NET8_0_OR_GREATER + /// + /// Matches any parameter and replaces its value + /// with the result of when the method is invoked. + /// + /// + /// is a ref struct, so the setup-side payload is the non-ref-struct + /// . The wrapper's implicit conversion + /// operators carry the value across the ref boundary in both directions. + /// + /// The element type of the ref- parameter. + /// Factory that takes the caller's wrapped current value and returns the replacement wrapper. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IRefParameter> IsRefSpan( + Func, Setup.SpanWrapper> setter, + [CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") + => new RefParameterMatch>(_ => true, setter, null, doNotPopulateThisValue); + + /// + /// Matches a parameter whose wrapped current value + /// satisfies , and replaces its value with the result of + /// . + /// + /// The element type of the ref- parameter. + /// The predicate evaluated against the caller's wrapped current value. + /// Factory that takes the caller's wrapped current value and returns the replacement wrapper. + /// Do not populate - captured automatically by the compiler. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IRefParameter> IsRefSpan( + Func, bool> predicate, + Func, Setup.SpanWrapper> setter, + [CallerArgumentExpression("predicate")] + string doNotPopulateThisValue1 = "", + [CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") + => new RefParameterMatch>(predicate, setter, doNotPopulateThisValue1, doNotPopulateThisValue2); + + /// + /// Matches a parameter whose wrapped current value + /// satisfies , without replacing it. + /// + /// The element type of the ref- parameter. + /// The predicate evaluated against the caller's wrapped current value. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IRefParameter> IsRefSpan( + Func, bool> predicate, + [CallerArgumentExpression("predicate")] + string doNotPopulateThisValue = "") + => new RefParameterMatch>(predicate, null, doNotPopulateThisValue, null); + + /// + /// Matches any parameter without replacing its value. + /// + /// The element type of the ref- parameter. + /// An over . + public static IRefParameter> IsAnyRefSpan() + => new AnyRefParameterMatch>(); + + /// + /// Matches any parameter and replaces its + /// value with the result of when the method is invoked. + /// + /// The element type of the ref- parameter. + /// Factory that takes the caller's wrapped current value and returns the replacement wrapper. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IRefParameter> IsRefReadOnlySpan( + Func, Setup.ReadOnlySpanWrapper> setter, + [CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") + => new RefParameterMatch>(_ => true, setter, null, doNotPopulateThisValue); + + /// + /// Matches a parameter whose wrapped current + /// value satisfies , and replaces its value with the result of + /// . + /// + /// The element type of the ref- parameter. + /// The predicate evaluated against the caller's wrapped current value. + /// Factory that takes the caller's wrapped current value and returns the replacement wrapper. + /// Do not populate - captured automatically by the compiler. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IRefParameter> IsRefReadOnlySpan( + Func, bool> predicate, + Func, Setup.ReadOnlySpanWrapper> setter, + [CallerArgumentExpression("predicate")] + string doNotPopulateThisValue1 = "", + [CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") + => new RefParameterMatch>(predicate, setter, doNotPopulateThisValue1, doNotPopulateThisValue2); + + /// + /// Matches a parameter whose wrapped current + /// value satisfies , without replacing it. + /// + /// The element type of the ref- parameter. + /// The predicate evaluated against the caller's wrapped current value. + /// Do not populate - captured automatically by the compiler. + /// An over . + public static IRefParameter> IsRefReadOnlySpan( + Func, bool> predicate, + [CallerArgumentExpression("predicate")] + string doNotPopulateThisValue = "") + => new RefParameterMatch>(predicate, null, doNotPopulateThisValue, null); + + /// + /// Matches any parameter without replacing + /// its value. + /// + /// The element type of the ref- parameter. + /// An over . + public static IRefParameter> IsAnyRefReadOnlySpan() + => new AnyRefParameterMatch>(); +#endif + /// /// Matches a method parameter against an expectation. /// @@ -144,14 +260,17 @@ protected override bool Matches(T value) } /// - /// Matches a method parameter against an expectation. + /// Matches a method parameter against an expectation. /// #if !DEBUG [System.Diagnostics.DebuggerNonUserCode] #endif private sealed class InvokedRefParameterMatch : IVerifyRefParameter, IParameterMatch +#if NET9_0_OR_GREATER + where T : allows ref struct +#endif { - /// + /// bool IParameterMatch.Matches(T value) => true; @@ -205,6 +324,147 @@ public IRefParameter Do(Action callback) /// protected abstract bool Matches(T value); } + +#if NET9_0_OR_GREATER + /// + /// Matches any parameter of a ref struct type + /// and replaces its value with the result of + /// when the method is invoked. + /// + /// + /// The ref-struct-safe counterpart to does + /// not support callbacks because + /// cannot carry the allows ref struct anti-constraint. + /// defers + /// to the overload when both are viable. + /// + /// The ref-parameter's ref struct type. + /// Factory that takes the caller's current value and returns the replacement value. + /// Do not populate - captured automatically by the compiler. + /// An that mutates the caller's ref-variable via . + [OverloadResolutionPriority(-1)] + public static IRefRefStructParameter IsRef(RefStructTransform setter, + [CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") + where T : allows ref struct + => new RefRefStructParameterMatch(static _ => true, setter, null, doNotPopulateThisValue); + + /// + /// Matches a parameter of a ref struct type whose current value + /// satisfies , and replaces its value with the result of + /// . + /// + /// The ref-parameter's ref struct type. + /// The predicate evaluated against the caller's current value. + /// Factory that takes the caller's current value and returns the replacement value. + /// Do not populate - captured automatically by the compiler. + /// Do not populate - captured automatically by the compiler. + /// An that matches when is satisfied and mutates via . + [OverloadResolutionPriority(-1)] + public static IRefRefStructParameter IsRef(RefStructPredicate predicate, RefStructTransform setter, + [CallerArgumentExpression("predicate")] + string doNotPopulateThisValue1 = "", + [CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") + where T : allows ref struct + => new RefRefStructParameterMatch(predicate, setter, doNotPopulateThisValue1, doNotPopulateThisValue2); + + /// + /// Matches a parameter of a ref struct type whose current value + /// satisfies , without replacing it. + /// + /// The ref-parameter's ref struct type. + /// The predicate evaluated against the caller's current value. + /// Do not populate - captured automatically by the compiler. + /// An that matches when is satisfied and does not mutate the ref-variable. + [OverloadResolutionPriority(-1)] + public static IRefRefStructParameter IsRef(RefStructPredicate predicate, + [CallerArgumentExpression("predicate")] + string doNotPopulateThisValue = "") + where T : allows ref struct + => new RefRefStructParameterMatch(predicate, null, doNotPopulateThisValue, null); + + /// + /// Matches any parameter of a ref struct type + /// without replacing its value. + /// + /// The ref-parameter's ref struct type. + /// An that matches any ref-argument and leaves it unchanged. + public static IRefRefStructParameter IsAnyRefRefStruct() + where T : allows ref struct + => new AnyRefRefStructParameterMatch(); + + /// + /// Matches a method parameter of a ref struct type against an expectation. + /// +#if !DEBUG + [System.Diagnostics.DebuggerNonUserCode] +#endif + private sealed class RefRefStructParameterMatch( + RefStructPredicate predicate, + RefStructTransform? setter, + string? predicateExpression, + string? setterExpression) : IRefRefStructParameter, IParameterMatch + where T : allows ref struct + { + /// + public T GetValue(T value) + { + if (setter is null) + { + return value; + } + + return setter(value); + } + + /// + public bool Matches(T value) + => predicate(value); + + /// + public void InvokeCallbacks(T value) + { + // No callbacks: Action cannot carry the 'allows ref struct' anti-constraint. + } + + /// + public override string ToString() + => (predicateExpression is not null, setterExpression is not null) switch + { + (true, true) => $"It.IsRef<{typeof(T).FormatType()}>({predicateExpression}, {setterExpression})", + (true, false) => $"It.IsRef<{typeof(T).FormatType()}>({predicateExpression})", + (false, _) => $"It.IsRef<{typeof(T).FormatType()}>({setterExpression})", + }; + } + + /// + /// Matches any method parameter of a ref struct type without + /// mutating the value. + /// +#if !DEBUG + [System.Diagnostics.DebuggerNonUserCode] +#endif + private sealed class AnyRefRefStructParameterMatch : IRefRefStructParameter, IParameterMatch + where T : allows ref struct + { + /// + public T GetValue(T value) + => value; + + /// + public bool Matches(T value) + => true; + + /// + public void InvokeCallbacks(T value) + { + // No callbacks: Action cannot carry the 'allows ref struct' anti-constraint. + } + + /// + public override string ToString() => $"It.IsAnyRefRefStruct<{typeof(T).FormatType()}>()"; + } + +#endif } #pragma warning restore S3218 // Inner class members should not shadow outer class "static" or type members #pragma warning restore S3453 // This class can't be instantiated; make its constructor 'public'. diff --git a/Source/Mockolate/Parameters/IOutRefStructParameter.cs b/Source/Mockolate/Parameters/IOutRefStructParameter.cs new file mode 100644 index 00000000..b4382787 --- /dev/null +++ b/Source/Mockolate/Parameters/IOutRefStructParameter.cs @@ -0,0 +1,28 @@ +#if NET9_0_OR_GREATER +namespace Mockolate.Parameters; + +/// +/// Matches an parameter of a ref struct type +/// against an expectation. +/// +/// +/// The ref-struct-safe counterpart to . The surface is +/// deliberately narrower: no Do(Action<T>) callback because +/// cannot carry the allows ref struct anti-constraint. +/// Use a non-ref-struct for types that are not ref structs. +/// +public interface IOutRefStructParameter + where T : allows ref struct +{ + /// + /// Tries to get the value to which the parameter should be set. + /// + /// + /// When the method returns , is the value + /// the mock should write back to the parameter. When it returns + /// , the mock writes () + /// instead. + /// + bool TryGetValue(out T value); +} +#endif diff --git a/Source/Mockolate/Parameters/IRefRefStructParameter.cs b/Source/Mockolate/Parameters/IRefRefStructParameter.cs new file mode 100644 index 00000000..f62adaee --- /dev/null +++ b/Source/Mockolate/Parameters/IRefRefStructParameter.cs @@ -0,0 +1,23 @@ +#if NET9_0_OR_GREATER +namespace Mockolate.Parameters; + +/// +/// Matches a parameter of a ref struct type +/// against an expectation. +/// +/// +/// The ref-struct-safe counterpart to . The surface is +/// deliberately narrower: no Do(Action<T>) callback because +/// cannot carry the allows ref struct anti-constraint. +/// Use a non-ref-struct for types that are not ref structs. +/// +public interface IRefRefStructParameter + where T : allows ref struct +{ + /// + /// Retrieves the value to which the parameter should be set, + /// given the caller's current . + /// + T GetValue(T value); +} +#endif diff --git a/Source/Mockolate/Parameters/IVerifyOutParameter.cs b/Source/Mockolate/Parameters/IVerifyOutParameter.cs index 8db51973..4e92234a 100644 --- a/Source/Mockolate/Parameters/IVerifyOutParameter.cs +++ b/Source/Mockolate/Parameters/IVerifyOutParameter.cs @@ -5,5 +5,9 @@ namespace Mockolate.Parameters; /// /// Matches any parameter. /// -public interface IVerifyOutParameter; +public interface IVerifyOutParameter +#if NET9_0_OR_GREATER + where T : allows ref struct +#endif +; #pragma warning restore S2326 // Unused type parameters should be removed diff --git a/Source/Mockolate/Parameters/IVerifyRefParameter.cs b/Source/Mockolate/Parameters/IVerifyRefParameter.cs index 3287e454..9c576c3a 100644 --- a/Source/Mockolate/Parameters/IVerifyRefParameter.cs +++ b/Source/Mockolate/Parameters/IVerifyRefParameter.cs @@ -5,5 +5,9 @@ namespace Mockolate.Parameters; /// /// Matches any parameter. /// -public interface IVerifyRefParameter; +public interface IVerifyRefParameter +#if NET9_0_OR_GREATER + where T : allows ref struct +#endif +; #pragma warning restore S2326 // Unused type parameters should be removed diff --git a/Source/Mockolate/RefStructFactory.cs b/Source/Mockolate/RefStructFactory.cs new file mode 100644 index 00000000..ca24513a --- /dev/null +++ b/Source/Mockolate/RefStructFactory.cs @@ -0,0 +1,17 @@ +#if NET9_0_OR_GREATER +namespace Mockolate; + +/// +/// Produces a ref struct value of type . +/// +/// +/// Unlike , this delegate carries the +/// allows ref struct anti-constraint on the return type, so +/// can be a ref struct such as +/// or a user-defined ref struct. Used by +/// to produce the value assigned to +/// a caller's variable when the mock is invoked. +/// +public delegate T RefStructFactory() + where T : allows ref struct; +#endif diff --git a/Source/Mockolate/RefStructTransform.cs b/Source/Mockolate/RefStructTransform.cs new file mode 100644 index 00000000..d701c839 --- /dev/null +++ b/Source/Mockolate/RefStructTransform.cs @@ -0,0 +1,16 @@ +#if NET9_0_OR_GREATER +namespace Mockolate; + +/// +/// Transforms a ref struct value of type into a replacement of +/// the same type. +/// +/// +/// Unlike , this delegate carries the +/// allows ref struct anti-constraint, so can be a ref +/// struct. Used by to compute the +/// replacement value for a caller's variable when the mock is invoked. +/// +public delegate T RefStructTransform(T value) + where T : allows ref struct; +#endif diff --git a/Source/Mockolate/Setup/ReadOnlySpanWrapper.cs b/Source/Mockolate/Setup/ReadOnlySpanWrapper.cs index f3f50e38..60f79584 100644 --- a/Source/Mockolate/Setup/ReadOnlySpanWrapper.cs +++ b/Source/Mockolate/Setup/ReadOnlySpanWrapper.cs @@ -25,10 +25,11 @@ public ReadOnlySpanWrapper(ReadOnlySpan span) /// /// Implicitly converts a to a . + /// A wrapper yields (). /// - public static implicit operator ReadOnlySpan(ReadOnlySpanWrapper wrapper) + public static implicit operator ReadOnlySpan(ReadOnlySpanWrapper? wrapper) { - return new ReadOnlySpan(wrapper.ReadOnlySpanValues); + return wrapper is null ? default : new ReadOnlySpan(wrapper.ReadOnlySpanValues); } /// diff --git a/Source/Mockolate/Setup/RefStructIndexerSetup.cs b/Source/Mockolate/Setup/RefStructIndexerSetup.cs index 891131f1..66da26f8 100644 --- a/Source/Mockolate/Setup/RefStructIndexerSetup.cs +++ b/Source/Mockolate/Setup/RefStructIndexerSetup.cs @@ -66,7 +66,7 @@ public RefStructIndexerSetup(string getterName, string setterName, IParameterMat /// abstract contract and returns so the combined instance never /// participates in lookups. /// - protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) => false; + protected override bool MatchesInteraction(Interactions.IMethodInteraction interaction) => false; IRefStructIndexerSetup IRefStructIndexerSetup.SkippingBaseClass(bool skipBaseClass) { @@ -147,7 +147,7 @@ public RefStructIndexerSetup(string getterName, string setterName, } /// - protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) => false; + protected override bool MatchesInteraction(Interactions.IMethodInteraction interaction) => false; IRefStructIndexerSetup IRefStructIndexerSetup.SkippingBaseClass(bool skipBaseClass) { @@ -231,7 +231,7 @@ public RefStructIndexerSetup(string getterName, string setterName, } /// - protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) => false; + protected override bool MatchesInteraction(Interactions.IMethodInteraction interaction) => false; IRefStructIndexerSetup IRefStructIndexerSetup.SkippingBaseClass(bool skipBaseClass) { @@ -317,7 +317,7 @@ public RefStructIndexerSetup(string getterName, string setterName, } /// - protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) => false; + protected override bool MatchesInteraction(Interactions.IMethodInteraction interaction) => false; IRefStructIndexerSetup IRefStructIndexerSetup.SkippingBaseClass(bool skipBaseClass) { diff --git a/Source/Mockolate/Setup/RefStructReturnMethodSetup.cs b/Source/Mockolate/Setup/RefStructReturnMethodSetup.cs index 45ea5e99..979763ab 100644 --- a/Source/Mockolate/Setup/RefStructReturnMethodSetup.cs +++ b/Source/Mockolate/Setup/RefStructReturnMethodSetup.cs @@ -37,6 +37,13 @@ public RefStructReturnMethodSetup(string name, IParameterMatch? matcher = nul public bool Matches(T value) => _matcher is null || _matcher.Matches(value); + /// + /// Returns the matcher for the first (and only) parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher; + /// /// Invokes matcher callbacks, applies any configured throw, and returns the configured /// value. If no return value is configured, falls back to @@ -156,6 +163,20 @@ public bool Matches(T1 value1, T2 value2) => (_matcher1 is null || _matcher1.Matches(value1)) && (_matcher2 is null || _matcher2.Matches(value2)); + /// + /// Returns the matcher for the first parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher1; + + /// + /// Returns the matcher for the second parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher2() + => _matcher2; + /// public TReturn Invoke(T1 value1, T2 value2, Func? defaultFactory = null) { @@ -267,6 +288,27 @@ public bool Matches(T1 value1, T2 value2, T3 value3) && (_matcher2 is null || _matcher2.Matches(value2)) && (_matcher3 is null || _matcher3.Matches(value3)); + /// + /// Returns the matcher for the first parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher1; + + /// + /// Returns the matcher for the second parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher2() + => _matcher2; + + /// + /// Returns the matcher for the third parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher3() + => _matcher3; + /// public TReturn Invoke(T1 value1, T2 value2, T3 value3, Func? defaultFactory = null) { @@ -384,6 +426,34 @@ public bool Matches(T1 value1, T2 value2, T3 value3, T4 value4) && (_matcher3 is null || _matcher3.Matches(value3)) && (_matcher4 is null || _matcher4.Matches(value4)); + /// + /// Returns the matcher for the first parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher1; + + /// + /// Returns the matcher for the second parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher2() + => _matcher2; + + /// + /// Returns the matcher for the third parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher3() + => _matcher3; + + /// + /// Returns the matcher for the fourth parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher4() + => _matcher4; + /// public TReturn Invoke(T1 value1, T2 value2, T3 value3, T4 value4, Func? defaultFactory = null) { diff --git a/Source/Mockolate/Setup/RefStructVoidMethodSetup.cs b/Source/Mockolate/Setup/RefStructVoidMethodSetup.cs index 3d743905..938aa172 100644 --- a/Source/Mockolate/Setup/RefStructVoidMethodSetup.cs +++ b/Source/Mockolate/Setup/RefStructVoidMethodSetup.cs @@ -58,6 +58,15 @@ public RefStructVoidMethodSetup(string name, IParameterMatch? matcher = null) public bool Matches(T value) => _matcher is null || _matcher.Matches(value); + /// + /// Returns the matcher for the first (and only) parameter slot, or + /// when the slot has no matcher configured. Used by generated mock bodies to access ref-struct + /// out/ref matcher payloads (e.g. ). + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher; + /// /// Invokes any matcher-level callbacks against the live and then /// applies the currently configured throw (if any). @@ -162,6 +171,20 @@ public bool Matches(T1 value1, T2 value2) => (_matcher1 is null || _matcher1.Matches(value1)) && (_matcher2 is null || _matcher2.Matches(value2)); + /// + /// Returns the matcher for the first parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher1; + + /// + /// Returns the matcher for the second parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher2() + => _matcher2; + /// /// Invokes any matcher-level callbacks and applies the currently configured throw (if any). /// @@ -269,6 +292,27 @@ public bool Matches(T1 value1, T2 value2, T3 value3) && (_matcher2 is null || _matcher2.Matches(value2)) && (_matcher3 is null || _matcher3.Matches(value3)); + /// + /// Returns the matcher for the first parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher1; + + /// + /// Returns the matcher for the second parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher2() + => _matcher2; + + /// + /// Returns the matcher for the third parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher3() + => _matcher3; + /// /// Invokes any matcher-level callbacks and applies the currently configured throw (if any). /// @@ -382,6 +426,34 @@ public bool Matches(T1 value1, T2 value2, T3 value3, T4 value4) && (_matcher3 is null || _matcher3.Matches(value3)) && (_matcher4 is null || _matcher4.Matches(value4)); + /// + /// Returns the matcher for the first parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher1() + => _matcher1; + + /// + /// Returns the matcher for the second parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher2() + => _matcher2; + + /// + /// Returns the matcher for the third parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher3() + => _matcher3; + + /// + /// Returns the matcher for the fourth parameter slot. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public IParameterMatch? GetMatcher4() + => _matcher4; + /// /// Invokes any matcher-level callbacks and applies the currently configured throw (if any). /// diff --git a/Source/Mockolate/Setup/SpanWrapper.cs b/Source/Mockolate/Setup/SpanWrapper.cs index 7f333f23..8b77adb0 100644 --- a/Source/Mockolate/Setup/SpanWrapper.cs +++ b/Source/Mockolate/Setup/SpanWrapper.cs @@ -24,11 +24,12 @@ public SpanWrapper(Span span) public T[] SpanValues { get; } /// - /// Implicitly converts a to a . + /// Implicitly converts a to a . A + /// wrapper yields (). /// - public static implicit operator Span(SpanWrapper wrapper) + public static implicit operator Span(SpanWrapper? wrapper) { - return new Span(wrapper.SpanValues); + return wrapper is null ? default : new Span(wrapper.SpanValues); } /// diff --git a/Tests/Mockolate.Analyzers.Tests/MockabilityAnalyzerRefStructTests.cs b/Tests/Mockolate.Analyzers.Tests/MockabilityAnalyzerRefStructTests.cs index 11fd9644..711c58e4 100644 --- a/Tests/Mockolate.Analyzers.Tests/MockabilityAnalyzerRefStructTests.cs +++ b/Tests/Mockolate.Analyzers.Tests/MockabilityAnalyzerRefStructTests.cs @@ -84,7 +84,7 @@ public void MyTest() ); [Fact] - public async Task WhenMockingAbstractClassWithInheritedRefStructViolation_ShouldBeFlagged() => await Verifier + public async Task WhenMockingAbstractClassWithInheritedRefStructOutParameter_ShouldNotBeFlagged() => await Verifier .VerifyAnalyzerAsync( $$""" {{GeneratedPrefix("MyNamespace.DerivedProducer")}} @@ -95,7 +95,7 @@ namespace MyNamespace public abstract class BaseProducer { - // Virtual members on a base class are also walked and must trip the analyzer. + // Out ref-struct parameters are supported via IOutRefStructParameter. public abstract void Produce(out Packet packet); } @@ -108,15 +108,11 @@ public class MyClass { public void MyTest() { - {|#0:DerivedProducer|}.CreateMock(); + DerivedProducer.CreateMock(); } } } - """, - new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) - .WithLocation(0) - .WithArguments("MyNamespace.DerivedProducer", "Produce", - "out/ref ref-struct parameters are not supported") + """ ); [Fact] @@ -158,8 +154,9 @@ namespace MyNamespace { public readonly ref struct Packet(int id) { public int Id { get; } = id; } - // Delegate Invoke methods are analyzed the same as interface methods; out/ref on - // a ref-struct parameter must be rejected. + // Delegate Invoke methods are analyzed the same as interface methods; ref-struct + // parameters (any RefKind) must be rejected because the emitted VoidMethodSetup + // has no 'allows ref struct' constraint. public delegate void PacketProducerDelegate(out Packet packet); public class MyClass @@ -174,11 +171,11 @@ public void MyTest() new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) .WithLocation(0) .WithArguments("MyNamespace.PacketProducerDelegate", "Invoke", - "out/ref ref-struct parameters are not supported") + "ref-struct parameters are not supported on delegate types") ); [Fact] - public async Task WhenMockingDelegateWithPlainRefStructParameter_ShouldNotBeFlagged() => await Verifier + public async Task WhenMockingDelegateWithPlainRefStructParameter_ShouldBeFlagged() => await Verifier .VerifyAnalyzerAsync( $$""" {{GeneratedPrefix("MyNamespace.PacketHandler")}} @@ -187,23 +184,28 @@ namespace MyNamespace { public readonly ref struct Packet(int id) { public int Id { get; } = id; } - // Delegate Invoke with a plain ref-struct parameter — routed through the - // generator's ref-struct pipeline, no diagnostic expected. + // Delegate Invoke with a plain ref-struct parameter — the generator emits + // VoidMethodSetup, which lacks 'allows ref struct' and therefore fails + // to compile. The analyzer must reject this case as well. public delegate void PacketHandler(Packet packet); public class MyClass { public void MyTest() { - PacketHandler.CreateMock(); + {|#0:PacketHandler|}.CreateMock(); } } } - """ + """, + new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) + .WithLocation(0) + .WithArguments("MyNamespace.PacketHandler", "Invoke", + "ref-struct parameters are not supported on delegate types") ); [Fact] - public async Task WhenMockingInterfaceInheritingFromInterfaceWithBadMethod_ShouldBeFlagged() => await Verifier + public async Task WhenMockingInterfaceInheritingRefStructOutMethod_ShouldNotBeFlagged() => await Verifier .VerifyAnalyzerAsync( $$""" {{GeneratedPrefix("MyNamespace.IDerivedSink")}} @@ -217,8 +219,6 @@ public interface IBaseSink void Produce(out Packet packet); } - // Inherited members are walked via ITypeSymbol.AllInterfaces — the violation on - // the base must surface against the derived interface being mocked. public interface IDerivedSink : IBaseSink { void Extra(); @@ -228,15 +228,11 @@ public class MyClass { public void MyTest() { - {|#0:IDerivedSink|}.CreateMock(); + IDerivedSink.CreateMock(); } } } - """, - new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) - .WithLocation(0) - .WithArguments("MyNamespace.IDerivedSink", "Produce", - "out/ref ref-struct parameters are not supported") + """ ); [Fact] @@ -270,7 +266,7 @@ public void MyTest() ); [Fact] - public async Task WhenMockingInterfaceWithOutRefStructParameter_ShouldBeFlagged() => await Verifier + public async Task WhenMockingInterfaceWithOutRefStructParameter_ShouldNotBeFlagged() => await Verifier .VerifyAnalyzerAsync( $$""" {{GeneratedPrefix("MyNamespace.IPacketProducer")}} @@ -288,19 +284,15 @@ public class MyClass { public void MyTest() { - {|#0:IPacketProducer|}.CreateMock(); + IPacketProducer.CreateMock(); } } } - """, - new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) - .WithLocation(0) - .WithArguments("MyNamespace.IPacketProducer", "Produce", - "out/ref ref-struct parameters are not supported") + """ ); [Fact] - public async Task WhenMockingInterfaceWithOverloads_OnlyViolatingOverloadIsFlagged() => await Verifier + public async Task WhenMockingInterfaceWithRefStructOutAndPlainOverloads_ShouldNotBeFlagged() => await Verifier .VerifyAnalyzerAsync( $$""" {{GeneratedPrefix("MyNamespace.IOverloadedSink")}} @@ -309,9 +301,8 @@ namespace MyNamespace { public readonly ref struct Packet(int id) { public int Id { get; } = id; } - // Two overloads share a name; GetSignatureKey includes RefKind so de-duplication - // does not collapse them. The plain-parameter overload is fine; the out-parameter - // overload must produce exactly one diagnostic. + // Both overloads are now supported — the by-value variant via the standard + // ref-struct pipeline, the out variant via IOutRefStructParameter. public interface IOverloadedSink { void Consume(Packet packet); @@ -322,15 +313,11 @@ public class MyClass { public void MyTest() { - {|#0:IOverloadedSink|}.CreateMock(); + IOverloadedSink.CreateMock(); } } } - """, - new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) - .WithLocation(0) - .WithArguments("MyNamespace.IOverloadedSink", "Consume", - "out/ref ref-struct parameters are not supported") + """ ); [Fact] @@ -360,7 +347,7 @@ public void MyTest() ); [Fact] - public async Task WhenMockingInterfaceWithRefReadonlyRefStructParameter_ShouldBeFlagged() => await Verifier + public async Task WhenMockingInterfaceWithRefReadonlyRefStructParameter_ShouldNotBeFlagged() => await Verifier .VerifyAnalyzerAsync( $$""" {{GeneratedPrefix("MyNamespace.IPacketInspector")}} @@ -371,8 +358,6 @@ namespace MyNamespace public interface IPacketInspector { - // `ref readonly` on a parameter produces RefKind.RefReadOnlyParameter, which - // is explicitly listed alongside Out and Ref in MockabilityAnalyzer. void Inspect(ref readonly Packet packet); } @@ -380,19 +365,15 @@ public class MyClass { public void MyTest() { - {|#0:IPacketInspector|}.CreateMock(); + IPacketInspector.CreateMock(); } } } - """, - new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) - .WithLocation(0) - .WithArguments("MyNamespace.IPacketInspector", "Inspect", - "out/ref ref-struct parameters are not supported") + """ ); [Fact] - public async Task WhenMockingInterfaceWithRefRefStructParameter_ShouldBeFlagged() => await Verifier + public async Task WhenMockingInterfaceWithRefRefStructParameter_ShouldNotBeFlagged() => await Verifier .VerifyAnalyzerAsync( $$""" {{GeneratedPrefix("MyNamespace.IPacketMutator")}} @@ -410,15 +391,11 @@ public class MyClass { public void MyTest() { - {|#0:IPacketMutator|}.CreateMock(); + IPacketMutator.CreateMock(); } } } - """, - new DiagnosticResult("Mockolate0003", DiagnosticSeverity.Warning) - .WithLocation(0) - .WithArguments("MyNamespace.IPacketMutator", "Mutate", - "out/ref ref-struct parameters are not supported") + """ ); [Fact] diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt index 1a087e99..9335d2df 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt @@ -97,8 +97,14 @@ namespace Mockolate public static Mockolate.It.IIsParameter Is(T value, [System.Runtime.CompilerServices.CallerArgumentExpression("value")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IParameterWithCallback IsAny() { } public static Mockolate.Parameters.IOutParameter IsAnyOut() { } + public static Mockolate.Parameters.IOutParameter> IsAnyOutReadOnlySpan() { } + public static Mockolate.Parameters.IOutRefStructParameter IsAnyOutRefStruct() { } + public static Mockolate.Parameters.IOutParameter> IsAnyOutSpan() { } public static Mockolate.Parameters.IVerifyReadOnlySpanParameter IsAnyReadOnlySpan() { } public static Mockolate.Parameters.IRefParameter IsAnyRef() { } + public static Mockolate.Parameters.IRefParameter> IsAnyRefReadOnlySpan() { } + public static Mockolate.Parameters.IRefRefStructParameter IsAnyRefRefStruct() { } + public static Mockolate.Parameters.IRefParameter> IsAnyRefSpan() { } public static Mockolate.Parameters.IParameter IsAnyRefStruct() { } public static Mockolate.Parameters.IVerifySpanParameter IsAnySpan() { } public static Mockolate.Parameters.IParameterWithCallback IsFalse() { } @@ -110,12 +116,24 @@ namespace Mockolate public static Mockolate.Parameters.IParameterWithCallback IsNull(string? toString = null) { } public static Mockolate.It.IIsOneOfParameter IsOneOf(System.Collections.Generic.IEnumerable values) { } public static Mockolate.Parameters.IVerifyOutParameter IsOut() { } + public static Mockolate.Parameters.IOutRefStructParameter IsOut(Mockolate.RefStructFactory setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IOutParameter IsOut(System.Func setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IOutParameter> IsOutReadOnlySpan(System.Func> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IOutParameter> IsOutSpan(System.Func> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IVerifyReadOnlySpanParameter IsReadOnlySpan(System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IVerifyRefParameter IsRef() { } + public static Mockolate.Parameters.IRefRefStructParameter IsRef(Mockolate.RefStructPredicate predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefRefStructParameter IsRef(Mockolate.RefStructTransform setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IRefParameter IsRef(System.Func setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IRefParameter IsRef(System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefRefStructParameter IsRef(Mockolate.RefStructPredicate predicate, Mockolate.RefStructTransform setter, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") { } public static Mockolate.Parameters.IRefParameter IsRef(System.Func predicate, System.Func setter, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefReadOnlySpan(System.Func, Mockolate.Setup.ReadOnlySpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefReadOnlySpan(System.Func, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefReadOnlySpan(System.Func, bool> predicate, System.Func, Mockolate.Setup.ReadOnlySpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefSpan(System.Func, Mockolate.Setup.SpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefSpan(System.Func, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefSpan(System.Func, bool> predicate, System.Func, Mockolate.Setup.SpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") { } public static Mockolate.Parameters.IParameter IsRefStruct(Mockolate.RefStructPredicate predicate) { } public static Mockolate.Parameters.IParameter IsRefStructBy(Mockolate.RefStructProjection projection) where TProjected : notnull { } @@ -285,8 +303,10 @@ namespace Mockolate public static Mockolate.Parameters.IParameterWithCallback Monitor(this Mockolate.Parameters.IParameterWithCallback parameter, out Mockolate.Parameters.IParameterMonitor monitor) { } public static Mockolate.Parameters.IRefParameter Monitor(this Mockolate.Parameters.IRefParameter parameter, out Mockolate.Parameters.IParameterMonitor monitor) { } } + public delegate T RefStructFactory(); public delegate bool RefStructPredicate(T value); public delegate TProjected RefStructProjection(T value); + public delegate T RefStructTransform(T value); public static class ReturnsThrowsAsyncExtensions { public static Mockolate.Setup.IReturnMethodSetupReturnBuilder> ReturnsAsync(this Mockolate.Setup.IReturnMethodSetup> setup, System.Func callback) { } @@ -1065,6 +1085,10 @@ namespace Mockolate.Parameters Mockolate.Parameters.IOutParameter Do(System.Action callback); bool TryGetValue(out T value); } + public interface IOutRefStructParameter + { + bool TryGetValue(out T value); + } public interface IParameter { void InvokeCallbacks(object? value); @@ -1095,6 +1119,10 @@ namespace Mockolate.Parameters Mockolate.Parameters.IRefParameter Do(System.Action callback); T GetValue(T value); } + public interface IRefRefStructParameter + { + T GetValue(T value); + } public interface IRefStructProjectionMatch : Mockolate.Parameters.IParameterMatch { object Project(T value); @@ -2395,7 +2423,7 @@ namespace Mockolate.Setup { public ReadOnlySpanWrapper(System.ReadOnlySpan span) { } public T[] ReadOnlySpanValues { get; } - public static System.ReadOnlySpan op_Implicit(Mockolate.Setup.ReadOnlySpanWrapper wrapper) { } + public static System.ReadOnlySpan op_Implicit(Mockolate.Setup.ReadOnlySpanWrapper? wrapper) { } public static Mockolate.Setup.ReadOnlySpanWrapper op_Implicit(System.ReadOnlySpan span) { } } public sealed class RefStructIndexerGetterSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.IRefStructIndexerGetterSetup, Mockolate.Setup.ISetup @@ -2506,6 +2534,7 @@ namespace Mockolate.Setup { public RefStructReturnMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher = null) { } public bool HasReturnValue { get; } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } public TReturn Invoke(T value, System.Func? defaultFactory = null) { } public bool Matches(T value) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2516,6 +2545,8 @@ namespace Mockolate.Setup { public RefStructReturnMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher1 = null, Mockolate.Parameters.IParameterMatch? matcher2 = null) { } public bool HasReturnValue { get; } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher2() { } public TReturn Invoke(T1 value1, T2 value2, System.Func? defaultFactory = null) { } public bool Matches(T1 value1, T2 value2) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2526,6 +2557,9 @@ namespace Mockolate.Setup { public RefStructReturnMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher1 = null, Mockolate.Parameters.IParameterMatch? matcher2 = null, Mockolate.Parameters.IParameterMatch? matcher3 = null) { } public bool HasReturnValue { get; } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher2() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher3() { } public TReturn Invoke(T1 value1, T2 value2, T3 value3, System.Func? defaultFactory = null) { } public bool Matches(T1 value1, T2 value2, T3 value3) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2536,6 +2570,10 @@ namespace Mockolate.Setup { public RefStructReturnMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher1 = null, Mockolate.Parameters.IParameterMatch? matcher2 = null, Mockolate.Parameters.IParameterMatch? matcher3 = null, Mockolate.Parameters.IParameterMatch? matcher4 = null) { } public bool HasReturnValue { get; } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher2() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher3() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher4() { } public TReturn Invoke(T1 value1, T2 value2, T3 value3, T4 value4, System.Func? defaultFactory = null) { } public bool Matches(T1 value1, T2 value2, T3 value3, T4 value4) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2553,6 +2591,7 @@ namespace Mockolate.Setup public sealed class RefStructVoidMethodSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.IRefStructVoidMethodSetup, Mockolate.Setup.ISetup { public RefStructVoidMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher = null) { } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } public void Invoke(T value) { } public bool Matches(T value) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2562,6 +2601,8 @@ namespace Mockolate.Setup public sealed class RefStructVoidMethodSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.IRefStructVoidMethodSetup, Mockolate.Setup.ISetup { public RefStructVoidMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher1 = null, Mockolate.Parameters.IParameterMatch? matcher2 = null) { } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher2() { } public void Invoke(T1 value1, T2 value2) { } public bool Matches(T1 value1, T2 value2) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2571,6 +2612,9 @@ namespace Mockolate.Setup public sealed class RefStructVoidMethodSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.IRefStructVoidMethodSetup, Mockolate.Setup.ISetup { public RefStructVoidMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher1 = null, Mockolate.Parameters.IParameterMatch? matcher2 = null, Mockolate.Parameters.IParameterMatch? matcher3 = null) { } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher2() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher3() { } public void Invoke(T1 value1, T2 value2, T3 value3) { } public bool Matches(T1 value1, T2 value2, T3 value3) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2580,6 +2624,10 @@ namespace Mockolate.Setup public sealed class RefStructVoidMethodSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.IRefStructVoidMethodSetup, Mockolate.Setup.ISetup { public RefStructVoidMethodSetup(string name, Mockolate.Parameters.IParameterMatch? matcher1 = null, Mockolate.Parameters.IParameterMatch? matcher2 = null, Mockolate.Parameters.IParameterMatch? matcher3 = null, Mockolate.Parameters.IParameterMatch? matcher4 = null) { } + public Mockolate.Parameters.IParameterMatch? GetMatcher1() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher2() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher3() { } + public Mockolate.Parameters.IParameterMatch? GetMatcher4() { } public void Invoke(T1 value1, T2 value2, T3 value3, T4 value4) { } public bool Matches(T1 value1, T2 value2, T3 value3, T4 value4) { } protected override bool MatchesInteraction(Mockolate.Interactions.IMethodInteraction interaction) { } @@ -2732,7 +2780,7 @@ namespace Mockolate.Setup { public SpanWrapper(System.Span span) { } public T[] SpanValues { get; } - public static System.Span op_Implicit(Mockolate.Setup.SpanWrapper wrapper) { } + public static System.Span op_Implicit(Mockolate.Setup.SpanWrapper? wrapper) { } public static Mockolate.Setup.SpanWrapper op_Implicit(System.Span span) { } } public abstract class VoidMethodSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.ISetup, Mockolate.Setup.IVoidMethodSetup, Mockolate.Setup.IVoidMethodSetupCallbackBuilder, Mockolate.Setup.IVoidMethodSetupCallbackWhenBuilder, Mockolate.Setup.IVoidMethodSetupParallelCallbackBuilder, Mockolate.Setup.IVoidMethodSetupReturnBuilder, Mockolate.Setup.IVoidMethodSetupReturnWhenBuilder diff --git a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt index a02916fd..5956d0d4 100644 --- a/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt +++ b/Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt @@ -96,8 +96,12 @@ namespace Mockolate public static Mockolate.It.IIsParameter Is(T value, [System.Runtime.CompilerServices.CallerArgumentExpression("value")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IParameterWithCallback IsAny() { } public static Mockolate.Parameters.IOutParameter IsAnyOut() { } + public static Mockolate.Parameters.IOutParameter> IsAnyOutReadOnlySpan() { } + public static Mockolate.Parameters.IOutParameter> IsAnyOutSpan() { } public static Mockolate.Parameters.IVerifyReadOnlySpanParameter IsAnyReadOnlySpan() { } public static Mockolate.Parameters.IRefParameter IsAnyRef() { } + public static Mockolate.Parameters.IRefParameter> IsAnyRefReadOnlySpan() { } + public static Mockolate.Parameters.IRefParameter> IsAnyRefSpan() { } public static Mockolate.Parameters.IVerifySpanParameter IsAnySpan() { } public static Mockolate.Parameters.IParameterWithCallback IsFalse() { } public static Mockolate.It.IInRangeParameter IsInRange(T minimum, T maximum, [System.Runtime.CompilerServices.CallerArgumentExpression("minimum")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("maximum")] string doNotPopulateThisValue2 = "") @@ -109,11 +113,19 @@ namespace Mockolate public static Mockolate.It.IIsOneOfParameter IsOneOf(System.Collections.Generic.IEnumerable values) { } public static Mockolate.Parameters.IVerifyOutParameter IsOut() { } public static Mockolate.Parameters.IOutParameter IsOut(System.Func setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IOutParameter> IsOutReadOnlySpan(System.Func> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IOutParameter> IsOutSpan(System.Func> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IVerifyReadOnlySpanParameter IsReadOnlySpan(System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IVerifyRefParameter IsRef() { } public static Mockolate.Parameters.IRefParameter IsRef(System.Func setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IRefParameter IsRef(System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IRefParameter IsRef(System.Func predicate, System.Func setter, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefReadOnlySpan(System.Func, Mockolate.Setup.ReadOnlySpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefReadOnlySpan(System.Func, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefReadOnlySpan(System.Func, bool> predicate, System.Func, Mockolate.Setup.ReadOnlySpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefSpan(System.Func, Mockolate.Setup.SpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefSpan(System.Func, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } + public static Mockolate.Parameters.IRefParameter> IsRefSpan(System.Func, bool> predicate, System.Func, Mockolate.Setup.SpanWrapper> setter, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue1 = "", [System.Runtime.CompilerServices.CallerArgumentExpression("setter")] string doNotPopulateThisValue2 = "") { } public static Mockolate.Parameters.IVerifySpanParameter IsSpan(System.Func predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { } public static Mockolate.Parameters.IParameterWithCallback IsTrue() { } public static Mockolate.It.IIsParameter IsValue(T value) { } @@ -2173,7 +2185,7 @@ namespace Mockolate.Setup { public ReadOnlySpanWrapper(System.ReadOnlySpan span) { } public T[] ReadOnlySpanValues { get; } - public static System.ReadOnlySpan op_Implicit(Mockolate.Setup.ReadOnlySpanWrapper wrapper) { } + public static System.ReadOnlySpan op_Implicit(Mockolate.Setup.ReadOnlySpanWrapper? wrapper) { } public static Mockolate.Setup.ReadOnlySpanWrapper op_Implicit(System.ReadOnlySpan span) { } } public abstract class ReturnMethodSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.IReturnMethodSetupCallbackBuilder, Mockolate.Setup.IReturnMethodSetupCallbackWhenBuilder, Mockolate.Setup.IReturnMethodSetupParallelCallbackBuilder, Mockolate.Setup.IReturnMethodSetupReturnBuilder, Mockolate.Setup.IReturnMethodSetupReturnWhenBuilder, Mockolate.Setup.IReturnMethodSetup, Mockolate.Setup.ISetup @@ -2322,7 +2334,7 @@ namespace Mockolate.Setup { public SpanWrapper(System.Span span) { } public T[] SpanValues { get; } - public static System.Span op_Implicit(Mockolate.Setup.SpanWrapper wrapper) { } + public static System.Span op_Implicit(Mockolate.Setup.SpanWrapper? wrapper) { } public static Mockolate.Setup.SpanWrapper op_Implicit(System.Span span) { } } public abstract class VoidMethodSetup : Mockolate.Setup.MethodSetup, Mockolate.Setup.IMethodSetup, Mockolate.Setup.ISetup, Mockolate.Setup.IVoidMethodSetup, Mockolate.Setup.IVoidMethodSetupCallbackBuilder, Mockolate.Setup.IVoidMethodSetupCallbackWhenBuilder, Mockolate.Setup.IVoidMethodSetupParallelCallbackBuilder, Mockolate.Setup.IVoidMethodSetupReturnBuilder, Mockolate.Setup.IVoidMethodSetupReturnWhenBuilder diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockTests.RefStructTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockTests.RefStructTests.cs index 9804b78d..5873deb8 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/MockTests.RefStructTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/MockTests.RefStructTests.cs @@ -515,7 +515,7 @@ await That(result.Sources["Mock.IPacketTransformer.g.cs"]) } [Fact] - public async Task MethodWithOutRefStructParameter_ShouldEmitNotSupportedExceptionAndSkipSetupSurface() + public async Task MethodWithOutRefStructParameter_ShouldEmitRefStructOutParameterPipeline() { GeneratorResult result = Generator .Run(""" @@ -542,12 +542,12 @@ public static void Main(string[] args) await That(result.Sources).ContainsKey("Mock.IPacketBag.g.cs"); await That(result.Sources["Mock.IPacketBag.g.cs"]) - .Contains("Mockolate: out/ref ref-struct parameters are not supported. Method 'global::MyCode.IPacketBag.Take'.") - .Because("the method body must throw NotSupportedException because the ref-struct out parameter cannot flow through the setup pipeline").And - .DoesNotContain("IRefStructVoidMethodSetup") - .Because("the setup-interface declaration must be skipped for unsupported ref-struct signatures").And - .DoesNotContain("new global::Mockolate.Setup.RefStructVoidMethodSetup") - .Because("the setup-interface implementation must be skipped for unsupported ref-struct signatures"); + .Contains("IRefStructVoidMethodSetup") + .Because("the setup-interface declaration is now emitted for out ref-struct parameters").And + .Contains("new global::Mockolate.Setup.RefStructVoidMethodSetup") + .Because("the setup-builder constructs the ref-struct setup type").And + .Contains("global::Mockolate.Parameters.IOutRefStructParameter") + .Because("the mock body routes the out slot through IOutRefStructParameter instead of IOutParameter"); } [Fact] diff --git a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/Mock.IRefStructConsumer.g.cs b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/Mock.IRefStructConsumer.g.cs index 76840834..1c809374 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/Mock.IRefStructConsumer.g.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/Mock.IRefStructConsumer.g.cs @@ -24,7 +24,15 @@ internal class IRefStructConsumer : internal const int MemberId_Indexer_global__Mockolate_Tests_GeneratorCoverage_Packet_int_global__Mockolate_Tests_GeneratorCoverage_Packet_int_global__Mockolate_Tests_GeneratorCoverage_Packet_Get = 0; internal const int MemberId_Indexer_global__Mockolate_Tests_GeneratorCoverage_Packet_int_global__Mockolate_Tests_GeneratorCoverage_Packet_int_global__Mockolate_Tests_GeneratorCoverage_Packet_Set = 1; internal const int MemberId_Consume5 = 2; - internal const int MemberCount = 3; + internal const int MemberId_Produce = 3; + internal const int MemberId_Mutate = 4; + internal const int MemberId_Inspect = 5; + internal const int MemberId_ProduceSpan = 6; + internal const int MemberId_MutateSpan = 7; + internal const int MemberId_ProduceReadOnlySpan = 8; + internal const int MemberId_MutateReadOnlySpan = 9; + internal const int MemberId_InspectSpan = 10; + internal const int MemberCount = 11; /// /// Creates a FastMockInteractions sized to MemberCount for use as the mock's interaction store. @@ -47,6 +55,18 @@ internal class IRefStructConsumer : global::Mockolate.MockRegistry global::Mockolate.IMock.MockRegistry => this.MockRegistry; private global::Mockolate.MockRegistry MockRegistry { get; } + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + private global::Mockolate.Interactions.FastMethod1Buffer> MockolateBuffer_ProduceSpan + => field ?? (field = ((global::Mockolate.Interactions.FastMockInteractions)this.MockRegistry.Interactions).GetOrCreateBuffer>>(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, static fast => new global::Mockolate.Interactions.FastMethod1Buffer>(fast))); + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + private global::Mockolate.Interactions.FastMethod1Buffer> MockolateBuffer_MutateSpan + => field ?? (field = ((global::Mockolate.Interactions.FastMockInteractions)this.MockRegistry.Interactions).GetOrCreateBuffer>>(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, static fast => new global::Mockolate.Interactions.FastMethod1Buffer>(fast))); + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + private global::Mockolate.Interactions.FastMethod1Buffer> MockolateBuffer_ProduceReadOnlySpan + => field ?? (field = ((global::Mockolate.Interactions.FastMockInteractions)this.MockRegistry.Interactions).GetOrCreateBuffer>>(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, static fast => new global::Mockolate.Interactions.FastMethod1Buffer>(fast))); + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + private global::Mockolate.Interactions.FastMethod1Buffer> MockolateBuffer_MutateReadOnlySpan + => field ?? (field = ((global::Mockolate.Interactions.FastMockInteractions)this.MockRegistry.Interactions).GetOrCreateBuffer>>(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, static fast => new global::Mockolate.Interactions.FastMethod1Buffer>(fast))); /// [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] @@ -187,6 +207,402 @@ public void Consume5(global::Mockolate.Tests.GeneratorCoverage.Packet p1, global #endif } + /// + public void Produce(out global::Mockolate.Tests.GeneratorCoverage.Packet packet) + { +#if NET9_0_OR_GREATER + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.RefStructMethodInvocation("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Produce", "packet")); + packet = default!; + bool matched = false; + foreach (global::Mockolate.Setup.RefStructVoidMethodSetup setup in this.MockRegistry.GetMethodSetups>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Produce")) + { + if (!setup.Matches(packet)) + { + continue; + } + + matched = true; + if (setup.GetMatcher1() is global::Mockolate.Parameters.IOutRefStructParameter outParam1 && outParam1.TryGetValue(out packet)) { } + else + { + packet = default!; + } + setup.Invoke(packet); + return; + } + if (!matched && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Produce(Packet)' was invoked without prior setup."); + } +#else +#error Mockolate: methods with ref-struct parameters require .NET 9 or later (uses the 'allows ref struct' anti-constraint). + throw new global::System.NotSupportedException(); +#endif + } + + /// + public void Mutate(ref global::Mockolate.Tests.GeneratorCoverage.Packet packet) + { +#if NET9_0_OR_GREATER + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.RefStructMethodInvocation("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Mutate", "packet")); + bool matched = false; + foreach (global::Mockolate.Setup.RefStructVoidMethodSetup setup in this.MockRegistry.GetMethodSetups>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Mutate")) + { + if (!setup.Matches(packet)) + { + continue; + } + + matched = true; + if (setup.GetMatcher1() is global::Mockolate.Parameters.IRefRefStructParameter refParam1) + { + packet = refParam1.GetValue(packet); + } + setup.Invoke(packet); + return; + } + if (!matched && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Mutate(Packet)' was invoked without prior setup."); + } +#else +#error Mockolate: methods with ref-struct parameters require .NET 9 or later (uses the 'allows ref struct' anti-constraint). + throw new global::System.NotSupportedException(); +#endif + } + + /// + public void Inspect(ref readonly global::Mockolate.Tests.GeneratorCoverage.Packet packet) + { +#if NET9_0_OR_GREATER + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.RefStructMethodInvocation("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Inspect", "packet")); + bool matched = false; + foreach (global::Mockolate.Setup.RefStructVoidMethodSetup setup in this.MockRegistry.GetMethodSetups>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Inspect")) + { + if (!setup.Matches(packet)) + { + continue; + } + + matched = true; + setup.Invoke(packet); + return; + } + if (!matched && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Inspect(Packet)' was invoked without prior setup."); + } +#else +#error Mockolate: methods with ref-struct parameters require .NET 9 or later (uses the 'allows ref struct' anti-constraint). + throw new global::System.NotSupportedException(); +#endif + } + + /// + public void ProduceSpan(out global::System.Span span) + { + global::Mockolate.Setup.VoidMethodSetup>? methodSetup = null; + if (string.IsNullOrEmpty(this.MockRegistry.Scenario)) + { + global::Mockolate.Setup.MethodSetup[]? snapshot_methodSetup = this.MockRegistry.GetMethodSetupSnapshot(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan); + if (snapshot_methodSetup is not null) + { + for (int i_methodSetup = snapshot_methodSetup.Length - 1; i_methodSetup >= 0; i_methodSetup--) + { + if (snapshot_methodSetup[i_methodSetup] is global::Mockolate.Setup.VoidMethodSetup> s_methodSetup && s_methodSetup.Matches(default)) + { + methodSetup = s_methodSetup; + break; + } + } + } + } + if (methodSetup is null) + { + foreach (global::Mockolate.Setup.VoidMethodSetup> s_methodSetup in this.MockRegistry.GetMethodSetups>>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan")) + { + if (s_methodSetup.Matches(default)) + { + methodSetup = s_methodSetup; + break; + } + } + } + bool hasWrappedResult = false; + span = default!; + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockolateBuffer_ProduceSpan.Append("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", new global::Mockolate.Setup.SpanWrapper(span)); + } + try + { + if (this.MockRegistry.Wraps is global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer wraps) + { + wraps.ProduceSpan(out span); + hasWrappedResult = true; + } + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection) + { + if (methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection wpc) + { + if (wpc.Parameter1 is not global::Mockolate.Parameters.IOutParameter> outParam1 || !outParam1.TryGetValue(out global::Mockolate.Setup.SpanWrapper outTemp1)) + { + span = this.MockRegistry.Behavior.DefaultValue.Generate(default(global::Mockolate.Setup.SpanWrapper)!); + } + else + { + span = outTemp1; + } + } + else + { + span = this.MockRegistry.Behavior.DefaultValue.Generate(default(global::Mockolate.Setup.SpanWrapper)!); + } + } + } + finally + { + methodSetup?.TriggerCallbacks(new global::Mockolate.Setup.SpanWrapper(span)); + } + if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan(Span)' was invoked without prior setup."); + } + } + + /// + public void MutateSpan(ref global::System.Span span) + { + var ref_span = span; + global::Mockolate.Setup.VoidMethodSetup>? methodSetup = null; + if (string.IsNullOrEmpty(this.MockRegistry.Scenario)) + { + global::Mockolate.Setup.MethodSetup[]? snapshot_methodSetup = this.MockRegistry.GetMethodSetupSnapshot(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan); + if (snapshot_methodSetup is not null) + { + for (int i_methodSetup = snapshot_methodSetup.Length - 1; i_methodSetup >= 0; i_methodSetup--) + { + if (snapshot_methodSetup[i_methodSetup] is global::Mockolate.Setup.VoidMethodSetup> s_methodSetup && s_methodSetup.Matches(ref_span)) + { + methodSetup = s_methodSetup; + break; + } + } + } + } + if (methodSetup is null) + { + foreach (global::Mockolate.Setup.VoidMethodSetup> s_methodSetup in this.MockRegistry.GetMethodSetups>>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan")) + { + if (s_methodSetup.Matches(ref_span)) + { + methodSetup = s_methodSetup; + break; + } + } + } + bool hasWrappedResult = false; + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockolateBuffer_MutateSpan.Append("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", new global::Mockolate.Setup.SpanWrapper(span)); + } + try + { + if (this.MockRegistry.Wraps is global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer wraps) + { + wraps.MutateSpan(ref span); + hasWrappedResult = true; + } + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection) + { + if (methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection wpc) + { + if (wpc.Parameter1 is global::Mockolate.Parameters.IRefParameter> refParam1) + { + span = refParam1.GetValue(span); + } + } + else + { + } + } + } + finally + { + methodSetup?.TriggerCallbacks(new global::Mockolate.Setup.SpanWrapper(span)); + } + if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan(Span)' was invoked without prior setup."); + } + } + + /// + public void ProduceReadOnlySpan(out global::System.ReadOnlySpan span) + { + global::Mockolate.Setup.VoidMethodSetup>? methodSetup = null; + if (string.IsNullOrEmpty(this.MockRegistry.Scenario)) + { + global::Mockolate.Setup.MethodSetup[]? snapshot_methodSetup = this.MockRegistry.GetMethodSetupSnapshot(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan); + if (snapshot_methodSetup is not null) + { + for (int i_methodSetup = snapshot_methodSetup.Length - 1; i_methodSetup >= 0; i_methodSetup--) + { + if (snapshot_methodSetup[i_methodSetup] is global::Mockolate.Setup.VoidMethodSetup> s_methodSetup && s_methodSetup.Matches(default)) + { + methodSetup = s_methodSetup; + break; + } + } + } + } + if (methodSetup is null) + { + foreach (global::Mockolate.Setup.VoidMethodSetup> s_methodSetup in this.MockRegistry.GetMethodSetups>>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan")) + { + if (s_methodSetup.Matches(default)) + { + methodSetup = s_methodSetup; + break; + } + } + } + bool hasWrappedResult = false; + span = default!; + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockolateBuffer_ProduceReadOnlySpan.Append("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", new global::Mockolate.Setup.ReadOnlySpanWrapper(span)); + } + try + { + if (this.MockRegistry.Wraps is global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer wraps) + { + wraps.ProduceReadOnlySpan(out span); + hasWrappedResult = true; + } + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection) + { + if (methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection wpc) + { + if (wpc.Parameter1 is not global::Mockolate.Parameters.IOutParameter> outParam1 || !outParam1.TryGetValue(out global::Mockolate.Setup.ReadOnlySpanWrapper outTemp1)) + { + span = this.MockRegistry.Behavior.DefaultValue.Generate(default(global::Mockolate.Setup.ReadOnlySpanWrapper)!); + } + else + { + span = outTemp1; + } + } + else + { + span = this.MockRegistry.Behavior.DefaultValue.Generate(default(global::Mockolate.Setup.ReadOnlySpanWrapper)!); + } + } + } + finally + { + methodSetup?.TriggerCallbacks(new global::Mockolate.Setup.ReadOnlySpanWrapper(span)); + } + if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan(ReadOnlySpan)' was invoked without prior setup."); + } + } + + /// + public void MutateReadOnlySpan(ref global::System.ReadOnlySpan span) + { + var ref_span = span; + global::Mockolate.Setup.VoidMethodSetup>? methodSetup = null; + if (string.IsNullOrEmpty(this.MockRegistry.Scenario)) + { + global::Mockolate.Setup.MethodSetup[]? snapshot_methodSetup = this.MockRegistry.GetMethodSetupSnapshot(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan); + if (snapshot_methodSetup is not null) + { + for (int i_methodSetup = snapshot_methodSetup.Length - 1; i_methodSetup >= 0; i_methodSetup--) + { + if (snapshot_methodSetup[i_methodSetup] is global::Mockolate.Setup.VoidMethodSetup> s_methodSetup && s_methodSetup.Matches(ref_span)) + { + methodSetup = s_methodSetup; + break; + } + } + } + } + if (methodSetup is null) + { + foreach (global::Mockolate.Setup.VoidMethodSetup> s_methodSetup in this.MockRegistry.GetMethodSetups>>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan")) + { + if (s_methodSetup.Matches(ref_span)) + { + methodSetup = s_methodSetup; + break; + } + } + } + bool hasWrappedResult = false; + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockolateBuffer_MutateReadOnlySpan.Append("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", new global::Mockolate.Setup.ReadOnlySpanWrapper(span)); + } + try + { + if (this.MockRegistry.Wraps is global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer wraps) + { + wraps.MutateReadOnlySpan(ref span); + hasWrappedResult = true; + } + if (!hasWrappedResult || methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection) + { + if (methodSetup is global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection wpc) + { + if (wpc.Parameter1 is global::Mockolate.Parameters.IRefParameter> refParam1) + { + span = refParam1.GetValue(span); + } + } + else + { + } + } + } + finally + { + methodSetup?.TriggerCallbacks(new global::Mockolate.Setup.ReadOnlySpanWrapper(span)); + } + if (methodSetup is null && !hasWrappedResult && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan(ReadOnlySpan)' was invoked without prior setup."); + } + } + + /// + public void InspectSpan(ref readonly global::System.Span span) + { +#if NET9_0_OR_GREATER + this.MockRegistry.RegisterInteraction(new global::Mockolate.Interactions.RefStructMethodInvocation("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.InspectSpan", "span")); + bool matched = false; + foreach (global::Mockolate.Setup.RefStructVoidMethodSetup> setup in this.MockRegistry.GetMethodSetups>>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.InspectSpan")) + { + if (!setup.Matches(span)) + { + continue; + } + + matched = true; + setup.Invoke(span); + return; + } + if (!matched && this.MockRegistry.Behavior.ThrowWhenNotSetup) + { + throw new global::Mockolate.Exceptions.MockNotSetupException("The method 'global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.InspectSpan(Span)' was invoked without prior setup."); + } +#else +#error Mockolate: methods with ref-struct parameters require .NET 9 or later (uses the 'allows ref struct' anti-constraint). + throw new global::System.NotSupportedException(); +#endif + } + #endregion Mockolate.Tests.GeneratorCoverage.IRefStructConsumer #region IMockSetupForIRefStructConsumer @@ -215,10 +631,162 @@ public void Consume5(global::Mockolate.Tests.GeneratorCoverage.Packet p1, global } #endif +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup global::Mockolate.Mock.IMockSetupForIRefStructConsumer.Produce(global::Mockolate.Parameters.IOutRefStructParameter? packet) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Produce", (global::Mockolate.Parameters.IParameterMatch?)packet); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_Produce, methodSetup); + return methodSetup; + } +#endif + +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup global::Mockolate.Mock.IMockSetupForIRefStructConsumer.Mutate(global::Mockolate.Parameters.IRefRefStructParameter? packet) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Mutate", (global::Mockolate.Parameters.IParameterMatch?)packet); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_Mutate, methodSetup); + return methodSetup; + } +#endif + +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup global::Mockolate.Mock.IMockSetupForIRefStructConsumer.Inspect(global::Mockolate.Parameters.IRefRefStructParameter? packet) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Inspect", (global::Mockolate.Parameters.IParameterMatch?)packet); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_Inspect, methodSetup); + return methodSetup; + } +#endif + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IOutParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IRefParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IOutParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IRefParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, methodSetup); + return methodSetup; + } + +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.InspectSpan(global::Mockolate.Parameters.IRefRefStructParameter>? span) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.InspectSpan", (global::Mockolate.Parameters.IParameterMatch>?)span); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_InspectSpan, methodSetup); + return methodSetup; + } +#endif + #endregion IMockSetupForIRefStructConsumer #region IMockVerifyForIRefStructConsumer + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"ProduceSpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IVerifyOutParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.SpanWrapper))), () => $"ProduceSpan({span})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"MutateSpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IVerifyRefParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.SpanWrapper))), () => $"MutateSpan({span})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"ProduceReadOnlySpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IVerifyOutParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.ReadOnlySpanWrapper))), () => $"ProduceReadOnlySpan({span})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"MutateReadOnlySpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IVerifyRefParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.ReadOnlySpanWrapper))), () => $"MutateReadOnlySpan({span})"); #endregion IMockVerifyForIRefStructConsumer } @@ -229,6 +797,54 @@ private sealed class VerifyMonitorIRefStructConsumer(global::Mockolate.MockRegis #region IMockVerifyForIRefStructConsumer + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"ProduceSpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IVerifyOutParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.SpanWrapper))), () => $"ProduceSpan({span})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"MutateSpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IVerifyRefParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.SpanWrapper))), () => $"MutateSpan({span})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"ProduceReadOnlySpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IVerifyOutParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.ReadOnlySpanWrapper))), () => $"ProduceReadOnlySpan({span})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", __i => parameters switch + { + global::Mockolate.Parameters.IParametersMatch m => m.Matches([__i.Parameter1]), + global::Mockolate.Parameters.INamedParametersMatch m => m.Matches([("span", __i.Parameter1)]), + _ => true + }, () => $"MutateReadOnlySpan({parameters})"); + /// + global::Mockolate.Verify.VerificationResult IMockVerifyForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IVerifyRefParameter span) + => this.MockRegistry.VerifyMethod>>(this, global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", __i => + (span is global::Mockolate.Parameters.IParameterMatch> spanMatch ? spanMatch.Matches(__i.Parameter1) : global::System.Collections.Generic.EqualityComparer>.Default.Equals(__i.Parameter1, default(global::Mockolate.Setup.ReadOnlySpanWrapper))), () => $"MutateReadOnlySpan({span})"); #endregion IMockVerifyForIRefStructConsumer } @@ -274,6 +890,110 @@ public MockInScenarioForIRefStructConsumer(global::Mockolate.MockRegistry mockRe } #endif +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup global::Mockolate.Mock.IMockSetupForIRefStructConsumer.Produce(global::Mockolate.Parameters.IOutRefStructParameter? packet) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Produce", (global::Mockolate.Parameters.IParameterMatch?)packet); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_Produce, _scenarioName, methodSetup); + return methodSetup; + } +#endif + +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup global::Mockolate.Mock.IMockSetupForIRefStructConsumer.Mutate(global::Mockolate.Parameters.IRefRefStructParameter? packet) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Mutate", (global::Mockolate.Parameters.IParameterMatch?)packet); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_Mutate, _scenarioName, methodSetup); + return methodSetup; + } +#endif + +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup global::Mockolate.Mock.IMockSetupForIRefStructConsumer.Inspect(global::Mockolate.Parameters.IRefRefStructParameter? packet) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.Inspect", (global::Mockolate.Parameters.IParameterMatch?)packet); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_Inspect, _scenarioName, methodSetup); + return methodSetup; + } +#endif + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, _scenarioName, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceSpan(global::Mockolate.Parameters.IOutParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceSpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceSpan, _scenarioName, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, _scenarioName, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateSpan(global::Mockolate.Parameters.IRefParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateSpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateSpan, _scenarioName, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, _scenarioName, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.ProduceReadOnlySpan(global::Mockolate.Parameters.IOutParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.ProduceReadOnlySpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_ProduceReadOnlySpan, _scenarioName, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IParameters parameters) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameters(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", parameters, "span"); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, _scenarioName, methodSetup); + return methodSetup; + } + + /// + global::Mockolate.Setup.IVoidMethodSetupWithCallback> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.MutateReadOnlySpan(global::Mockolate.Parameters.IRefParameter> span) + { + var methodSetup = new global::Mockolate.Setup.VoidMethodSetup>.WithParameterCollection(MockRegistry, "global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.MutateReadOnlySpan", (global::Mockolate.Parameters.IParameterMatch>)(span)); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_MutateReadOnlySpan, _scenarioName, methodSetup); + return methodSetup; + } + +#if NET9_0_OR_GREATER + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup> global::Mockolate.Mock.IMockSetupForIRefStructConsumer.InspectSpan(global::Mockolate.Parameters.IRefRefStructParameter>? span) + { + var methodSetup = new global::Mockolate.Setup.RefStructVoidMethodSetup>("global::Mockolate.Tests.GeneratorCoverage.IRefStructConsumer.InspectSpan", (global::Mockolate.Parameters.IParameterMatch>?)span); + this.MockRegistry.SetupMethod(global::Mockolate.Mock.IRefStructConsumer.MemberId_InspectSpan, _scenarioName, methodSetup); + return methodSetup; + } +#endif + #endregion IMockSetupForIRefStructConsumer } @@ -411,6 +1131,106 @@ internal interface IMockSetupForIRefStructConsumer : global::Mockolate.Setup.IMo global::Mockolate.Setup.IRefStructVoidMethodSetup Consume5(global::Mockolate.Parameters.IParameter? p1, global::Mockolate.Parameters.IParameter? p2, global::Mockolate.Parameters.IParameter? p3, global::Mockolate.Parameters.IParameter? p4, global::Mockolate.Parameters.IParameter? p5); #endif +#if NET9_0_OR_GREATER + /// + /// Setup for the method Produce(Packet) — ref-struct parameter pipeline (narrow setup surface). + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup Produce(global::Mockolate.Parameters.IOutRefStructParameter? packet); +#endif + +#if NET9_0_OR_GREATER + /// + /// Setup for the method Mutate(Packet) — ref-struct parameter pipeline (narrow setup surface). + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup Mutate(global::Mockolate.Parameters.IRefRefStructParameter? packet); +#endif + +#if NET9_0_OR_GREATER + /// + /// Setup for the method Inspect(Packet) — ref-struct parameter pipeline (narrow setup surface). + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup Inspect(global::Mockolate.Parameters.IRefRefStructParameter? packet); +#endif + + /// + /// Setup for the method ProduceSpan(out Span<int>) with the given . + /// + /// + /// This overload configures the setup via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> ProduceSpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Setup for the method ProduceSpan(out Span<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> ProduceSpan(global::Mockolate.Parameters.IOutParameter> span); + + /// + /// Setup for the method MutateSpan(ref Span<int>) with the given . + /// + /// + /// This overload configures the setup via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> MutateSpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Setup for the method MutateSpan(ref Span<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> MutateSpan(global::Mockolate.Parameters.IRefParameter> span); + + /// + /// Setup for the method ProduceReadOnlySpan(out ReadOnlySpan<int>) with the given . + /// + /// + /// This overload configures the setup via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> ProduceReadOnlySpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Setup for the method ProduceReadOnlySpan(out ReadOnlySpan<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> ProduceReadOnlySpan(global::Mockolate.Parameters.IOutParameter> span); + + /// + /// Setup for the method MutateReadOnlySpan(ref ReadOnlySpan<int>) with the given . + /// + /// + /// This overload configures the setup via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> MutateReadOnlySpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Setup for the method MutateReadOnlySpan(ref ReadOnlySpan<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Setup.IVoidMethodSetupWithCallback> MutateReadOnlySpan(global::Mockolate.Parameters.IRefParameter> span); + +#if NET9_0_OR_GREATER + /// + /// Setup for the method InspectSpan(Span<int>) — ref-struct parameter pipeline (narrow setup surface). + /// + global::Mockolate.Setup.IRefStructVoidMethodSetup> InspectSpan(global::Mockolate.Parameters.IRefRefStructParameter>? span); +#endif + } /// @@ -418,6 +1238,78 @@ internal interface IMockSetupForIRefStructConsumer : global::Mockolate.Setup.IMo /// internal interface IMockVerifyForIRefStructConsumer : global::Mockolate.Verify.IMockVerify { + /// + /// Verify invocations for the method ProduceSpan(out Span<int>) with the given . + /// + /// + /// This overload matches invocations via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Verify.VerificationResult ProduceSpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Verify invocations for the method ProduceSpan(out Span<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Verify.VerificationResult ProduceSpan(global::Mockolate.Parameters.IVerifyOutParameter span); + + /// + /// Verify invocations for the method MutateSpan(ref Span<int>) with the given . + /// + /// + /// This overload matches invocations via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Verify.VerificationResult MutateSpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Verify invocations for the method MutateSpan(ref Span<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Verify.VerificationResult MutateSpan(global::Mockolate.Parameters.IVerifyRefParameter span); + + /// + /// Verify invocations for the method ProduceReadOnlySpan(out ReadOnlySpan<int>) with the given . + /// + /// + /// This overload matches invocations via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Verify.VerificationResult ProduceReadOnlySpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Verify invocations for the method ProduceReadOnlySpan(out ReadOnlySpan<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Verify.VerificationResult ProduceReadOnlySpan(global::Mockolate.Parameters.IVerifyOutParameter span); + + /// + /// Verify invocations for the method MutateReadOnlySpan(ref ReadOnlySpan<int>) with the given . + /// + /// + /// This overload matches invocations via a custom Match predicate (for example AnyParameters() or Parameters(Func<object?[], bool>, string)) rather than per-parameter matchers. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue - 1)] + global::Mockolate.Verify.VerificationResult MutateReadOnlySpan(global::Mockolate.Parameters.IParameters parameters); + + /// + /// Verify invocations for the method MutateReadOnlySpan(ref ReadOnlySpan<int>) with the given . + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + global::Mockolate.Verify.VerificationResult MutateReadOnlySpan(global::Mockolate.Parameters.IVerifyRefParameter span); + } } /// diff --git a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/RefStructMethodSetups.g.cs b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/RefStructMethodSetups.g.cs index 869bd79b..9a801a50 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/RefStructMethodSetups.g.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/RefStructMethodSetups.g.cs @@ -65,6 +65,17 @@ public bool Matches(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) && (_matcher4 is null || _matcher4.Matches(value4)) && (_matcher5 is null || _matcher5.Matches(value5)); + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public global::Mockolate.Parameters.IParameterMatch? GetMatcher1() => _matcher1; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public global::Mockolate.Parameters.IParameterMatch? GetMatcher2() => _matcher2; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public global::Mockolate.Parameters.IParameterMatch? GetMatcher3() => _matcher3; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public global::Mockolate.Parameters.IParameterMatch? GetMatcher4() => _matcher4; + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + public global::Mockolate.Parameters.IParameterMatch? GetMatcher5() => _matcher5; + public void Invoke(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) { _matcher1?.InvokeCallbacks(value1); diff --git a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/MockGenerationSnapshotTests.cs b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/MockGenerationSnapshotTests.cs index 463061c0..b6297d62 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/MockGenerationSnapshotTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/MockGenerationSnapshotTests.cs @@ -41,7 +41,7 @@ await That(SnapshotStorage.StripConfigSpecificLines(generated[fileName])) [ new( "BaseClass_WithMultipleAdditionalInterfaces_CanBeCreated", - ["ComprehensiveAbstractClass.cs", "ICombinationParts.cs",], + ["ComprehensiveAbstractClass.cs", "MyAbstractBase.cs", "ICombinationMockA.cs", "ICombinationMockB.cs",], """ ComprehensiveAbstractClass sut = ComprehensiveAbstractClass.CreateMock() .Implementing() @@ -50,7 +50,7 @@ await That(SnapshotStorage.StripConfigSpecificLines(generated[fileName])) []), new( "ComprehensiveAbstractClass_CanBeCreated", - ["ComprehensiveAbstractClass.cs",], + ["ComprehensiveAbstractClass.cs", "MyAbstractBase.cs",], """ ComprehensiveAbstractClass sut = ComprehensiveAbstractClass.CreateMock(); """, @@ -64,7 +64,10 @@ await That(SnapshotStorage.StripConfigSpecificLines(generated[fileName])) []), new( "ComprehensiveInterface_CanBeCreated", - ["ComprehensiveDelegate.cs", "IComprehensiveInterface.cs",], + [ + "ComprehensiveDelegate.cs", "IComprehensiveInterface.cs", + "MyBase.cs", "MyEnum.cs", "MyEventArgs.cs", "MyStruct.cs", + ], """ IComprehensiveInterface sut = IComprehensiveInterface.CreateMock(); """, @@ -78,14 +81,14 @@ await That(SnapshotStorage.StripConfigSpecificLines(generated[fileName])) [typeof(HttpClient), typeof(HttpStatusCode),]), new( "KeywordEdgeCases_CanBeCreated", - ["KeywordEdgeCases.cs",], + ["IKeywordEdgeCases.cs",], """ IKeywordEdgeCases sut = IKeywordEdgeCases.CreateMock(); """, []), new( "RefStructConsumer_CanBeCreated", - ["IRefStructConsumer.cs",], + ["IRefStructConsumer.cs", "Packet.cs",], """ IRefStructConsumer sut = IRefStructConsumer.CreateMock(); """, diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/ComprehensiveAbstractClass.cs b/Tests/Mockolate.Tests/GeneratorCoverage/ComprehensiveAbstractClass.cs index 94b99d3d..ce4f9505 100644 --- a/Tests/Mockolate.Tests/GeneratorCoverage/ComprehensiveAbstractClass.cs +++ b/Tests/Mockolate.Tests/GeneratorCoverage/ComprehensiveAbstractClass.cs @@ -3,17 +3,6 @@ namespace Mockolate.Tests.GeneratorCoverage; -/// -/// Base class without a parameterless constructor — forces the mock to chain a -/// : base(...) call from every generated constructor. -/// -public abstract class MyAbstractBase -{ - protected MyAbstractBase(int seed) { Seed = seed; } - - public int Seed { get; } -} - /// /// Class-only generator branches: multiple constructors with different shapes, /// a constructor parameter named mockRegistry that collides with the diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/ICombinationParts.cs b/Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockA.cs similarity index 85% rename from Tests/Mockolate.Tests/GeneratorCoverage/ICombinationParts.cs rename to Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockA.cs index 1597ac02..ba73f762 100644 --- a/Tests/Mockolate.Tests/GeneratorCoverage/ICombinationParts.cs +++ b/Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockA.cs @@ -12,9 +12,4 @@ public interface ICombinationMockA void Run(); } -public interface ICombinationMockB -{ - int Value { get; } - void Run(); -} #endif diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockB.cs b/Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockB.cs new file mode 100644 index 00000000..3bf7a2bd --- /dev/null +++ b/Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockB.cs @@ -0,0 +1,7 @@ +namespace Mockolate.Tests.GeneratorCoverage; + +public interface ICombinationMockB +{ + int Value { get; } + void Run(); +} \ No newline at end of file diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs b/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs index 9e2dfc36..2c27058c 100644 --- a/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs +++ b/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs @@ -4,22 +4,6 @@ namespace Mockolate.Tests.GeneratorCoverage; -public class MyBase -{ -} - -public enum MyEnum { A, B, C, } - -public struct MyStruct -{ - public int X; -} - -public class MyEventArgs : EventArgs -{ - public int N; -} - /// /// Squeezes every interface-shaped generator branch we can fit into a single type: /// property accessor combinations, indexers (single + arity-5), all three event flavors, diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/KeywordEdgeCases.cs b/Tests/Mockolate.Tests/GeneratorCoverage/IKeywordEdgeCases.cs similarity index 100% rename from Tests/Mockolate.Tests/GeneratorCoverage/KeywordEdgeCases.cs rename to Tests/Mockolate.Tests/GeneratorCoverage/IKeywordEdgeCases.cs diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/IRefStructConsumer.cs b/Tests/Mockolate.Tests/GeneratorCoverage/IRefStructConsumer.cs index a9fd02c9..3c176e8c 100644 --- a/Tests/Mockolate.Tests/GeneratorCoverage/IRefStructConsumer.cs +++ b/Tests/Mockolate.Tests/GeneratorCoverage/IRefStructConsumer.cs @@ -1,11 +1,6 @@ #if NET10_0_OR_GREATER namespace Mockolate.Tests.GeneratorCoverage; -public readonly ref struct Packet(int id) -{ - public int Id { get; } = id; -} - /// /// Isolates the ref-struct setup pipeline: ref-struct method parameters at arity > 4 /// trigger RefStructMethodSetups.g.cs, and ref-struct indexer keys at arity > 4 @@ -15,5 +10,13 @@ public interface IRefStructConsumer { string this[Packet k1, int k2, Packet k3, int k4, Packet k5] { get; set; } void Consume5(Packet p1, Packet p2, Packet p3, Packet p4, Packet p5); + void Produce(out Packet packet); + void Mutate(ref Packet packet); + void Inspect(ref readonly Packet packet); + void ProduceSpan(out System.Span span); + void MutateSpan(ref System.Span span); + void ProduceReadOnlySpan(out System.ReadOnlySpan span); + void MutateReadOnlySpan(ref System.ReadOnlySpan span); + void InspectSpan(ref readonly System.Span span); } #endif diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/MyAbstractBase.cs b/Tests/Mockolate.Tests/GeneratorCoverage/MyAbstractBase.cs new file mode 100644 index 00000000..a7e00e7e --- /dev/null +++ b/Tests/Mockolate.Tests/GeneratorCoverage/MyAbstractBase.cs @@ -0,0 +1,12 @@ +namespace Mockolate.Tests.GeneratorCoverage; + +/// +/// Base class without a parameterless constructor — forces the mock to chain a +/// : base(...) call from every generated constructor. +/// +public abstract class MyAbstractBase +{ + protected MyAbstractBase(int seed) { Seed = seed; } + + public int Seed { get; } +} \ No newline at end of file diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/MyBase.cs b/Tests/Mockolate.Tests/GeneratorCoverage/MyBase.cs new file mode 100644 index 00000000..1d87bc33 --- /dev/null +++ b/Tests/Mockolate.Tests/GeneratorCoverage/MyBase.cs @@ -0,0 +1,5 @@ +namespace Mockolate.Tests.GeneratorCoverage; + +public class MyBase +{ +} \ No newline at end of file diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/MyEnum.cs b/Tests/Mockolate.Tests/GeneratorCoverage/MyEnum.cs new file mode 100644 index 00000000..3350484e --- /dev/null +++ b/Tests/Mockolate.Tests/GeneratorCoverage/MyEnum.cs @@ -0,0 +1,3 @@ +namespace Mockolate.Tests.GeneratorCoverage; + +public enum MyEnum { A, B, C, } \ No newline at end of file diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/MyEventArgs.cs b/Tests/Mockolate.Tests/GeneratorCoverage/MyEventArgs.cs new file mode 100644 index 00000000..d46ab095 --- /dev/null +++ b/Tests/Mockolate.Tests/GeneratorCoverage/MyEventArgs.cs @@ -0,0 +1,6 @@ +namespace Mockolate.Tests.GeneratorCoverage; + +public class MyEventArgs : EventArgs +{ + public int N; +} \ No newline at end of file diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/MyStruct.cs b/Tests/Mockolate.Tests/GeneratorCoverage/MyStruct.cs new file mode 100644 index 00000000..4d85fecd --- /dev/null +++ b/Tests/Mockolate.Tests/GeneratorCoverage/MyStruct.cs @@ -0,0 +1,6 @@ +namespace Mockolate.Tests.GeneratorCoverage; + +public struct MyStruct +{ + public int X; +} \ No newline at end of file diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/Packet.cs b/Tests/Mockolate.Tests/GeneratorCoverage/Packet.cs new file mode 100644 index 00000000..06e366a5 --- /dev/null +++ b/Tests/Mockolate.Tests/GeneratorCoverage/Packet.cs @@ -0,0 +1,8 @@ +#if NET10_0_OR_GREATER +namespace Mockolate.Tests.GeneratorCoverage; + +public readonly ref struct Packet(int id) +{ + public int Id { get; } = id; +} +#endif \ No newline at end of file diff --git a/Tests/Mockolate.Tests/ItTests.IsAnyOutReadOnlySpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsAnyOutReadOnlySpanTests.cs new file mode 100644 index 00000000..9429d813 --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsAnyOutReadOnlySpanTests.cs @@ -0,0 +1,33 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsAnyOutReadOnlySpanTests + { + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IOutParameter> sut = It.IsAnyOutReadOnlySpan(); + string expectedValue = "It.IsAnyOut>()"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task TryGetValue_ShouldReturnFalse() + { + IOutParameter> sut = It.IsAnyOutReadOnlySpan(); + + bool result = sut.TryGetValue(out _); + + await That(result).IsFalse(); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsAnyOutRefStructTests.cs b/Tests/Mockolate.Tests/ItTests.IsAnyOutRefStructTests.cs new file mode 100644 index 00000000..5b67dc7a --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsAnyOutRefStructTests.cs @@ -0,0 +1,32 @@ +#if NET9_0_OR_GREATER +using Mockolate.Parameters; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsAnyOutRefStructTests + { + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IOutRefStructParameter> sut = It.IsAnyOutRefStruct>(); + string expectedValue = "It.IsAnyOutRefStruct>()"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task TryGetValue_ShouldReturnFalse() + { + IOutRefStructParameter> sut = It.IsAnyOutRefStruct>(); + + bool result = sut.TryGetValue(out _); + + await That(result).IsFalse(); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsAnyOutSpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsAnyOutSpanTests.cs new file mode 100644 index 00000000..a9536030 --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsAnyOutSpanTests.cs @@ -0,0 +1,33 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsAnyOutSpanTests + { + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IOutParameter> sut = It.IsAnyOutSpan(); + string expectedValue = "It.IsAnyOut>()"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task TryGetValue_ShouldReturnFalse() + { + IOutParameter> sut = It.IsAnyOutSpan(); + + bool result = sut.TryGetValue(out _); + + await That(result).IsFalse(); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsAnyRefReadOnlySpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsAnyRefReadOnlySpanTests.cs new file mode 100644 index 00000000..a62285fe --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsAnyRefReadOnlySpanTests.cs @@ -0,0 +1,37 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsAnyRefReadOnlySpanTests + { + [Fact] + public async Task GetValue_ShouldReturnSameValue() + { + IRefParameter> sut = It.IsAnyRefReadOnlySpan(); + ReadOnlySpanWrapper input = new(new[] + { + 1, 2, 3, + }); + + ReadOnlySpanWrapper result = sut.GetValue(input); + + await That(result.ReadOnlySpanValues.Length).IsEqualTo(3); + } + + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IRefParameter> sut = It.IsAnyRefReadOnlySpan(); + string expectedValue = "It.IsAnyRef>()"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsAnyRefRefStructTests.cs b/Tests/Mockolate.Tests/ItTests.IsAnyRefRefStructTests.cs new file mode 100644 index 00000000..97df2be2 --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsAnyRefRefStructTests.cs @@ -0,0 +1,36 @@ +#if NET9_0_OR_GREATER +using Mockolate.Parameters; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsAnyRefRefStructTests + { + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IRefRefStructParameter> sut = It.IsAnyRefRefStruct>(); + string expectedValue = "It.IsAnyRefRefStruct>()"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task GetValue_ShouldReturnSameValue() + { + IRefRefStructParameter> sut = It.IsAnyRefRefStruct>(); + int[] backing = [1, 2, 3]; + + Span roundTripped = sut.GetValue(backing.AsSpan()); + int length = roundTripped.Length; + int first = roundTripped[0]; + + await That(length).IsEqualTo(3); + await That(first).IsEqualTo(1); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsAnyRefSpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsAnyRefSpanTests.cs new file mode 100644 index 00000000..91029daf --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsAnyRefSpanTests.cs @@ -0,0 +1,37 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsAnyRefSpanTests + { + [Fact] + public async Task GetValue_ShouldReturnSameValue() + { + IRefParameter> sut = It.IsAnyRefSpan(); + SpanWrapper input = new(new[] + { + 1, 2, 3, + }); + + SpanWrapper result = sut.GetValue(input); + + await That(result.SpanValues.Length).IsEqualTo(3); + } + + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IRefParameter> sut = It.IsAnyRefSpan(); + string expectedValue = "It.IsAnyRef>()"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsOutReadOnlySpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsOutReadOnlySpanTests.cs new file mode 100644 index 00000000..6412ee98 --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsOutReadOnlySpanTests.cs @@ -0,0 +1,37 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsOutReadOnlySpanTests + { + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IOutParameter> sut = + It.IsOutReadOnlySpan(() => new ReadOnlySpanWrapper([1, 2,])); + string expectedValue = "It.IsOut>(() => new ReadOnlySpanWrapper([1, 2,]))"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task TryGetValue_ShouldReturnSetterValue() + { + IOutParameter> sut = + It.IsOutReadOnlySpan(() => new ReadOnlySpanWrapper([7, 8,])); + + bool found = sut.TryGetValue(out ReadOnlySpanWrapper value); + + await That(found).IsTrue(); + await That(value.ReadOnlySpanValues.Length).IsEqualTo(2); + await That(value.ReadOnlySpanValues[0]).IsEqualTo(7); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsOutSpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsOutSpanTests.cs new file mode 100644 index 00000000..e6af9e63 --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsOutSpanTests.cs @@ -0,0 +1,37 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsOutSpanTests + { + [Fact] + public async Task ToString_ShouldReturnExpectedValue() + { + IOutParameter> sut = + It.IsOutSpan(() => new SpanWrapper([1, 2, 3,])); + string expectedValue = "It.IsOut>(() => new SpanWrapper([1, 2, 3,]))"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task TryGetValue_ShouldReturnSetterValue() + { + IOutParameter> sut = + It.IsOutSpan(() => new SpanWrapper([7, 8,])); + + bool found = sut.TryGetValue(out SpanWrapper value); + + await That(found).IsTrue(); + await That(value.SpanValues.Length).IsEqualTo(2); + await That(value.SpanValues[0]).IsEqualTo(7); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsOutTests.cs b/Tests/Mockolate.Tests/ItTests.IsOutTests.cs index 55573e05..995fe868 100644 --- a/Tests/Mockolate.Tests/ItTests.IsOutTests.cs +++ b/Tests/Mockolate.Tests/ItTests.IsOutTests.cs @@ -28,6 +28,19 @@ public async Task ToString_Verify_ShouldReturnExpectedValue() await That(result).IsEqualTo(expectedValue); } +#if NET9_0_OR_GREATER + [Fact] + public async Task ToString_WithRefStructFactory_ShouldReturnExpectedValue() + { + IOutRefStructParameter> sut = It.IsOut>(() => default); + string expectedValue = "It.IsOut>(() => default)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } +#endif + [Theory] [InlineData(42)] [InlineData(-2)] diff --git a/Tests/Mockolate.Tests/ItTests.IsRefReadOnlySpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsRefReadOnlySpanTests.cs new file mode 100644 index 00000000..5b6b9229 --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsRefReadOnlySpanTests.cs @@ -0,0 +1,50 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsRefReadOnlySpanTests + { + [Fact] + public async Task ToString_WithPredicateAndSetter_ShouldReturnExpectedValue() + { + IRefParameter> sut = It.IsRefReadOnlySpan( + value => value.ReadOnlySpanValues.Length > 0, + value => value); + string expectedValue = + "It.IsRef>(value => value.ReadOnlySpanValues.Length > 0, value => value)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task ToString_WithPredicateOnly_ShouldReturnExpectedValue() + { + IRefParameter> sut = + It.IsRefReadOnlySpan(value => value.ReadOnlySpanValues.Length > 0); + string expectedValue = + "It.IsRef>(value => value.ReadOnlySpanValues.Length > 0)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task ToString_WithSetterOnly_ShouldReturnExpectedValue() + { + IRefParameter> sut = It.IsRefReadOnlySpan(value => value); + string expectedValue = "It.IsRef>(value => value)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsRefSpanTests.cs b/Tests/Mockolate.Tests/ItTests.IsRefSpanTests.cs new file mode 100644 index 00000000..832da341 --- /dev/null +++ b/Tests/Mockolate.Tests/ItTests.IsRefSpanTests.cs @@ -0,0 +1,49 @@ +#if NET8_0_OR_GREATER +using Mockolate.Parameters; +using Mockolate.Setup; + +namespace Mockolate.Tests; + +public sealed partial class ItTests +{ + public sealed class IsRefSpanTests + { + [Fact] + public async Task ToString_WithPredicateAndSetter_ShouldReturnExpectedValue() + { + IRefParameter> sut = It.IsRefSpan( + value => value.SpanValues.Length > 0, + value => value); + string expectedValue = + "It.IsRef>(value => value.SpanValues.Length > 0, value => value)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task ToString_WithPredicateOnly_ShouldReturnExpectedValue() + { + IRefParameter> sut = + It.IsRefSpan(value => value.SpanValues.Length > 0); + string expectedValue = "It.IsRef>(value => value.SpanValues.Length > 0)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task ToString_WithSetterOnly_ShouldReturnExpectedValue() + { + IRefParameter> sut = It.IsRefSpan(value => value); + string expectedValue = "It.IsRef>(value => value)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + } +} +#endif diff --git a/Tests/Mockolate.Tests/ItTests.IsRefTests.cs b/Tests/Mockolate.Tests/ItTests.IsRefTests.cs index d670e769..70c9d714 100644 --- a/Tests/Mockolate.Tests/ItTests.IsRefTests.cs +++ b/Tests/Mockolate.Tests/ItTests.IsRefTests.cs @@ -38,5 +38,41 @@ public async Task Verify_ShouldAlwaysMatch() await That(result).IsTrue(); await That(() => ((IParameterMatch)sut).InvokeCallbacks(0)).DoesNotThrow(); } + +#if NET9_0_OR_GREATER + [Fact] + public async Task ToString_WithRefStructTransform_ShouldReturnExpectedValue() + { + IRefRefStructParameter> sut = It.IsRef>(value => value); + string expectedValue = "It.IsRef>(value => value)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task ToString_WithRefStructPredicateAndTransform_ShouldReturnExpectedValue() + { + IRefRefStructParameter> sut = + It.IsRef>(value => value.Length > 0, value => value); + string expectedValue = "It.IsRef>(value => value.Length > 0, value => value)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } + + [Fact] + public async Task ToString_WithRefStructPredicateOnly_ShouldReturnExpectedValue() + { + IRefRefStructParameter> sut = It.IsRef>(value => value.Length > 0); + string expectedValue = "It.IsRef>(value => value.Length > 0)"; + + string? result = sut.ToString(); + + await That(result).IsEqualTo(expectedValue); + } +#endif } } diff --git a/Tests/Mockolate.Tests/RefStruct/RefStructOutRefParameterTests.cs b/Tests/Mockolate.Tests/RefStruct/RefStructOutRefParameterTests.cs new file mode 100644 index 00000000..23cb3a14 --- /dev/null +++ b/Tests/Mockolate.Tests/RefStruct/RefStructOutRefParameterTests.cs @@ -0,0 +1,363 @@ +#if NET9_0_OR_GREATER +using GcPacket = Mockolate.Tests.GeneratorCoverage.Packet; +using Mockolate.Setup; +using Mockolate.Tests.GeneratorCoverage; + +namespace Mockolate.Tests.RefStruct; + +/// +/// End-to-end coverage for `out`, `ref`, and `ref readonly` ref-struct parameters routed +/// through / +/// . Uses the +/// ref struct (single-int ctor) to isolate this +/// scenario from the payload-carrying used by other RefStruct tests. +/// +/// +/// Ref struct values cannot survive an await boundary, so each test captures +/// into an int before awaiting the assertion. +/// +public sealed class RefStructOutRefParameterTests +{ + [Fact] + public async Task OutRefStruct_WithIsOut_AssignsSetterValue() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.Produce(It.IsOut(() => new GcPacket(7))); + + GcPacket packet = default; + sut.Produce(out packet); + int id = packet.Id; + + await That(id).IsEqualTo(7); + } + + [Fact] + public async Task OutRefStruct_WithIsAnyOutRefStruct_AssignsDefault() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.Produce(It.IsAnyOutRefStruct()); + + GcPacket packet = new(99); + sut.Produce(out packet); + int id = packet.Id; + + await That(id).IsEqualTo(0); + } + + [Fact] + public async Task OutRefStruct_NoSetup_AssignsDefault() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + + GcPacket packet = new(99); + sut.Produce(out packet); + int id = packet.Id; + + await That(id).IsEqualTo(0); + } + + [Fact] + public async Task RefRefStruct_WithIsRef_TransformsValue() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.Mutate(It.IsRef(p => new GcPacket(p.Id + 1))); + + GcPacket packet = new(41); + sut.Mutate(ref packet); + int id = packet.Id; + + await That(id).IsEqualTo(42); + } + + [Fact] + public async Task RefRefStruct_WithIsAnyRefRefStruct_LeavesValueUnchanged() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.Mutate(It.IsAnyRefRefStruct()); + + GcPacket packet = new(13); + sut.Mutate(ref packet); + int id = packet.Id; + + await That(id).IsEqualTo(13); + } + + [Fact] + public async Task RefRefStruct_WithPredicate_OnlyTransformsWhenPredicateHolds() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.Mutate(It.IsRef(p => p.Id == 41, _ => new GcPacket(99))); + + GcPacket matching = new(41); + sut.Mutate(ref matching); + int matchingId = matching.Id; + + GcPacket nonMatching = new(1); + sut.Mutate(ref nonMatching); + int nonMatchingId = nonMatching.Id; + + await That(matchingId).IsEqualTo(99); + await That(nonMatchingId).IsEqualTo(1); + } + + [Fact] + public async Task RefReadOnlyRefStruct_PassesValueUnchanged() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + + GcPacket packet = new(17); + sut.Inspect(in packet); + int idAfter = packet.Id; + + await That(idAfter).IsEqualTo(17); + } + + [Fact] + public async Task OutSpan_WithIsOutSpan_AssignsSetterArray() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.ProduceSpan(It.IsOutSpan(() => new SpanWrapper(new[] { 1, 2, 3 }))); + + Span span = default; + sut.ProduceSpan(out span); + int length = span.Length; + int first = span[0]; + int last = span[2]; + + await That(length).IsEqualTo(3); + await That(first).IsEqualTo(1); + await That(last).IsEqualTo(3); + } + + [Fact] + public async Task OutSpan_WithIsAnyOutSpan_AssignsDefault() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.ProduceSpan(It.IsAnyOutSpan()); + + Span span = new[] { 99 }; + sut.ProduceSpan(out span); + int length = span.Length; + + await That(length).IsEqualTo(0); + } + + [Fact] + public async Task OutSpan_NoSetup_AssignsDefault() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + + Span span = new[] { 99 }; + sut.ProduceSpan(out span); + int length = span.Length; + + await That(length).IsEqualTo(0); + } + + [Fact] + public async Task OutSpan_WithDoCallback_RunsAction() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + int observedFirst = 0; + int observedLength = 0; + sut.Mock.Setup.ProduceSpan( + It.IsOutSpan(() => new SpanWrapper(new[] { 7, 8 })) + .Do(w => + { + observedFirst = w.SpanValues[0]; + observedLength = w.SpanValues.Length; + })); + + Span span = default; + sut.ProduceSpan(out span); + + await That(observedFirst).IsEqualTo(7); + await That(observedLength).IsEqualTo(2); + } + + [Fact] + public async Task RefSpan_WithIsRefSpan_TransformsArray() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.MutateSpan(It.IsRefSpan(w => new SpanWrapper(new[] { w.SpanValues[0] + 1 }))); + + Span span = new[] { 41 }; + sut.MutateSpan(ref span); + int first = span[0]; + int length = span.Length; + + await That(first).IsEqualTo(42); + await That(length).IsEqualTo(1); + } + + [Fact] + public async Task RefSpan_WithIsAnyRefSpan_LeavesValueUnchanged() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.MutateSpan(It.IsAnyRefSpan()); + + Span span = new[] { 13, 14 }; + sut.MutateSpan(ref span); + int first = span[0]; + int length = span.Length; + + await That(first).IsEqualTo(13); + await That(length).IsEqualTo(2); + } + + [Fact] + public async Task RefSpan_WithPredicate_OnlyTransformsWhenPredicateHolds() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.MutateSpan( + It.IsRefSpan( + w => w.SpanValues.Length > 0 && w.SpanValues[0] == 41, + _ => new SpanWrapper(new[] { 99 }))); + + Span matching = new[] { 41 }; + sut.MutateSpan(ref matching); + int matchingFirst = matching[0]; + + Span nonMatching = new[] { 1 }; + sut.MutateSpan(ref nonMatching); + int nonMatchingFirst = nonMatching[0]; + + await That(matchingFirst).IsEqualTo(99); + await That(nonMatchingFirst).IsEqualTo(1); + } + + [Fact] + public async Task OutReadOnlySpan_WithIsOutReadOnlySpan_AssignsSetterArray() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.ProduceReadOnlySpan( + It.IsOutReadOnlySpan(() => new ReadOnlySpanWrapper(new[] { 5, 6, 7 }))); + + ReadOnlySpan span = default; + sut.ProduceReadOnlySpan(out span); + int length = span.Length; + int first = span[0]; + + await That(length).IsEqualTo(3); + await That(first).IsEqualTo(5); + } + + [Fact] + public async Task OutReadOnlySpan_WithIsAnyOutReadOnlySpan_AssignsDefault() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.ProduceReadOnlySpan(It.IsAnyOutReadOnlySpan()); + + ReadOnlySpan span = new[] { 99 }; + sut.ProduceReadOnlySpan(out span); + int length = span.Length; + + await That(length).IsEqualTo(0); + } + + [Fact] + public async Task OutReadOnlySpan_NoSetup_AssignsDefault() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + + ReadOnlySpan span = new[] { 99 }; + sut.ProduceReadOnlySpan(out span); + int length = span.Length; + + await That(length).IsEqualTo(0); + } + + [Fact] + public async Task RefReadOnlySpan_WithIsRefReadOnlySpan_TransformsArray() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.MutateReadOnlySpan( + It.IsRefReadOnlySpan( + w => new ReadOnlySpanWrapper(new[] { w.ReadOnlySpanValues[0] + 1 }))); + + ReadOnlySpan span = new[] { 41 }; + sut.MutateReadOnlySpan(ref span); + int first = span[0]; + + await That(first).IsEqualTo(42); + } + + [Fact] + public async Task RefReadOnlySpan_WithIsAnyRefReadOnlySpan_LeavesValueUnchanged() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.MutateReadOnlySpan(It.IsAnyRefReadOnlySpan()); + + ReadOnlySpan span = new[] { 13 }; + sut.MutateReadOnlySpan(ref span); + int first = span[0]; + + await That(first).IsEqualTo(13); + } + + [Fact] + public async Task RefReadOnlySpan_WithPredicateOnly_GatesMatchWithoutMutating() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.MutateReadOnlySpan( + It.IsRefReadOnlySpan(w => w.ReadOnlySpanValues.Length == 1)) + .Throws(new InvalidOperationException("matched")); + + ReadOnlySpan matching = new[] { 42 }; + Exception? matchingThrew = null; + try + { + sut.MutateReadOnlySpan(ref matching); + } + catch (Exception ex) + { + matchingThrew = ex; + } + int matchingFirst = matching[0]; + int matchingLength = matching.Length; + + ReadOnlySpan nonMatching = new[] { 13, 14 }; + Exception? nonMatchingThrew = null; + try + { + sut.MutateReadOnlySpan(ref nonMatching); + } + catch (Exception ex) + { + nonMatchingThrew = ex; + } + int nonMatchingFirst = nonMatching[0]; + + await That(matchingThrew).IsNotNull(); + await That(matchingFirst).IsEqualTo(42); + await That(matchingLength).IsEqualTo(1); + await That(nonMatchingThrew).IsNull(); + await That(nonMatchingFirst).IsEqualTo(13); + } + + [Fact] + public async Task RefReadOnlySpan_WithIsAnyRefRefStruct_MatchesViaRefStructPipeline() + { + IRefStructConsumer sut = IRefStructConsumer.CreateMock(); + sut.Mock.Setup.InspectSpan(It.IsAnyRefRefStruct>()) + .Throws(new InvalidOperationException("matched")); + + Span span = new[] { 17, 18, 19 }; + Exception? thrown = null; + try + { + sut.InspectSpan(in span); + } + catch (Exception ex) + { + thrown = ex; + } + int length = span.Length; + int first = span[0]; + + await That(thrown).IsNotNull(); + await That(length).IsEqualTo(3); + await That(first).IsEqualTo(17); + } +} +#endif