Skip to content

fix: delegate wrapped calls of hidden interface members to the declaring interface - #847

Merged
vbreuss merged 1 commit into
mainfrom
worktree-fix-hidden-member-wrapping
Aug 25, 2026
Merged

fix: delegate wrapped calls of hidden interface members to the declaring interface#847
vbreuss merged 1 commit into
mainfrom
worktree-fix-hidden-member-wrapping

Conversation

@vbreuss

@vbreuss vbreuss commented Aug 25, 2026

Copy link
Copy Markdown
Member

A method that hides a base interface member via new is emitted as an explicit interface implementation, but its wrapping delegation cast MockRegistry.Wraps to the mocked type instead of the declaring interface. The delegated call therefore bound to the hiding member:

  • same constraints, different return type: wrong member invoked silently
  • hiding member adds a constraint: CS0452
  • hiding member narrows a constraint: CS0453
  • three-level hierarchy: CS0266

Cast to method.ExplicitImplementation when set, mirroring how events already treat explicitly implemented members.

@vbreuss vbreuss self-assigned this Aug 25, 2026
Copilot AI lite review requested due to automatic review settings August 25, 2026 14:35
@vbreuss vbreuss added the bug Something isn't working label Aug 25, 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.

Pull request overview

Fixes Mockolate’s source generator wrapping behavior for interface members that hide base interface members via new and are emitted as explicit interface implementations. The generator previously cast MockRegistry.Wraps to the mocked type, which could bind the delegated call to the hiding member (wrong return type / constraint issues); this change casts to the declaring interface when method.ExplicitImplementation is set.

Changes:

  • Update method wrapping delegation in the source generator to cast MockRegistry.Wraps to method.ExplicitImplementation (when present) instead of the mocked type.
  • Add runtime test coverage for wrapping hidden generic interface methods.
  • Add generator tests covering hidden-method wrapping across constraint/return-type hierarchy variations.

Reviewed changes

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

File Description
Tests/Mockolate.Tests/TestHelpers/IChocolateCatalog.cs Adds interfaces that model a hidden generic method (new) scenario for runtime wrapping tests.
Tests/Mockolate.Tests/MockTests.WrappingInterfaceTests.cs Adds a runtime regression test and helper implementation to validate correct delegation for hidden interface members.
Tests/Mockolate.SourceGenerators.Tests/MockTests.cs Adds generator-level assertions to ensure generated wrapping code casts to the declaring interface for hidden members.
Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs Adjusts generated wrapping cast to use method.ExplicitImplementation ?? className for delegation binding correctness.

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

Comment thread Tests/Mockolate.Tests/MockTests.WrappingInterfaceTests.cs
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Test Results

    24 files  ± 0      24 suites  ±0   10m 57s ⏱️ +29s
 4 325 tests + 5   4 323 ✅ + 5  2 💤 ±0  0 ❌ ±0 
27 587 runs  +19  27 583 ✅ +19  4 💤 ±0  0 ❌ ±0 

Results for commit 3945b96. ± Comparison against base commit 8444151.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

🚀 Benchmark Results

Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 3.69GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.400
[Host] : .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v4

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

Event Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 265.9 ns 4.70 ns 4.40 ns 1.16 1.7 KB 1.00
Mockolate 229.1 ns 1.41 ns 1.32 ns 1.00 1.7 KB 1.00
Imposter 1,044.8 ns 10.51 ns 9.31 ns 4.56 8.8 KB 5.17
TUnitMocks 138.3 ns 2.34 ns 1.95 ns 0.60 1.34 KB 0.79
Moq 11,105.5 ns 95.83 ns 84.95 ns 48.48 12.51 KB 7.34
NSubstitute 4,184.8 ns 37.62 ns 35.19 ns 18.27 9.05 KB 5.31
FakeItEasy 186,147.0 ns 841.85 ns 787.46 ns 812.62 15.37 KB 9.03
Details

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

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

Method N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 370.4 ns 3.57 ns 3.16 ns 0.86 1.93 KB 1.00
Mockolate 1 430.0 ns 14.72 ns 13.77 ns 1.00 1.93 KB 1.00
Imposter 1 747.6 ns 20.86 ns 19.51 ns 1.74 4.04 KB 2.09
TUnitMocks 1 578.6 ns 9.32 ns 8.72 ns 1.35 2.02 KB 1.04
Moq 1 188,428.3 ns 1,145.57 ns 1,015.52 ns 438.66 14.73 KB 7.63
NSubstitute 1 6,572.6 ns 54.98 ns 51.43 ns 15.30 9.12 KB 4.72
FakeItEasy 1 6,500.7 ns 215.67 ns 201.73 ns 15.13 8.05 KB 4.17
baseline* 10 651.4 ns 5.08 ns 4.25 ns 0.87 2.14 KB 1.00
Mockolate 10 744.7 ns 13.86 ns 12.96 ns 1.00 2.14 KB 1.00
Imposter 10 1,399.8 ns 41.03 ns 38.38 ns 1.88 5.52 KB 2.58
TUnitMocks 10 1,556.6 ns 23.19 ns 21.69 ns 2.09 3.73 KB 1.74
Moq 10 194,521.1 ns 711.99 ns 594.54 ns 261.28 18.64 KB 8.71
NSubstitute 10 9,643.4 ns 54.69 ns 51.16 ns 12.95 12.07 KB 5.64
FakeItEasy 10 10,288.2 ns 279.98 ns 261.89 ns 13.82 15.42 KB 7.20
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.400
[Host] : .NET 10.0.11 (10.0.11, 10.0.1126.37416), 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 937.1 ns 23.67 ns 22.14 ns 0.94 3.77 KB 1.00
Mockolate 1 1,000.6 ns 31.96 ns 29.90 ns 1.00 3.77 KB 1.00
Imposter 1 910.8 ns 10.95 ns 8.55 ns 0.91 5.16 KB 1.37
Moq 1 214,478.2 ns 573.78 ns 479.14 ns 214.53 20.37 KB 5.41
NSubstitute 1 10,509.5 ns 95.90 ns 85.01 ns 10.51 12.84 KB 3.41
FakeItEasy 1 12,066.4 ns 48.60 ns 40.58 ns 12.07 13.63 KB 3.62
baseline* 10 2,428.1 ns 12.38 ns 11.58 ns 0.99 4.82 KB 1.00
Mockolate 10 2,455.1 ns 9.81 ns 8.70 ns 1.00 4.82 KB 1.00
Imposter 10 2,221.4 ns 30.09 ns 28.15 ns 0.90 7.97 KB 1.65
Moq 10 224,109.5 ns 740.97 ns 656.85 ns 91.29 29.89 KB 6.20
NSubstitute 10 25,150.4 ns 316.88 ns 296.41 ns 10.24 25.63 KB 5.32
FakeItEasy 10 25,317.1 ns 107.35 ns 95.16 ns 10.31 32.97 KB 6.84
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 10.0.400
[Host] : .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v3

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

Callback Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 318.6 ns 9.53 ns 8.92 ns 0.94 1.57 KB 1.00
Mockolate 340.0 ns 7.32 ns 6.84 ns 1.00 1.57 KB 1.00
Imposter 436.8 ns 4.56 ns 4.04 ns 1.29 2.38 KB 1.52
TUnitMocks 568.7 ns 5.73 ns 5.36 ns 1.67 1.99 KB 1.27
Moq 97,913.2 ns 362.93 ns 339.48 ns 288.06 8.88 KB 5.66
NSubstitute 4,829.1 ns 35.54 ns 33.24 ns 14.21 7.71 KB 4.91
FakeItEasy 4,732.9 ns 68.62 ns 60.83 ns 13.92 6.81 KB 4.33
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.400
[Host] : .NET 10.0.11 (10.0.11, 10.0.1126.37416), 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 553.8 ns 15.82 ns 14.02 ns 0.91 2.41 KB 1.00
Mockolate 1 610.2 ns 7.59 ns 6.34 ns 1.00 2.41 KB 1.00
Imposter 1 427.9 ns 1.48 ns 1.31 ns 0.70 3.13 KB 1.29
TUnitMocks 1 462.8 ns 1.86 ns 1.74 ns 0.76 1.64 KB 0.68
Moq 1 10,544.6 ns 48.22 ns 37.64 ns 17.28 10.27 KB 4.25
NSubstitute 1 7,012.1 ns 33.09 ns 27.63 ns 11.49 11.45 KB 4.74
FakeItEasy 1 7,112.1 ns 17.07 ns 15.14 ns 11.66 11.24 KB 4.66
baseline* 10 1,071.6 ns 14.90 ns 13.94 ns 1.05 2.91 KB 1.00
Mockolate 10 1,022.0 ns 6.84 ns 6.07 ns 1.00 2.91 KB 1.00
Imposter 10 1,072.9 ns 15.24 ns 13.51 ns 1.05 4.67 KB 1.61
TUnitMocks 10 1,590.6 ns 13.57 ns 12.70 ns 1.56 3.94 KB 1.35
Moq 10 16,762.6 ns 81.14 ns 75.89 ns 16.40 17.03 KB 5.86
NSubstitute 10 16,438.4 ns 38.21 ns 33.87 ns 16.08 21.08 KB 7.25
FakeItEasy 10 16,873.5 ns 53.18 ns 47.14 ns 16.51 30.81 KB 10.60
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.400
[Host] : .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v3

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

CreateMock Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 19.02 ns 0.499 ns 0.467 ns 1.03 160 B 1.00
Mockolate 18.53 ns 0.287 ns 0.268 ns 1.00 160 B 1.00
Imposter 299.91 ns 5.688 ns 4.749 ns 16.19 2248 B 14.05
TUnitMocks 38.61 ns 0.226 ns 0.212 ns 2.08 200 B 1.25
Moq 1,399.43 ns 2.222 ns 1.735 ns 75.53 2096 B 13.10
NSubstitute 1,873.49 ns 6.418 ns 5.689 ns 101.11 5048 B 31.55
FakeItEasy 1,912.94 ns 36.671 ns 34.302 ns 103.24 2763 B 17.27

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

…ing interface

A method that hides a base interface member via `new` is emitted as an
explicit interface implementation, but its wrapping delegation cast
`MockRegistry.Wraps` to the mocked type instead of the declaring
interface. The delegated call therefore bound to the hiding member:

- same constraints, different return type: wrong member invoked silently
- hiding member adds a constraint: CS0452
- hiding member narrows a constraint: CS0453
- three-level hierarchy: CS0266

Cast to `method.ExplicitImplementation` when set, mirroring how events
already treat explicitly implemented members.
Copilot AI review requested due to automatic review settings August 25, 2026 14:45
@vbreuss
vbreuss force-pushed the worktree-fix-hidden-member-wrapping branch from 132f47f to 3945b96 Compare August 25, 2026 14:45

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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Tests/Mockolate.Tests/MockTests.WrappingInterfaceTests.cs:200

  • The explicit interface implementation IChocolateSource.Get<T>() is missing the where T : notnull constraint required by IChocolateSource.Get<T>(). This won’t compile (constraints on an implementing generic method must match the interface method).
			IEnumerable<T> IChocolateSource.Get<T>()
			{
				ReceivedCalls.Add("source");
				return [];
			}

@vbreuss
vbreuss enabled auto-merge (squash) August 25, 2026 14:51
@sonarqubecloud

Copy link
Copy Markdown

@vbreuss
vbreuss merged commit 72c5ed1 into main Aug 25, 2026
18 checks passed
@vbreuss
vbreuss deleted the worktree-fix-hidden-member-wrapping branch August 25, 2026 14:54
github-actions Bot added a commit that referenced this pull request Aug 25, 2026
…nterface members to the declaring interface (#847) by Valentin Breuß
github-actions Bot added a commit that referenced this pull request Aug 25, 2026
…nterface members to the declaring interface (#847) by Valentin Breuß
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants