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 implementations of interface static abstract methods in classes and structures #52969

Conversation

AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Apr 27, 2021

In metadata an implementation should be a static, not newslot, not final, not virtual, not abstract method. Type should have a MethodImpl entry pointing to the ”body” method with a MethodDef index. The “body” must be a method declared within the type. There is no concept of an implicit interface implementation for static methods in metadata. I.e., if there is no corresponding MethodImpl entry, the method isn’t an implementation from runtime point of view.

Language supports both implicit and explicit interface implementation.

Relevant ECMA-335 changes PR - dotnet/runtime#49558

…in classes and structures

In metadata an implementation should be a static, not newslot, not final, not virtual, not abstract method. Type should have a MethodImpl entry pointing to the ”body” method with a MethodDef index. The “body” must be a method declared within the type. There is no concept of an implicit interface implementation for static methods in metadata. I.e., if there is no corresponding MethodImpl entry, the method isn’t an implementation from runtime point of view.

Language supports both implicit and explicit interface implementation.
Copy link
Contributor

@RikkiGibson RikkiGibson left a comment

Choose a reason for hiding this comment

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

Looks good. Had some questions, some doc change suggestions, some diagnostic wording suggestions, and some minor test suggestions.

@@ -1553,22 +1574,23 @@ private void CheckInterfaceUnification(BindingDiagnosticBag diagnostics)
/// <param name="implementingMemberAndDiagnostics">Returned from FindImplementationForInterfaceMemberWithDiagnostics.</param>
/// <param name="interfaceMember">The interface method or property that is being implemented.</param>
/// <returns>Synthesized implementation or null if not needed.</returns>
private SynthesizedExplicitImplementationForwardingMethod SynthesizeInterfaceMemberImplementation(SymbolAndDiagnostics implementingMemberAndDiagnostics, Symbol interfaceMember)
private (SynthesizedExplicitImplementationForwardingMethod ForwardingMethod, (MethodSymbol Body, MethodSymbol Implemented) MethodImpl)
Copy link
Contributor

@RikkiGibson RikkiGibson Apr 27, 2021

Choose a reason for hiding this comment

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

It would be helpful to have nullable annotations on this signature, and if it's not too much trouble to #nullable enable the implementation as well. #Resolved

@@ -1553,22 +1574,23 @@ private void CheckInterfaceUnification(BindingDiagnosticBag diagnostics)
/// <param name="implementingMemberAndDiagnostics">Returned from FindImplementationForInterfaceMemberWithDiagnostics.</param>
/// <param name="interfaceMember">The interface method or property that is being implemented.</param>
/// <returns>Synthesized implementation or null if not needed.</returns>
Copy link
Contributor

@RikkiGibson RikkiGibson Apr 27, 2021

Choose a reason for hiding this comment

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

Should this <returns> comment be updated? #Resolved

@@ -60,7 +60,7 @@ internal abstract class SourceOrdinaryMethodSymbolBase : SourceMemberMethodSymbo
DeclarationModifiers declarationModifiers;
(declarationModifiers, HasExplicitAccessModifier) = this.MakeModifiers(methodKind, isPartial, hasBody, location, diagnostics);

var isMetadataVirtualIgnoringModifiers = methodKind == MethodKind.ExplicitInterfaceImplementation; //explicit impls must be marked metadata virtual
var isMetadataVirtualIgnoringModifiers = methodKind == MethodKind.ExplicitInterfaceImplementation && (declarationModifiers & DeclarationModifiers.Static) == 0; //explicit impls must be marked metadata virtual unless static
Copy link
Contributor

@RikkiGibson RikkiGibson Apr 27, 2021

Choose a reason for hiding this comment

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

Consider moving the trailing comment to above this line #Resolved

@RikkiGibson
Copy link
Contributor

Incidentally, I found that reviewing many of the tests went easier for me when I rotated my monitor into portrait orientation and I could see the whole test without needing to scroll.

@AlekseyTs
Copy link
Contributor Author

@333fred, @dotnet/roslyn-compiler For a second review

1 similar comment
@AlekseyTs
Copy link
Contributor Author

@333fred, @dotnet/roslyn-compiler For a second review

switch (member.Visibility)
{
case TypeMemberVisibility.Private:
return context.IncludePrivateMembers;
acceptBasedOnVisibility = context.IncludePrivateMembers;
Copy link
Member

@333fred 333fred Apr 29, 2021

Choose a reason for hiding this comment

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

context.IncludePrivateMembers

When would this ever be anything but false? We already did a shortcut out at the beginning of this method if this was true. It seems like the only thing we need to check is IVT for Assembly and FamilyAndAssembly members. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When would this ever be anything but false? We already did a shortcut out at the beginning of this method if this was true. It seems like the only thing we need to check is IVT for Assembly and FamilyAndAssembly members.

It looks like you are correct, but I prefer not changing the logic here.

// (27,20): error CS0539: 'I6.M01()' in explicit interface declaration is not found among members of the interface that can be implemented
// static void I1.M01() {}
Diagnostic(ErrorCode.ERR_InterfaceMemberNotFound, "M01").WithArguments("I6.M01()").WithLocation(27, 20),
// (32,26): warning CS0108: 'I7.M01()' hides inherited member 'I1.M01()'. Use the new keyword if hiding was intended.
Copy link
Member

@333fred 333fred Apr 30, 2021

Choose a reason for hiding this comment

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

If this isn't marked as newslot, how does the runtime know this hides the base version? Consider also verifying the newslot behavior of this method. #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

This is probably me just not understanding part of the spec, I guess: I would have expected that the runtime would be updated so newslot could apply to static methods, so that a future world where you can say static abstract new void M1(); would someday exist.


In reply to: 623509643 [](ancestors = 623509643)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If this isn't marked as newslot, how does the runtime know this hides the base version?

This is how static methods work, this is part of runtime specification. New slot is set to specify that a virtual method is not an override, static methods never override anything.

Consider also verifying the newslot behavior of this method.

DefineAbstractStaticMethod_01 already covers that.

Copy link
Member

Choose a reason for hiding this comment

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

so that a future world where you can say static abstract new void M1(); would someday exist.

ie, something like this:

interface I1
{
    static abstract void M1();
}
interface I1 : I2
{
    static void I1.M1() {}
    static abstract new void M1();
}

In reply to: 623522140 [](ancestors = 623522140)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so that a future world where you can say static abstract new void M1(); would someday exist.

This future is already here. It exists. This is a warning, not an error.

Assert.Equal("void modopt(I1) C1.I1.M01()", c1M01.ToTestDisplayString());
Assert.Same(m01, c1M01.ExplicitInterfaceImplementations.Single());

c1M01 = module.GlobalNamespace.GetMember<MethodSymbol>("C1.M01");
Copy link
Member

@333fred 333fred Apr 30, 2021

Choose a reason for hiding this comment

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

c1M01

Consider asserting the display for this method to show the modopt difference. #Resolved

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

LGTM (commit 1). A minor test comment that isn't blocking.

@AlekseyTs AlekseyTs enabled auto-merge (squash) April 30, 2021 16:04
@AlekseyTs AlekseyTs merged commit 2de7aa4 into dotnet:features/StaticAbstractMembersInInterfaces Apr 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants