Skip to content

feat: support out/ref/ref-readonly ref-struct parameters#774

Merged
vbreuss merged 8 commits into
mainfrom
topic/support-out-ref-ref-readonly-ref-struct-parameters
May 11, 2026
Merged

feat: support out/ref/ref-readonly ref-struct parameters#774
vbreuss merged 8 commits into
mainfrom
topic/support-out-ref-ref-readonly-ref-struct-parameters

Conversation

@vbreuss
Copy link
Copy Markdown
Member

@vbreuss vbreuss commented May 11, 2026

Lift the analyzer and generator rejection of out, ref, and ref readonly ref-struct parameters on interface and class methods, and route them through a new IRefStructOutParameter<T> / IRefStructRefParameter<T> pipeline. Delegates continue to reject these via a narrower analyzer rule.

New matchers: It.IsOut<T>(RefStructFactory<T>), It.IsAnyRefStructOut<T>(), It.IsRef<T>(RefStructTransform<T>), It.IsAnyRefStructRef<T>() plus predicate-gated variants. Setup-side mapping in Helpers.ToParameter() branches on NeedsRefStructPipeline; mock body emits per-slot casts via new GetMatcher{n}() accessors on RefStructVoidMethodSetup / RefStructReturnMethodSetup. Out-slot fallback writes default! directly because MockBehavior.DefaultValue cannot produce ref-struct values.

@vbreuss vbreuss self-assigned this May 11, 2026
@vbreuss vbreuss added the enhancement New feature or request label May 11, 2026
Copilot AI review requested due to automatic review settings May 11, 2026 04:07
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

Test Results

    24 files  ±  0      24 suites  ±0   9m 20s ⏱️ -40s
 4 144 tests + 47   4 142 ✅ + 47  2 💤 ±0  0 ❌ ±0 
26 660 runs  +195  26 656 ✅ +195  4 💤 ±0  0 ❌ ±0 

Results for commit 114f5d3. ± Comparison against base commit 9cf4fad.

This pull request removes 8 and adds 55 tests. Note that renamed tests count towards both.
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingAbstractClassWithInheritedRefStructViolation_ShouldBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingDelegateWithPlainRefStructParameter_ShouldNotBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceInheritingFromInterfaceWithBadMethod_ShouldBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithOutRefStructParameter_ShouldBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithOverloads_OnlyViolatingOverloadIsFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithRefReadonlyRefStructParameter_ShouldBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithRefRefStructParameter_ShouldBeFlagged
Mockolate.SourceGenerators.Tests.MockTests+RefStructTests ‑ MethodWithOutRefStructParameter_ShouldEmitNotSupportedExceptionAndSkipSetupSurface
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingAbstractClassWithInheritedRefStructOutParameter_ShouldNotBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingDelegateWithPlainRefStructParameter_ShouldBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceInheritingRefStructOutMethod_ShouldNotBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithOutRefStructParameter_ShouldNotBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithRefReadonlyRefStructParameter_ShouldNotBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithRefRefStructParameter_ShouldNotBeFlagged
Mockolate.Analyzers.Tests.MockabilityAnalyzerRefStructTests ‑ WhenMockingInterfaceWithRefStructOutAndPlainOverloads_ShouldNotBeFlagged
Mockolate.SourceGenerators.Tests.MockTests+RefStructTests ‑ MethodWithOutRefStructParameter_ShouldEmitRefStructOutParameterPipeline
Mockolate.Tests.ItTests+IsAnyOutReadOnlySpanTests ‑ ToString_ShouldReturnExpectedValue
Mockolate.Tests.ItTests+IsAnyOutReadOnlySpanTests ‑ TryGetValue_ShouldReturnFalse
…

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 11, 2026

🚀 Benchmark Results

Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 3.24GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.203
[Host] : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

