-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Add support for implementations of interface static abstract methods in classes and structures #52969
Conversation
…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.
There was a problem hiding this 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) |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
src/Compilers/CSharp/Portable/Symbols/Source/SourceOrdinaryMethodSymbolBase.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs
Show resolved
Hide resolved
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. |
@333fred, @dotnet/roslyn-compiler For a second review |
1 similar comment
@333fred, @dotnet/roslyn-compiler For a second review |
switch (member.Visibility) | ||
{ | ||
case TypeMemberVisibility.Private: | ||
return context.IncludePrivateMembers; | ||
acceptBasedOnVisibility = context.IncludePrivateMembers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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.
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