Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.CallBase for default interface implementations #1130

Merged
merged 5 commits into from
Jan 17, 2021

Conversation

stakx
Copy link
Contributor

@stakx stakx commented Jan 16, 2021

As of version 4.4.0, DynamicProxy is not able to reliably .Proceed() to a base method implementation if it is provided by an interface. From a user perspective, one would likely try to invoke a default interface implementation using .CallBase().

This is an attempt to work around this current DynamicProxy limitation by performing the non-virtual call to the interface method manually. This involves some System.Reflection.Emit code inspired by @thomaslevesque's approach mentioned in castleproject/Core#447 (comment).

This works mostly, except for one important detail: Moq will always call the least specific default implementation, instead of the most specific one. (This matters when one overrides a default implementation in a derived interface, then mocks that derived interface or a type inheriting/implementing it.) (Update: Most specific override identification is now implemented, too.)

The code being added here is rather horrible, and it would be very good to get rid of it as soon as DynamicProxy properly supports proceeding to default interface implementations.

Resolves #972.

@stakx stakx marked this pull request as draft January 16, 2021 22:03
// Put the return value in a local variable for later retrieval.
if (method.ReturnType != typeof(void))
{
il.Emit(OpCodes.Box, method.ReturnType);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would fail on a by-ref return type (which AFAIK neither we nor DynamicProxy currently support anyway).

@stakx stakx marked this pull request as ready for review January 17, 2021 03:03
@stakx stakx added this to the 4.16.1 milestone Jan 17, 2021
@stakx stakx force-pushed the default-interface-implementations branch from b1f2a0b to e2c4a27 Compare January 17, 2021 03:14
@stakx stakx force-pushed the default-interface-implementations branch from e2c4a27 to 72db581 Compare January 17, 2021 03:16
 * We don't really need two new classes for the helper methods. Define
   those directly inside `CastleProxyFactory`.

 * Rename methods and change their signatures so `Invocation.CallBase`
   in `CastleProxyFactory` reads a little more clearly.

 * Add some more explanation comments to the code.
@stakx stakx force-pushed the default-interface-implementations branch from 72db581 to 3f54703 Compare January 17, 2021 10:42
@stakx stakx merged commit cdb32ec into devlooped:main Jan 17, 2021
@stakx stakx deleted the default-interface-implementations branch January 17, 2021 10:49
mburumaxwell pushed a commit to faluapp/falu-dotnet that referenced this pull request Jun 12, 2021
Bumps [Moq](https://github.com/moq/moq4) from 4.16.0 to 4.16.1.

#Changelog

*Sourced from [Moq's changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md).*

> ## 4.16.1 (2021-02-23)
>
> #### Added
>
> * `CallBase` can now be used with interface methods that have a default interface implementation. It will call [the most specific override](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods#the-most-specific-override-rule). (@stakx, [#1130](devlooped/moq#1130))
>
> #### Changed
>
> * Improved error message formatting of `It.Is` lambda expressions that capture local variables. (@bfriesen, [#1140](devlooped/moq#1140))
>
> #### Fixed
>
> * `AmbiguousMatchException` raised when interface has property indexer besides property in VB. (@mujdatdinc, [#1129](devlooped/moq#1129))
> * Interface default methods are ignored (@hahn-kev, [#972](devlooped/moq#972))
> * Callback validation too strict when setting up a task's `.Result` property (@stakx, [#1132](devlooped/moq#1132))
> * `setup.Returns(InvocationFunc)` wraps thrown exceptions in `TargetInvocationException` (@stakx, [#1141](devlooped/moq#1141))

#Commits

- [`fc484fb`](devlooped/moq@fc484fb) Update version to 4.16.1
- [`0ddfdb8`](devlooped/moq@0ddfdb8) `Returns(InvocationFunc)` shouldn't throw `TargetInvocationException`
- [`f36d3e8`](devlooped/moq@f36d3e8) Merge pull request [#1140](devlooped/moq#1140) from bfriesen/lambda_closure_support
- [`e96804f`](devlooped/moq@e96804f) Update the changelog
- [`5ae449c`](devlooped/moq@5ae449c) Exclude name of the closure class
- [`8a2d2ed`](devlooped/moq@8a2d2ed) Add test for closure access
- [`cf5af87`](devlooped/moq@cf5af87) Format lambda expression variables
- [`5b10a8c`](devlooped/moq@5b10a8c) Add test for lamba matcher variables
- [`653db31`](devlooped/moq@653db31) Some minor renames for consistency
- [`fc73131`](devlooped/moq@fc73131) Add missing copyright notices
- Additional commits viewable in [compare view](devlooped/moq@v4.16.0...v4.16.1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Interface Default methods are ignored
1 participant