CreateMock Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 63.43 ns 0.776 ns 0.688 ns 0.81 440 B 1.00
Mockolate 78.34 ns 2.399 ns 2.244 ns 1.00 440 B 1.00
Imposter 348.41 ns 13.596 ns 12.718 ns 4.45 2248 B 5.11
TUnitMocks 48.65 ns 1.953 ns 1.827 ns 0.62 192 B 0.44
Moq 1,458.45 ns 3.815 ns 3.568 ns 18.63 2096 B 4.76
NSubstitute 2,133.84 ns 26.638 ns 23.614 ns 27.26 5048 B 11.47
FakeItEasy 1,996.02 ns 100.797 ns 94.286 ns 25.50 2763 B 6.28
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.66GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.203
[Host] : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Event Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 363.7 ns 3.21 ns 2.68 ns 1.15 1.78 KB 1.00
Mockolate 316.8 ns 5.62 ns 5.26 ns 1.00 1.78 KB 1.00
Imposter 1,352.2 ns 15.48 ns 12.92 ns 4.27 8.8 KB 4.94
TUnitMocks 182.6 ns 2.95 ns 2.76 ns 0.58 1.34 KB 0.75
Moq 16,079.7 ns 63.66 ns 56.44 ns 50.77 12.51 KB 7.02
NSubstitute 5,559.0 ns 39.23 ns 36.69 ns 17.55 9.05 KB 5.08
FakeItEasy 211,444.7 ns 848.21 ns 793.42 ns 667.60 15.26 KB 8.57
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.203
[Host] : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Indexer N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 1,043.5 ns 18.63 ns 17.43 ns 1.06 3.82 KB 1.00
Mockolate 1 984.6 ns 10.61 ns 8.86 ns 1.00 3.82 KB 1.00
Imposter 1 856.0 ns 4.27 ns 3.57 ns 0.87 5.16 KB 1.35
Moq 1 170,269.6 ns 1,094.12 ns 969.91 ns 172.94 20.32 KB 5.32
NSubstitute 1 8,388.9 ns 56.05 ns 52.43 ns 8.52 12.78 KB 3.35
FakeItEasy 1 9,677.0 ns 30.66 ns 28.68 ns 9.83 13.63 KB 3.57
baseline* 10 2,624.3 ns 9.97 ns 8.33 ns 1.08 4.88 KB 1.00
Mockolate 10 2,426.9 ns 14.46 ns 12.81 ns 1.00 4.88 KB 1.00
Imposter 10 2,009.9 ns 11.19 ns 10.47 ns 0.83 7.97 KB 1.63
Moq 10 181,551.9 ns 1,107.97 ns 925.21 ns 74.81 28.72 KB 5.89
NSubstitute 10 20,884.9 ns 61.94 ns 51.72 ns 8.61 25.58 KB 5.25
FakeItEasy 10 20,979.2 ns 265.93 ns 248.75 ns 8.64 32.97 KB 6.76
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Intel Xeon Platinum 8370C CPU 2.80GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.203
[Host] : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Method N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 388.2 ns 9.61 ns 8.99 ns 0.96 2.04 KB 1.00
Mockolate 1 404.2 ns 12.58 ns 11.16 ns 1.00 2.04 KB 1.00
Imposter 1 629.9 ns 24.25 ns 21.49 ns 1.56 4.04 KB 1.98
TUnitMocks 1 498.0 ns 10.34 ns 9.67 ns 1.23 1.88 KB 0.92
Moq 1 142,103.2 ns 1,132.46 ns 1,003.90 ns 351.82 14.74 KB 7.23
NSubstitute 1 6,277.6 ns 28.52 ns 26.68 ns 15.54 9.12 KB 4.47
FakeItEasy 1 5,735.9 ns 56.88 ns 53.21 ns 14.20 8.06 KB 3.95
baseline* 10 664.3 ns 8.30 ns 7.36 ns 0.79 2.25 KB 1.00
Mockolate 10 842.1 ns 15.59 ns 14.58 ns 1.00 2.25 KB 1.00
Imposter 10 1,210.5 ns 24.75 ns 21.94 ns 1.44 5.52 KB 2.45
TUnitMocks 10 1,523.1 ns 18.01 ns 16.85 ns 1.81 3.52 KB 1.57
Moq 10 146,513.7 ns 1,272.98 ns 1,190.75 ns 174.03 18.64 KB 8.29
NSubstitute 10 9,111.6 ns 33.54 ns 31.37 ns 10.82 12.07 KB 5.36
FakeItEasy 10 9,189.8 ns 53.38 ns 49.94 ns 10.92 15.42 KB 6.85
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.87GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.203
[Host] : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Property N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 615.0 ns 7.62 ns 7.13 ns 0.74 2.47 KB 1.00
Mockolate 1 834.0 ns 11.64 ns 10.89 ns 1.00 2.47 KB 1.00
Imposter 1 417.6 ns 0.88 ns 0.74 ns 0.50 3.13 KB 1.27
TUnitMocks 1 421.8 ns 3.91 ns 3.47 ns 0.51 1.49 KB 0.60
Moq 1 10,185.9 ns 32.82 ns 29.10 ns 12.22 10.27 KB 4.16
NSubstitute 1 6,661.5 ns 34.05 ns 30.19 ns 7.99 11.45 KB 4.64
FakeItEasy 1 7,063.3 ns 28.58 ns 26.74 ns 8.47 11.24 KB 4.55
baseline* 10 1,135.8 ns 5.24 ns 4.91 ns 1.07 2.96 KB 1.00
Mockolate 10 1,065.4 ns 3.16 ns 2.95 ns 1.00 2.96 KB 1.00
Imposter 10 1,044.3 ns 5.64 ns 5.27 ns 0.98 4.67 KB 1.58
TUnitMocks 10 1,422.8 ns 4.32 ns 4.04 ns 1.34 3.65 KB 1.23
Moq 10 16,388.7 ns 27.42 ns 24.31 ns 15.38 17.03 KB 5.75
NSubstitute 10 15,514.4 ns 52.65 ns 46.67 ns 14.56 21.08 KB 7.12
FakeItEasy 10 17,126.8 ns 161.38 ns 143.06 ns 16.08 30.81 KB 10.40
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.93GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.203
[Host] : .NET 10.0.7 (10.0.7, 10.0.726.21808), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Callback Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 354.8 ns 22.61 ns 21.15 ns 1.03 1.68 KB 1.00
Mockolate 345.7 ns 7.67 ns 6.41 ns 1.00 1.68 KB 1.00
Imposter 409.3 ns 6.15 ns 5.75 ns 1.18 2.38 KB 1.42
TUnitMocks 460.4 ns 7.11 ns 6.65 ns 1.33 1.86 KB 1.11
Moq 99,831.8 ns 414.29 ns 387.53 ns 288.91 8.88 KB 5.29
NSubstitute 4,452.9 ns 37.02 ns 34.63 ns 12.89 7.74 KB 4.61
FakeItEasy 4,794.8 ns 40.72 ns 36.10 ns 13.88 6.81 KB 4.05

baseline* rows show the corresponding Mockolate benchmark from the most recent successful main branch build with results, for regression comparison.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Mockolate’s ref-struct support to allow out, ref, and ref readonly ref-struct parameters on interface/class members by introducing a dedicated matcher/parameter pipeline (IRefStructOutParameter<T> / IRefStructRefParameter<T>) and updating the source generator + analyzer accordingly.

Changes:

  • Added new ref-struct-safe delegates and parameter interfaces (RefStructFactory<T>, RefStructTransform<T>, IRefStructOutParameter<T>, IRefStructRefParameter<T>) and new It.* matcher overloads.
  • Updated the source generator to route out/ref/ref readonly ref-struct parameters through the new pipeline and to expose per-slot matcher accessors on ref-struct setup types.
  • Updated analyzer rules and tests/snapshots to reflect the newly supported signatures (while keeping delegate Invoke restrictions).

Reviewed changes

Copilot reviewed 34 out of 36 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Tests/Mockolate.Tests/RefStruct/RefStructOutRefParameterTests.cs New end-to-end tests for out/ref/ref readonly ref-struct parameters (Packet + Span/ROS cases).
Tests/Mockolate.Tests/GeneratorCoverage/Packet.cs Splits Packet ref struct into its own file for generator coverage inputs.
Tests/Mockolate.Tests/GeneratorCoverage/MyStruct.cs Splits generator-coverage helper type into its own file.
Tests/Mockolate.Tests/GeneratorCoverage/MyEventArgs.cs Splits generator-coverage helper type into its own file.
Tests/Mockolate.Tests/GeneratorCoverage/MyEnum.cs Splits generator-coverage helper type into its own file.
Tests/Mockolate.Tests/GeneratorCoverage/MyBase.cs Splits generator-coverage helper type into its own file.
Tests/Mockolate.Tests/GeneratorCoverage/MyAbstractBase.cs Splits generator-coverage helper base class into its own file.
Tests/Mockolate.Tests/GeneratorCoverage/IRefStructConsumer.cs Adds new out/ref/in ref-struct members; removes inline Packet definition.
Tests/Mockolate.Tests/GeneratorCoverage/IKeywordEdgeCases.cs New generator-coverage interface to validate keyword escaping / qualification logic.
Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs Removes previously inline helper type definitions (now in separate files).
Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockB.cs Extracts ICombinationMockB into its own file for snapshot scenarios.
Tests/Mockolate.Tests/GeneratorCoverage/ICombinationMockA.cs Removes inline ICombinationMockB definition.
Tests/Mockolate.Tests/GeneratorCoverage/ComprehensiveAbstractClass.cs Removes inline MyAbstractBase definition (now separate file).
Tests/Mockolate.SourceGenerators.Tests/Snapshot/MockGenerationSnapshotTests.cs Updates snapshot scenarios’ coverage-file lists for renamed/extracted inputs.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/RefStructMethodSetups.g.cs Removes outdated expected snapshot output file.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/RefStructConsumer_CanBeCreated/Mock.IRefStructConsumer.g.cs Updates expected generated mock output for new ref/out/refreadonly handling.
Tests/Mockolate.SourceGenerators.Tests/MockTests.RefStructTests.cs Adjusts generator tests to expect the new out-ref-struct pipeline.
Tests/Mockolate.Api.Tests/Expected/Mockolate_net8.0.txt Updates API surface expectations for new Span/ROS out/ref helper matchers.
Tests/Mockolate.Api.Tests/Expected/Mockolate_net10.0.txt Updates API surface expectations for new ref-struct out/ref pipeline types + delegates.
Tests/Mockolate.Analyzers.Tests/MockabilityAnalyzerRefStructTests.cs Updates analyzer expectations: allow out/ref ref-struct params on interfaces/classes; keep delegate restriction.
Source/Mockolate/Setup/SpanWrapper.cs Makes implicit conversion from wrapper to Span<T> null-safe.
Source/Mockolate/Setup/ReadOnlySpanWrapper.cs Makes implicit conversion from wrapper to ReadOnlySpan<T> null-safe.
Source/Mockolate/Setup/RefStructVoidMethodSetup.cs Adds per-slot GetMatcher{n}() accessors for generated mock bodies.
Source/Mockolate/Setup/RefStructReturnMethodSetup.cs Adds per-slot GetMatcher{n}() accessors for generated mock bodies.
Source/Mockolate/RefStructTransform.cs Adds ref-struct-safe transform delegate used by It.IsRef overloads.
Source/Mockolate/RefStructFactory.cs Adds ref-struct-safe factory delegate used by It.IsOut overloads.
Source/Mockolate/Parameters/IVerifyRefParameter.cs Allows ref-struct type arguments under NET9+ via allows ref struct.
Source/Mockolate/Parameters/IVerifyOutParameter.cs Allows ref-struct type arguments under NET9+ via allows ref struct.
Source/Mockolate/Parameters/IRefStructRefParameter.cs New ref-struct-safe ref-parameter interface for setup payloads.
Source/Mockolate/Parameters/IRefStructOutParameter.cs New ref-struct-safe out-parameter interface for setup payloads.
Source/Mockolate/It.IsRef.cs Adds Span/ROS ref helpers + new ref-struct-safe It.IsRef overloads and matchers.
Source/Mockolate/It.IsOut.cs Adds Span/ROS out helpers + new ref-struct-safe It.IsOut overloads and matchers.
Source/Mockolate.SourceGenerators/Sources/Sources.RefStructMethodSetups.cs Emits per-slot matcher accessors for generated ref-struct setup types.
Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs Updates mock-body emission for out/ref/refreadonly ref-struct params + Span out temp unpacking.
Source/Mockolate.SourceGenerators/Helpers.cs Updates parameter-type mapping to select new ref-struct out/ref parameter interfaces when needed.
Source/Mockolate.Analyzers/MockabilityAnalyzer.cs Narrows delegate restriction + updates diagnostic messaging for delegate out/ref ref-struct params.
Comments suppressed due to low confidence (1)

Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs:38

  • Several supporting types used by IComprehensiveInterface (e.g., MyBase, MyEnum, MyStruct, MyEventArgs) were split into separate files. The source-generator snapshot harness compiles scenarios from an explicit coverage-file list, so any snapshot scenario that only includes IComprehensiveInterface.cs will now see these as error types and stop covering the intended generator branches. Update the relevant snapshot scenario inputs to include the new files so coverage remains end-to-end and symbol-accurate.
