-
Notifications
You must be signed in to change notification settings - Fork 92
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. |
|
||
When an interface `IB` extends an interface `IA`, it is a compile-time error for `IA` to depend on `IB`. An interface **directly depends on** its direct base interfaces (if any) and **directly depends on** the type within which it is immediately nested (if any). | ||
|
||
Given these definitions, the complete set of types upon which a type depends is the transitive closure of the *directly depends on* relationship. |
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.
Given these definitions, the complete set of types upon which a type depends is the transitive closure of the *directly depends on* relationship. | |
Given these definitions, the complete set of types upon which a type depends is the reflexive and transitive closure of the *directly depends on* relationship. |
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.
The suggested change is required to disallow class A : A {}
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 been a long day, but does not adding reflexive require “A depends on A” not disallow it?
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'll make the alternative suggestion to delete line 277. I don't think it's needed.
> | ||
> *end example* | ||
|
||
Every interface and class shall have a most specific override for every virtual member 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. |
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.
What is "an override"?
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 use the term from the feature spec "most specific implementation". The feature spec defines it as
The most specific implementation is a unique implementation that is more specific than every other implementation. If there is no implementation, the member itself is considered the most specific implementation.
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... |
@jskeet I have on my list to rebase this PR, and do an edit pass with notes so we have a basis for assigning at this coming meeting. |
@jskeet & @BillWagner – I have a note to myself to “compare grammar vs. semantic by Jul 3”, no idea whether I will make that… |
Enclosing a paragraph in brackets breaks the word converter. It looks for a link.
efb69e5
to
4418683
Compare
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.
We know that there's more work coming here. For this next meeting, I'd like to see the committee agree on some key points regarding the direction for this PR:
- Terms used. Neal had some earlier comments, I added more on what terms to use for the new concepts in this feature.
- The overall strategy of defining members of
class
,struct
andinterface
declarations. Right now, we mostly define everything in terms of class, then deltas for interfaces and structs. But, sometimes those deltas are in the class clause, sometimes in interfaces or structs. Over the next several releases, the differences continue to shrink (static abstract members in interfaces, struct default constructors and field initializers, and so on.) I see two potential directions:
- Continue to define interfaces and structs in terms of a delta from class types. If we follow that, I'd recommend moving all the remaining text for deltas out of the class clause into structs or interfaces (including some added in this PR)
- Create a new "Members" clause the defines the common features of members, and then classes, interfaces, and structs can reference that with deltas.
Personally, I think the former is easier because "Members" are hard to define without one of class, struct, interface. And all of class, struct, and interface are incomplete without fully specifying all the members.
/cc @jskeet @RexJaeschke for meeting agenda notes.
|
||
When an interface `IB` extends an interface `IA`, it is a compile-time error for `IA` to depend on `IB`. An interface **directly depends on** its direct base interfaces (if any) and **directly depends on** the type within which it is immediately nested (if any). | ||
|
||
Given these definitions, the complete set of types upon which a type depends is the transitive closure of the *directly depends on* relationship. |
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'll make the alternative suggestion to delete line 277. I don't think it's needed.
@@ -4,7 +4,9 @@ | |||
|
|||
An interface defines a contract. A class or struct that implements an interface shall adhere to its contract. An interface may inherit from multiple base interfaces, and a class or struct may implement multiple interfaces. | |||
|
|||
Interfaces can contain methods, properties, events, and indexers. The interface itself does not provide implementations for the members that it declares. The interface merely specifies the members that shall be supplied by classes or structs that implement the interface. | |||
Interfaces may contain various kinds of members, as described in [§18.4](interfaces.md#184-interface-members). The interface itself may provide ***default implementation***s for some or all of the function members that it declares, in which case, those members are *not* part of the interface contract. For those members for which the interface does not provide default implementations, the interface merely specifies the members that shall be supplied by classes or structs that implement the interface. |
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 think we should remove this phrase. Any interface members that have a default implementation are part of the interface contract, but they have a default implementation:
Interfaces may contain various kinds of members, as described in [§18.4](interfaces.md#184-interface-members). The interface itself may provide ***default implementation***s for some or all of the function members that it declares, in which case, those members are *not* part of the interface contract. For those members for which the interface does not provide default implementations, the interface merely specifies the members that shall be supplied by classes or structs that implement the interface. | |
Interfaces may contain various kinds of members, as described in [§18.4](interfaces.md#184-interface-members). The interface itself may provide ***default implementation***s for some or all of the function members that it declares. For those members for which the interface does not provide default implementations, the interface merely specifies the members that shall be supplied by classes or structs that implement the interface. |
| interface_event_declaration | ||
| interface_indexer_declaration | ||
: constant_declaration | ||
| field_declaration |
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 incorrect. Interface can't include non-static fields. We'll need to rework the grammar in classes.md to provide a distinction between instance and static data fields.
| field_declaration | ||
| method_declaration | ||
| property_declaration | ||
| event_declaration |
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.
field-like events can't be declared in an interface. This will also require more splitting of the grammar rules for events to distinguish the two syntaxes for them.
; | ||
``` | ||
|
||
An interface declaration declares zero or more members. The members of an interface shall be methods, properties, events, or indexers. An interface cannot contain constants, fields, operators, instance constructors, finalizers, or types, nor can an interface contain static members of any kind. | ||
An interface declaration declares zero or more members. The members of an interface shall be constants, fields, methods, properties, events, indexers, operators, constructors, and types, some of which may be instance, others static, as described in the subclauses for each interface member kind. |
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 needs to clarify that interfaces can't include instance data fields (including field-like events). It might be the best place to mention that interfaces use the same syntax for auto-implemented properties for an abstract property.
@@ -242,24 +255,133 @@ If a `new` modifier is included in a declaration that doesn’t hide an inherite | |||
|
|||
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. | |||
|
|||
Consider an interface with a default implementation for a member `M`. As `M` is not part of that interface’s contract, outside that interface or any interface derived from it, that name is not visible. How then can it be accessed? The following code shows how: |
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.
Two thoughts:
- Lines 258 - 294 should probably be non-normative. The normative language should be that an interface member must be accessed as an explicit interface member.
- I don't like the phrasing about "not part of the contract". Instead, I'd suggest we phrase this in terms of "visibility" or even "inheritance". An interface member with a default implementation must be accessed through a reference of that interface type. It isn't visible as a member of an implementing class, or derived interface.
- A static declaration that is not extern or abstract shall have a *block* as a *method_body*. | ||
- A virtual declaration that is not extern shall have a *block* as a *method_body*. | ||
- A private declaration that is not extern shall have a *block* as a *method_body*. | ||
- A sealed declaration that is not extern shall have a *block* as a *method_body*. | ||
- An async declaration shall have a *block* as a *method_body*. |
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 needs some wordsmithing. I don't think we want "block" here. Expression bodied members are legal.
|
||
*method_modifier* shall not include `override`. | ||
|
||
An interface method declaration that has a *block* as a *method_body* is a default implementation ([§18.1](interfaces.md#181-general)), so it is *not* part of the interface’s contract. A *method_declaration* shall not have *type_parameter_constraints_clause*s unless it also has a *type_parameter_list*. |
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.
Two bits here:
Again, my concern with "part of the contract" as the terminology. And, an expression bodied member is legal, so block is the wrong term.
> | ||
> *end example* | ||
|
||
Every interface and class shall have a most specific override for every virtual member 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. |
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 use the term from the feature spec "most specific implementation". The feature spec defines it as
The most specific implementation is a unique implementation that is more specific than every other implementation. If there is no implementation, the member itself is considered the most specific implementation.
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.