Skip to content

feat: union-typed setup and verify overloads for C# 15 - #856

Open
vbreuss wants to merge 14 commits into
mainfrom
feat/union-parameters
Open

feat: union-typed setup and verify overloads for C# 15#856
vbreuss wants to merge 14 commits into
mainfrom
feat/union-parameters

Conversation

@vbreuss

@vbreuss vbreuss commented Sep 5, 2026

Copy link
Copy Markdown
Member

Predicate lambdas were never offered as setup/verify arguments because every parameter would need a value, matcher and predicate overload, so the overload count grows with 3^n. C# 15 unions collapse value and matcher into one ParameterArg<T>? parameter, which makes a predicate overload per parameter affordable (2^n) without boxing:

sut.Mock.Setup.Register(x => x > 0, "admin").Returns(true);
sut.Mock.Verify.Register(It.IsAny<int>(), name => name.StartsWith("a")).Once();

Union mode is used when the consumer compiles with C# 15 or sets MockolateUnionParameters=true; attribute polyfills are generated so it also works on older target frameworks. Below C# 15 the generated output is unchanged.

The repository moves to the .NET 11 preview SDK, adds net11.0 targets, and runs the whole test suite a second time in union mode as the parity proof.

Classic-mode change worth a release note: Mockolate0001 now also flags unused all-value and parameterless verifications, which return IgnoreParameters.

Moves global.json to 11.0.100-preview.7 and adds net11.0 to the runtime
and to the three multi-target test projects; the generator and analyzer
test projects, which pin a single target, move from net10.0 to net11.0.
On net11.0 only, the test projects compile with the preview language
version and set MockolateUnionParameters, so that the upcoming
union-typed setup surface is exercised there while every other target
keeps today's behaviour. Consumers are unaffected: the shipped assembly
still compiles as C# 14 and the README keeps recommending the .NET 10 SDK.

- Pipeline: new unlisted UnionTests target running the test projects on
  net11.0.
- CI: every setup-dotnet step that runs the Nuke build (build.yml,
  ci.yml, ci-analysis.yml) pins the SDK via global.json; the package
  push step keeps the runner SDK since it only runs dotnet nuget push.
  Dedicated union-tests job in build.yml and ci.yml, awaited by
  publish-test-results.
- API snapshot for net11.0 (identical to net10.0 apart from the header).
- HttpClient generator snapshot: .NET 11 marks Send(HttpRequestMessage,
  CancellationToken) as unsupported on android/ios/tvos, which the
  generated mock now mirrors.
- Benchmarks project moves to net11.0; target lists in README, docs
  index, CLAUDE.md and copilot-instructions mention .NET 11.
Adds the opt-in switch and the building block for union-typed setup and
verify parameters, without changing any generated mock yet.

- The generator reads the effective language version and the
  MockolateUnionParameters build property. The property wins when set
  ("true" opts in on a preview compiler, anything else is the kill
  switch); otherwise unions are used once the host Roslyn ships C# 15 and
  the compilation targets it. Below that, nothing is emitted.
- When enabled, a ParameterArg.g.cs is generated into the consuming
  assembly: a [Union] struct with the case types IParameter<T> and T?,
  stored in typed slots (no boxing), with TryGetValue accessors and a
  ToParameterMatch() bridge to IParameterMatch<T>. UnionAttribute is
  declared in that file only when neither the framework (it ships with
  .NET 11) nor the consuming assembly already declares it, so net48,
  netstandard2.0 and net8.0 consumers and PolySharp-style polyfills work.
- build/Mockolate.props (packed as build and buildTransitive) makes the
  property visible to the generator; Tests/Directory.Build.props imports
  it so project references behave like the package.
- Generator tests cover the detection matrix, the polyfill decision and
  the emitted text. The generator test project pins the minimum Roslyn,
  which predates unions, so the union conversions themselves are tested
  in Mockolate.Tests on net11.0 with the SDK compiler.
When union support is detected, every method whose name is unique on the
mocked type and that has value-capable parameters gets one Setup and one
Verify overload per assignment of its parameters to either
ParameterArg<T>? (an It matcher or a literal value; null and default
stand for the literal default) or Func<T, bool> (a predicate forwarded
to It.Satisfies together with the caller's argument text). The count
stays at 2^n for n eligible parameters, the same as today's
matcher/value set, so predicates come for free. Without union support
the generator output is unchanged.

Rules that keep the surface sound:
- Overloaded method groups (including a generic sibling) keep the classic
  set: a union conversion loses to the identity and numeric conversions
  of a sibling, so Setup.M(5) on M(int)/M(long) would be ambiguous.
- Delegate-typed parameters offer the raw delegate instead of a
  predicate, because a lambda never converts to a union type.
- ref/out/in and Span parameters keep their matcher slot.
- Generic methods, params methods and ref-struct pipelines keep the
  classic overloads: a union slot hides the type argument from type
  inference, and a params slot cannot survive inside a union type.
- Above four parameters only the all-union overload is emitted.
- Priorities mirror the classic set (all-union int.MaxValue, object-typed
  slots below IParameters, predicate combinations by union count), so
  null and default arguments still bind to the union overload.
- All-literal calls keep the WithLiteralValues / literal VerifyMethod
  fast paths at runtime; everything else uses ToParameterMatch().

ParameterArg.g.cs now also polyfills OverloadResolutionPriorityAttribute
and CallerArgumentExpressionAttribute where the framework lacks them, and
in union mode every overload set of the compilation carries priorities,
so union mode works the same on net48, netstandard2.0 and net8.0.
Generators cannot see each other's output, so a project that gets those
two attributes from PolySharp sets MockolateUnionAttributePolyfills=false
(or lists the attribute names it provides itself); UnionAttribute keeps
following the compilation.

Tests: union-mode snapshot scenarios (classic scenarios pinned to C# 14
so they never flip), generator tests for the overload shapes and the
classic fallbacks, and Mockolate.Tests on net11.0, where the whole
existing suite now runs against the union overloads plus dedicated tests
for predicates, null, default, delegate-typed, optional, object and out
parameters, overloaded groups, scenarios, verify AnyParameters and a
delegate mock.
Extends union mode to indexers: every indexer that is the only one of its
key count on the type and has value-capable keys gets one Setup and one
Verify indexer per assignment of its keys to ParameterArg<T>? or
Func<T, bool>, with the same slot rules as methods (delegate-typed keys
offer the raw delegate and dispatch as literal values, Span keys keep
their matcher slot, ref-struct and params keys keep the classic set).
Indexers of the same arity keep the classic set, because two indexers
with convertible key types (this[int]/this[long]) would make Setup[5]
ambiguous; indexers of different arity never compete. Indexers have no
IParameters overload, so the all-union indexer takes the classic
all-matcher priority (int.MaxValue) and predicate combinations rank by
their union slot count. Keys always dispatch through ToParameterMatch()
because IndexerSetup has no literal fast path; the typed verify path is
used up to four keys, the IndexerGetterAccess/IndexerSetterAccess
predicate above that. Without union support the output is unchanged.

Tests: a new IUnionIndexers coverage interface with one indexer per key
count (getter/setter, getter-only, setter-only, delegate-typed key,
five keys) and its union snapshot, generator assertions for the two-key
indexer overloads, the delegate-typed key and same-arity indexers
falling back to the classic set, and net11.0 behavioural tests for
indexer setup and verify with predicates, literals and matchers, the
setter verification, the failure message and overloaded indexers.
UseVerificationAnalyzer only recognised a return type named
VerificationResult, so verifications returning the nested
VerificationResult<T>.IgnoreParameters were never reported when left
unused. That shape is returned by the classic all-value overload
(Verify.Method(42)), by the parameterless overload (Verify.Method()) and,
since union mode, by every union-typed verify overload, which had
silently disabled Mockolate0001 for that surface. The analyzer now
unwraps the nested type; the code fixer needs no change because the
count assertions are extension members on VerificationResult<T>.

Classic-mode consumers with an unused Verify.Method(42) or Verify.Method()
statement get Mockolate0001 from now on, which is what the rule is for
(such a statement asserts nothing).
- A "Union Parameters (C# 15)" section in the parameter matching page:
  how union mode is enabled, predicates without It.Satisfies, the
  null/default fallback to the declared default, which parameters keep a
  matcher slot and which members keep the classic overloads, and the
  MockolateUnionAttributePolyfills switch. Short notes on the methods and
  indexers pages, a README feature bullet with a link, and a CLAUDE.md
  note on the generator's union mode.
- Benchmarks/Mockolate.Benchmarks.Unions compiles the CompleteMethod
  workflow and a setup-only variant in union mode (literal, matcher,
  predicate) with the same job as the classic Mockolate.Benchmarks, for a
  job-for-job comparison. Union mode is a compilation-wide switch, so it
  cannot share a project with the classic benchmarks; it is part of the
  solution build but not of the CI benchmark matrix, so CI compiles it
  and never runs it. Measured setup-only: literal 91 ns / 584 B (same
  allocation as the classic value path, so ParameterArg<T> does not box),
  matcher 98 ns / 592 B, predicate 88 ns / 632 B.
Copilot AI lite review requested due to automatic review settings September 5, 2026 06:02
@vbreuss vbreuss self-assigned this Sep 5, 2026
@vbreuss vbreuss added breaking change The changes require a new major version enhancement New feature or request labels Sep 5, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The Sources.ParameterArg XML doc <see ... /> markup currently disagrees with the checked-in expected snapshots, which is likely to cause snapshot/test failures unless aligned.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a C# 15 “union mode” to Mockolate’s generated setup/verify surface, enabling predicate arguments without exploding overload counts, and updates the repo/tooling to validate parity via an additional net11.0 union-mode test run.

Changes:

  • Generate union-typed ParameterArg<T>? / predicate overloads when C# 15 unions are available or MockolateUnionParameters=true, including attribute polyfills when needed.
  • Add net11.0 union-mode test coverage (runtime + generator snapshots) and CI/Nuke targets to run the suite twice (classic + union mode).
  • Update docs/readme and add benchmark project to measure union-mode overhead.
File summaries
File Description
Tests/Mockolate.Tests/UnionSetupTests.cs Adds runtime tests covering union-mode setup/verify binding and predicate behavior.
Tests/Mockolate.Tests/ParameterArgTests.cs Adds runtime tests for ParameterArg<T> union conversion semantics on net11.0.
Tests/Mockolate.Tests/GeneratorCoverage/IUnionIndexers.cs Adds union-mode indexer shapes for snapshot coverage.
Tests/Mockolate.SourceGenerators.Tests/UnionParameterArgTests.cs Validates ParameterArg emission/polyfill behavior under minimum Roslyn.
Tests/Mockolate.SourceGenerators.Tests/UnionOverloadTests.cs Validates union-mode overload shapes and generated implementation patterns.
Tests/Mockolate.SourceGenerators.Tests/TestHelpers/TestAnalyzerConfigOptionsProvider.cs Adds test helper for passing global analyzer config options to the generator.
Tests/Mockolate.SourceGenerators.Tests/TestHelpers/Generator.cs Extends test driver to vary language version and pass global options into the generator driver.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/SnapshotScenario.cs Adds UnionMode flag to snapshot scenario definition.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/MockGenerationSnapshotTests.cs Adds union-mode snapshot scenarios and pins classic vs union generation paths.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/UnionIndexers_CanBeCreated_Unions/ParameterArg.g.cs New expected union-mode ParameterArg<T> output snapshot.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/UnionIndexers_CanBeCreated_Unions/MockBehaviorExtensions.g.cs New expected union-mode extensions snapshot for UnionIndexers scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/UnionIndexers_CanBeCreated_Unions/Mock.g.cs New expected union-mode base Mock snapshot for UnionIndexers scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/KeywordEdgeCases_CanBeCreated_Unions/ParameterArg.g.cs New expected union-mode ParameterArg<T> output snapshot.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/KeywordEdgeCases_CanBeCreated_Unions/MockBehaviorExtensions.g.cs New expected union-mode extensions snapshot for KeywordEdgeCases scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/KeywordEdgeCases_CanBeCreated_Unions/Mock.g.cs New expected union-mode base Mock snapshot for KeywordEdgeCases scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/HttpClient_CanBeCreated/Mock.HttpClient.g.cs Updates expected snapshot (platform attributes) for classic HttpClient scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/HttpClient_CanBeCreated_Unions/ParameterArg.g.cs New expected union-mode ParameterArg<T> output snapshot.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/HttpClient_CanBeCreated_Unions/MockBehaviorExtensions.g.cs New expected union-mode extensions snapshot for HttpClient scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/HttpClient_CanBeCreated_Unions/Mock.g.cs New expected union-mode base Mock snapshot for HttpClient scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated_Unions/ParameterArg.g.cs New expected union-mode ParameterArg<T> output snapshot.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated_Unions/MockBehaviorExtensions.g.cs New expected union-mode extensions snapshot for ComprehensiveInterface scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated_Unions/Mock.g.cs New expected union-mode base Mock snapshot for ComprehensiveInterface scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated_Unions/ActionFunc.g.cs New expected union-mode snapshot for generated Action/Func polyfills.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveDelegate_CanBeCreated_Unions/ReturnsThrowsAsyncExtensions.g.cs New expected union-mode snapshot for async returns/throws extensions.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveDelegate_CanBeCreated_Unions/ParameterArg.g.cs New expected union-mode ParameterArg<T> output snapshot.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveDelegate_CanBeCreated_Unions/MockBehaviorExtensions.g.cs New expected union-mode extensions snapshot for ComprehensiveDelegate scenario.
Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveDelegate_CanBeCreated_Unions/Mock.g.cs New expected union-mode base Mock snapshot for ComprehensiveDelegate scenario.
Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj Moves generator tests to net11.0.
Tests/Mockolate.Analyzers.Tests/UseVerificationAnalyzerTests.cs Adds analyzer coverage for unused IgnoreParameters verification results.
Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj Moves analyzer tests to net11.0.
Tests/Directory.Build.props Adds net11.0 to test TFMs and enables preview + union mode for net11.0 test runs.
Source/Mockolate/Mockolate.csproj Packs build/Mockolate.props into NuGet (build/buildTransitive).
Source/Mockolate/build/Mockolate.props Exposes compiler-visible properties for union mode and polyfill coordination.
Source/Mockolate.SourceGenerators/Sources/Sources.ParameterArg.cs Adds ParameterArg<T> source template with optional attribute polyfills.
Source/Mockolate.SourceGenerators/Sources/Sources.MockDelegate.cs Extends delegate mock generation to support union-mode overload emission.
Source/Mockolate.SourceGenerators/Sources/Sources.MockCombination.cs Threads union-mode flag through combination mock generation pipeline.
Source/Mockolate.SourceGenerators/MockGenerator.cs Detects union capability/options, emits ParameterArg.g.cs, and toggles union-mode overloads.
Source/Mockolate.SourceGenerators/Entities/Type.cs Tracks delegate-typed parameters to preserve “lambda as value” semantics.
Source/Mockolate.Analyzers/UseVerificationAnalyzer.cs Expands analyzer matching to include nested VerificationResult<T>.IgnoreParameters.
Source/Directory.Build.props Adds net11.0 to source project TFMs.
README.md Updates supported TFMs and documents C# 15 predicate support at a high level.
Pipeline/Build.UnitTest.cs Adds UnionTests Nuke target to run net11.0 union-mode test suite.
Mockolate.slnx Adds the union benchmarks project to the solution.
global.json Pins SDK to .NET 11 preview.
Docs/pages/setup/04-parameter-matching.md Documents union parameters behavior, activation, and polyfill coordination.
Docs/pages/setup/03-indexers.md Documents union parameter behavior for indexers.
Docs/pages/setup/02-methods.md Notes predicate support via union parameters.
Docs/pages/00-index.md Updates supported TFMs list in docs index.
CLAUDE.md Updates supported TFMs and adds union-mode generator notes.
Benchmarks/Mockolate.Benchmarks/Mockolate.Benchmarks.csproj Moves benchmarks to net11.0.
Benchmarks/Mockolate.Benchmarks.Unions/UnionSetupBenchmarks.cs Adds micro-benchmark for union-mode setup argument binding.
Benchmarks/Mockolate.Benchmarks.Unions/UnionParameterBenchmarks.cs Adds end-to-end benchmark (setup/invoke/verify) for union mode.
Benchmarks/Mockolate.Benchmarks.Unions/Program.cs Adds BenchmarkDotNet entry point for union benchmarks.
Benchmarks/Mockolate.Benchmarks.Unions/Mockolate.Benchmarks.Unions.csproj Adds union benchmarks project with preview + MockolateUnionParameters=true.
Benchmarks/Mockolate.Benchmarks.Unions/IMyMethodInterface.cs Defines benchmark interface used for union-mode benchmarks.
Benchmarks/Mockolate.Benchmarks.Unions/BenchmarksBase.cs Adds baseline BenchmarkDotNet config for union-mode benchmarks.
.github/workflows/ci.yml Adds a dedicated “union tests” job and ensures global.json is honored.
.github/workflows/ci-analysis.yml Ensures global.json is honored for analysis workflow jobs.
.github/workflows/build.yml Adds a dedicated “union tests” job and ensures global.json is honored.
.github/copilot-instructions.md Updates supported TFMs list to include net11.0.
Review details

Suppressed comments (1)

Source/Mockolate.SourceGenerators/Sources/Sources.ParameterArg.cs:177

  • This <see ... /> reference to IParameterMatch<T> is self-closing, but the expected generated snapshots use the form with explicit display text (<see ...>IParameterMatch<T></see>). Aligning the template avoids snapshot drift and produces clearer XML docs.
		          		/// <summary>
		          		///     The <see cref="global::Mockolate.Parameters.IParameterMatch{T}" /> for this argument: the matcher itself,
		          		///     or an equality match for the literal value.
  • Files reviewed: 68/74 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Source/Mockolate.SourceGenerators/Sources/Sources.ParameterArg.cs
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Test Results

    34 files  +    10      34 suites  +10   18m 29s ⏱️ + 6m 4s
 8 019 tests + 3 622   8 019 ✅ + 3 624  0 💤  - 2  0 ❌ ±0 
44 137 runs  +16 218  44 137 ✅ +16 222  0 💤  - 4  0 ❌ ±0 

Results for commit bee4f40. ± Comparison against base commit 81e17dd.

This pull request removes 15 and adds 3637 tests. Note that renamed tests count towards both.
Mockolate.Api.Tests.ApiAcceptance ‑ AcceptApiChanges
Mockolate.Api.Tests.ApiApprovalTests ‑ VerifyPublicApiForMockolate(framework: "net10.0")
Mockolate.Api.Tests.ApiApprovalTests ‑ VerifyPublicApiForMockolate(framework: "net8.0")
Mockolate.Api.Tests.ApiApprovalTests ‑ VerifyPublicApiForMockolate(framework: "netstandard2.0")
Mockolate.Tests.Setup.CallbackTests+InvokeForCallbacksTests ‑ ShouldIncrementIndexOnceWhenCallbackIsExhausted(only: 2, when: 0, expectResult: [0, 1, 2, 2, 2, ┬╖┬╖┬╖])
Mockolate.Tests.Setup.CallbackTests+InvokeForCallbacksTests ‑ ShouldIncrementIndexOnceWhenCallbackIsExhausted(only: 2, when: 1, expectResult: [0, 0, 1, 2, 2, ┬╖┬╖┬╖])
Mockolate.Tests.Setup.CallbackTests+InvokeForCallbacksTests ‑ ShouldIncrementIndexWheneverForIsExhausted(for: 2, only: 2, expectResult: [0, 1, 1, 2, 2, ┬╖┬╖┬╖])
Mockolate.Tests.Setup.CallbackTests+InvokeForCallbacksTests ‑ ShouldIncrementIndexWheneverForIsExhausted(for: 2, only: 3, expectResult: [0, 1, 1, 2, 2, ┬╖┬╖┬╖])
Mockolate.Tests.Setup.CallbackTests+InvokeWithStateForCallbacksTests ‑ ShouldIncrementIndexWheneverForIsExhausted(for: 2, only: 2, expectResult: [0, 1, 1, 2, 2, ┬╖┬╖┬╖])
Mockolate.Tests.Setup.CallbackTests+InvokeWithStateForCallbacksTests ‑ ShouldIncrementIndexWheneverForIsExhausted(for: 2, only: 3, expectResult: [0, 1, 1, 2, 2, ┬╖┬╖┬╖])
…
Mockolate.Analyzers.Tests.UseVerificationAnalyzerTests ‑ WhenIgnoreParametersResultIsNotUsed_ShouldBeFlagged
Mockolate.Analyzers.Tests.UseVerificationAnalyzerTests ‑ WhenIgnoreParametersResultIsUsed_ShouldNotBeFlagged
Mockolate.Api.Tests ‑ ApiApprovalTests.VerifyPublicApiForMockolate(framework: "net10.0")
Mockolate.Api.Tests ‑ ApiApprovalTests.VerifyPublicApiForMockolate(framework: "net11.0")
Mockolate.Api.Tests ‑ ApiApprovalTests.VerifyPublicApiForMockolate(framework: "net8.0")
Mockolate.Api.Tests ‑ ApiApprovalTests.VerifyPublicApiForMockolate(framework: "netstandard2.0")
Mockolate.ExampleTests.ExampleTests ‑ Any_ShouldAlwaysMatch
Mockolate.ExampleTests.ExampleTests ‑ Azure_ShouldBeMockable
Mockolate.ExampleTests.ExampleTests ‑ BaseClassWithConstructorParameters
Mockolate.ExampleTests.ExampleTests ‑ HttpClientTest(statusCode: NotFound)
…

♻️ This comment has been updated with latest results.

Classify Mockolate.Benchmarks.Unions as a test project for SonarCloud, matching the
classic benchmark project (which is only classified as test code because it references
mocking frameworks), so its lines are neither analysed as production code nor counted as
uncovered new code. Split the nested ternary in GenerateUnionSlotCombinations (S3358).
Copilot AI review requested due to automatic review settings September 5, 2026 06:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The NuGet packing entry for build/Mockolate.props uses PackagePath="build;buildTransitive", which is unlikely to place the props in both intended locations and can break generator option visibility for consumers.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 69/75 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread Source/Mockolate/Mockolate.csproj
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

🚀 Benchmark Results

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 11.0.100-preview.7.26381.103
[Host] : .NET 11.0.0 (11.0.0-preview.7.26381.103, 11.0.26.38203), 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 802.9 ns 54.94 ns 45.88 ns 0.79 3.77 KB 1.02
Mockolate 1 1,021.9 ns 39.69 ns 37.13 ns 1.00 3.7 KB 1.00
Imposter 1 813.5 ns 12.18 ns 10.17 ns 0.80 5.04 KB 1.36
Moq 1 188,039.3 ns 886.43 ns 785.79 ns 184.24 20.32 KB 5.50
NSubstitute 1 10,030.9 ns 35.45 ns 33.16 ns 9.83 12.73 KB 3.44
FakeItEasy 1 9,774.2 ns 54.95 ns 45.88 ns 9.58 13.35 KB 3.61
baseline* 10 2,323.5 ns 76.45 ns 63.84 ns 0.92 4.82 KB 1.01
Mockolate 10 2,521.8 ns 24.60 ns 23.01 ns 1.00 4.75 KB 1.00
Imposter 10 1,956.7 ns 21.76 ns 20.36 ns 0.78 7.85 KB 1.65
Moq 10 199,302.0 ns 782.55 ns 732.00 ns 79.04 29.7 KB 6.25
NSubstitute 10 23,782.4 ns 110.08 ns 102.97 ns 9.43 25.52 KB 5.37
FakeItEasy 10 21,196.3 ns 124.79 ns 104.20 ns 8.41 32.59 KB 6.86
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 11.0.100-preview.7.26381.103
[Host] : .NET 11.0.0 (11.0.0-preview.7.26381.103, 11.0.26.38203), 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 552.2 ns 13.51 ns 12.64 ns 1.09 2.41 KB 1.01
Mockolate 1 505.5 ns 3.63 ns 3.22 ns 1.00 2.38 KB 1.00
Imposter 1 412.2 ns 2.01 ns 1.78 ns 0.82 3.08 KB 1.30
TUnitMocks 1 447.4 ns 2.44 ns 2.16 ns 0.89 1.64 KB 0.69
Moq 1 9,808.1 ns 64.89 ns 54.19 ns 19.41 10.34 KB 4.35
NSubstitute 1 6,932.2 ns 37.64 ns 35.21 ns 13.72 11.39 KB 4.80
FakeItEasy 1 6,935.1 ns 95.31 ns 89.15 ns 13.72 10.95 KB 4.61
baseline* 10 1,128.5 ns 16.85 ns 14.07 ns 1.13 2.91 KB 1.01
Mockolate 10 1,001.4 ns 3.73 ns 3.49 ns 1.00 2.87 KB 1.00
Imposter 10 1,009.0 ns 5.28 ns 4.94 ns 1.01 4.63 KB 1.61
TUnitMocks 10 1,562.0 ns 5.13 ns 4.55 ns 1.56 3.94 KB 1.37
Moq 10 15,729.3 ns 61.23 ns 54.28 ns 15.71 18.09 KB 6.31
NSubstitute 10 15,766.1 ns 89.79 ns 83.99 ns 15.74 21.02 KB 7.33
FakeItEasy 10 16,438.9 ns 109.15 ns 102.10 ns 16.42 30.13 KB 10.51
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 3.00GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 11.0.0 (11.0.0-preview.7.26381.103, 11.0.26.38203), X64 RyuJIT x86-64-v3

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

Event Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 322.9 ns 7.44 ns 6.96 ns 1.18 1.7 KB 1.01
Mockolate 274.4 ns 6.26 ns 5.85 ns 1.00 1.69 KB 1.00
Imposter 1,229.5 ns 13.31 ns 11.80 ns 4.48 8.8 KB 5.21
TUnitMocks 169.2 ns 1.01 ns 0.89 ns 0.62 1.34 KB 0.80
Moq 15,054.7 ns 188.45 ns 147.13 ns 54.89 12.46 KB 7.39
NSubstitute 5,571.2 ns 18.79 ns 17.58 ns 20.31 8.98 KB 5.32
FakeItEasy 224,189.0 ns 815.16 ns 680.69 ns 817.36 15.14 KB 8.97
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 11.0.0 (11.0.0-preview.7.26381.103, 11.0.26.38203), X64 RyuJIT x86-64-v3

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

CreateMock Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 17.75 ns 0.178 ns 0.166 ns 0.97 160 B 1.00
Mockolate 18.22 ns 1.104 ns 1.033 ns 1.00 160 B 1.00
Imposter 272.79 ns 7.954 ns 7.440 ns 15.01 2248 B 14.05
TUnitMocks 34.73 ns 1.175 ns 1.099 ns 1.91 200 B 1.25
Moq 1,349.19 ns 19.244 ns 18.001 ns 74.24 2088 B 13.05
NSubstitute 1,849.55 ns 16.181 ns 13.512 ns 101.78 5032 B 31.45
FakeItEasy 1,561.57 ns 26.769 ns 25.040 ns 85.93 2755 B 17.22
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 11.0.100-preview.7.26381.103
[Host] : .NET 11.0.0 (11.0.0-preview.7.26381.103, 11.0.26.38203), X64 RyuJIT x86-64-v3

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

Callback Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 325.5 ns 6.98 ns 6.53 ns 0.94 1.57 KB 1.01
Mockolate 345.4 ns 11.02 ns 10.31 ns 1.00 1.55 KB 1.00
Imposter 574.6 ns 13.57 ns 12.70 ns 1.66 2.38 KB 1.53
TUnitMocks 536.7 ns 11.12 ns 10.40 ns 1.56 1.98 KB 1.28
Moq 110,064.6 ns 761.44 ns 712.25 ns 318.92 8.85 KB 5.69
NSubstitute 4,663.9 ns 34.37 ns 30.46 ns 13.51 7.68 KB 4.94
FakeItEasy 4,567.3 ns 37.07 ns 34.67 ns 13.23 6.77 KB 4.35
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 11.0.100-preview.7.26381.103
[Host] : .NET 11.0.0 (11.0.0-preview.7.26381.103, 11.0.26.38203), 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 276.3 ns 3.44 ns 3.05 ns 1.03 1.93 KB 1.02
Mockolate 1 269.1 ns 4.39 ns 4.11 ns 1.00 1.89 KB 1.00
Imposter 1 404.6 ns 7.14 ns 6.68 ns 1.50 3.98 KB 2.11
TUnitMocks 1 390.8 ns 8.48 ns 7.52 ns 1.45 2.02 KB 1.07
Moq 1 119,240.3 ns 1,034.64 ns 967.81 ns 443.25 14.78 KB 7.82
NSubstitute 1 4,380.8 ns 77.08 ns 72.10 ns 16.28 9.1 KB 4.81
FakeItEasy 1 4,018.0 ns 33.26 ns 31.11 ns 14.94 7.91 KB 4.19
baseline* 10 484.2 ns 2.23 ns 2.09 ns 1.02 2.14 KB 1.02
Mockolate 10 473.4 ns 4.20 ns 3.72 ns 1.00 2.1 KB 1.00
Imposter 10 782.6 ns 3.70 ns 3.46 ns 1.65 5.46 KB 2.60
TUnitMocks 10 1,092.1 ns 10.05 ns 9.40 ns 2.31 3.73 KB 1.77
Moq 10 121,851.5 ns 492.98 ns 461.14 ns 257.40 18.68 KB 8.89
NSubstitute 10 6,454.2 ns 29.13 ns 25.82 ns 13.63 12.05 KB 5.74
FakeItEasy 10 6,541.2 ns 10.33 ns 9.66 ns 13.82 15.49 KB 7.37

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

The .NET 11 SDK writes a runtimeconfig.dev.json with hot reload options but without
additionalProbingPaths for Debug builds. VsTest 18.0.1, bundled with Stryker 4.10.0, reads
that file on Linux (where no testhost.exe exists next to the test assembly) and throws a
NullReferenceException in DotnetTestHostManager.GetTestHostPath, so test discovery is
aborted and mutation testing fails with "No test result reported". Reproduced and verified
in a Debian WSL environment with the same SDK and Stryker version: 0 tests discovered before,
3770 after.
Copilot AI review requested due to automatic review settings September 5, 2026 07:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The NuGet packing path for Mockolate.props appears incorrect and the new union-mode test target set does not match the PR’s stated “whole test suite parity” intent.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

Pipeline/Build.UnitTest.cs:61

  • The PR description says the whole test suite is run a second time in union mode as a parity proof, but UnionTestProjects omits Mockolate_Analyzers_Tests (and also Build_Tests). That means analyzer changes (including the new IgnoreParameters usage rule) won’t be exercised under the net11.0/union configuration.

Source/Mockolate/Mockolate.csproj:10

  • PackagePath is set to build;buildTransitive, which NuGet/MSBuild will treat as a literal folder name (including the semicolon), not as “pack to both locations”. This likely places Mockolate.props under an unexpected path in the nupkg, preventing it from being imported and making MockolateUnionParameters non-compiler-visible for consumers.
		<ProjectReference Include="..\Mockolate.Analyzers.CodeFixers\Mockolate.Analyzers.CodeFixers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
		<None Include="$(MSBuildProjectDirectory)\..\Mockolate.Analyzers.CodeFixers\bin\$(Configuration)\netstandard2.0\Mockolate.Analyzers.CodeFixers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
		<None Include="$(MSBuildProjectDirectory)\..\Mockolate.SourceGenerators\bin\$(Configuration)\netstandard2.0\Mockolate.SourceGenerators.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
		<None Include="build\Mockolate.props" Pack="true" PackagePath="build;buildTransitive"/>
	</ItemGroup>
  • Files reviewed: 69/75 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 5, 2026 15:09
- Delegate mocks: emit [OverloadResolutionPriority] on the IParameters
  setup/verify overloads so they no longer lose to the union overloads
- Methods with more than four parameters keep a raw-delegate value
  overload so lambda arguments still compile in union mode
- Treat System.Delegate/System.MulticastDelegate parameters as delegates
  (raw value slot instead of a predicate)
- Union indexer keys resolve null/default to the declared parameter
  default, like the method path
- Scope the union-eligibility uniqueness checks per member scope and
  evaluate them lazily (never when union mode is off)
- Accept suffix-less attribute names in MockolateUnionAttributePolyfills
- Detect C# 15 by enum name instead of the hardcoded value 1500
- Pin Mockolate.Benchmarks to classic mode so the classic-vs-union
  benchmark comparison survives the C# 15 GA
- Share one CovariantParameterAdapter<T> (emitted in Mock.g.cs), the
  slow-path verify condition, and the summary cref formatting between
  classic and union emission; hoist union indexer key matchers out of
  the per-interaction lambdas; use MaxExplicitParameters consistently
@vbreuss
vbreuss force-pushed the feat/union-parameters branch from 78d9b3b to b66af9e Compare September 5, 2026 15:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are at least two concrete correctness/packaging issues in the changed code (ParameterArg’s boxed Value behavior and NuGet PackagePath usage) that should be fixed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Source/Mockolate/Mockolate.csproj:9

  • PackagePath="build;buildTransitive" is unlikely to produce a valid NuGet layout; PackagePath is a single path, so this may end up packing the props file into a literal build;buildTransitive folder (or otherwise not importing correctly). If you intend to ship the same props in both build/ and buildTransitive/, it needs two <None .../> items (one per target path).
  • Files reviewed: 87/93 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread Source/Mockolate.SourceGenerators/Sources/Sources.ParameterArg.cs
Copilot AI review requested due to automatic review settings September 5, 2026 15:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The nupkg packing metadata for Source/Mockolate/build/Mockolate.props appears incorrect (PackagePath="build;buildTransitive"), which risks breaking automatic import of the props and therefore union-mode configuration for consumers.

Review details

Suppressed comments (1)

Source/Mockolate/Mockolate.csproj:9

  • PackagePath="build;buildTransitive" will likely be treated as a single literal folder name in the nupkg ("build;buildTransitive/") rather than placing the props into both build/ and buildTransitive/. That would prevent consuming projects from automatically importing Mockolate.props, so the generator may not see MockolateUnionParameters as compiler-visible.
  • Files reviewed: 87/93 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Mockolate.Benchmarks.Unions was a local classic-vs-union measurement rig;
it is not wired into the Benchmarks pipeline and would only rot. The
classic pin on Mockolate.Benchmarks stays so results remain comparable
to the CI baseline once the SDK defaults to C# 15.
- Store files identical across scenarios once under Expected/_Shared,
  referenced by a _shared.txt manifest per scenario (~29% fewer lines)
- Drop the HttpClient union scenario; its shapes are covered by the
  comprehensive interface scenario
- Mark the Expected snapshots as linguist-generated so GitHub collapses
  them in pull request diffs
Copilot AI review requested due to automatic review settings September 5, 2026 15:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are at least two concrete correctness issues (a missing using Mockolate; causing a test compile failure and a likely incorrect NuGet PackagePath that prevents Mockolate.props from landing in both build import locations).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Source/Mockolate/Mockolate.csproj:10

  • PackagePath="build;buildTransitive" is likely treated as a literal folder name during packing, so Mockolate.props won’t be placed into both build/ and buildTransitive/ as intended. Duplicate the item with two separate PackagePath values.
		<None Include="$(MSBuildProjectDirectory)\..\Mockolate.SourceGenerators\bin\$(Configuration)\netstandard2.0\Mockolate.SourceGenerators.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
		<None Include="build\Mockolate.props" Pack="true" PackagePath="build;buildTransitive"/>
	</ItemGroup>
  • Files reviewed: 81/92 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread Tests/Mockolate.Tests/ParameterArgTests.cs
Comment thread Tests/Mockolate.SourceGenerators.Tests/TestHelpers/SnapshotStorage.cs Outdated
The default instance is the literal case everywhere else (IsLiteral,
Literal, ToParameterMatch); Value returned null instead of the boxed
default(T) for value types.
The coverlet VSTest collector cannot reliably read the coverage hits of
xunit.v3 out-of-process runs (intermittent EndOfStreamException at
session end), which silently dropped the whole Mockolate.SourceGenerators
coverage from the SonarCloud quality gate. xunit v2 runs in-process in
the testhost, where the collector works (reproduced and verified
locally in Release). The snapshot acceptance test is gated by the
MOCKOLATE_ACCEPT_SNAPSHOTS environment variable instead of the v3-only
Explicit flag, and the snapshot test helpers use tabs like the rest of
the repository.
Copilot AI review requested due to automatic review settings September 5, 2026 16:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The NuGet packing metadata for build/Mockolate.props uses PackagePath="build;buildTransitive", which likely won’t place the props file into both build/ and buildTransitive/ as intended.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

Tests/Mockolate.SourceGenerators.Tests/Snapshot/MockGenerationSnapshotTests.cs:188

  • Indentation in this block uses spaces, but the repo .editorconfig requires tabs for *.cs (indent_style = tab). Reindenting this file to tabs would keep formatting consistent and avoid style-only churn in future diffs.

Source/Mockolate/Mockolate.csproj:10

  • PackagePath contains a semicolon (build;buildTransitive), which will be treated as a literal path segment during packing rather than placing the file into both build/ and buildTransitive/. This likely prevents consumers (or transitive consumers) from importing Mockolate.props as intended.
		<None Include="$(MSBuildProjectDirectory)\..\Mockolate.Analyzers.CodeFixers\bin\$(Configuration)\netstandard2.0\Mockolate.Analyzers.CodeFixers.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
		<None Include="$(MSBuildProjectDirectory)\..\Mockolate.SourceGenerators\bin\$(Configuration)\netstandard2.0\Mockolate.SourceGenerators.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
		<None Include="build\Mockolate.props" Pack="true" PackagePath="build;buildTransitive"/>
	</ItemGroup>
  • Files reviewed: 82/93 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change The changes require a new major version enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants