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

Add support for covariant returns #619

Merged
merged 8 commits into from
Jul 19, 2022

Conversation

stakx
Copy link
Member

@stakx stakx commented May 13, 2022

This should fix #601.

Right now, there are only two very basic unit tests. Some more tests may be needed to test various aspects of support for covariant returns. For example:

  • interplay between covariant returns and generics (if that's a feasible scenario at all)
  • ensuring DynamicProxy always picks the "most derived" method for proxying

Some more things to think about (I'll expand this list as further concerns show up):

  • Do we need to detect runtime support for covariant returns, or are the required changes compatible with older platforms that don't support them?

@stakx stakx marked this pull request as draft May 13, 2022 11:37
@stakx
Copy link
Member Author

stakx commented May 13, 2022

Do we need to detect runtime support for covariant returns, or are the required changes compatible with older platforms that don't support them?

The .NET runtime design document Covariant Return Methods mentions PreserveBaseOverridesAttribute, which a compiler is expected to add to covariant override methods. I don't yet understand the finer details of this, but perhaps we might leverage the presence of that custom attribute on a method to decide whether or not to allow covariant returns.

@stakx stakx force-pushed the bugfix/covariant-returns branch from 16bf56b to 62e90b8 Compare May 13, 2022 19:42
@stakx
Copy link
Member Author

stakx commented May 13, 2022

ensuring DynamicProxy always picks the "most derived" method for proxying

This is ensured by the fact that Reflection reports methods from a derived class before methods from its base class. Because of that, DynamicProxy sees override methods before overridden methods, and will treat only the latter as duplicates.

It's important to note that our implementation relies on a specific behavior of .NET Reflection. I've documented that assumption as a unit test.

@stakx stakx force-pushed the bugfix/covariant-returns branch 2 times, most recently from fb6a724 to b8703ba Compare May 13, 2022 20:08
@stakx stakx marked this pull request as ready for review May 13, 2022 20:08
@stakx stakx requested a review from jonorossi May 13, 2022 20:08
@stakx
Copy link
Member Author

stakx commented May 13, 2022

This is now ready for reviewing. As I mentioned in #601, I'm open to suggestions for further tests to be added to the test suite.

stakx added 8 commits May 14, 2022 14:10
On .NET 6+ this fails with a `TypeLoadException`:

> Return type in method `DerivedEmptyRecord.<Clone>$()` [...] is not
> compatible with base type method `EmptyRecord.<Clone>$()`

From dotnet/efcore#26602 (comment):

> The C# compiler changed the emit strategy for records in .NET 6 to
> take advantage of covariant returns. The Clone method now always has a
> return type that matches the containing type. Covariant returns were
> added to the runtime and language in .NET 5 but records didn't take
> advantage of them (just ran out of time). In .NET 6 though we finished
> off that feature and added it to records.

So we'll need to add support for covariant returns to DynamicProxy.
The previous commit's test fails due to DynamicProxy treating the base
and derived record classes' `<Clone>$` methods as two distinct methods
(because they differ in their exact return type), instead of as a single
overridden method. We can change this by adjusting `MethodSignature-
Comparer` such that it also accepts assignment-compatible return types.
Covariant returns were introduced with .NET 5. For all earlier runtimes,
the last commit may have relaxed method signature comparison too much.

We can resolve this by checking for a specific custom attribute that
.NET compilers are expected to put on override methods using covariant
returns.

Reference:
https://github.com/dotnet/runtime/blob/main/docs/design/features/covariant-return-methods.md
So far, we simply assumed that `x` := override method, and `y` := over-
ridden method, but there is actually no guarantee for that.
@stakx stakx force-pushed the bugfix/covariant-returns branch from b8703ba to 01c2c6a Compare May 14, 2022 12:11
Copy link

@ajcvickers ajcvickers left a comment

Choose a reason for hiding this comment

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

This looks good to me. Many thanks for looking into this.

@CesarD
Copy link

CesarD commented Jun 14, 2022

@jonorossi ping

Copy link
Member

@jonorossi jonorossi left a comment

Choose a reason for hiding this comment

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

Great work @stakx, turned out to be a very clean implementation.

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.

Proxies using Records with a base class broken using .NET 6 compiler
4 participants