Skip to content

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

Open
wants to merge 14 commits into
base: draft-v8
Choose a base branch
from

Conversation

RexJaeschke
Copy link
Contributor

@RexJaeschke RexJaeschke commented Dec 5, 2022

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 a Main method with void/int return and no/one string[] parameter list is already permitted, which not only allows Main 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.

@RexJaeschke RexJaeschke added this to the C# 8.0 milestone Dec 5, 2022
@RexJaeschke RexJaeschke self-assigned this Dec 5, 2022
@RexJaeschke RexJaeschke marked this pull request as draft December 5, 2022 13:54
@RexJaeschke
Copy link
Contributor Author

RexJaeschke commented Dec 6, 2022

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.

@BillWagner BillWagner force-pushed the default-interface-function-members branch from 84a1703 to caba5c7 Compare February 6, 2023 13:52
@RexJaeschke RexJaeschke added the type: feature This issue describes a new feature label Jul 22, 2023
@BillWagner BillWagner force-pushed the default-interface-function-members branch from caba5c7 to dc8d7a0 Compare September 25, 2023 20:54
@BillWagner
Copy link
Member

rebased on the latest draft-v8 on 09-23-2023

@KalleOlaviNiemitalo
Copy link
Contributor

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:

  • Remove them; C# 8 implementations cannot target "the CLI".
  • Change them to say "targeting a CLI with extensions" or the like.
  • Keep them; a C# implementation can still target the standard CLI, although the mapping may become complex or inefficient or have restrictions (like not supporting dynamic loading of assemblies that were not provided at build time).

@RexJaeschke RexJaeschke added the Review: pending Proposal is available for review label Oct 13, 2023
@RexJaeschke RexJaeschke removed their assignment Oct 22, 2023
@RexJaeschke RexJaeschke marked this pull request as ready for review January 11, 2024 14:00
@gafter gafter self-assigned this Jan 16, 2024
@RexJaeschke
Copy link
Contributor Author

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:

... Although Microsoft’s implementation of C# relies on CLI for library and run-time support, other implementations of C# need not, provided they support an alternate way of getting at the minimum CLI features required by this C# standard (see Annex C).

It now says:

... Although Microsoft’s implementation of C# relies on CLI for library and run-time support, other implementations of C# need not, provided they support the features and API required by this C# Standard (see Annex C).

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.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Member

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 {}

Copy link
Contributor

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?

Copy link
Member

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.
Copy link
Member

Choose a reason for hiding this comment

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

What is "an override"?

Copy link
Member

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.

@gafter
Copy link
Member

gafter commented Dec 15, 2024

I don't recall why this is assigned to me. Is it for general review, or to answer the question about base(T).M()? If the latter, see the following:

In short, the base(T).M() syntax has not been added to the language yet.

@BillWagner
Copy link
Member

@gafter

I don't recall why this is assigned to me. Is it for general review, or to answer the question about base(T).M()?

Going from memory, but I believe as a general reviewer.

@RexJaeschke
Copy link
Contributor Author

closed and reopened to get all tests to run

@RexJaeschke RexJaeschke added the meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting label Jun 10, 2025
@jskeet
Copy link
Contributor

jskeet commented Jun 11, 2025

@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.)

@BillWagner BillWagner force-pushed the default-interface-function-members branch from 22549fb to efb69e5 Compare June 11, 2025 13:57
@BillWagner
Copy link
Member

@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.)

@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.

@jskeet
Copy link
Contributor

jskeet commented Jun 25, 2025

@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...

@BillWagner
Copy link
Member

@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.

@Nigel-Ecma
Copy link
Contributor

@jskeet & @BillWagner – I have a note to myself to “compare grammar vs. semantic by Jul 3”, no idea whether I will make that…

@BillWagner BillWagner force-pushed the default-interface-function-members branch from efb69e5 to 4418683 Compare June 27, 2025 15:48
Copy link
Member

@BillWagner BillWagner left a 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:

  1. Terms used. Neal had some earlier comments, I added more on what terms to use for the new concepts in this feature.
  2. The overall strategy of defining members of class, struct and interface 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.
Copy link
Member

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.
Copy link
Member

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:

Suggested change
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
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 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
Copy link
Member

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.
Copy link
Member

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:
Copy link
Member

Choose a reason for hiding this comment

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

Two thoughts:

  1. 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.
  2. 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.

Comment on lines +380 to +384
- 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*.
Copy link
Member

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*.
Copy link
Member

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.
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
meeting: priority Review before meeting. Merge, merge with issues, or reject at the next TC49-TC2 meeting Review: pending Proposal is available for review type: feature This issue describes a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants