Skip to content

Allow abstract and sealed modifiers on static methods/properties/events in interfaces.#52228

Merged
AlekseyTs merged 2 commits into
dotnet:features/StaticAbstractMembersInInterfacesfrom
AlekseyTs:StaticAbstractMembersInInterfaces_02
Apr 2, 2021
Merged

Allow abstract and sealed modifiers on static methods/properties/events in interfaces.#52228
AlekseyTs merged 2 commits into
dotnet:features/StaticAbstractMembersInInterfacesfrom
AlekseyTs:StaticAbstractMembersInInterfaces_02

Conversation

@AlekseyTs
Copy link
Copy Markdown
Contributor

@AlekseyTs AlekseyTs commented Mar 29, 2021

Spec: https://github.com/dotnet/csharplang/blob/9a0dddbf0643993db0da8f48436f2aac60bc20b1/proposals/statics-in-interfaces.md

Related to #52202.

Relevant quotes from the spec:

Abstract virtual members

Static interface members other than fields are allowed to also have the abstract modifier. Abstract static members are not allowed to have a body (or in the case of properties, the accessors are not allowed to have a body).

interface I<T> where T : I<T>
{
    static abstract void M();
    static abstract T P { get; set; }
    static abstract event Action E;
    static abstract T operator +(T l, T r);
}

Explicitly non-virtual static members

For symmetry with non-virtual instance members, static members should be allowed an optional sealed modifier, even though they are non-virtual by default:

interface I0
{
    static sealed void M() => Console.WriteLine("Default behavior");
    
    static sealed int f = 0;
    
    static sealed int P1 { get; set; }
    static sealed int P2 { get => f; set => f = value; }
    
    static sealed event Action E1;
    static sealed event Action E2 { add => E1 += value; remove => E1 -= value; }
    
    static sealed I0 operator +(I0 l, I0 r) => l;
}

@AlekseyTs AlekseyTs requested review from a team, 333fred and RikkiGibson March 29, 2021 21:31
@AlekseyTs
Copy link
Copy Markdown
Contributor Author

@333fred, @RikkiGibson, @dotnet/roslyn-compiler Please review.

{
var defaultAccess = DeclarationModifiers.Private;
// PROTOTYPE(StaticAbstractMembersInInterfaces): Do we need to check the lack of access modifier in an interface against language version?
// See https://github.com/dotnet/roslyn/issues/52202
Copy link
Copy Markdown
Member

@333fred 333fred Mar 30, 2021

Choose a reason for hiding this comment

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

IMO I agree with Jared on the issue, this feels like a bug fix. #Closed

var requiredVersionArgument = new CSharpRequiredLanguageVersion(requiredVersion);
var availableVersionArgument = availableVersion.ToDisplayString();

reportModifier(result, DeclarationModifiers.Abstract, location, diagnostics, requiredVersionArgument, availableVersionArgument);
Copy link
Copy Markdown
Member

@333fred 333fred Mar 30, 2021

Choose a reason for hiding this comment

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

Nit: this reads a bit awkwardly. I think it would be more clear if you just checked which of the flags to report up front, rather than appearing to report both modifiers (until the reader goes and reads the body of reportModifier and pieces it together with the previous logic that ensures only one will be reported). #WontFix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it would be more clear if you just checked which of the flags to report up front, rather than appearing to report both modifiers (until the reader goes and reads the body of reportModifier and pieces it together with the previous logic that ensures only one will be reported).

It sounds like you are suggesting to move the bit test out from the shared helper to here. I do not think this is the right thing to do. Also, I believe it is pretty clear that we can get here in a situation when only one of the bits is set (that is even ignoring the code that conditionally clears the DeclarationModifiers.Sealed bit above). So, it shoudl be obvious that this code doesn't report both modifiers all the time.


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

@333fred
Copy link
Copy Markdown
Member

333fred commented Mar 30, 2021

        Assert.False(m03.IsSealed);

This feels odd to me, and somewhat goes back to the same argument about IsStatic on local functions. We eventually made static local functions return true for IsStatic, but this feels even more different because sealed implies that it was virtual, but is no longer allowed to be overridden, whereas this method really isn't virtual to begin with. To me, this raises a question of whether we should even allow sealed in this location regardless of language version, as we don't allow it for non-virtual instance members either (in reference types). Can you take a note that it should be asked in LDM? #Resolved


Refers to: src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs:171 in 1cd7bb4. [](commit_id = 1cd7bb4, deletion_comment = False)

@333fred
Copy link
Copy Markdown
Member

333fred commented Mar 30, 2021

            // (19,33): error CS0106: The modifier 'sealed' is not valid for this item

This error feels a bit wrong. The equivalent instance method error is 'symbol' cannot be sealed because it is not an override. By the current spec, this modifier is valid for the item, just not in conjunction with abstract, and also not without a body. I think we'd benefit from a more specific error. #WontFix


Refers to: src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs:95 in 1cd7bb4. [](commit_id = 1cd7bb4, deletion_comment = False)

@333fred
Copy link
Copy Markdown
Member

333fred commented Mar 30, 2021

        m01 = m01.PartialImplementationPart;

I think it would be useful to validate that ReferenceEquals(m01.PartialImplementationPart.PartialDefinitionPart, m01). #Closed


Refers to: src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs:1079 in 1cd7bb4. [](commit_id = 1cd7bb4, deletion_comment = False)

@333fred
Copy link
Copy Markdown
Member

333fred commented Mar 30, 2021

Done review pass (commit 1). A couple of minor comments.

@AlekseyTs
Copy link
Copy Markdown
Contributor Author

AlekseyTs commented Mar 30, 2021

        Assert.False(m03.IsSealed);

This feels odd to me, and somewhat goes back to the same argument about IsStatic on local functions. We eventually made static local functions return true for IsStatic, but this feels even more different because sealed implies that it was virtual, but is no longer allowed to be overridden, whereas this method really isn't virtual to begin with. To me, this raises a question of whether we should even allow sealed in this location regardless of language version, as we don't allow it for non-virtual instance members either (in reference types). Can you take a note that it should be asked in LDM?

I am not sure I am following. The behavior is consistent with instance methods today (the method is not virtual):

interface I1
{
    sealed void M() {}
}

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


Refers to: src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs:171 in 1cd7bb4. [](commit_id = 1cd7bb4, deletion_comment = False)

@AlekseyTs
Copy link
Copy Markdown
Contributor Author

            // (19,33): error CS0106: The modifier 'sealed' is not valid for this item

I think we'd benefit from a more specific error.

I believe the error is sufficiently good, it is intuitively clear that removing the modifier is the way out. If we get feedback from real users, we can improve it in the future.


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


Refers to: src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs:95 in 1cd7bb4. [](commit_id = 1cd7bb4, deletion_comment = False)

@333fred
Copy link
Copy Markdown
Member

333fred commented Mar 30, 2021

        Assert.False(m03.IsSealed);

The behavior is consistent with instance methods today (the method is not virtual):

Well, M() is virtual by default, and (I'm expecting) will return true for this check. A static method is not virtual by default, and won't return true for this call regardless of whether it's legal or not (as in MethodModifiers_04). There is certainly a question of whether we should be consistent with classes or interface types here, and while I originally thought we should have consistency with interface instance methods here, I feel that, after looking at the consequence, I change my mind.


In reply to: 810504598 [](ancestors = 810504598,810441501)


Refers to: src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs:171 in 1cd7bb4. [](commit_id = 1cd7bb4, deletion_comment = False)

Copy link
Copy Markdown
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 2)

@AlekseyTs
Copy link
Copy Markdown
Contributor Author

@RikkiGibson, @dotnet/roslyn-compiler Please review, need a second sign-off.

1 similar comment
@AlekseyTs
Copy link
Copy Markdown
Contributor Author

@RikkiGibson, @dotnet/roslyn-compiler Please review, need a second sign-off.

@RikkiGibson RikkiGibson self-assigned this Apr 1, 2021
{
var source1 =
@"
class C1
Copy link
Copy Markdown
Member

@RikkiGibson RikkiGibson Apr 1, 2021

Choose a reason for hiding this comment

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

Was this meant to be a struct? #Resolved

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Was this meant to be a struct?

Good catch, I'll adjust this in th enext PR.


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

@AlekseyTs AlekseyTs merged commit ff59bc0 into dotnet:features/StaticAbstractMembersInInterfaces Apr 2, 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