#if NET10_0_OR_GREATER
using System;
using System.Threading.Tasks;

namespace Mockolate.Tests.GeneratorCoverage;

/// <summary>
///     Squeezes every interface-shaped generator branch we can fit into a single type:
///     property accessor combinations, indexers (single + arity-5), all three event flavors,
///     static abstract members, every parameter modifier, every default-value kind,
///     every "reserved" parameter name, nullable annotations, async returns, special return
///     shapes (Span/ReadOnlySpan/ref/ref-readonly/tuple/Nullable&lt;T&gt;),
///     every generic-constraint kind, plus arity-5 and arity-17 methods to trigger the
///     <c>MethodSetups.g.cs</c> / <c>ActionFunc.g.cs</c> aggregates.
/// </summary>
public interface IComprehensiveInterface
{
	int GetSet { get; set; }
	int GetOnly { get; }
	int SetOnly { set; }
	string? NullableProp { get; set; }
	string InitOnly { get; init; }

	string this[int i] { get; set; }
	string this[int a, int b, int c, int d, int e] { get; set; }

	static abstract int StaticAbstractValue { get; }

	event EventHandler PlainEvent;
	event EventHandler<MyEventArgs> TypedEvent;
	event ComprehensiveDelegate CustomEvent;
	static abstract int StaticAbstractMethod();

	void WithModifiers(ref int a, out string b, in long c, params int[] tail);

	void WithDefaults(int i = 5, MyEnum e = MyEnum.B, decimal d = 1.5m,
		float f = 0.25f, char c = 'x', string? s = null, MyStruct st = default);

Comment thread Source/Mockolate/It.IsOut.cs Outdated
Comment thread Source/Mockolate/It.IsRef.cs Outdated
Comment thread Tests/Mockolate.Tests/GeneratorCoverage/IRefStructConsumer.cs
@vbreuss vbreuss force-pushed the topic/support-out-ref-ref-readonly-ref-struct-parameters branch from a01b5d6 to fc95481 Compare May 11, 2026 06:49
Copilot AI review requested due to automatic review settings May 11, 2026 07:06
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 49 changed files in this pull request and generated 3 comments.

Comment thread Source/Mockolate/Setup/SpanWrapper.cs Outdated
Comment thread Source/Mockolate/Setup/ReadOnlySpanWrapper.cs Outdated
Comment thread Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs Outdated
Copilot AI review requested due to automatic review settings May 11, 2026 11:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 49 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs:2277

  • For ref Span<T> / ref ReadOnlySpan<T> parameters, this branch currently captures var ref_x = x (a span) rather than a SpanWrapper<T>. That means every Matches(ref_x) call relies on the implicit conversion from Span<T> to SpanWrapper<T>, which allocates (ToArray()) each time it runs (potentially once per setup scanned). Consider handling Span/ReadOnlySpan before the by-ref capture (or special-casing ref-span here) so you materialize the wrapper once via p.ToNameOrWrapper() and avoid repeated allocations during matching.
			if (p.RefKind == RefKind.Ref || p.RefKind == RefKind.In || p.RefKind == RefKind.RefReadOnlyParameter)
			{
				string paramRef = Helpers.GetUniqueLocalVariableName($"ref_{p.Name}", method.Parameters);

				sb.Append("\t\t\tvar ").Append(paramRef).Append(" = ").Append(p.Name).Append(';').AppendLine();
				sb2.Append(paramRef);
			}
			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);

				sb.Append("\t\t\tvar ").Append(paramRef).Append(" = ").Append(p.ToNameOrWrapper()).Append(';')
					.AppendLine();

Comment thread Source/Mockolate.Analyzers/MockabilityAnalyzer.cs Outdated
Comment thread Tests/Mockolate.Tests/RefStruct/RefStructOutRefParameterTests.cs
Comment thread Tests/Mockolate.Tests/RefStruct/RefStructOutRefParameterTests.cs
Copilot AI review requested due to automatic review settings May 11, 2026 11:53
vbreuss and others added 8 commits May 11, 2026 13:53
Lift the analyzer and generator rejection of `out`, `ref`, and `ref readonly` ref-struct parameters on interface and class methods, and route them through a new `IRefStructOutParameter<T>` / `IRefStructRefParameter<T>` pipeline. Delegates continue to reject these via a narrower analyzer rule.

New matchers: `It.IsOut<T>(RefStructFactory<T>)`, `It.IsAnyRefStructOut<T>()`, `It.IsRef<T>(RefStructTransform<T>)`, `It.IsAnyRefStructRef<T>()` plus predicate-gated variants. Setup-side mapping in Helpers.ToParameter() branches on NeedsRefStructPipeline; mock body emits per-slot casts via new `GetMatcher{n}()` accessors on RefStructVoidMethodSetup / RefStructReturnMethodSetup. Out-slot fallback writes `default!` directly because MockBehavior.DefaultValue cannot produce ref-struct values.
Lift the compile and setup-side mismatch for `out`/`ref` Span<T> and
ReadOnlySpan<T> parameters. The generator now emits a wrapper-typed temp
local for `out` so the implicit conversion fires at assignment, and the
setup-side type for `out`/`ref` Span/ROS slots resolves to the existing
SpanWrapper / ReadOnlySpanWrapper carve-out (previously routed to a bare
inner-type matcher that the user setup never matched).

Add `It.IsOutSpan` / `It.IsAnyOutSpan` / `It.IsRefSpan` / `It.IsAnyRefSpan`
(and ReadOnlySpan analogs, with full predicate-gated symmetry on the ref
side) so users don't have to type the wrapper explicitly. The
`.Do(Action<SpanWrapper<T>>)` callback path remains available.

`ref readonly` x Span/ReadOnlySpan now falls back to the generic ref-struct
pipeline; users reach for the existing `It.IsAnyRefStructRef<Span<T>>()` /
`It.IsRef<Span<T>>(...)`. `Helpers.GetMethodParameterType` preserves the
full `Span<T>` / `ReadOnlySpan<T>` type in the ref-struct pipeline so those
matchers bind correctly.

Make `SpanWrapper<T>` / `ReadOnlySpanWrapper<T>` null-safe at the implicit
conversion to span: a null wrapper yields `default`, which is the natural
no-match fallback for `out` Span/ROS.

Also fix two stale coverage file references in the snapshot scenarios
(`ICombinationParts.cs` was split into `ICombinationMockA.cs` and
`ICombinationMockB.cs`; `KeywordEdgeCases.cs` was renamed to
`IKeywordEdgeCases.cs`). Without these the snapshot acceptance test could
not run at all.
Aligns ref-struct matcher names with the Span/ReadOnlySpan convention
where the parameter modifier (Ref/Out) precedes the type qualifier:

- It.IsAnyRefStructRef<T>()  -> It.IsAnyRefRefStruct<T>()
- It.IsAnyRefStructOut<T>()  -> It.IsAnyOutRefStruct<T>()
- IRefStructRefParameter<T>  -> IRefRefStructParameter<T>
- IRefStructOutParameter<T>  -> IOutRefStructParameter<T>

Also unifies the preprocessor gate for the ref-struct out methods
from NET10_0_OR_GREATER to NET9_0_OR_GREATER to match the ref-struct
ref methods and the underlying allows-ref-struct delegate types.
- Reject by-value ref-struct parameters on delegate types in MockabilityAnalyzer; the emitted VoidMethodSetup<T> / ReturnMethodSetup<T> lack the 'allows ref struct' constraint and otherwise fail with CS9244.
- Strengthen RefReadOnlySpan_WithPredicateOnly_GatesMatchWithoutMutating and RefReadOnlySpan_WithIsAnyRefRefStruct_MatchesViaRefStructPipeline to actually exercise the gate via Throws(), instead of relying on the unchanged-span post-condition that holds even without a matching setup.
@vbreuss vbreuss force-pushed the topic/support-out-ref-ref-readonly-ref-struct-parameters branch from 5e2ddb2 to 114f5d3 Compare May 11, 2026 11:53
@sonarqubecloud
Copy link
Copy Markdown

@vbreuss vbreuss merged commit 520954f into main May 11, 2026
17 checks passed
@vbreuss vbreuss deleted the topic/support-out-ref-ref-readonly-ref-struct-parameters branch May 11, 2026 12:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 48 out of 49 changed files in this pull request and generated 1 comment.

Comment on lines 294 to 317
@@ -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<T> / ReturnMethodSetup<T> 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;
}
}
github-actions Bot added a commit that referenced this pull request May 11, 2026
github-actions Bot added a commit that referenced this pull request May 11, 2026
@github-actions
Copy link
Copy Markdown

This is addressed in release v3.2.0.

@github-actions github-actions Bot added the state: released The issue is released label May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request state: released The issue is released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants