-
Notifications
You must be signed in to change notification settings - Fork 93
Adding support for default interface function members #681
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
base: draft-v8
Are you sure you want to change the base?
Adding support for default interface function members #681
Conversation
Depending how PR #680 is resolved, some tweaks might be needed with this PR. As at 2025-05-03, that PR is still pending, and is tied to the review of this PR. |
84a1703
to
caba5c7
Compare
caba5c7
to
dc8d7a0
Compare
rebased on the latest draft-v8 on 09-23-2023 |
Should the references to "C# implementations targeting the CLI" be changed somehow because the ECMA standard CLI does not support default interface members? Possible answers include:
|
Re @KalleOlaviNiemitalo's concern about the Ecma CLI standard not supporting default interface function members, we previously made a related change to the draft-v8 Introduction, which in V7, said:
It now says:
I think that allows us to say nothing more for this feature with regard to the CLI. |
I don't recall why this is assigned to me. Is it for general review, or to answer the question about
In short, the |
Going from memory, but I believe as a general reviewer. |
closed and reopened to get all tests to run |
@RexJaeschke and @BillWagner could you look at resolving the conflict? I'm not sure whether we can realistically expect most members to have reviewed this before the meeting later today though. (I'll see whether I can find time, but I'm prioritizing #606.) |
22549fb
to
efb69e5
Compare
@jskeet I did a full rebase. My main goal in prioritizing this for today's meeting is to get it assigned to someone for next meeting. I agree that there isn't time to get it reviewed thoroughly. |
@BillWagner Looking again, did we get as far as assigning it during the last meeting? We spent quite a bit of time talking about prose vs grammar for constraints, but we didn't record the next step... |
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.
Updated in the latest commit.
Clarify the rules on overriding interface members with implementations, and on accessing members with an implementation in an interface.
Thanks to @Nigel-Ecma for the updated grammar validation. This is ready for a 2nd review. I'll watch for comments between now and our meeting, and address all those I can before the meeting. I've updated the semantics on visibility and accessibility of members defined (not just declared) in an interface, along with the definition of "most specific override". Reviews should check the semantics both for correctness and clarify. Open question for meeting discussion: See #681. Looking into our future, do we want to make the |
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.
A few comments - these are all I could actively find as new points of concern, but I have weak confidence in my own reviewing capability of this PR.
In the meeting, we weren't sure of the behavior of this: class Program
{
static void Main()
{
C c = new C();
I i = c;
c.M();
i.M();
}
}
interface I
{
void M() => Console.WriteLine("I.M()");
}
class C : I
{
public void M() => Console.WriteLine("C.M()");
} It compiles, and prints |
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.
adding comments
Edit files other than interfaces post meeting
Updates terminology from 'override' to 'implementation' for interface members, clarifies rules for hiding and explicitly implementing inherited members, and improves examples and notes for interface member access and inheritance. Adds details about the use of the 'new' modifier and compiler warnings, and refines explanations of most specific implementation in diamond inheritance scenarios.
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.
I addressed all comments from the 9/25 meeting. I'll resolve relevant conversations.
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.
(@BillWagner – you were making updates in parallel so some of my comments may have been preemptively dealt with.)
I will admit I ran out of steam going through all the semantic restrictions on the grammar in interfaces.md
.
I haven’t checked the grammar does what is expected, or written any tests to add to the grammar checking phase – I will leave both till after this is merged.
I think this is close :-)
- An explicitly implemented function member shall not have the modifier `sealed`. | ||
|
||
An *interface_declaration* creates a new declaration space ([§7.3](basic-concepts.md#73-declarations)), and the type parameters and *interface_member_declaration*s immediately contained by the *interface_declaration* introduce new members into this declaration space. The following rules apply to *interface_member_declaration*s: | ||
Some declarations, such as *constant_declaration* (§15.4) have no restrictions in interfaces. |
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 feels a little superfluous as everything that is different is listed, but I also guess its harmless…
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.
I don't have strong feelings. I added this so that readers that compare class members with interface members and might think it was an omission.
Interface indexers are declared using *indexer_declaration*s ([§15.9](classes.md#159-indexers)), with the following additional rules: | ||
- *indexer_modifier* shall not include `override`. | ||
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed. |
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.
Maybe:
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed. | |
- An *indexer_declaration* that has an expression body or contains an accessor with a block body is `virtual`; the `virtual` modifier is not required, but is allowed. |
in other clauses here expression body is not emphasised and block body is used.
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 the one comment where we crossed. They are all consistent now. You can resolve this is you're satisfied with the change.
Address review comments from Nigel on 9/25-26
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.
Comments from edits base on Nigel's latest review
- An explicitly implemented function member shall not have the modifier `sealed`. | ||
|
||
An *interface_declaration* creates a new declaration space ([§7.3](basic-concepts.md#73-declarations)), and the type parameters and *interface_member_declaration*s immediately contained by the *interface_declaration* introduce new members into this declaration space. The following rules apply to *interface_member_declaration*s: | ||
Some declarations, such as *constant_declaration* (§15.4) have no restrictions in interfaces. |
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.
I don't have strong feelings. I added this so that readers that compare class members with interface members and might think it was an omission.
Interface indexers are declared using *indexer_declaration*s ([§15.9](classes.md#159-indexers)), with the following additional rules: | ||
- *indexer_modifier* shall not include `override`. | ||
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed. |
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 the one comment where we crossed. They are all consistent now. You can resolve this is you're satisfied with the change.
The set of members of an interface declared in multiple parts ([§15.2.7](classes.md#1527-partial-type-declarations)) is the union of the members declared in each part. The bodies of all parts of the interface declaration share the same declaration space ([§7.3](basic-concepts.md#73-declarations)), and the scope of each member ([§7.7](basic-concepts.md#77-scopes)) extends to the bodies of all the parts. | ||
|
||
> *Example*: Consider an interface `IA` with an implementation for a member `M`. As `M` is not abstract, outside that interface or any interface derived from it, that name is not visible. It must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA`. | ||
> *Example*: Consider an interface `IA` with an implementation for a member `M` and a property `P`. An implementing type `C` doesn't provide an implementation for either `M` or `P`. They must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA` of `IB`. These members aren't found through member lookup on a variable of type `C`. |
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.
> *Example*: Consider an interface `IA` with an implementation for a member `M` and a property `P`. An implementing type `C` doesn't provide an implementation for either `M` or `P`. They must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA` of `IB`. These members aren't found through member lookup on a variable of type `C`. | |
> *Example*: Consider an interface `IA` with an implementation for a member `M` and a property `P`. An implementing type `C` doesn't provide an implementation for either `M` or `P`. They must be accessed through a reference whose compile-time type is an interface that is implicitly convertible to `IA` or `IB`. These members aren't found through member lookup on a variable of type `C`. |
- *method_modifier* shall not include `override`. | ||
- An interface method declaration that has a block body or expression body as a *method_body* has an implementation ([§19.1](interfaces.md#191-general)), so it is *not* abstract. | ||
- A method whose body is a semi-colon (`;`) is `abstract`; the `abstract` modifier is not required, but is allowed. |
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.
Interesting. I intuitively refer to the presence of the semicolon as "having no body." Does the spec have an opinion yet on whether methods with semicolons have "semicolon bodies" or "no bodies"?
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.
One nice thing about referring to this as "having no body" is that we can then just say "has a body" instead of saying "has a block body or expression body."
- *indexer_modifier* shall not include `override`. | ||
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is an implementation ([§19.1](interfaces.md#191-general)), so it is *not* abstract. | ||
- An *indexer_declaration* that has an *expression body* or contains an accessor with a body is `virtual`; the `virtual` modifier is not required, but is allowed. | ||
- An *indexer_declaration* whose accessors are semi-colons (`;`) is `abstract`; the `abstract` modifier is not required, but is allowed. |
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.
I would say that the accessor is get;
, not that the accessor is ;
. The accessor has a semi-colon. Per the previous comment, could we adopt the lingo here of "whose accessors have no bodies"?
### §most-specific-implementation most specific implementation | ||
Every interface and class shall have a most specific override for every virtual member declared in all interfaces implemented by that type among the overrides appearing in the type or its direct and indirect interfaces. The ***most specific override*** is a unique override that is more specific than every other override. If there is no override, the member itself is considered the most specific override. | ||
Every class and struct shall have a most specific implementation for every virtual member declared in all interfaces implemented by that type among the overrides appearing in the type or its direct and indirect interfaces. The ***most specific implementation*** is a unique override that is more specific than every other override. If there is no override, the member itself is considered the most specific 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.
I'm finding it confusing to bring in the topic of overrides at all. Do we need to?
Every class and struct shall have a most specific implementation for every virtual member declared in all interfaces implemented by that type among the overrides appearing in the type or its direct and indirect interfaces. The ***most specific implementation*** is a unique override that is more specific than every other override. If there is no override, the member itself is considered the most specific implementation. | |
Every class and struct shall have a most specific implementation for every virtual member declared in all interfaces implemented by that type among the implementations appearing in the type or its direct and indirect interfaces. The ***most specific implementation*** is a unique implementation that is more specific than every other implementation. |
- The expression `base.M()` is valid only if `B` provides a definition for `M`. The expression `base.M()` is valid only if the most specific implementation (§most-specific-implementation) of `M` is in a class type. To access the most specific implementation if it was defined in an interface, use the cast expression `(this as I).M()` to refer to the most specific implementation (§most-specific-implementation) of `M` in `I` or an interface derived from `I`. | ||
- If `B` contains a method `M` without the `virtual` or `override` modifier, that method hides `I.M()` rather than overriding it. | ||
- If `B` contains a method `M` with a different parameter list, that method `M` is a distinct overload of `M`. | ||
In a class `D`, with direct or indirect base class `B`, where `B` directly or indirectly implements interface `I` and `I` defines a method `M()`, the expression `base.M()` is valid only if the most specific implementation (§most-specific-implementation) of `M()` is in a class type. |
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's much clearer to say that the validity of base.M()
depends entirely on whether some base class defines its own member M()
. Whether or not its own member M()
is an implementation of some I.M()
does not affect the validity of base.M()
one way or another.
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 again another case of: interface member names do not appear on classes and structs. An interface member is never available for lookup on a class- or struct-typed expression. A member is available for lookup on a class- or struct-typed expression if, and only if, the class or struct manually declares such a member of its own, accessibly. It may or may not be implementing an interface member in doing so.
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.
Also, here's an example where the expression base.M()
is valid, but the most specific implementation of I.M()
is not in a class type:
interface I
{
void M() => Console.WriteLine("impl");
}
class B : I
{
public Action M => () => Console.WriteLine("Unrelated impl via a property");
}
class D : B
{
void Example() => base.M(); // This is not I.M();
}
Re the MS proposal: The main challenge when reading it was the (misleading) occurrence of the modifier
override
in numerous examples and narrative. As best as I can tell, this modifier is not actually permitted on any interface member. Instead, overriding is achieved via explicit implementation.There is one open question, which has to do with the topic "Base Interface Invocations," for which the decision "Decided on
base(N.I1<T>).M(s)
" was made. However, as far as I can tell, support for this was not added to the V8 compiler. Can someone please confirm (or refute) that.BTW, I think the proposal is misnamed: it really applies to all interface function members (properties, indexers, and events as well), not just methods. (it also allows nested types.)
Note: No spec changes are needed to support entry point
Main
in an interface. Any non-generic type containing aMain
method withvoid
/int
return and no/onestring[]
parameter list is already permitted, which not only allowsMain
in a struct or class, but now also in an interface.As of V7, the descriptions of the various member kinds has been located in classes.md, with augmenting and/or overriding text in structs.md, with pointers from structs.md back into classes.md, and that continues. However, now that many members kinds can have implementations in interfaces as well, I've added an intro para to the start of most member sections in classes.md, which contains forward pointers to that member kind's occurrence in structs.md and interfaces.md, as appropriate.
Fixes #982 (which reinstates
abstract override
.)