|
|
@@ -0,0 +1,838 @@
|
|
|
+# Consistent `class` and `interface` syntax
|
|
|
+
|
|
|
+<!--
|
|
|
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
|
+Exceptions. See /LICENSE for license information.
|
|
|
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
+-->
|
|
|
+
|
|
|
+[Pull request](https://github.com/carbon-language/carbon-lang/pull/2760)
|
|
|
+
|
|
|
+<!-- toc -->
|
|
|
+
|
|
|
+## Table of contents
|
|
|
+
|
|
|
+- [Abstract](#abstract)
|
|
|
+- [Problem](#problem)
|
|
|
+- [Background](#background)
|
|
|
+- [Proposal](#proposal)
|
|
|
+- [Details](#details)
|
|
|
+ - [Class inheritance](#class-inheritance)
|
|
|
+ - [Class implementing an interface](#class-implementing-an-interface)
|
|
|
+ - [Class conditional implementation](#class-conditional-implementation)
|
|
|
+ - [Adapters](#adapters)
|
|
|
+ - [Interfaces](#interfaces)
|
|
|
+- [Future work: mixins](#future-work-mixins)
|
|
|
+- [Rationale](#rationale)
|
|
|
+- [Alternatives considered](#alternatives-considered)
|
|
|
+ - [Use `extends` instead of `extend`](#use-extends-instead-of-extend)
|
|
|
+ - [Allow interfaces to `require` another interface without writing `Self impls`](#allow-interfaces-to-require-another-interface-without-writing-self-impls)
|
|
|
+ - [Allow other kinds of `where` clauses after `require`](#allow-other-kinds-of-where-clauses-after-require)
|
|
|
+ - [Continue to use `impl as` for interface requirements](#continue-to-use-impl-as-for-interface-requirements)
|
|
|
+ - [Continue to use `adapter` or `adaptor` instead of `adapt`](#continue-to-use-adapter-or-adaptor-instead-of-adapt)
|
|
|
+ - [Use some other syntax for extending adapters](#use-some-other-syntax-for-extending-adapters)
|
|
|
+ - [Continue to have some term for "external" or non-extended implementations](#continue-to-have-some-term-for-external-or-non-extended-implementations)
|
|
|
+ - [More direct support for conditionally implementing internal interfaces](#more-direct-support-for-conditionally-implementing-internal-interfaces)
|
|
|
+ - [Use `external` consistently instead of `extend`](#use-external-consistently-instead-of-extend)
|
|
|
+ - [Use `mix` keyword for mixins](#use-mix-keyword-for-mixins)
|
|
|
+ - [Allow more control over access to mixins](#allow-more-control-over-access-to-mixins)
|
|
|
+ - [List base class in class declaration](#list-base-class-in-class-declaration)
|
|
|
+
|
|
|
+<!-- tocstop -->
|
|
|
+
|
|
|
+## Abstract
|
|
|
+
|
|
|
+Update syntax of `class` and `interface` definitions to be more consistent.
|
|
|
+Constructs that add names to the class or interface from another definition are
|
|
|
+always prefixed by the `extend` keyword.
|
|
|
+
|
|
|
+Implements the decisions in:
|
|
|
+
|
|
|
+- [#995: Generics external impl versus extends](https://github.com/carbon-language/carbon-lang/issues/995),
|
|
|
+- [#1159: adaptor versus adapter may be harder to spell than we'd like](https://github.com/carbon-language/carbon-lang/issues/1159),
|
|
|
+- [#2580: How should Carbon handle conditionally implemented internal interfaces](https://github.com/carbon-language/carbon-lang/issues/2580),
|
|
|
+ and
|
|
|
+- [#2770: Terminology for internal and external implementations](https://github.com/carbon-language/carbon-lang/issues/2770).
|
|
|
+
|
|
|
+## Problem
|
|
|
+
|
|
|
+Classes and adapters, prior to this proposal, use `impl` to say that an
|
|
|
+interface is
|
|
|
+[implemented internally](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/terminology.md#internal-impl),
|
|
|
+which means that the names that are members of the interface are included as
|
|
|
+names of the class. The keyword `external` is added to indicate the names should
|
|
|
+not be included. Interfaces and named constraints, in contrast, use `impl` to
|
|
|
+mean another interface is required, but its names are not included. Instead, to
|
|
|
+include the names, the `extends` keyword used instead of `impl`.
|
|
|
+
|
|
|
+| Include names: | Yes | No |
|
|
|
+| ------------------------- | --------- | --------------- |
|
|
|
+| `class`, `adapter` | `impl` | `external impl` |
|
|
|
+| `interface`, `constraint` | `extends` | `impl` |
|
|
|
+
|
|
|
+In the time since this syntax has been introduced, we have found `external` in
|
|
|
+particular easy to accidentally omit.
|
|
|
+
|
|
|
+In addition to resolving this inconsistency, it would be an advantage if readers
|
|
|
+of a class could quickly scan the definition to identify other places to look
|
|
|
+for members that contribute to the class' API.
|
|
|
+
|
|
|
+## Background
|
|
|
+
|
|
|
+These proposals that defined the syntax for these entities that are being
|
|
|
+modified:
|
|
|
+
|
|
|
+- [#553: Generics details part 1](https://github.com/carbon-language/carbon-lang/pull/553)
|
|
|
+ defined the syntax for classes implementing interfaces, internally or
|
|
|
+ externally, and the syntax for named constraints (then "structural
|
|
|
+ interfaces") and interfaces requiring or extending other interfaces.
|
|
|
+- [#731: Generics details 2: adapters, associated types, parameterized interfaces](https://github.com/carbon-language/carbon-lang/pull/731)
|
|
|
+ defined the syntax for adapters and extending adapters.
|
|
|
+- [#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084)
|
|
|
+ allowed forward declaration of implementations, so internal `impl`
|
|
|
+ declarations may appear outside of a class definition, and `external impl`
|
|
|
+ declarations may appear inside.
|
|
|
+- [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777)
|
|
|
+ defined the syntax for a class to extend a base class.
|
|
|
+
|
|
|
+No proposal so far has defined how forward declarations work for classes. The
|
|
|
+rule used for forward `interface`, `constraint`, and `impl` declarations is that
|
|
|
+the declaration part of the definition is everything up to the opening `{` of
|
|
|
+the definition body. See
|
|
|
+[the forward declaration section of the Generics details design doc](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#forward-declarations-and-cyclic-references)
|
|
|
+added in proposal
|
|
|
+[#1084: Generics details 9: forward declarations](https://github.com/carbon-language/carbon-lang/pull/1084).
|
|
|
+
|
|
|
+This proposal incorporates the decisions made in these question-for-leads
|
|
|
+issues:
|
|
|
+
|
|
|
+- [#995: Generics external impl versus extends](https://github.com/carbon-language/carbon-lang/issues/995),
|
|
|
+- [#1159: adaptor versus adapter may be harder to spell than we'd like](https://github.com/carbon-language/carbon-lang/issues/1159),
|
|
|
+- [#2580: How should Carbon handle conditionally implemented internal interfaces](https://github.com/carbon-language/carbon-lang/issues/2580),
|
|
|
+ and
|
|
|
+- [#2770: Terminology for internal and external implementations](https://github.com/carbon-language/carbon-lang/issues/2770).
|
|
|
+
|
|
|
+Some of thinking around the resolution of #995 was documented in
|
|
|
+[issue #2293: reconsider syntax for internal / external implementation of interfaces](https://github.com/carbon-language/carbon-lang/issues/2293),
|
|
|
+which was closed as a duplicate of #995.
|
|
|
+
|
|
|
+In addition to modifying syntax from previous proposals,
|
|
|
+[#995](https://github.com/carbon-language/carbon-lang/issues/995) also gives a
|
|
|
+syntax for using a mixin in a class. Mixins are described as a use case in
|
|
|
+[#561: Basic classes: use cases, struct literals, struct types, and future work](https://github.com/carbon-language/carbon-lang/pull/561),
|
|
|
+but have not been added in any proposal. Question-for-leads issue
|
|
|
+[#1000: Mixins: base classes or data members?](https://github.com/carbon-language/carbon-lang/issues/1000),
|
|
|
+does state that a class will treat a mixin syntactically like a data member
|
|
|
+instead of a base class.
|
|
|
+
|
|
|
+## Proposal
|
|
|
+
|
|
|
+Any declaration that adds the names from another entity shall start with the
|
|
|
+(new) `extend` keyword. This includes:
|
|
|
+
|
|
|
+- _Inheritance_: A class now indicates that it inherits from a base class
|
|
|
+ using an
|
|
|
+
|
|
|
+ > `extend base:` _base-class_ `;`
|
|
|
+
|
|
|
+ declaration inside the class definition. The `extend` keyword indicates that
|
|
|
+ the API of the base class is included.
|
|
|
+
|
|
|
+- _Adapters_: Adapter types are now declared as a class, with an
|
|
|
+
|
|
|
+ > [ `extend` ] `adapt` _adapted-class_ `;`
|
|
|
+
|
|
|
+ declaration inside the definition, as an alternative to a base class
|
|
|
+ declaration. The optional `extend` keyword controls whether the API of the
|
|
|
+ adapted class is included.
|
|
|
+
|
|
|
+- _Implementations_: Internal implementations are marked with the `extend`
|
|
|
+ keyword on the declaration inside the class. Only the declaration inside the
|
|
|
+ class, which is required for internal implementations, uses the `extend`
|
|
|
+ keyword. External implementations are not marked.
|
|
|
+
|
|
|
+ > [ `extend` ] `impl` ...
|
|
|
+
|
|
|
+- _Interfaces_: The `extends` declaration in an interface definition is
|
|
|
+ replaced by an `extend` declaration, with no change except removing the `s`
|
|
|
+ from the end of the keyword. Other interface requirements are now written
|
|
|
+ using a `require` declaration, with a constraint that matches a `where`
|
|
|
+ clause. This means
|
|
|
+
|
|
|
+ > `impl as` _required-interface_ `;`
|
|
|
+
|
|
|
+ will now be written as
|
|
|
+
|
|
|
+ > `require Self impls` _required-interface_ `;`
|
|
|
+
|
|
|
+ and
|
|
|
+
|
|
|
+ > `impl` _type-expression_ `as` _required-interface_ `;`
|
|
|
+
|
|
|
+ will now be written as
|
|
|
+
|
|
|
+ > `require` _type-expression_ `impls` _required-interface_ `;`
|
|
|
+
|
|
|
+ For now, only the `impls` forms of `where` clauses are permitted after
|
|
|
+ `require`.
|
|
|
+
|
|
|
+In summary:
|
|
|
+
|
|
|
+| Before | After |
|
|
|
+| ------------------------------- | --------------------------------------- |
|
|
|
+| `class D extends B { ... }` | `class D { extend base: B; ... }` |
|
|
|
+| `external impl C as Sub;` | `impl C as Sub;` |
|
|
|
+| `class C { impl as Sortable; }` | `class C { extend impl as Sortable; }` |
|
|
|
+| `adapter A for C { ... }` | `class A { adapt C; ... }` |
|
|
|
+| `adapter A extends C { ... }` | `class A { extend adapt C; ... }` |
|
|
|
+| `interface I { impl as J; }` | `interface I { require Self impls J; }` |
|
|
|
+| `interface I { impl T as J; }` | `interface I { require T impls J; }` |
|
|
|
+| `interface I { extends J; }` | `interface I { extend J; }` |
|
|
|
+
|
|
|
+None of `adapter`, `extends`, `external` will continue to be keywords. To match
|
|
|
+these changes, "internal implementations" will now be referred to as "extended
|
|
|
+implementations," and we will no longer use "external" to refer to
|
|
|
+implementations.
|
|
|
+
|
|
|
+In addition, we drop the syntax for conditionally implemented internal
|
|
|
+interfaces. Instead, an external interface implementation can be combined with
|
|
|
+aliases to the members of the interface.
|
|
|
+
|
|
|
+## Details
|
|
|
+
|
|
|
+### Class inheritance
|
|
|
+
|
|
|
+What was previously written:
|
|
|
+
|
|
|
+```
|
|
|
+base class B;
|
|
|
+class D extends B;
|
|
|
+class D extends B {
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+is now written:
|
|
|
+
|
|
|
+```
|
|
|
+base class B;
|
|
|
+class D;
|
|
|
+class D {
|
|
|
+ extend base: B;
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+An extend base class declaration may appear in the body of a class definition,
|
|
|
+and has this form:
|
|
|
+
|
|
|
+> `extend` `base` `:` _type-expression_ `;`
|
|
|
+
|
|
|
+The `extend base: B;` declaration must appear before any other data member
|
|
|
+declaration, including any [mixin declaration](#future-work-mixins), once those
|
|
|
+are added. This reflects both the importance of the information, and the fact
|
|
|
+that the base subobject appears first in the memory layout of objects.
|
|
|
+
|
|
|
+Note that `base` is already a keyword, for example used in `base class`
|
|
|
+declarations. The colon in `base: B` is to indicate that `base` acts like a data
|
|
|
+member for
|
|
|
+[purposes of initialization](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#constructors).
|
|
|
+
|
|
|
+This makes the part of a class definition that is used in forward declarations
|
|
|
+be exactly the part before the curly braces (`{`...`}`). Before this proposal,
|
|
|
+class forward declarations would exclude the `extends` clause from the the first
|
|
|
+line of the class definition. This change makes classes consistent with other
|
|
|
+entities that may be forward declared.
|
|
|
+
|
|
|
+### Class implementing an interface
|
|
|
+
|
|
|
+What was previously written:
|
|
|
+
|
|
|
+```
|
|
|
+interface Sortable;
|
|
|
+interface Add;
|
|
|
+interface Sub;
|
|
|
+class C;
|
|
|
+
|
|
|
+// Forward declaration says whether external.
|
|
|
+impl C as Sortable;
|
|
|
+external impl C as Add;
|
|
|
+
|
|
|
+class C {
|
|
|
+ // Internal impl contributes to the API.
|
|
|
+ impl as Sortable;
|
|
|
+
|
|
|
+ // External impl of an operator.
|
|
|
+ external impl as Add;
|
|
|
+}
|
|
|
+
|
|
|
+// External impl of an operator.
|
|
|
+external impl C as Sub;
|
|
|
+
|
|
|
+// Definition of `impl` declared earlier.
|
|
|
+impl C as Sortable { ... }
|
|
|
+external impl C as Add { ... }
|
|
|
+```
|
|
|
+
|
|
|
+is now written:
|
|
|
+
|
|
|
+```
|
|
|
+interface Sortable;
|
|
|
+interface Add;
|
|
|
+interface Sub;
|
|
|
+class C;
|
|
|
+
|
|
|
+// Forward declaration same whether extended or not.
|
|
|
+impl C as Sortable;
|
|
|
+impl C as Add;
|
|
|
+
|
|
|
+class C {
|
|
|
+ // Extended impl contributes to the API.
|
|
|
+ extend impl as Sortable;
|
|
|
+
|
|
|
+ // (Non-extended) Impl of an operator.
|
|
|
+ impl as Add;
|
|
|
+}
|
|
|
+
|
|
|
+// (Non-extended) Impl of an operator.
|
|
|
+impl C as Sub;
|
|
|
+
|
|
|
+// Definition of `impl` declared earlier.
|
|
|
+impl C as Sortable { ... }
|
|
|
+impl C as Add { ... }
|
|
|
+```
|
|
|
+
|
|
|
+Whether an interface is extended or not is now only reflected in its declaration
|
|
|
+inside the class body, not in any declaration or definition outside.
|
|
|
+
|
|
|
+An `impl` declaration, with this proposal, must have one of these two forms:
|
|
|
+
|
|
|
+- Without an `extend` keyword prefix, used for non-extended `impl`
|
|
|
+ declarations and for all `impl` declarations outside of a class body:
|
|
|
+
|
|
|
+ <!-- prettier-ignore -->
|
|
|
+ > `impl` [`forall` `[` _deduced-parameters_ `]`] [_type-expression_] `as`
|
|
|
+ > _facet-type-expression_ (`;`|`{` _impl-body_ `}`)
|
|
|
+
|
|
|
+ The _type-expression_ is required outside of a class body, otherwise it
|
|
|
+ defaults to `Self`.
|
|
|
+
|
|
|
+- With an `extend` keyword prefix, to indicate this implementation is
|
|
|
+ extended, only in a class body:
|
|
|
+
|
|
|
+ > `extend` `impl` `as` _facet-type-expression_ (`;`|`{` _impl-body_ `}`)
|
|
|
+
|
|
|
+ Note that this form does not allow either a `forall` clause nor a
|
|
|
+ _type-expression_ before the `as` keyword. This reflects the restriction
|
|
|
+ that
|
|
|
+ [wildcard `impl` declarations must never be extended (formerly: "always be external")](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#wildcard-impl-declarations),
|
|
|
+ and that this proposal removes support for
|
|
|
+ [extended (formerly "internal") conditional implementation](#class-conditional-implementation).
|
|
|
+
|
|
|
+### Class conditional implementation
|
|
|
+
|
|
|
+We remove direct support for conditionally implemented extended (formerly
|
|
|
+"internal") interfaces, called
|
|
|
+[conditional conformance](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/generics/details.md#conditional-conformance).
|
|
|
+We can work around this restriction by using an non-extended interface
|
|
|
+implementation and aliases to the members of the interface.
|
|
|
+
|
|
|
+What was previously written:
|
|
|
+
|
|
|
+```
|
|
|
+interface Printable {
|
|
|
+ fn Print[self: Self]();
|
|
|
+}
|
|
|
+
|
|
|
+class Vector(T:! type) {
|
|
|
+ // ...
|
|
|
+
|
|
|
+ impl forall [U:! Printable] Vector(U) as Printable {
|
|
|
+ fn Print[self: Self]();
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+is now written:
|
|
|
+
|
|
|
+```
|
|
|
+interface Printable {
|
|
|
+ fn Print[self: Self]();
|
|
|
+}
|
|
|
+
|
|
|
+class Vector(T:! type) {
|
|
|
+ // ...
|
|
|
+ alias Print = Printable.Print;
|
|
|
+}
|
|
|
+
|
|
|
+impl forall [U:! Printable] Vector(U) as Printable {
|
|
|
+ fn Print[self: Self]();
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+The way this works is that `Vector.Print` is equivalent to
|
|
|
+`Vector.(Printable.Print)`, which may or may not be defined. The name
|
|
|
+`Vector.Print` can no longer be conditional, and the meaning of that name is
|
|
|
+fixed. However, the implementation of `Printable` for `Vector(T)` may not exist
|
|
|
+for some types `T`.
|
|
|
+
|
|
|
+### Adapters
|
|
|
+
|
|
|
+What was previously written:
|
|
|
+
|
|
|
+```
|
|
|
+class C;
|
|
|
+// Forward declarations of adapters.
|
|
|
+adapter A for C;
|
|
|
+adapter E extends C;
|
|
|
+
|
|
|
+// Definitions of an adapter.
|
|
|
+adapter A for C {
|
|
|
+ ...
|
|
|
+}
|
|
|
+// Definition of an extending adapter.
|
|
|
+adapter E extends C {
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+is now written:
|
|
|
+
|
|
|
+```
|
|
|
+class C;
|
|
|
+// Forward declarations of adapters.
|
|
|
+class A;
|
|
|
+class E;
|
|
|
+
|
|
|
+// Definitions of an adapter.
|
|
|
+class A {
|
|
|
+ adapt C;
|
|
|
+ ...
|
|
|
+}
|
|
|
+// Definition of an extending adapter.
|
|
|
+class E {
|
|
|
+ extend adapt C;
|
|
|
+ ...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Note:
|
|
|
+
|
|
|
+- Adapters are now a special case of classes, not a distinct top-level
|
|
|
+ declaration.
|
|
|
+- Classes with `adapt` still must not contain anything that was previously
|
|
|
+ forbidden for adapters: no fields, no base class, no virtual methods, no
|
|
|
+ implementations of virtual methods, and so on.
|
|
|
+- The `adapt` declaration must appear before
|
|
|
+ [mixin declarations](#future-work-mixins), if any.
|
|
|
+- The syntax for an `adapt` declaration inside a class body is:
|
|
|
+
|
|
|
+ > [`extend`] `adapt` _type-expression_ `;`
|
|
|
+
|
|
|
+### Interfaces
|
|
|
+
|
|
|
+What was previously written:
|
|
|
+
|
|
|
+```
|
|
|
+interface A { let T:! Type; }
|
|
|
+interface B { let U:! Type; }
|
|
|
+interface C(V:! Type) { }
|
|
|
+
|
|
|
+interface I {
|
|
|
+ // `A`'s interface is incorporated into `I`:
|
|
|
+ extends A where .T = i32;
|
|
|
+
|
|
|
+ // No impact on `I`s interface, but an
|
|
|
+ // implementation must exist:
|
|
|
+ impl as B where .U = i32;
|
|
|
+
|
|
|
+ // Implementation must exist on another type:
|
|
|
+ impl i32 as C(Self);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+is now written:
|
|
|
+
|
|
|
+```
|
|
|
+interface A { let T:! Type; }
|
|
|
+interface B { let U:! Type; }
|
|
|
+interface C(V:! Type) { }
|
|
|
+
|
|
|
+interface I {
|
|
|
+ // `A`'s interface is incorporated into `I`:
|
|
|
+ extend A where .T = i32;
|
|
|
+
|
|
|
+ // No impact on `I`s interface, but an
|
|
|
+ // implementation must exist:
|
|
|
+ require Self impls B where .U = i32;
|
|
|
+
|
|
|
+ // Implementation must exist on another type:
|
|
|
+ require i32 impls C(Self);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Notes:
|
|
|
+
|
|
|
+- The same change applies to named constraints, and is intended to be used in
|
|
|
+ the future for named predicates used as template constraints.
|
|
|
+- One syntax for constraints in either `where` clauses or `require`
|
|
|
+ declarations.
|
|
|
+- Want to open up the syntax to expressing more general constraints.
|
|
|
+- Syntax for a `require` declaration in an interface or named constraint:
|
|
|
+
|
|
|
+ > `require` _type-expression_ `impls` _facet-type-expression_ `;`
|
|
|
+
|
|
|
+ As
|
|
|
+ [with `impl`...`as` declarations before](/docs/design/generics/details.md#interface-requiring-other-interfaces-revisited),
|
|
|
+ a `require` declaration must use `Self`, either to the left or right of
|
|
|
+ `impls`. Note that `require` only supports this subset of `where` clause
|
|
|
+ expressions. Adding other kinds of constraints is future work.
|
|
|
+
|
|
|
+- Syntax for an `extend` declaration in an interface or named constraint:
|
|
|
+
|
|
|
+ > `extend` _facet-type-expression_ `;`
|
|
|
+
|
|
|
+## Future work: mixins
|
|
|
+
|
|
|
+Mixins have not been defined in a proposal so far. However, part of the process
|
|
|
+of resolving
|
|
|
+[issue #995](https://github.com/carbon-language/carbon-lang/issues/995) was
|
|
|
+deciding on a syntax for including a mixin in a class. This was done in order to
|
|
|
+make sure that class declarations that included names from another entity were
|
|
|
+treated consistently, for example always starting with the `extend` keyword.
|
|
|
+
|
|
|
+```
|
|
|
+// Mixin declarations and definitions are
|
|
|
+// outside the scope of this proposal.
|
|
|
+mixin M1;
|
|
|
+mixin M2;
|
|
|
+
|
|
|
+class C {
|
|
|
+ // Mixing in mixin M1
|
|
|
+ extend m: M1;
|
|
|
+
|
|
|
+ // Mixing in mixin M2. This member is not named.
|
|
|
+ // Initialized using `M2.MakeDefault()`.
|
|
|
+ extend _: M2 = M2.MakeDefault();
|
|
|
+
|
|
|
+ // Alternative to the above `M2` that uses a
|
|
|
+ // private name instead of no name:
|
|
|
+ extend private m2: M2;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+The declaration that a class uses a mixin is called a "mix" declaration. The
|
|
|
+syntax of a mix declaration is:
|
|
|
+
|
|
|
+<!-- prettier-ignore -->
|
|
|
+> `extend` [`private`|`protected`] (`_`|_id_) `:` _mixin-expression_ [`=`
|
|
|
+> _initializer-expression_] `;`
|
|
|
+
|
|
|
+The _id_ part of the mix declaration defines the name assigned to that mixin
|
|
|
+subobject. This name is may be used to access members of the mixin and to
|
|
|
+initialize the mixin in a constructor for the class. The optional `private` or
|
|
|
+`protected` access specifier controls the access to this name.
|
|
|
+
|
|
|
+With this proposal, base class declarations appear in the body of the class
|
|
|
+definition, like data members, so decision of whether mixins are more like base
|
|
|
+classes or data members of issue
|
|
|
+[#1000: Mixins: base classes or data members?](https://github.com/carbon-language/carbon-lang/issues/1000)
|
|
|
+is less significant. Like base classes, the mix declaration syntax begins with
|
|
|
+`extend`. Like data members, a class may have multiple mix declarations and they
|
|
|
+may be intermixed with field declarations. The layout of the memory of an object
|
|
|
+reflects the order of the declarations in the class body, defining the order of
|
|
|
+the mixin and field subobjects.
|
|
|
+
|
|
|
+## Rationale
|
|
|
+
|
|
|
+The main reason for the new syntax is consistency and simplification:
|
|
|
+
|
|
|
+- The use of the `extend` keyword is the consistent way to mark what other
|
|
|
+ entities are consulted during name lookup.
|
|
|
+- The `class` declaration is simplified by moving more into the definition
|
|
|
+ body.
|
|
|
+- Making adapters a kind of class removes a kind of top-level declaration, a
|
|
|
+ simplification, and matches how base classes are declared, a consistency.
|
|
|
+- Dropping the class conditional implementation is a simplification.
|
|
|
+
|
|
|
+These consistency and simplification improvements help:
|
|
|
+
|
|
|
+- ease the implementation of
|
|
|
+ [language tools and ecosystem](/docs/project/goals.md#language-tools-and-ecosystem)
|
|
|
+ by reducing the size of the language, and providing regularities
|
|
|
+ implementations can use to reuse code or more easily identify relevant parts
|
|
|
+ of the code for queries;
|
|
|
+- make Carbon code
|
|
|
+ [easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write).
|
|
|
+- teaching the language, since there is less to learn.
|
|
|
+
|
|
|
+## Alternatives considered
|
|
|
+
|
|
|
+### Use `extends` instead of `extend`
|
|
|
+
|
|
|
+Keyword `extend` was chosen over `extends` to parallel `impl`, a declaration,
|
|
|
+instead of `impls`, a binary predicate, decided in
|
|
|
+[issue #2495](https://github.com/carbon-language/carbon-lang/issues/2495) and
|
|
|
+accepted in
|
|
|
+[proposal #2483](https://github.com/carbon-language/carbon-lang/pull/2483).
|
|
|
+
|
|
|
+We chose to use `require` instead of `requires` and `adapt` instead of `adapts`
|
|
|
+for the same consistency.
|
|
|
+
|
|
|
+### Allow interfaces to `require` another interface without writing `Self impls`
|
|
|
+
|
|
|
+We
|
|
|
+[considered](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1418387971)
|
|
|
+allowing `interface I { require J; }` as a short-hand for
|
|
|
+`interface I { require Self impls J; }`. This is something we would consider
|
|
|
+adding in the future based on experience with the current approach, but for now
|
|
|
+we wanted to maintain consistency with the constraint syntax of `where` clauses.
|
|
|
+
|
|
|
+This decision and rationale was described in
|
|
|
+[this comment in #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1416659207).
|
|
|
+
|
|
|
+### Allow other kinds of `where` clauses after `require`
|
|
|
+
|
|
|
+The decision on
|
|
|
+[#995](https://github.com/carbon-language/carbon-lang/issues/995), see
|
|
|
+[1](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1439336141)
|
|
|
+and
|
|
|
+[2](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1496781087),
|
|
|
+called for the same constraint syntax after `require` as we are using after
|
|
|
+`where`. This proposal _only_ allows `impls` clauses after `require`, since
|
|
|
+there were concerns about the other kinds of clauses:
|
|
|
+
|
|
|
+- We had questions about what syntax to use to refer to members of the
|
|
|
+ interface, such as associated types, to constrain them. Must they start with
|
|
|
+ `Self.` or `.`? See
|
|
|
+ [this comment thread on #2760](https://github.com/carbon-language/carbon-lang/pull/2760#discussion_r1213789364).
|
|
|
+- Modifying the constraints on an associated type after the declaration for
|
|
|
+ that associated type was not something we clearly wanted to allow. Allowing
|
|
|
+ that would mean having to read the whole interface definition body to
|
|
|
+ understand a single member.
|
|
|
+- It was unclear if rewrite constraints (using `=` instead of `==`), would be
|
|
|
+ allowed in a `require` declaration, and if they were, whether they would be
|
|
|
+ changed into equality constraints (as if they were declared using `==`). See
|
|
|
+ [this comment on #2760](https://github.com/carbon-language/carbon-lang/pull/2760#discussion_r1213791304).
|
|
|
+- It would have provided more different ways of declaring equivalent
|
|
|
+ interfaces. This concern was raised
|
|
|
+ [in this comment on #2760](https://github.com/carbon-language/carbon-lang/pull/2760#discussion_r1214571215).
|
|
|
+
|
|
|
+For now, we only needed to replace the existing uses of `impl as` constraints,
|
|
|
+which had none of these concerns. We did not want to block this proposal, so we
|
|
|
+made sure the `require` clauses were consistent with that _subset_ of `where`
|
|
|
+clauses.
|
|
|
+
|
|
|
+### Continue to use `impl as` for interface requirements
|
|
|
+
|
|
|
+There were a few reasons motivating the change to use the new `require`
|
|
|
+declarations in interfaces and named constraints, instead of using `impl as` to
|
|
|
+match how a type could satisfy that requirement. These mostly came down to some
|
|
|
+observed breaks in the parallel structure between the requirement in interfaces
|
|
|
+and the satisfaction of that requirement in types.
|
|
|
+
|
|
|
+- Whether an interface `I` extends or just requires another interface `J` is
|
|
|
+ independent of whether a type implementing `I` extends or just implements
|
|
|
+ `J`.
|
|
|
+- If `R` requires interface `I`, the implementation of `R` for a type won't
|
|
|
+ have the implementation of `I` as a nested sub-block.
|
|
|
+- With the change in
|
|
|
+ [#2173: Associated constant assignment versus equality](https://github.com/carbon-language/carbon-lang/pull/2173),
|
|
|
+ the behavior of `impl as` in interfaces is different from in classes with
|
|
|
+ respect to rewrites of associated types, motivating a change to make those
|
|
|
+ look more different.
|
|
|
+
|
|
|
+Furthermore, we had a desire to be able to express the full range of constraints
|
|
|
+in `where` clauses in named constraints, and we wanted the transformation from a
|
|
|
+`where` clause to a named constraint to be straightforward. We also wanted the
|
|
|
+syntax for constraints to be the same between interfaces and named constraints,
|
|
|
+at least for all constraints that were allowed in both.
|
|
|
+
|
|
|
+This was discussed in
|
|
|
+[#generics-and-templates on 2023-01-30](https://discord.com/channels/655572317891461132/941071822756143115/1069691751968817283)
|
|
|
+and in
|
|
|
+[issue #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1412478644).
|
|
|
+
|
|
|
+### Continue to use `adapter` or `adaptor` instead of `adapt`
|
|
|
+
|
|
|
+We didn't want to allow both `adapter` and `adaptor`, since that adds complexity
|
|
|
+for readers and tooling, but neither seemed clearly dominant enough in usage to
|
|
|
+pick one over the other. By moving the declaration into the body of the class
|
|
|
+definition, we were able to switch from the noun form to the verb form of
|
|
|
+`adapt`, which doesn't have an alternate form in common usage. See
|
|
|
+[#1159: adaptor versus adapter may be harder to spell than we'd like](https://github.com/carbon-language/carbon-lang/issues/1159)
|
|
|
+for the discussion.
|
|
|
+
|
|
|
+We also considered using `adapts` in a class declaration, as in
|
|
|
+`class PlayableSong adapts Song { ... }`, see
|
|
|
+[this comment in #1159](https://github.com/carbon-language/carbon-lang/issues/1159#issuecomment-1316669416).
|
|
|
+This would have also worked, but was not consistent with our resolution of
|
|
|
+[#995: Generics external impl versus extends](https://github.com/carbon-language/carbon-lang/issues/995).
|
|
|
+
|
|
|
+### Use some other syntax for extending adapters
|
|
|
+
|
|
|
+In the
|
|
|
+[open discussion on 2023-02-27](https://docs.google.com/document/d/1gnJBTfY81fZYvI_QXjwKk1uQHYBNHGqRLI2BS_cYYNQ/edit?resourcekey=0-ql1Q1WvTcDvhycf8LbA9DQ#heading=h.9steyq834zuq),
|
|
|
+we discussed some alternatives to `extend adapt` _adapted-class_ `;`:
|
|
|
+
|
|
|
+- Adding `and` to make it read more like fluent English:
|
|
|
+
|
|
|
+ > `extend and adapt` _adapted-class_ `;`
|
|
|
+
|
|
|
+ However, this felt arbitrary and not compositional.
|
|
|
+
|
|
|
+- Make the extending and adapting be separate declarations:
|
|
|
+
|
|
|
+ > `extend` _adapted-class_ `;` <br> `adapt` _adapted-class_ `;`
|
|
|
+
|
|
|
+ This felt too repetitive.
|
|
|
+
|
|
|
+- Make the only way to make an extending adapter be trying to inherit from a
|
|
|
+ final base class
|
|
|
+
|
|
|
+ > `extend base:` _adapted-class_ `;`
|
|
|
+
|
|
|
+ However, this meant using the same syntax for two different things that
|
|
|
+ could only be distinguished by looking at the declaration of the adapted
|
|
|
+ class, which could be far away. It also would have meant making an extending
|
|
|
+ adapter of a non-final class much more cumbersome.
|
|
|
+
|
|
|
+Ultimately, we decided that `extend adapt` would be the most compositional way
|
|
|
+of combining `extend` and `adapt`, so that is what users would expect. Reading
|
|
|
+like natural English was not considered essential.
|
|
|
+
|
|
|
+### Continue to have some term for "external" or non-extended implementations
|
|
|
+
|
|
|
+The fact that there were some different rules for external implementations was
|
|
|
+brought up in
|
|
|
+[this post in #2770](https://github.com/carbon-language/carbon-lang/issues/2770#issuecomment-1509288478).
|
|
|
+However,
|
|
|
+[this reply](https://github.com/carbon-language/carbon-lang/issues/2770#issuecomment-1515522070)
|
|
|
+pointed out those rules could be clearly stated in terms of where you are
|
|
|
+allowed to write `extend`. That same post made the convincing argument that to
|
|
|
+get the maximum benefit of the decision on
|
|
|
+[#995](https://github.com/carbon-language/carbon-lang/issues/995), we should
|
|
|
+treat `extend` and `impl` as separate orthogonal concepts as much as possible.
|
|
|
+
|
|
|
+### More direct support for conditionally implementing internal interfaces
|
|
|
+
|
|
|
+We considered two different ways of more directly supporting conditionally
|
|
|
+extending a class by an interface:
|
|
|
+
|
|
|
+- Prior to this proposal, both name lookup and the implementation were
|
|
|
+ conditional on the same condition. This has the disadvantage of entangling
|
|
|
+ the concerns of name lookup and implementation, and was considered a complex
|
|
|
+ and difficult model.
|
|
|
+- We also considered fully separating these features, and allowing a type to
|
|
|
+ separately extend its API with an interface and then only conditionally
|
|
|
+ implement that interface. This would allow name lookup to succeed
|
|
|
+ unconditionally, but in cases where the names were in fact not implemented
|
|
|
+ it would produce an error.
|
|
|
+
|
|
|
+Both of these approaches would have required some support for expressing this in
|
|
|
+the new syntax. None of the syntactic approaches we considered were found to be
|
|
|
+satisfactory. By making name lookup never conditional, it made it much easier to
|
|
|
+have a consistent marker for declarations that extended name lookup.
|
|
|
+
|
|
|
+Ultimately, the alternative of not having a dedicated syntax to support this
|
|
|
+case seemed the simplest in the short term, given the workaround of conditional
|
|
|
+external (non-extending) implementation paired with aliases that provide the
|
|
|
+name lookup, unconditionally. We can always add dedicated syntax later, given
|
|
|
+sufficient motivating information.
|
|
|
+
|
|
|
+The options were considered in
|
|
|
+[#2580: How should Carbon handle conditionally implemented internal interfaces](https://github.com/carbon-language/carbon-lang/issues/2580).
|
|
|
+
|
|
|
+### Use `external` consistently instead of `extend`
|
|
|
+
|
|
|
+We considered making "extending name lookup" the default and overriding that
|
|
|
+default with the `external` keyword. The argument for this option rested on it
|
|
|
+being more convenient to express conditional internal implementation, and wasn't
|
|
|
+seen as attractive once that feature was removed. In the discussion, we
|
|
|
+generally preferred marking additional places to consult in name lookup since
|
|
|
+that was something we expected readers of the code to want to specifically look
|
|
|
+for.
|
|
|
+
|
|
|
+This option was proposed as the third option in the
|
|
|
+[original #995 question](https://github.com/carbon-language/carbon-lang/issues/995#issue-1085174338),
|
|
|
+and was considered in
|
|
|
+[this comment](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1006096692).
|
|
|
+
|
|
|
+### Use `mix` keyword for mixins
|
|
|
+
|
|
|
+We considered a variety of different syntax options for using a mixin for a type
|
|
|
+in
|
|
|
+[this comment on #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1315948818).
|
|
|
+Many of them used the `mix` keyword, but we ultimately decided that `mix` was
|
|
|
+redundant with saying `extend`, which we wanted to be included with all
|
|
|
+constructs extending the type's API by another entity.
|
|
|
+
|
|
|
+### Allow more control over access to mixins
|
|
|
+
|
|
|
+There were three different aspects of mixins that we considered giving control
|
|
|
+over access:
|
|
|
+
|
|
|
+- the inclusion of names exported by the mixin into the mixer class,
|
|
|
+- the ability to cast between the mixin and the mixer class types, and
|
|
|
+- the ability to use the name of the mixin given by the mixer class to access
|
|
|
+ members of the mixin.
|
|
|
+
|
|
|
+This was particularly relevant when we were considering separate declarations
|
|
|
+for declaring the mixin member and the API extension
|
|
|
+([1](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1315948818),
|
|
|
+[2](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1317598076)).
|
|
|
+
|
|
|
+This approach had the problem that the common case for mixins was extending the
|
|
|
+API, which would not have been the default. The only examples we had where the
|
|
|
+mixer class would not want to include the names exported by the mixin were cases
|
|
|
+where the mixin had nothing to export. This led to
|
|
|
+[the position](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1325746398)
|
|
|
+that the mixin would control which of its member would be included into the
|
|
|
+mixer class' API -- that is the mixin would "inject" members rather than
|
|
|
+"export" them and leave it up to the mixer class to import them.
|
|
|
+
|
|
|
+We had concerns that there might be name conflicts, but we thought those might
|
|
|
+be handled by some other mechanism. This is being considered in
|
|
|
+question-for-leads issue
|
|
|
+[#2745: Name conflicts beyond inheritance](https://github.com/carbon-language/carbon-lang/issues/2745).
|
|
|
+
|
|
|
+We wanted mixin member names to behave consistently like other class member
|
|
|
+names, and so default to public but can have a `private` modifier to make
|
|
|
+private, following
|
|
|
+[#665: `private` vs `public` _syntax_ strategy, as well as other visibility tools like `external`/`api`/etc.](https://github.com/carbon-language/carbon-lang/issues/665).
|
|
|
+We decided to put the `private` keyword _between_ the `extend` keyword and the
|
|
|
+member name for two reasons:
|
|
|
+
|
|
|
+- to make it easier to scan for all uses of `extend` in a class, and
|
|
|
+- to make it clearer that the `private` access control only applies to the
|
|
|
+ member name, not what the `extend` controls.
|
|
|
+
|
|
|
+We did not see a use case for controlling the ability to cast between the mixin
|
|
|
+and the mixer class types separately from being able to access the name the
|
|
|
+mixin member of the mixin class. This was consistent with our desire to limit
|
|
|
+declarations to a single access control specifier per declaration, see
|
|
|
+[this update in #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1322892195).
|
|
|
+
|
|
|
+### List base class in class declaration
|
|
|
+
|
|
|
+By moving the base class into the class body, we accomplished three things:
|
|
|
+
|
|
|
+- made forward declarations shorter,
|
|
|
+- made it possible to inherit from a type declared inside the body of the
|
|
|
+ class definition, and
|
|
|
+- allowed greater consistency, all API extensions are now declarations in the
|
|
|
+ body of the class definition starting with `extend`.
|
|
|
+
|
|
|
+This was decided in
|
|
|
+[this comment on #995](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1317598076).
|
|
|
+
|
|
|
+This left open the question of what keyword introducer to use in base class
|
|
|
+declarations, since using `base` would cause an ambiguity with declaring a
|
|
|
+member class that could be extended, as considered in
|
|
|
+[this comment](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1320614313).
|
|
|
+Ultimately we avoided this problem by requiring base class declarations to
|
|
|
+always begin with `extend`, not support any form of private or protected
|
|
|
+inheritance (see
|
|
|
+[this comment](https://github.com/carbon-language/carbon-lang/issues/995#issuecomment-1325746398)),
|
|
|
+and not support any combination of an `extend` declaration with a member class
|
|
|
+declaration.
|