From 419b80fb302e58f145aee1a423fd2c91ebf1c327 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Mon, 6 Mar 2023 15:34:45 +0100 Subject: [PATCH 1/8] Update class modifiers specification. * Allow `interface mixin class` etc. * Tighten the modifiers implied on anonymous mixin class applications. * A little rephrasing, to allow reusing a concept. --- .../class-modifiers/feature-specification.md | 148 ++++++++++++------ 1 file changed, 100 insertions(+), 48 deletions(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index 2c91f5e7da..52a492a4e4 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -500,38 +500,31 @@ class UsesAsMixin extends OtherSuperclass with Both {} // OK. ## Syntax This proposal builds on the existing sealed types proposal so the grammar -includes those changes. The full set of modifiers that can appear before a class -or mixin declaration are `abstract`, `sealed`, `base`, `interface`, `final`, and -`mixin`. +includes those changes. The full set of modifiers that can appear before a class declaration are `abstract`, `sealed`, `base`, `interface`, `final`, and +`mixin`. Only the `base` modifier can appear before a `mixin` declaration. *The modifiers do not apply to other declarations like `enum`, `typedef`, or `extension`.* -Many combinations don't make sense: +Some combinations don't make sense: * `base`, `interface`, and `final` all control the same two capabilities so are mutually exclusive. * `sealed` types cannot be constructed so it's redundant to combine with `abstract`. -* `sealed` types cannot be extended or implemented, so it's redundant to - combine with `final`, `base`, or `interface`. -* `sealed` types cannot be mixed in outside of their library, so it - contradicts `mixin` on a class. *It's useful to allow `sealed` on a mixin - declaration because the mixin can be applied within the same library. - A `sealed mixin class` does not provide any significant extra - functionality over a `sealed mixin`, you can replace `extends MixinClass` - with `with Mixin`, so a `sealed mixin class` is not allowed.* -* `interface` and `final` classes would prevent a mixin class from being used - as a superclass or mixin outside of its library. *Like for `sealed`, an - `interface mixin class` and `final mixin class` are not allowed, and - `interface mixin` and `final mixin` declaration are recommended instead.* +* `sealed` types cannot be mixed in, extended or implemented, + so it's redundant to combine with `final`, `base`, or `interface`. * `mixin` as a modifier can obviously only be applied to a `class` - declaration, which makes it also a `mixin` declaration. + declaration, which makes it also a introduce a mixin declaration. * `mixin` as a modifier cannot be applied to a mixin-application `class` declaration (the `class C = S with M;` syntax for declaring a class). The remaining modifiers can. +* A `mixin` is intended to be mixed in, so it cannot be combined with an + `interface`, `final` or `sealed` modifier. * Mixin declarations cannot be constructed, so `abstract` is redundant. +_An `interface mixin class` seems redundant, because it disallows mixing in the declaration, so the `mixin` makes no difference for the AP. However, a `mixin` is required in order to mix in the declaration inside the same library, where the `interface` modifier’s restriction can be ignored. From outside the library, there is no distinction between `interface mixin class` and `interface class`, or even between `abstract interface mixin class`, `abstract interface class` and `interface mixin` declarations, as the following table also shows._ + The remaining valid combinations and their capabilities are: | Declaration | Construct? | Extend? | Implement? | Mix in? | Exhaustive? | @@ -547,8 +540,13 @@ The remaining valid combinations and their capabilities are: |`abstract final class` |No |No |No |No |No | |`mixin class` |**Yes**|**Yes**|**Yes**|**Yes**|No | |`base mixin class` |**Yes**|**Yes**|No |**Yes**|No | +|`interface mixin class` |**Yes**|No |**Yes**|No |No | +|`final mixin class` |**Yes**|No |No |No |No | +|`sealed mixin class` |No |No |No |No |**Yes**| |`abstract mixin class` |No |**Yes**|**Yes**|**Yes**|No | |`abstract base mixin class`|No |**Yes**|No |**Yes**|No | +|`abstract interface mixin class`|No |No |**Yes**|No |No | +|`abstract final mixin class`|No |No |No |No |No | |`mixin` |No |No |**Yes**|**Yes**|No | |`base mixin` |No |No |No |**Yes**|No | |`interface mixin` |No |No |**Yes**|No |No | @@ -656,20 +654,29 @@ It is a compile-time error to: class C3 with SM {} // Error. ``` -A typedef cannot be used to subvert these restrictions or any of the -restrictions below. When extending, implementing, or mixing in a typedef, we -look at the library where class or mixin the typedef resolves to is defined to -determine if the behavior is allowed. *Note that the library where the _typedef_ -is defined does not come into play. Typedefs cannot be marked with any of the +A type alias (`typedef`) cannot be used to subvert these restrictions or any of the +restrictions below. When extending, implementing, or mixing in a type alias, we +look at the library where class or mixin the type alias resolves to is defined to +determine if the behavior is allowed. *Note that the library where the _type alias_ +is defined does not come into play. Type aliases cannot be marked with any of the new modifiers.* ### Disallowing implementation -It is a compile-time error if a subtype of a declaration marked `base` or +We say that an interface _prevents implementation_ if its declaration is marked +`base` or `final`, or if it has any (immediate) superinterface which prevents +implementation. +_(This definition is inherently transitive, so it has no effect to add or remove_ +_the “immediate”.)_ + +It’s a compile-time error if a declaration’s interface prevents implementation, +and the declaration is not marked `base`, `final` or `sealed`. + +_Effectively, it is a compile-time error if any subtype of a declaration marked `base` or `final` is not marked `base`, `final`, or `sealed`. This restriction applies to both direct and indirect subtypes and along all paths that introduce subtypes: `implements` clauses, `extends` clauses, `with` clauses, and `on` clauses. This -restriction applies even to types within the same library. +restriction applies even to types within the same library._ *Once the ability to use as an interface is removed, it cannot be reintroduced in a subtype. If a class is marked `base` or `final`, you may still implement @@ -681,15 +688,15 @@ Further, while you can ignore some restrictions on declarations within the same library, you cannot use that to ignore restrictions inherited from other libraries. -We say that `S` is a _direct declared superinterface_ of a class, mixin, or +We say that a declaration `S` is a _direct declared superdeclaration_ of a class, mixin, or mixin class declaration `D` if `D` has a superclass clause of the form `C with M1 .. Mk` (where `k` may be zero when there is no `with` clause) -and `S` is `C`, or `S` is `Mj` for some `j` in 1 .. k, -or if `D` has an `implements` or `on` clause and `S` occurs as one of the operands of -that clause. +and `S` is is the declaration denoted by `C`, or by `Mj` for some `j` in 1 .. k, +or if `D` has an `implements` or `on` clause and `S` is the declaration denoted by +one of the operands of such a clause. We then say that a class or mixin declaration `D` *cannot be implemented locally* if it -has a direct declared superinterface `S` such that: +has a direct declared superdeclaration `S` such that: * `S` is from another library than `D`, and `S` has the modifier `base`, `final` or `sealed`, or @@ -732,7 +739,7 @@ Otherwise, `D` can be implemented locally. It is a compile-time error if: * A class, mixin, or mixin class declaration `D` has an `implements` clause - where `S` is an operand, and `S` is a class, mixin, or mixin class + where `S` is an operand, and `S` denotes a class, mixin, or mixin class declaration declared in the same library as `D`, and `S` cannot be implemented locally. @@ -836,6 +843,14 @@ the latter, even if the class is being used as a mixin in a post-feature library where it does happen to be possible to distinguish those two intents.* +### Enum classes + +The class introduced by an `enum` declaration is considered `final`. + +Since the class cannot have any subclasses, so the modifier does not prevent +any otherwise allowed operation, and adding it ensures that the enum class +satisfies any requirements introduced by its super-interfaces. + ### Anonymous mixin applications An *anonymous mixin application* class is a class resulting from a mixin application @@ -848,26 +863,50 @@ An anonymous mixin application class cannot be referenced anywhere except in the context where the application occurs, so its only role is to be a superclass of another class in the same library. -To ensure reasonable and correct behavior, we infer class modifiers on anonymous -mixin application classes as follows. +To ensure reasonable and correct behavior, without having to special-case such +anonymous mixin application classes elsewhere, we infer class modifiers as follows. + +We define the following transitive property on declarations: + +* A declaration *prohibits inheritance* _(of its implementation)_ if: + + * the declaration is marked `interface` or `final`, or + * the declaration extends or mixes in a declaration which prohibits inheritance. + +Let then *C* be an anonymous mixin application occurring in a post-feature library, with +superclass *S* and mixin *M*. -Let *C* be an anonymous mixin application with superclass *S* and mixin *M*. Then: +* If either *S* or *M* has a `sealed` modifier, then *C* has an implicit `sealed` modifier. -* If any of *S* or *M* has a `sealed` modifier, *C* is has a `sealed` modifier. -* Otherwise: - * *C* is `abstract`, and - * If either of *S* or *M* has a `base` or `final` modifier, then *C* has a `final` modifier. +* Otherwise *C* is `abstract`, and -_We do not distinguish whether *S* or *M* has `base` or `final` modifiers. -The modifier on *C* is there to satisfy the requirement that a subtype of a `base` or `final` -declaration is itself `base`, `final` or `sealed`. The anonymous mixin application class will -be immediately extended inside the same library, which is allowed by both `base` and `final`., -and will not be used for anything else._ + * If *C* *prevents implementation* and *prohibits inheritance*, + then *C* has an implicit `final` modifier. -Adding `sealed` to an anonymous mixin application class with only one subclass ensures -that the subclass extending the mixin application class can be used -in exhaustiveness checking of the sealed superclass. -This is necessary since the anonymous mixin application class itself cannot be referenced. + * Otherwise if *C* *prevents implementation*, then *C* has an implicit `base` modifier. + + * Otherwise if *C* *prohibits inheritance*, then *C* has an implicit `interface` modifier. + + * Otherwise *C* has no modifier. + +Adding `sealed` to an anonymous mixin application class, which always has precisely +one subclass, ensures that the subclass can be used in exhaustiveness checking +of the sealed superclass. + +Adding `final` or `base` satisfies the requirement that a subtype of a `base` or `final` +declaration is itself `base`, `final` or `sealed`. + +Adding `interface` ensures that the mixin application class doesn’t remove a restriction +on the implementation inherited from *S* or *M*. This makes the “reopen” lint described below +easier to implement. + +Adding these modifiers on the anonymous mixin application class ensures that +the anonymous class can mostly be ignored, since it satisfies all possible requirements +of its superclasses, while still propagating those requirements to its subclass. +Treating the anonymous class as having these modifiers allows the algorithms +used to check that restrictions are satisfied, and for the “reopen” lint described below, +to treat the anonymouse mixin application class as any other class, without needing +special cases. ### `@reopen` lint @@ -917,8 +956,14 @@ non-breaking. when they upgrade their library's language version. _It will still not be possible to, e.g., extend or implement the `int` class, even if will now have a `final` modifier._ - - This is a special case behavior only available to platform libraries. + Going through a pre-feature library does not remove transitive restrictions + for code in post-feature libraries. Any post-feature library declaration which has a + platform library class marked `base` or `final` as a superinterface must be marked + `base`, `final` or `sealed`, and cannot be implemented locally, + even if the superinterface chain goes through a pre-feature library declaration, + and even if that declaration ignores the `base` modifier. + + This is special case behavior only available to platform libraries. Package libraries should use versioning to to introduce breaking restrictions instead, and those libraries can then rely on the restrictions being enforced. @@ -1064,6 +1109,13 @@ errors and fixups would help keep them on the rails. - Add implementation suggestions about errors, error recovery, and fixups for class modifiers. +1.7 + +* Update the modifiers applied to anonymous mixin applications to closer + match the superclass/mixin modifiers. +* Allow any modifier with `mixin class`. +* Some rephrasing to allow concept reuse. + 1.5 - Fix mixin application grammar to match prose where `mixin` can't be applied From b806c58db814c03445220494c38a0319419b2ccb Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Wed, 8 Mar 2023 20:22:04 +0100 Subject: [PATCH 2/8] Don't allow interface mixin classes. But do allow mixing in non-mixin platform classes from pre-feature libraries. --- .../class-modifiers/feature-specification.md | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index 52a492a4e4..ac985e32c2 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -506,14 +506,31 @@ includes those changes. The full set of modifiers that can appear before a class *The modifiers do not apply to other declarations like `enum`, `typedef`, or `extension`.* -Some combinations don't make sense: +Many combinations don't make sense: * `base`, `interface`, and `final` all control the same two capabilities so are mutually exclusive. * `sealed` types cannot be constructed so it's redundant to combine with `abstract`. +<<<<<<< HEAD * `sealed` types cannot be mixed in, extended or implemented, so it's redundant to combine with `final`, `base`, or `interface`. +======= +* `sealed` types cannot be extended or implemented, so it's redundant to + combine with `final`. +* `sealed` types cannot be extended so it contradicts `base`. +* `sealed` types cannot be implemented, so it contradicts `interface`. +* `sealed` types cannot be mixed in outside of their library, so it + contradicts `mixin` on a class. *It's useful to allow `sealed` on a mixin + declaration because the mixin can be applied within the same library. + A `sealed mixin class` does not provide any significant extra + functionality over a `sealed mixin`, you can replace `extends MixinClass` + with `with Mixin`, so a `sealed mixin class` is not allowed.* +* `interface` and `final` classes would prevent a mixin class from being used + as a superclass or mixin outside of its library. *Like for `sealed`, an + `interface mixin class` and `final mixin class` are not allowed, and + `interface mixin` and `final mixin` declaration are recommended instead.* +>>>>>>> 5b79277 (Don't allow interface mixin classes.) * `mixin` as a modifier can obviously only be applied to a `class` declaration, which makes it also a introduce a mixin declaration. * `mixin` as a modifier cannot be applied to a mixin-application `class` @@ -523,8 +540,6 @@ Some combinations don't make sense: `interface`, `final` or `sealed` modifier. * Mixin declarations cannot be constructed, so `abstract` is redundant. -_An `interface mixin class` seems redundant, because it disallows mixing in the declaration, so the `mixin` makes no difference for the AP. However, a `mixin` is required in order to mix in the declaration inside the same library, where the `interface` modifier’s restriction can be ignored. From outside the library, there is no distinction between `interface mixin class` and `interface class`, or even between `abstract interface mixin class`, `abstract interface class` and `interface mixin` declarations, as the following table also shows._ - The remaining valid combinations and their capabilities are: | Declaration | Construct? | Extend? | Implement? | Mix in? | Exhaustive? | @@ -540,13 +555,8 @@ The remaining valid combinations and their capabilities are: |`abstract final class` |No |No |No |No |No | |`mixin class` |**Yes**|**Yes**|**Yes**|**Yes**|No | |`base mixin class` |**Yes**|**Yes**|No |**Yes**|No | -|`interface mixin class` |**Yes**|No |**Yes**|No |No | -|`final mixin class` |**Yes**|No |No |No |No | -|`sealed mixin class` |No |No |No |No |**Yes**| |`abstract mixin class` |No |**Yes**|**Yes**|**Yes**|No | |`abstract base mixin class`|No |**Yes**|No |**Yes**|No | -|`abstract interface mixin class`|No |No |**Yes**|No |No | -|`abstract final mixin class`|No |No |No |No |No | |`mixin` |No |No |**Yes**|**Yes**|No | |`base mixin` |No |No |No |**Yes**|No | |`interface mixin` |No |No |**Yes**|No |No | @@ -688,7 +698,7 @@ Further, while you can ignore some restrictions on declarations within the same library, you cannot use that to ignore restrictions inherited from other libraries. -We say that a declaration `S` is a _direct declared superdeclaration_ of a class, mixin, or +We say that a declaration `S` is a _direct superdeclaration_ of a class, mixin, or mixin class declaration `D` if `D` has a superclass clause of the form `C with M1 .. Mk` (where `k` may be zero when there is no `with` clause) and `S` is is the declaration denoted by `C`, or by `Mj` for some `j` in 1 .. k, @@ -696,7 +706,7 @@ or if `D` has an `implements` or `on` clause and `S` is the declaration denoted one of the operands of such a clause. We then say that a class or mixin declaration `D` *cannot be implemented locally* if it -has a direct declared superdeclaration `S` such that: +has a direct superdeclaration `S` such that: * `S` is from another library than `D`, and `S` has the modifier `base`, `final` or `sealed`, or @@ -739,7 +749,7 @@ Otherwise, `D` can be implemented locally. It is a compile-time error if: * A class, mixin, or mixin class declaration `D` has an `implements` clause - where `S` is an operand, and `S` denotes a class, mixin, or mixin class + where `S` is an operand, and `S` denotes a class, mixin, or mixin class declaration declared in the same library as `D`, and `S` cannot be implemented locally. @@ -847,7 +857,7 @@ intents.* The class introduced by an `enum` declaration is considered `final`. -Since the class cannot have any subclasses, so the modifier does not prevent +Since the class cannot have any subclasses, the modifier does not prevent any otherwise allowed operation, and adding it ensures that the enum class satisfies any requirements introduced by its super-interfaces. @@ -946,12 +956,13 @@ non-breaking. to all other libraries, regardless of the versions of those libraries. "Ignorance of the law is no defense."* -* We would like to add modifiers to some classes in platform (i.e. `dart:`) +* We would like to add modifiers to some classes in platform (i.e., `dart:`) libraries when this feature ships. But we would also like to not immediately break existing code. To avoid forcing users to immediately migrate, declarations in pre-feature libraries can ignore *some* `base`, `interface` and `final` modifiers on *some* declarations - in platform libraries. + in platform libraries, and to mix in non-`mixin` classes from platform libraries, + as long as those classes has `Object` as superclass and declares no constructors. Instead, users will only have to abide by those restrictions when they upgrade their library's language version. _It will still not be possible to, e.g., extend or implement the `int` class, @@ -1113,8 +1124,10 @@ errors and fixups would help keep them on the rails. * Update the modifiers applied to anonymous mixin applications to closer match the superclass/mixin modifiers. -* Allow any modifier with `mixin class`. +* State that `enum` declarations count as `final`. * Some rephrasing to allow concept reuse. +* Say that pre-feature libraries can mix in non-`mixin` platform library classes + which satisfy the old requirements for being used as a mixin. 1.5 From db2779935bd4bfe623ae4212ea603ca10801e0ec Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Thu, 9 Mar 2023 10:01:14 +0100 Subject: [PATCH 3/8] Address comments Fix grammatical cases. --- .../class-modifiers/feature-specification.md | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index ac985e32c2..e83cc2c786 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -517,9 +517,7 @@ Many combinations don't make sense: so it's redundant to combine with `final`, `base`, or `interface`. ======= * `sealed` types cannot be extended or implemented, so it's redundant to - combine with `final`. -* `sealed` types cannot be extended so it contradicts `base`. -* `sealed` types cannot be implemented, so it contradicts `interface`. + combine with `final`, `base`, or `interface`. * `sealed` types cannot be mixed in outside of their library, so it contradicts `mixin` on a class. *It's useful to allow `sealed` on a mixin declaration because the mixin can be applied within the same library. @@ -855,11 +853,14 @@ intents.* ### Enum classes -The class introduced by an `enum` declaration is considered `final`. +The class introduced by an `enum` declaration is considered `final` for any purpose +where a class modifier is required. -Since the class cannot have any subclasses, the modifier does not prevent -any otherwise allowed operation, and adding it ensures that the enum class -satisfies any requirements introduced by its super-interfaces. +The behavior of `enum` declarations is unchanged. Since an `enum` class cannot have +any subclasses, the modifier would not prevent any otherwise allowed operation. +The implicit `final` also does not imply fewer restrictions than `enum` declarations +would otherwise have. The modifier is applied only to automatically satisfy any +requirements introduced by super-interfaces. ### Anonymous mixin applications @@ -915,8 +916,8 @@ the anonymous class can mostly be ignored, since it satisfies all possible requi of its superclasses, while still propagating those requirements to its subclass. Treating the anonymous class as having these modifiers allows the algorithms used to check that restrictions are satisfied, and for the “reopen” lint described below, -to treat the anonymouse mixin application class as any other class, without needing -special cases. +to treat the anonymous mixin application class as any other class, without needing +special cases, and without adding any new restrictions.. ### `@reopen` lint @@ -962,7 +963,7 @@ non-breaking. declarations in pre-feature libraries can ignore *some* `base`, `interface` and `final` modifiers on *some* declarations in platform libraries, and to mix in non-`mixin` classes from platform libraries, - as long as those classes has `Object` as superclass and declares no constructors. + as long as such a class has `Object` as superclass and declares no constructors. Instead, users will only have to abide by those restrictions when they upgrade their library's language version. _It will still not be possible to, e.g., extend or implement the `int` class, From 7ebb15120743a8861d567dfa46c15be15e36eefc Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Thu, 9 Mar 2023 15:58:29 +0100 Subject: [PATCH 4/8] Typo --- .../class-modifiers/feature-specification.md | 228 ++++++------------ 1 file changed, 80 insertions(+), 148 deletions(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index e83cc2c786..1f74c36657 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -4,7 +4,7 @@ Author: Bob Nystrom, Lasse Nielsen Status: Accepted -Version 1.6 +Version 1.7 Experiment flag: class-modifiers @@ -512,29 +512,15 @@ Many combinations don't make sense: are mutually exclusive. * `sealed` types cannot be constructed so it's redundant to combine with `abstract`. -<<<<<<< HEAD * `sealed` types cannot be mixed in, extended or implemented, so it's redundant to combine with `final`, `base`, or `interface`. -======= -* `sealed` types cannot be extended or implemented, so it's redundant to - combine with `final`, `base`, or `interface`. -* `sealed` types cannot be mixed in outside of their library, so it - contradicts `mixin` on a class. *It's useful to allow `sealed` on a mixin - declaration because the mixin can be applied within the same library. - A `sealed mixin class` does not provide any significant extra - functionality over a `sealed mixin`, you can replace `extends MixinClass` - with `with Mixin`, so a `sealed mixin class` is not allowed.* -* `interface` and `final` classes would prevent a mixin class from being used - as a superclass or mixin outside of its library. *Like for `sealed`, an - `interface mixin class` and `final mixin class` are not allowed, and - `interface mixin` and `final mixin` declaration are recommended instead.* ->>>>>>> 5b79277 (Don't allow interface mixin classes.) * `mixin` as a modifier can obviously only be applied to a `class` declaration, which makes it also a introduce a mixin declaration. * `mixin` as a modifier cannot be applied to a mixin-application `class` declaration (the `class C = S with M;` syntax for declaring a class). The remaining modifiers can. -* A `mixin` is intended to be mixed in, so it cannot be combined with an +* A `mixin`, from a `mixin` declaration or a `mixin class` declaration, + is intended to be mixed in, so its declaration cannot have an `interface`, `final` or `sealed` modifier. * Mixin declarations cannot be constructed, so `abstract` is redundant. @@ -557,9 +543,6 @@ The remaining valid combinations and their capabilities are: |`abstract base mixin class`|No |**Yes**|No |**Yes**|No | |`mixin` |No |No |**Yes**|**Yes**|No | |`base mixin` |No |No |No |**Yes**|No | -|`interface mixin` |No |No |**Yes**|No |No | -|`final mixin` |No |No |No |No |No | -|`sealed mixin` |No |No |No |No |**Yes**| The grammar is: @@ -572,7 +555,9 @@ classDeclaration ::= (classModifiers | mixinClassModifiers) 'class' typeIdentif classModifiers ::= 'sealed' | 'abstract'? ('base' | 'interface' | 'final')? -mixinClassModifiers ::= 'abstract'? 'base'? 'mixin' +mixinClassModifiers ::= 'abstract'? mixinModifiers 'mixin' + +mixinModifiers ::= 'base'? mixinDeclaration ::= mixinModifier? 'mixin' typeIdentifier typeParameters? ('on' typeNotVoidList)? interfaces? @@ -612,8 +597,9 @@ It is a compile-time error to: class C3 extends S {} // Error. ``` -* Implement the interface of a class, mixin, or mixin class marked `base`, - `final` or `sealed` outside of the library where it is declared. +* Implement the interface of a class, mixin, or mixin class declaration + marked `base`, `final` or `sealed` outside of the library + where it is declared. ```dart // a.dart @@ -637,30 +623,11 @@ It is a compile-time error to: class C3 implements SM {} // Error. ``` -* Mix in a mixin or mixin class marked `interface`, `final` or `sealed` - outside of the library where it is declared. - - ```dart - // a.dart - interface mixin class I {} - final mixin class F {} - sealed mixin class S {} +* Use the interface of a class declaration marked `sealed` + as a mixin `on` type outside of library where it is declared. - interface mixin IM {} - final mixin FM {} - sealed mixin SM {} - - // b.dart - import 'a.dart'; - - class C1 with I {} // Error. - class C2 with F {} // Error. - class C3 with S {} // Error. - - class C1 with IM {} // Error. - class C2 with FM {} // Error. - class C3 with SM {} // Error. - ``` +_An `enum` declaration still cannot be implemented, extended or mixed in +anywhere, independently of modifiers._ A type alias (`typedef`) cannot be used to subvert these restrictions or any of the restrictions below. When extending, implementing, or mixing in a type alias, we @@ -671,102 +638,66 @@ new modifiers.* ### Disallowing implementation -We say that an interface _prevents implementation_ if its declaration is marked -`base` or `final`, or if it has any (immediate) superinterface which prevents -implementation. -_(This definition is inherently transitive, so it has no effect to add or remove_ -_the “immediate”.)_ +We say that an interface _prevents implementation_ if it has *any* +superinterface whose declaration is marked `base` or `final`. -It’s a compile-time error if a declaration’s interface prevents implementation, -and the declaration is not marked `base`, `final` or `sealed`. +It is a compile-time error if a the interface of a +`class`, `mixin`, or `mixin class` declaration `D` prevents implementation, +and `D` does not have a `base`, `final` or `sealed` modifier. +_(Which must be a `base` modifier for `mixin` and `mixin class` declarations.)_ -_Effectively, it is a compile-time error if any subtype of a declaration marked `base` or -`final` is not marked `base`, `final`, or `sealed`. This restriction applies to -both direct and indirect subtypes and along all paths that introduce subtypes: +_Effectively, it is a compile-time error if any subtype of a declaration marked +`base` or `final` is not also marked `base`, `final`, or `sealed`. +This restriction applies to both direct and indirect subtypes +and along all paths that introduce subtypes: `implements` clauses, `extends` clauses, `with` clauses, and `on` clauses. This restriction applies even to types within the same library._ -*Once the ability to use as an interface is removed, it cannot be reintroduced +_Once the ability to use as an interface is removed, it cannot be reintroduced in a subtype. If a class is marked `base` or `final`, you may still implement the class's interface inside the same library, but the implementing class must again be marked `base`, `final`, or `sealed` to avoid it exposing an -implementable interface.* +implementable interface._ Further, while you can ignore some restrictions on declarations within the same library, you cannot use that to ignore restrictions inherited from other libraries. -We say that a declaration `S` is a _direct superdeclaration_ of a class, mixin, or -mixin class declaration `D` if `D` has a superclass clause of the form -`C with M1 .. Mk` (where `k` may be zero when there is no `with` clause) -and `S` is is the declaration denoted by `C`, or by `Mj` for some `j` in 1 .. k, -or if `D` has an `implements` or `on` clause and `S` is the declaration denoted by -one of the operands of such a clause. +We say that an interface *cannot be implemented locally* in a library *L* +if it has any superinterface *S* whose declaration is marked `base` or `final`, +and *S* is in a library other than *L*. -We then say that a class or mixin declaration `D` *cannot be implemented locally* if it -has a direct superdeclaration `S` such that: - -* `S` is from another library than `D`, and `S` has the modifier `base`, - `final` or `sealed`, or - - ```dart - // a.dart - base mixin class S {} - - // b.dart - import 'a.dart'; - - // These cannot be implemented locally: - sealed class DE extends S {} - final class DM with S {} - base mixin MO on S {} - ``` - -* `S` is from the same library as `D`, and `S` cannot be implemented locally. - - ```dart - // a.dart - base class B {} - - // b.dart - import 'a.dart'; +It's a compile-time error if a `class`, `mixin`, `mixin class` or `enum` +declaration in library *L* has an `implements` clause with an entry +denoting the interface *S*, and *S* prevents implementation locally in *L*. - // These cannot be implemented locally (from the previous rule): - base class S extends B {} - base mixin M on B {} - // And thus these also cannot be implemented locally: - base class DE extends S {} - base class DM extends B with M {} // (from this and the previous rule). - base mixin MO on S {} - base mixin M2 on M {} - ``` - -Otherwise, `D` can be implemented locally. - -It is a compile-time error if: - -* A class, mixin, or mixin class declaration `D` has an `implements` clause - where `S` is an operand, and `S` denotes a class, mixin, or mixin class - declaration declared in the same library as `D`, and `S` cannot be - implemented locally. - - ```dart - // a.dart - base class B {} - - // b.dart - import 'a.dart'; - - base class S extends B {} // Cannot be implemented locally but OK. - - base class D implements S {} // Error, cannot use "implements". - ``` - -* A class, mixin, or mixin class declaration `D` cannot be implemented - locally, and `D` does not have a `base`, `final` or `sealed` modifier. - _A declaration which cannot be implemented locally also cannot - be allowed to be implemented in another library._ +Examples: +```dart +// a.dart +// Interface of S prevents implementation +// and prevents implementation locally in any other library. +base mixin class S {} + +// You can implement `S` locally in the same library. +base _MyLittleS implements S {} + +// b.dart +import 'a.dart'; + +// These are valid, but cannot be implemented locally: +sealed class DE extends S {} +final class DM with S {} +base mixin MO on S {} + +// None of these interface can be implemented locally, +// because they all have the `base` interface `S` from another library +// as superinterface. +base class Invalid1 implements S {} // Error +base class Invalid1 implements DE {} // Error +base class Invalid1 implements DM {} // Error +base class Invalid1 implements MO {} // Error +``` ### Mixin restrictions @@ -776,25 +707,26 @@ versioning and backwards compatibility. Currently, a class may only be used as a mixin if it has a default constructor. This prevents the class from defining a `const` constructor or any factory -constructors. We loosen this somewhat. +constructors. We loosen this restriction for `mixin class`es. Define a *trivial generative constructor* to be a generative constructor that: -* Is not a redirecting constructor, +* Is not a redirecting constructor _(`Foo(...) : this.other(...);`), * declares no parameters, * has no initializer list (no `: ...` part, so no asserts or initializers, and - no super constructor invocation), + no explicit super constructor invocation), * has no body (only `;`), and * is not `external`. *An `external` constructor is considered to have an externally provided initializer list and/or body.* -A trivial constructor may be named or unnamed, and `const` or non-`const`. -A *non-trivial generative constructor* is a generative constructor which is not a -trivial generative constructor. +A trivial generative constructor may be named or unnamed, +and maybe be `const` or non-`const`. +A *non-trivial generative constructor* is a generative constructor which +is not a trivial generative constructor. Examples: @@ -820,9 +752,9 @@ class C { It's a compile-time error if: -* A `mixin class` declaration has a superclass other than `Object`. *The - declaration is limited to an `extends Object` clause or no `extends` clause, - and no `with` clauses. The class grammar prohibits `on` clauses.* +* A `mixin class` declaration has a superclass clause. + *The declaration is limited to having `Object` as superclass, and not + having any mixin applications. The class grammar prohibits `on` clauses.* * A `mixin class` declaration declares any non-trivial generative constructor. *It may declare no constructors, in which case it gets a default @@ -856,11 +788,12 @@ intents.* The class introduced by an `enum` declaration is considered `final` for any purpose where a class modifier is required. -The behavior of `enum` declarations is unchanged. Since an `enum` class cannot have -any subclasses, the modifier would not prevent any otherwise allowed operation. -The implicit `final` also does not imply fewer restrictions than `enum` declarations -would otherwise have. The modifier is applied only to automatically satisfy any -requirements introduced by super-interfaces. +The behavior of `enum` declarations is unchanged. Since an `enum` class cannot +have any subclasses, the implicit `final` modifier would not prevent any +otherwise allowed operation. +The implicit `final` also does not imply fewer restrictions than `enum` +declarations would otherwise have. The modifier is applied only to automatically +satisfy any requirements introduced by super-interfaces. ### Anonymous mixin applications @@ -908,8 +841,7 @@ Adding `final` or `base` satisfies the requirement that a subtype of a `base` or declaration is itself `base`, `final` or `sealed`. Adding `interface` ensures that the mixin application class doesn’t remove a restriction -on the implementation inherited from *S* or *M*. This makes the “reopen” lint described below -easier to implement. +on the implementation inherited from *S* or *M*. This makes the “reopen” lint described below easier to implement. Adding these modifiers on the anonymous mixin application class ensures that the anonymous class can mostly be ignored, since it satisfies all possible requirements @@ -962,7 +894,7 @@ non-breaking. break existing code. To avoid forcing users to immediately migrate, declarations in pre-feature libraries can ignore *some* `base`, `interface` and `final` modifiers on *some* declarations - in platform libraries, and to mix in non-`mixin` classes from platform libraries, + in platform libraries, and can mix in non-`mixin` classes from platform libraries, as long as such a class has `Object` as superclass and declares no constructors. Instead, users will only have to abide by those restrictions when they upgrade their library's language version. @@ -1116,11 +1048,6 @@ errors and fixups would help keep them on the rails. ## Changelog -1.6 - -- Add implementation suggestions about errors, error recovery, and fixups for - class modifiers. - 1.7 * Update the modifiers applied to anonymous mixin applications to closer @@ -1130,6 +1057,11 @@ errors and fixups would help keep them on the rails. * Say that pre-feature libraries can mix in non-`mixin` platform library classes which satisfy the old requirements for being used as a mixin. +1.6 + +- Add implementation suggestions about errors, error recovery, and fixups for + class modifiers. + 1.5 - Fix mixin application grammar to match prose where `mixin` can't be applied From c1905958b91bd218b59b744cfb1d121445aea4dc Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Wed, 15 Mar 2023 19:09:38 +0100 Subject: [PATCH 5/8] Updates to latest agreed behavior. --- .../class-modifiers/feature-specification.md | 123 ++++++++++-------- 1 file changed, 68 insertions(+), 55 deletions(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index 1f74c36657..b5719d15c2 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -20,7 +20,9 @@ Informally, the new syntax is: * `base`: As a modifier on a class, allows the class to be extended but not implemented. As a modifier on a mixin, allows it to be mixed in but not implemented. In other words, it takes away being able to implement - the interface of the declaration. + the interface of the declaration. _This also applies transitively + to all subtypes, since implementing a subtype also means implementing + the superinterface._ * `interface`: As a modifier on a class or mixin, allows the type to be implemented but not extended or mixed in. In other words, it takes away @@ -447,7 +449,7 @@ safe. Now consider: -``` +```dart // lib_b.dart import 'lib_a.dart'; @@ -500,7 +502,8 @@ class UsesAsMixin extends OtherSuperclass with Both {} // OK. ## Syntax This proposal builds on the existing sealed types proposal so the grammar -includes those changes. The full set of modifiers that can appear before a class declaration are `abstract`, `sealed`, `base`, `interface`, `final`, and +includes those changes. The full set of modifiers that can appear before a class +declaration are `abstract`, `sealed`, `base`, `interface`, `final`, and `mixin`. Only the `base` modifier can appear before a `mixin` declaration. *The modifiers do not apply to other declarations like `enum`, `typedef`, or @@ -515,7 +518,7 @@ Many combinations don't make sense: * `sealed` types cannot be mixed in, extended or implemented, so it's redundant to combine with `final`, `base`, or `interface`. * `mixin` as a modifier can obviously only be applied to a `class` - declaration, which makes it also a introduce a mixin declaration. + declaration, which makes it also introduce a mixin. * `mixin` as a modifier cannot be applied to a mixin-application `class` declaration (the `class C = S with M;` syntax for declaring a class). The remaining modifiers can. @@ -555,15 +558,13 @@ classDeclaration ::= (classModifiers | mixinClassModifiers) 'class' typeIdentif classModifiers ::= 'sealed' | 'abstract'? ('base' | 'interface' | 'final')? -mixinClassModifiers ::= 'abstract'? mixinModifiers 'mixin' - -mixinModifiers ::= 'base'? +mixinClassModifiers ::= 'abstract'? mixinModifier? 'mixin' mixinDeclaration ::= mixinModifier? 'mixin' typeIdentifier typeParameters? ('on' typeNotVoidList)? interfaces? '{' (metadata classMemberDeclaration)* '}' -mixinModifier ::= 'sealed' | 'base' | 'interface' | 'final' +mixinModifier ::= 'base' ``` ## Static semantics @@ -599,7 +600,7 @@ It is a compile-time error to: * Implement the interface of a class, mixin, or mixin class declaration marked `base`, `final` or `sealed` outside of the library - where it is declared. + where it is declared. _(This includes enum declarations.)_ ```dart // a.dart @@ -629,12 +630,12 @@ It is a compile-time error to: _An `enum` declaration still cannot be implemented, extended or mixed in anywhere, independently of modifiers._ -A type alias (`typedef`) cannot be used to subvert these restrictions or any of the -restrictions below. When extending, implementing, or mixing in a type alias, we -look at the library where class or mixin the type alias resolves to is defined to -determine if the behavior is allowed. *Note that the library where the _type alias_ -is defined does not come into play. Type aliases cannot be marked with any of the -new modifiers.* +A type alias (`typedef`) cannot be used to subvert these restrictions +or any of the restrictions below. When extending, implementing, or mixing in +a type alias, we look at the library where class or mixin the type alias +resolves to is defined to determine if the behavior is allowed. *Note that +the library where the _type alias_ is defined does not come into play. +Type aliases cannot be marked with any of the new modifiers.* ### Disallowing implementation @@ -644,7 +645,8 @@ superinterface whose declaration is marked `base` or `final`. It is a compile-time error if a the interface of a `class`, `mixin`, or `mixin class` declaration `D` prevents implementation, and `D` does not have a `base`, `final` or `sealed` modifier. -_(Which must be a `base` modifier for `mixin` and `mixin class` declarations.)_ +_(Which must be a `base` modifier for `mixin` and `mixin class` +declarations.)_ _Effectively, it is a compile-time error if any subtype of a declaration marked `base` or `final` is not also marked `base`, `final`, or `sealed`. @@ -785,8 +787,8 @@ intents.* ### Enum classes -The class introduced by an `enum` declaration is considered `final` for any purpose -where a class modifier is required. +The class introduced by an `enum` declaration is considered `final` for any +purpose where a class modifier is required. The behavior of `enum` declarations is unchanged. Since an `enum` class cannot have any subclasses, the implicit `final` modifier would not prevent any @@ -797,59 +799,68 @@ satisfy any requirements introduced by super-interfaces. ### Anonymous mixin applications -An *anonymous mixin application* class is a class resulting from a mixin application -that does not have its own declaration. +An *anonymous mixin application* class is a class resulting from +a mixin application that does not have its own declaration. That is all mixin applications classes other than the final class -of a class C = S with M1, …, Mn; declaration, the mixin application of Mn to -the superclass S with M1, …, Mn-1, which is denoted by declaration and name `C`. +of a class C = S with M1, …, Mn; declaration, +the mixin application of Mn to +the superclass S with M1, …, Mn-1, +which is denoted by declaration and name `C`. An anonymous mixin application class cannot be referenced anywhere except in -the context where the application occurs, so its only role is to be a superclass of -another class in the same library. +the context where the application occurs, so its only role is +to be a superclass of another class in the same library. To ensure reasonable and correct behavior, without having to special-case such -anonymous mixin application classes elsewhere, we infer class modifiers as follows. +anonymous mixin application classes elsewhere, we infer class modifiers as +follows. -We define the following transitive property on declarations: +First we define the following transitive property on declarations: * A declaration *prohibits inheritance* _(of its implementation)_ if: * the declaration is marked `interface` or `final`, or - * the declaration extends or mixes in a declaration which prohibits inheritance. + * the declaration is marked `sealed` and extends or mixes in + a declaration which prohibits inheritance. -Let then *C* be an anonymous mixin application occurring in a post-feature library, with -superclass *S* and mixin *M*. +Let then *C* be an anonymous mixin application occurring in +a post-feature library, with superclass *S* and mixin *M*. -* If either *S* or *M* has a `sealed` modifier, then *C* has an implicit `sealed` modifier. +* If either *S* or *M* has a `sealed` modifier, then *C* has an implicit + `sealed` modifier. * Otherwise *C* is `abstract`, and - * If *C* *prevents implementation* and *prohibits inheritance*, + * If *C* *prohibits inheritance* and its interface *prevents implementation*, then *C* has an implicit `final` modifier. - * Otherwise if *C* *prevents implementation*, then *C* has an implicit `base` modifier. + * Otherwise if *C* *prevents implementation*, then *C* has an implicit + `base` modifier. - * Otherwise if *C* *prohibits inheritance*, then *C* has an implicit `interface` modifier. + * Otherwise if *C* *prohibits inheritance*, then *C* has an implicit + `interface` modifier. * Otherwise *C* has no modifier. -Adding `sealed` to an anonymous mixin application class, which always has precisely -one subclass, ensures that the subclass can be used in exhaustiveness checking -of the sealed superclass. +Adding `sealed` to an anonymous mixin application class, which always has +precisely one subclass, ensures that the subclass can be used in +exhaustiveness checking of the sealed superclass. -Adding `final` or `base` satisfies the requirement that a subtype of a `base` or `final` -declaration is itself `base`, `final` or `sealed`. +Adding `final` or `base` satisfies the requirement that a subtype of a +`base` or `final` declaration is itself `base`, `final` or `sealed`. -Adding `interface` ensures that the mixin application class doesn’t remove a restriction -on the implementation inherited from *S* or *M*. This makes the “reopen” lint described below easier to implement. +Adding `interface` ensures that the mixin application class doesn’t +remove a restriction on the implementation inherited from *S* or *M*. +This makes the “reopen” lint described below easier to implement. Adding these modifiers on the anonymous mixin application class ensures that -the anonymous class can mostly be ignored, since it satisfies all possible requirements -of its superclasses, while still propagating those requirements to its subclass. +the anonymous class can mostly be ignored, since it satisfies all possible +requirements of its superclasses, while still propagating those requirements +to its subclass. Treating the anonymous class as having these modifiers allows the algorithms -used to check that restrictions are satisfied, and for the “reopen” lint described below, -to treat the anonymous mixin application class as any other class, without needing -special cases, and without adding any new restrictions.. +used to check that restrictions are satisfied, and for the “reopen” lint +described below, to treat the anonymous mixin application class as any other +class, without needing special cases, and without adding any new restrictions. ### `@reopen` lint @@ -893,19 +904,21 @@ non-breaking. libraries when this feature ships. But we would also like to not immediately break existing code. To avoid forcing users to immediately migrate, declarations in pre-feature libraries can ignore *some* - `base`, `interface` and `final` modifiers on *some* declarations - in platform libraries, and can mix in non-`mixin` classes from platform libraries, - as long as such a class has `Object` as superclass and declares no constructors. - Instead, users will only have to abide by those restrictions - when they upgrade their library's language version. + `base`, `interface` and `final` modifiers on *some* declarations in platform + libraries, and can mix in non-`mixin` classes from platform libraries, + as long as such a class has `Object` as superclass and declares + no constructors. + Instead, users will only have to abide by those restrictions when they + upgrade their library's language version to 3.0 or later. _It will still not be possible to, e.g., extend or implement the `int` class, even if will now have a `final` modifier._ Going through a pre-feature library does not remove transitive restrictions - for code in post-feature libraries. Any post-feature library declaration which has a - platform library class marked `base` or `final` as a superinterface must be marked - `base`, `final` or `sealed`, and cannot be implemented locally, - even if the superinterface chain goes through a pre-feature library declaration, - and even if that declaration ignores the `base` modifier. + for code in post-feature libraries. Any post-feature library declaration + which has a platform library class marked `base` or `final` as a + superinterface must be marked `base`, `final` or `sealed`, + and cannot be implemented locally, even if the superinterface chain goes + through a pre-feature library declaration, and even if that declaration + ignores the `base` modifier. This is special case behavior only available to platform libraries. Package libraries should use versioning to to introduce breaking From 24793d8ff8484063c75083d38283c19920271fb6 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 21 Mar 2023 16:20:59 +0100 Subject: [PATCH 6/8] Rephrase semantics. --- .../class-modifiers/feature-specification.md | 434 +++++++++--------- 1 file changed, 228 insertions(+), 206 deletions(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index b5719d15c2..333a35d346 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -569,7 +569,15 @@ mixinModifier ::= 'base' ## Static semantics -A pair of definitions: +The modifiers introduce restrictions on which other declarations can +depend on the modified declaration, and how. +To express this, we first introduce some terminology that makes it +easy to express the relations between declarations. + +### Terminology. + +We distinguish libraries by whether they have this feature enabled, +and whether they are platform libraries. * A *pre-feature library* is a library whose language version is lower than the version this feature is released in. @@ -577,290 +585,302 @@ A pair of definitions: * A *post-feature library* is a library whose language version is at or above the version this feature is released in. +* A *platform library* is a library with a `dart:...` URI. A platform library + is always a post-feature library in an SDK supporting the feature, + but for backwards compatibility, pre-feature libraries may ignore + some modifiers in platform lbiraries, as if the library was also a + pre-feature library. + +We define the relations between declarations and the other declarations +they are declared as subtypes of as follow. + +* A declaration *S* is _the declared superclass_ of a `class` declaration + *D* iff: + * *D* has an `extends T` clause and `T` denotes *S*. + * *D* has the form `... class ... = T with ...` and `T` denotes *S*. + + _A type clause `T` denotes a declaration *S* if `T` of the from + *id* or *id*\, and *id* + is an identifier or qualified identifier which resolves to *S*, + or which resolves to a type alias with a right-hand-side which + denotes *S*._ + _(This allows us to refer to the "declared superclass" uniformly + accross mixin-application `class` declaration and a "normal" `class` + declaration, even though the former cannot have any `extends` clause. + A `class` declaration has at most one declared superclass declaration, + it can have none if it's a non-mixin application declaration with no + `extends` clause.) + +* A declaration *S* is a _declared mixin_ of a `class` or `enum` declaration + which has a `with T1, ..., Tn` clause where any of `T1`,...,`Tn` + denotes *S*. + +* A declaration *S* is a _declared interface_ of a `class`, `mixin class`, + `mixin` or `enum` declaration which has an `implements T1, ..., Tn` clause + where any of `T1`,...,`Tn` denotes *S*. + +* A declaration *S* is a _declared `on` type_ of a `mixin` declaration + which has an `on T1, ..., Tn` clause where any of `T1`,...,`Tn` denotes *S*. + +_We need these independently, but we also need the union of these relations._ + +* A declaration *S* is a direct superdeclaration of a declaration *D* + iff *S* is a declared superclass, mixin, interface or `on` type of *D*. + +_We then define the transitive closure of this relation._ + +* A declaration *S* is a proper superdeclaration of a declartion *D* iff + either *S* is a direct superdeclaration of *D*, or there exists a + declaration *P* such that *P* is a direct superdeclaration of *D* and + *S* is a proper superdeclaration of *P*. + +_The language prevents dependency cycles in declartions, because it prevents +subtyping from being well-defined. Because of that, the +"proper superdeclaration" relation is a directed acyclic relation. +Or alternatively, we could write the rule against cycles as it being +a compile-time error if any declaration *S* is a proper superdeclaration +of itself._ + +_Finally we define the reflexive closure of the proper superdeclaration +relations._ + +* A declaration is a superdeclartion of a declaration *D* iff + *S* is *D* or *S* is a proper superdeclaration of *D*. + +_With all these syntactic relations between declarations in place, +we can specify the restrictions imposed by modifiers._ + ### Basic restrictions -It is a compile-time error to: +It's a compile-time error if: -* Extend a class marked `interface`, `final` or `sealed` outside of the - library where it is declared. +* A declaration marked `sealed` (necessarily a `class` declaration), + is a direct superdeclaration of any declaration outside of its own library. + _(You cannot directly depend on any sealed class outside of its own + library.)_ + + ```dart + // a.dart + sealed class S {} + + // b.dart + import 'a.dart'; + + class E extends S {} // Error. + class I implements S {} // Error. + mixin O on S {} // Error. + class M with S {} // Error, for several reasons. + ``` + +* A (`class`) declaration marked `interface` or `final` is a declard + superclass of any declaration outside of its own library _except + if the superclass declaration is from a platform library and + the subclass is from a pre-feature library._ + _(You cannot inherit implementation from a class marked `interface` + or `final` except inside the same library. Except for the "SDK exception" + which allow pre-feature libraries to ignore modifiers in platform + libraries.)_ ```dart // a.dart interface class I {} final class F {} - sealed class S {} // b.dart import 'a.dart'; class C1 extends I {} // Error. class C2 extends F {} // Error. - class C3 extends S {} // Error. ``` -* Implement the interface of a class, mixin, or mixin class declaration - marked `base`, `final` or `sealed` outside of the library - where it is declared. _(This includes enum declarations.)_ +* A `class` declartion marked `base` or `final`, or a `mixin class` or `mixin` + declartion marked `base`, is a declared superinterface of any + declaration outside of its own library _except if the superinterface + is from a platform library and the subclass is from a pre-feature library_. + _(You cannot implement the interface of something marked `base` or `final` + except inside the same library.)_ ```dart // a.dart - base class B {} + base class S {} + base mixin M {} final class F {} - sealed class S {} - - base mixin BM {} - final mixin FM {} - sealed mixin SM {} // b.dart import 'a.dart'; - class C1 implements B {} // Error. - class C2 implements F {} // Error. - class C3 implements S {} // Error. - - class C1 implements BM {} // Error. - class C2 implements FM {} // Error. - class C3 implements SM {} // Error. + class C implements S {} // Error. + mixin N implements M {} // Error. + enum E implements F { e } // Error. ``` -* Use the interface of a class declaration marked `sealed` - as a mixin `on` type outside of library where it is declared. +* A `class`, `mixin class` or `mixin` declaration *D* has any proper + superdeclaration marked `base` or `final`, and *D* is not marked + `base`, `final` or `sealed`. + _This also applies to declarations inside the same library._ + _(A `base` or `final` declaration doesn't provide an implementable + interface, and for that to matter, nor must any of its subclasses. + The entire subclass tree below such a declaration must prevent + implementation too.)_ -_An `enum` declaration still cannot be implemented, extended or mixed in -anywhere, independently of modifiers._ -A type alias (`typedef`) cannot be used to subvert these restrictions -or any of the restrictions below. When extending, implementing, or mixing in -a type alias, we look at the library where class or mixin the type alias -resolves to is defined to determine if the behavior is allowed. *Note that -the library where the _type alias_ is defined does not come into play. -Type aliases cannot be marked with any of the new modifiers.* + ```dart + // a.dart + base class B {} + sealed class S extend B {} + enum E extends S { e } + + class C0 extends B {} // Error. + class C1 implements B {} // Error. + + base mixin BM {} + final mixin FM {} -### Disallowing implementation + mixin M0 implements B {} // Error + mixin M1 on B {} // Error -We say that an interface _prevents implementation_ if it has *any* -superinterface whose declaration is marked `base` or `final`. + // b.dart + import 'a.dart'; -It is a compile-time error if a the interface of a -`class`, `mixin`, or `mixin class` declaration `D` prevents implementation, -and `D` does not have a `base`, `final` or `sealed` modifier. -_(Which must be a `base` modifier for `mixin` and `mixin class` -declarations.)_ + base class V1 extends B {} + final class V2 extends B {} + sealed class V3 extends B {} -_Effectively, it is a compile-time error if any subtype of a declaration marked -`base` or `final` is not also marked `base`, `final`, or `sealed`. -This restriction applies to both direct and indirect subtypes -and along all paths that introduce subtypes: -`implements` clauses, `extends` clauses, `with` clauses, and `on` clauses. This -restriction applies even to types within the same library._ + enum E2 with BM { e } // Not a class/mixin class/mixin declaration. -_Once the ability to use as an interface is removed, it cannot be reintroduced -in a subtype. If a class is marked `base` or `final`, you may still implement -the class's interface inside the same library, but the implementing class must -again be marked `base`, `final`, or `sealed` to avoid it exposing an -implementable interface._ + class C2 extends B {} // Error. + class C3 with BM {} // Error. + ``` -Further, while you can ignore some restrictions on declarations within the same -library, you cannot use that to ignore restrictions inherited from other -libraries. +* A declartion *P* has any superdeclaration marked `base` or `final` + from a library *L*, + and *P* is a declared interface of any declartion in a library + other than *L*. + _Even if the implementing class is in the same library as *P*, + which it always is, because otherwise `P` would be marked + `base`, `final` or `sealed` in another library and not be implementable + at all._ + _(You can only implement an interface if *all* `base` or `final` + superdeclarations are inside your own library.)_ -We say that an interface *cannot be implemented locally* in a library *L* -if it has any superinterface *S* whose declaration is marked `base` or `final`, -and *S* is in a library other than *L*. + ```dart + // a.dart + base class B {} -It's a compile-time error if a `class`, `mixin`, `mixin class` or `enum` -declaration in library *L* has an `implements` clause with an entry -denoting the interface *S*, and *S* prevents implementation locally in *L*. + // b.dart + import 'a.dart'; + base class P extends B {} + base class C implements P {} // Error. + ``` -Examples: -```dart -// a.dart -// Interface of S prevents implementation -// and prevents implementation locally in any other library. -base mixin class S {} - -// You can implement `S` locally in the same library. -base _MyLittleS implements S {} - -// b.dart -import 'a.dart'; - -// These are valid, but cannot be implemented locally: -sealed class DE extends S {} -final class DM with S {} -base mixin MO on S {} - -// None of these interface can be implemented locally, -// because they all have the `base` interface `S` from another library -// as superinterface. -base class Invalid1 implements S {} // Error -base class Invalid1 implements DE {} // Error -base class Invalid1 implements DM {} // Error -base class Invalid1 implements MO {} // Error -``` + +_An `enum` declaration still cannot be implemented, extended or mixed in +anywhere, independently of modifiers._ + +A type alias (`typedef`) cannot be used to subvert these restrictions +or any of the restrictions below. The actual superdeclaration used in +these checks is the one that the type alias expands to. *Note that +the library where the _type alias_ is defined does not come into play. +Type aliases cannot be marked with any of the new modifiers.* ### Mixin restrictions -There are a few changes around mixins to support `mixin class` and disallow -using normal `class` declarations as mixins while dealing with language -versioning and backwards compatibility. +As before, a declared superclass declaration must be a `class` declaration +_(you can only extend another class)_ and a declared interface declaration +must be a `class`, `mixin`, `enum` declaration, and now it may also +be a `mixin class` declartion _(you can only implement something which +has an interface)_. -Currently, a class may only be used as a mixin if it has a default constructor. -This prevents the class from defining a `const` constructor or any factory -constructors. We loosen this restriction for `mixin class`es. +The new `mixin class` declaration has a set of syntactic rules which +ensures that it can be used as both a `class` and a `mixin`. -Define a *trivial generative constructor* to be a generative constructor that: +It's a compile-time error if a `mixin class` declaration: +* has an `interface`, `final` or `sealed` modifier. _This is baked + into the grammar, but it bears repeating._ +* has an `extends` clause, +* has a `with` clause, or +* declares any non-trivial generative constructor. +A *trivial generative constructor* is a generative constructor that: * Is not a redirecting constructor _(`Foo(...) : this.other(...);`), - * declares no parameters, - * has no initializer list (no `: ...` part, so no asserts or initializers, and no explicit super constructor invocation), - * has no body (only `;`), and - * is not `external`. *An `external` constructor is considered to have an externally provided initializer list and/or body.* -A trivial generative constructor may be named or unnamed, -and maybe be `const` or non-`const`. +_A trivial generative constructor may be named or unnamed, +and may be be `const` or non-`const`._ A *non-trivial generative constructor* is a generative constructor which is not a trivial generative constructor. +_A trivial generative construtor has no effect on object construction, +so it can be safely ignored when the `mixin class` is used as a mixin, +but it allows the declaration to be used a superclass, even for subclasses +with constant constructors._ + Examples: ```dart -class C { +mixin class C { // Trivial generative constructors: C(); const C(); // Non-trivial generative constructors: - C(int x); - C(this.x); - C() {} - C(): assert(true); - C(): super(); + C(int x); // Error. + C(this.x); // Error. + C() {} // Error. + C(): assert(true); // Error. + C(): super(); // Error. // Not generative constructors, so neither trivial generative nor non-trivial // generative: factory C.f = C; factory C.f2() { ... } } -``` -It's a compile-time error if: - -* A `mixin class` declaration has a superclass clause. - *The declaration is limited to having `Object` as superclass, and not - having any mixin applications. The class grammar prohibits `on` clauses.* - -* A `mixin class` declaration declares any non-trivial generative constructor. - *It may declare no constructors, in which case it gets a default - constructor, or it can declare factory constructors and/or trivial - generative constructors.* - -These rules ensure that when you mark a `class` with `mixin` that it *can* be -used as one. +mixin class E extends Object {} // Error. +mixin class E with C {} // Error. +``` -A class not marked `mixin` can still be used as a mixin when the class's -declaration is in a pre-feature library and it satisfies specific requirements. -Specifically: +There are also changes to which declarations can be mixed in. -It's a compile-time error for a declaration in library `L` to mix in a -non-`mixin` class declaration `D` from library `K` if any of: +A post-feature class can no longer be used as a mixin unless it's declared +as a `mixin class`. In post-feature code, you can *only* mix in mixins. +Pre-feature code is not changed, so some pre-feature classes can still +be mixed in, and the SDK exception allows pre-feature code to pretend +platfrom libraries are still pre-feature libraries. -* `K` is a post-feature library, +The rules for which declarations can be mixed in become: -* The superclass of `D` is not `Object`, or +It's a compile-time error if *S* from library *K* +is a declared mixin of a declaration *D* from library *L* unless: +* `S` is a `mixin` or `mixin class` declaration _(necessarily from + a post-feature library)_, or +* `S` is a non-mixin `class` declaration which has `Object` as superclass + and declares no generative constructor, and either + * *K* is a pre-feature library, or + * *K* is a platform library and *L* is a pre-feature library. -* `D` declares any constructors. +_That is, a class not marked `mixin` can still be used as a mixin when the +class's declaration is in a pre-feature library and it satisfies specific +requirements._ -*For pre-feature libraries, we cannot tell if the intent of `class` was +_For pre-feature libraries, we cannot tell if the intent of `class` was "just a class" or "both a class and a mixin". For compatibility, we assume the latter, even if the class is being used as a mixin in a post-feature library where it does happen to be possible to distinguish those two -intents.* +intents._ ### Enum classes -The class introduced by an `enum` declaration is considered `final` for any -purpose where a class modifier is required. - -The behavior of `enum` declarations is unchanged. Since an `enum` class cannot -have any subclasses, the implicit `final` modifier would not prevent any -otherwise allowed operation. -The implicit `final` also does not imply fewer restrictions than `enum` -declarations would otherwise have. The modifier is applied only to automatically -satisfy any requirements introduced by super-interfaces. - -### Anonymous mixin applications - -An *anonymous mixin application* class is a class resulting from -a mixin application that does not have its own declaration. -That is all mixin applications classes other than the final class -of a class C = S with M1, …, Mn; declaration, -the mixin application of Mn to -the superclass S with M1, …, Mn-1, -which is denoted by declaration and name `C`. - -An anonymous mixin application class cannot be referenced anywhere except in -the context where the application occurs, so its only role is -to be a superclass of another class in the same library. - -To ensure reasonable and correct behavior, without having to special-case such -anonymous mixin application classes elsewhere, we infer class modifiers as -follows. - -First we define the following transitive property on declarations: - -* A declaration *prohibits inheritance* _(of its implementation)_ if: - - * the declaration is marked `interface` or `final`, or - * the declaration is marked `sealed` and extends or mixes in - a declaration which prohibits inheritance. - -Let then *C* be an anonymous mixin application occurring in -a post-feature library, with superclass *S* and mixin *M*. - -* If either *S* or *M* has a `sealed` modifier, then *C* has an implicit - `sealed` modifier. - -* Otherwise *C* is `abstract`, and - - * If *C* *prohibits inheritance* and its interface *prevents implementation*, - then *C* has an implicit `final` modifier. - - * Otherwise if *C* *prevents implementation*, then *C* has an implicit - `base` modifier. - - * Otherwise if *C* *prohibits inheritance*, then *C* has an implicit - `interface` modifier. - - * Otherwise *C* has no modifier. - -Adding `sealed` to an anonymous mixin application class, which always has -precisely one subclass, ensures that the subclass can be used in -exhaustiveness checking of the sealed superclass. - -Adding `final` or `base` satisfies the requirement that a subtype of a -`base` or `final` declaration is itself `base`, `final` or `sealed`. - -Adding `interface` ensures that the mixin application class doesn’t -remove a restriction on the implementation inherited from *S* or *M*. -This makes the “reopen” lint described below easier to implement. - -Adding these modifiers on the anonymous mixin application class ensures that -the anonymous class can mostly be ignored, since it satisfies all possible -requirements of its superclasses, while still propagating those requirements -to its subclass. -Treating the anonymous class as having these modifiers allows the algorithms -used to check that restrictions are satisfied, and for the “reopen” lint -described below, to treat the anonymous mixin application class as any other -class, without needing special cases, and without adding any new restrictions. +An `enum` declaration can still not be extended, mixed in or implemented. +It's effectively as closed as a `final` declaration, even inside its +own library. Therefore no modifiers can be applied to an `enum` declaration, +and it cannot be used as a `mixin`. ### `@reopen` lint @@ -873,7 +893,9 @@ A metadata annotation `@reopen` is added to package [meta][] and a lint warning is reported if a class or mixin is not annotated `@reopen` and it: * Extends or mixes in a class, mixin, or mixin class marked `interface` or - `final` and is not itself marked `interface` or `final`. + `final` and is not itself marked `interface` or `final`, + or extends or mixes in a `sealed` declaration which itself + transitively extends or mixes in a an `interface` or `final` declaration. [meta]: https://pub.dev/packages/meta [linter]: https://dart.dev/guides/language/analysis-options#enabling-linter-rules @@ -1066,8 +1088,8 @@ errors and fixups would help keep them on the rails. * Update the modifiers applied to anonymous mixin applications to closer match the superclass/mixin modifiers. * State that `enum` declarations count as `final`. -* Some rephrasing to allow concept reuse. -* Say that pre-feature libraries can mix in non-`mixin` platform library classes +* Rephrase semantics completely, based only on relations between declarations. +* Say that pre-feature libraries can mix in non-`mixin` platform library classes which satisfy the old requirements for being used as a mixin. 1.6 From 8160368046a490dc36d2b54e2ff3150ea1ee5071 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Wed, 22 Mar 2023 21:22:16 +0100 Subject: [PATCH 7/8] Fix typos, address comments, tweak wordings. --- .../class-modifiers/feature-specification.md | 213 ++++++++++-------- 1 file changed, 116 insertions(+), 97 deletions(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index 333a35d346..a7576126a6 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -349,10 +349,10 @@ does `MySubclass` now expose externally? We have a few options: implementing class must also be marked `base` or `final`. This avoids any confusion about whether a subtype removes a restriction. But - it comes at the expense of flexiblity. If a user *wants* to remove a + it comes at the expense of flexibility. If a user *wants* to remove a restriction, they have no ability to. - This would constrast with `sealed` where you can have subtypes of a sealed + This would contrast with `sealed` where you can have subtypes of a sealed type that are not themselves sealed. This is a deliberate choice because there's no *need* for the direct subtypes of a sealed to be sealed in order for exhaustiveness checking to be sound. Since exhaustiveness is the goal @@ -360,7 +360,7 @@ does `MySubclass` now expose externally? We have a few options: unsealed. It also prevents API designs that seem reasonable and useful to me. Imagine - a library for transportion with classes like: + a library for transportation with classes like: ```dart abstract final class Vehicle {} @@ -515,17 +515,20 @@ Many combinations don't make sense: are mutually exclusive. * `sealed` types cannot be constructed so it's redundant to combine with `abstract`. -* `sealed` types cannot be mixed in, extended or implemented, - so it's redundant to combine with `final`, `base`, or `interface`. +* `sealed` types already cannot be mixed in, extended or implemented + from another library, so it's redundant to combine with `final`, + `base`, or `interface`. * `mixin` as a modifier can obviously only be applied to a `class` declaration, which makes it also introduce a mixin. * `mixin` as a modifier cannot be applied to a mixin-application `class` declaration (the `class C = S with M;` syntax for declaring a class). The remaining modifiers can. -* A `mixin`, from a `mixin` declaration or a `mixin class` declaration, - is intended to be mixed in, so its declaration cannot have an - `interface`, `final` or `sealed` modifier. -* Mixin declarations cannot be constructed, so `abstract` is redundant. +* A `mixin` or `mixin class` declaration is intended to be mixed in, + so its declaration cannot have an `interface`, `final` or `sealed` modifier. +* A `mixin` declaration cannot be constructed, so `abstract` is redundant. +* `enum` declarations cannot be extended, implemented, mixed in, + and can always be instantiated, so no modifiers apply to `enum` + declarations. The remaining valid combinations and their capabilities are: @@ -558,13 +561,11 @@ classDeclaration ::= (classModifiers | mixinClassModifiers) 'class' typeIdentif classModifiers ::= 'sealed' | 'abstract'? ('base' | 'interface' | 'final')? -mixinClassModifiers ::= 'abstract'? mixinModifier? 'mixin' +mixinClassModifiers ::= 'abstract'? 'base'? 'mixin' -mixinDeclaration ::= mixinModifier? 'mixin' typeIdentifier typeParameters? +mixinDeclaration ::= 'base'? 'mixin' typeIdentifier typeParameters? ('on' typeNotVoidList)? interfaces? '{' (metadata classMemberDeclaration)* '}' - -mixinModifier ::= 'base' ``` ## Static semantics @@ -588,7 +589,7 @@ and whether they are platform libraries. * A *platform library* is a library with a `dart:...` URI. A platform library is always a post-feature library in an SDK supporting the feature, but for backwards compatibility, pre-feature libraries may ignore - some modifiers in platform lbiraries, as if the library was also a + some modifiers in platform libraries, as if the library was also a pre-feature library. We define the relations between declarations and the other declarations @@ -605,7 +606,7 @@ they are declared as subtypes of as follow. or which resolves to a type alias with a right-hand-side which denotes *S*._ _(This allows us to refer to the "declared superclass" uniformly - accross mixin-application `class` declaration and a "normal" `class` + across mixin-application `class` declaration and a "normal" `class` declaration, even though the former cannot have any `extends` clause. A `class` declaration has at most one declared superclass declaration, it can have none if it's a non-mixin application declaration with no @@ -622,19 +623,22 @@ they are declared as subtypes of as follow. * A declaration *S* is a _declared `on` type_ of a `mixin` declaration which has an `on T1, ..., Tn` clause where any of `T1`,...,`Tn` denotes *S*. -_We need these independently, but we also need the union of these relations._ +_We need these independently, but we also need the union of these relations, +capturing that a declaration depends directly on another in *any* way._ * A declaration *S* is a direct superdeclaration of a declaration *D* iff *S* is a declared superclass, mixin, interface or `on` type of *D*. -_We then define the transitive closure of this relation._ +_We then define the transitive closure of this relation, expression +that a declaration depends on another through any number of intermediate +declarations._ -* A declaration *S* is a proper superdeclaration of a declartion *D* iff +* A declaration *S* is a proper superdeclaration of a declaration *D* iff either *S* is a direct superdeclaration of *D*, or there exists a declaration *P* such that *P* is a direct superdeclaration of *D* and *S* is a proper superdeclaration of *P*. -_The language prevents dependency cycles in declartions, because it prevents +_The language prevents dependency cycles in declarations, because cycles prevent subtyping from being well-defined. Because of that, the "proper superdeclaration" relation is a directed acyclic relation. Or alternatively, we could write the rule against cycles as it being @@ -642,9 +646,10 @@ a compile-time error if any declaration *S* is a proper superdeclaration of itself._ _Finally we define the reflexive closure of the proper superdeclaration -relations._ +relations, because it's sometimes useful to talk about a the entire +super-hierarchy of a declaration including itself._ -* A declaration is a superdeclartion of a declaration *D* iff +* A declaration is a superdeclaration of a declaration *D* iff *S* is *D* or *S* is a proper superdeclaration of *D*. _With all these syntactic relations between declarations in place, @@ -654,10 +659,13 @@ we can specify the restrictions imposed by modifiers._ It's a compile-time error if: -* A declaration marked `sealed` (necessarily a `class` declaration), - is a direct superdeclaration of any declaration outside of its own library. - _(You cannot directly depend on any sealed class outside of its own - library.)_ +* A declaration depends directly on a `sealed` declaration from another + library. _No exceptions, not even for platform libraries._ + + More formally: + A declaration *D* from library *L* has a direct superdeclaration *S* + marked `sealed` (so necessarily a `class` declaration) in a library + different from *L*. ```dart // a.dart @@ -672,14 +680,18 @@ It's a compile-time error if: class M with S {} // Error, for several reasons. ``` -* A (`class`) declaration marked `interface` or `final` is a declard - superclass of any declaration outside of its own library _except - if the superclass declaration is from a platform library and - the subclass is from a pre-feature library._ +* A class extends or mixes in a declaration marked `interface` or `final` + from another library _(with some exceptions for platform libraries)_. + _(You cannot inherit implementation from a class marked `interface` - or `final` except inside the same library. Except for the "SDK exception" - which allow pre-feature libraries to ignore modifiers in platform - libraries.)_ + or `final` except inside the same library. Unless you are in a + pre-feature library and you are inheriting from a platform library.)_ + + More formally: + A declaration *C* from library *L* has a declared superclass or mixin + declaration *S* marked `interface` or `final` from library *K*, and neither + * *L* and *K* is the same library, nor + * *K* is a platform library and *L* is a pre-feature library. ```dart // a.dart @@ -693,12 +705,23 @@ It's a compile-time error if: class C2 extends F {} // Error. ``` -* A `class` declartion marked `base` or `final`, or a `mixin class` or `mixin` - declartion marked `base`, is a declared superinterface of any - declaration outside of its own library _except if the superinterface - is from a platform library and the subclass is from a pre-feature library_. - _(You cannot implement the interface of something marked `base` or `final` - except inside the same library.)_ +* A declaration implements another declaration, and the other + declaration itself, or any of its super-declarations, + are marked `base` or `final` and are not from the first declaration's + library _(with some exceptions for platform libraries)_. + + _(You can only implement an interface if *all* `base` or `final` + superdeclarations are inside your own library. Or if you're in + a pre-feature library and all `base` or `final` superdeclarations + are in platform libraries.)_ + + More formally: + A declaration *C* in library *L* has a declared interface *P*, + and *P* has any superdeclaration *S*, from a library *K*, + which is marked `base` or `final` _(including *S* being *P* itself)_, + and neither: + * *K* and *L* is the same library, mor + * *K* is a platform library and *L* is a pre-feature library. ```dart // a.dart @@ -709,20 +732,29 @@ It's a compile-time error if: // b.dart import 'a.dart'; - class C implements S {} // Error. + // Direct implementation of other-library `base` class. + base class D implements S {} // Error mixin N implements M {} // Error. enum E implements F { e } // Error. + + // Indirect implementation of other-library `base` class. + base class P extends S {} + base class C implements P {} // Error. ``` -* A `class`, `mixin class` or `mixin` declaration *D* has any proper - superdeclaration marked `base` or `final`, and *D* is not marked - `base`, `final` or `sealed`. +* A declaration has a `base` or `final` superdeclaration, + and is not itself marked `base`, `final` or `sealed`. _This also applies to declarations inside the same library._ - _(A `base` or `final` declaration doesn't provide an implementable + + _(A `base` or `final` declaration doesn't expose an implementable interface, and for that to matter, nor must any of its subclasses. The entire subclass tree below such a declaration must prevent implementation too.)_ + More formally: + A `class`, `mixin class` or `mixin` declaration *D* in a post-feature + library has any proper superdeclaration marked `base` or `final`, + and *D* is not itself marked `base`, `final` or `sealed`. ```dart // a.dart @@ -752,29 +784,6 @@ It's a compile-time error if: class C3 with BM {} // Error. ``` -* A declartion *P* has any superdeclaration marked `base` or `final` - from a library *L*, - and *P* is a declared interface of any declartion in a library - other than *L*. - _Even if the implementing class is in the same library as *P*, - which it always is, because otherwise `P` would be marked - `base`, `final` or `sealed` in another library and not be implementable - at all._ - _(You can only implement an interface if *all* `base` or `final` - superdeclarations are inside your own library.)_ - - ```dart - // a.dart - base class B {} - - // b.dart - import 'a.dart'; - - base class P extends B {} - base class C implements P {} // Error. - ``` - - _An `enum` declaration still cannot be implemented, extended or mixed in anywhere, independently of modifiers._ @@ -788,9 +797,9 @@ Type aliases cannot be marked with any of the new modifiers.* As before, a declared superclass declaration must be a `class` declaration _(you can only extend another class)_ and a declared interface declaration -must be a `class`, `mixin`, `enum` declaration, and now it may also -be a `mixin class` declartion _(you can only implement something which -has an interface)_. +must be a `class` or `mixin` declaration, and now it may also +be a `mixin class` declaration _(you can only implement something which +has an interface, and not `enum`s which cannot be implemented at all)_. The new `mixin class` declaration has a set of syntactic rules which ensures that it can be used as both a `class` and a `mixin`. @@ -804,22 +813,22 @@ It's a compile-time error if a `mixin class` declaration: A *trivial generative constructor* is a generative constructor that: * Is not a redirecting constructor _(`Foo(...) : this.other(...);`), -* declares no parameters, +* declares no parameters (parameter list is precisely `()`), * has no initializer list (no `: ...` part, so no asserts or initializers, and no explicit super constructor invocation), * has no body (only `;`), and -* is not `external`. *An `external` constructor is considered to have an - externally provided initializer list and/or body.* +* is not `external`. _An `external` constructor is considered to have an + externally provided initializer list and/or body._ _A trivial generative constructor may be named or unnamed, -and may be be `const` or non-`const`._ +and may be `const` or non-`const`._ A *non-trivial generative constructor* is a generative constructor which is not a trivial generative constructor. -_A trivial generative construtor has no effect on object construction, -so it can be safely ignored when the `mixin class` is used as a mixin, -but it allows the declaration to be used a superclass, even for subclasses -with constant constructors._ +_A trivial generative constructor has no effect on object construction, +so it can be safely ignored and omitted when the `mixin class` is used +as a mixin, but it allows the `mixin class` declaration to also be used a +superclass, even for subclasses with constant constructors._ Examples: @@ -828,20 +837,27 @@ mixin class C { // Trivial generative constructors: C(); const C(); + C.named(); + const C.alsoNamed(); // Non-trivial generative constructors: C(int x); // Error. C(this.x); // Error. C() {} // Error. + C(): x = 0; C(): assert(true); // Error. C(): super(); // Error. + C(): this.named(); // Not generative constructors, so neither trivial generative nor non-trivial // generative: factory C.f = C; factory C.f2() { ... } + + int? x; } +// Invalid mixin classes. mixin class E extends Object {} // Error. mixin class E with C {} // Error. ``` @@ -849,15 +865,17 @@ mixin class E with C {} // Error. There are also changes to which declarations can be mixed in. A post-feature class can no longer be used as a mixin unless it's declared -as a `mixin class`. In post-feature code, you can *only* mix in mixins. +as a `mixin class`. In post-feature code, you can *only* mix in +`mixin` or `mixin class` declarations Pre-feature code is not changed, so some pre-feature classes can still be mixed in, and the SDK exception allows pre-feature code to pretend -platfrom libraries are still pre-feature libraries. +platform libraries are still pre-feature libraries. -The rules for which declarations can be mixed in become: +The formal rules for which declarations can be mixed in become: -It's a compile-time error if *S* from library *K* -is a declared mixin of a declaration *D* from library *L* unless: + +It's a compile-time error if a `class` or `enum` declaration *D* from +library *L* has *S* from library *K* as a declared mixin, unless: * `S` is a `mixin` or `mixin class` declaration _(necessarily from a post-feature library)_, or * `S` is a non-mixin `class` declaration which has `Object` as superclass @@ -875,13 +893,6 @@ the latter, even if the class is being used as a mixin in a post-feature library where it does happen to be possible to distinguish those two intents._ -### Enum classes - -An `enum` declaration can still not be extended, mixed in or implemented. -It's effectively as closed as a `final` declaration, even inside its -own library. Therefore no modifiers can be applied to an `enum` declaration, -and it cannot be used as a `mixin`. - ### `@reopen` lint We don't specify lints and metadata annotations in the language specification, @@ -895,7 +906,7 @@ warning is reported if a class or mixin is not annotated `@reopen` and it: * Extends or mixes in a class, mixin, or mixin class marked `interface` or `final` and is not itself marked `interface` or `final`, or extends or mixes in a `sealed` declaration which itself - transitively extends or mixes in a an `interface` or `final` declaration. + transitively extends or mixes in an `interface` or `final` declaration. [meta]: https://pub.dev/packages/meta [linter]: https://dart.dev/guides/language/analysis-options#enabling-linter-rules @@ -922,14 +933,14 @@ non-breaking. to all other libraries, regardless of the versions of those libraries. "Ignorance of the law is no defense."* -* We would like to add modifiers to some classes in platform (i.e., `dart:`) - libraries when this feature ships. But we would also like to not immediately +* We will add modifiers to some classes in platform (i.e., `dart:`) + libraries when this feature ships. But we will also like to not immediately break existing code. To avoid forcing users to immediately migrate, declarations in pre-feature libraries can ignore *some* `base`, `interface` and `final` modifiers on *some* declarations in platform libraries, and can mix in non-`mixin` classes from platform libraries, as long as such a class has `Object` as superclass and declares - no constructors. + no constructors. ([legacy-mixin-tests][]). Instead, users will only have to abide by those restrictions when they upgrade their library's language version to 3.0 or later. _It will still not be possible to, e.g., extend or implement the `int` class, @@ -942,10 +953,18 @@ non-breaking. through a pre-feature library declaration, and even if that declaration ignores the `base` modifier. - This is special case behavior only available to platform libraries. - Package libraries should use versioning to to introduce breaking - restrictions instead, and those libraries can then rely on the restrictions - being enforced. + This ability to ignore modifiers only apply to platform libraries + accessed from pre-feature libraries, because code doesn't get to + decide the version of the SDK that it runs on, unlike how a package + can depend on specific versions of another package. + Packages should use package versioning to introduce breaking restrictions + instead (a major version semantic version upgrade), but those libraries + can then rely on the restrictions being enforced. + The platform libraries will bear the cost of not being able to rely + on its own modifiers until all code in a program is language version 3.0 + later. + +[legacy-mixin-tests]: https://dart-review.googlesource.com/c/sdk/+/287665 ### Compatibility From db162631ac76e2b49e50fc6582a96fe6bd73b2e6 Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Tue, 28 Mar 2023 22:37:53 +0200 Subject: [PATCH 8/8] Fix typo. Thanks Oleh. --- .../future-releases/class-modifiers/feature-specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accepted/future-releases/class-modifiers/feature-specification.md b/accepted/future-releases/class-modifiers/feature-specification.md index a7576126a6..b69abef75a 100644 --- a/accepted/future-releases/class-modifiers/feature-specification.md +++ b/accepted/future-releases/class-modifiers/feature-specification.md @@ -759,7 +759,7 @@ It's a compile-time error if: ```dart // a.dart base class B {} - sealed class S extend B {} + sealed class S extends B {} enum E extends S { e } class C0 extends B {} // Error.