Skip to content

Commit b7edc0b

Browse files
authored
Add "mixin class" to the "base-interface-final" proposal. (#2674)
* Add "mixin class" to the "base-interface-final" proposal. I went ahead and took the more aggressive step of also saying that you can't mixin a class unless it has the "mixin" modifier. However, that rule is gated by a language version so that it's not breaking. Since "mixin class" allows you to preserve the previous behavior of class+mixin, I think we can get away with doing this change in one step. Basically, whenever you upgrade your library to the latest version, you add "mixin" to whatever classes you want to support being used as mixins and you're done. If you think that's too aggressive, let me know and I can change this. If we do that, though, it ends up meaning that the "mixin" modifier doesn't actually *do* anything (except yell at you if you apply it to a class with a generative constructor or non-Object superclass). * Revise.
1 parent 0badadd commit b7edc0b

File tree

1 file changed

+99
-15
lines changed

1 file changed

+99
-15
lines changed

working/base-interface-final/feature-specification.md

Lines changed: 99 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ proposal, this proposal is non-breaking.
3131
https://github.com/dart-lang/language/issues/2595
3232
[leaf proposal]: https://github.com/dart-lang/language/issues/2595
3333

34+
This proposal also allows `mixin` to be used as a modifier on `class` to
35+
indicate that a type is explicitly intended to be used as both a class and a
36+
mixin.
37+
3438
## Motivation
3539

3640
Dart's ethos is to be permissive by default. When you declare a class, it can be
@@ -406,14 +410,59 @@ This proposal takes the last option where types have exactly the restrictions
406410
they declare but a lint can be turned on for users who want to be reminded if
407411
they re-add a capability in a subtype.
408412
409-
Finally, to the actual proposal...
413+
## Mixin classes
414+
415+
In line with Dart's permissive default nature, Dart allows any class declaration
416+
to also be used as a mixin (in spec parlance, it allows a mixin to be "derived
417+
from a class declaration"), provided the class meets the restrictions that
418+
mixins require: Its immediate superclass must be `Object` and it must not
419+
declare any generative constructors.
420+
421+
In practice, mixins are quite different from classes and it's uncommon for users
422+
to deliberately define a type that is used as both. It's easy to define a class
423+
without *intending* it to be used as a mixin and then accidentally forbid that
424+
usage by adding a generative constructor or superclass to the class. That is a
425+
breaking change to any downstream user that had that class in a `with` clause.
426+
427+
Eventually, we would like classes to not be usable as a mixin by default. Being
428+
extensible and implementable seems to be the right default for classes based on
429+
Dart's history and talking to users. But being usable as a mixin by default is
430+
rarely (but not *never*) useful and often confusing. It makes classes brittle
431+
for little upside.
432+
433+
Instead, under this proposal we require authors to explicitly opt in to allowing
434+
the class to be used as a mixin by adding a `mixin` modifier to the class:
435+
436+
```dart
437+
class OnlyClass {}
438+
439+
class FailUseAsMixin extends OtherSuperclass with OnlyClass {} // Error.
440+
441+
mixin class Both {}
442+
443+
class UsesAsSuperclass extends Both {}
444+
445+
class UsesAsMixin extends OtherSuperclass with Both {} // OK.
446+
```
447+
448+
This change is guarded by a language version. Any class declaration in a library
449+
whose language version is before the one this feature ships in can be used as a
450+
mixin as long as the class meets the mixin restrictions. Classes in libraries
451+
whose version is later than that must be explicitly marked `mixin class` to
452+
allow the class to be used in a `with` clause.
453+
454+
When upgrading your library to the new language version, you can preserve the
455+
existing behavior by adding `mixin` to every class declaration that can be used
456+
as a mixin. If the class defines a generative constructor or extends anything
457+
other than `Object`, then it already can't be used as a mixin and no change is
458+
needed.
410459

411460
## Syntax
412461

413462
This proposal builds on the existing sealed types proposal so the grammar
414463
includes those changes. The full set of modifiers that can appear before a class
415-
or mixin are `abstract`, `sealed`, `base`, `interface`, and `final`. Some
416-
combinations don't make sense:
464+
or mixin are `abstract`, `sealed`, `base`, `interface`, `final`, and `mixin`.
465+
Some combinations don't make sense:
417466

418467
* `sealed` implies `abstract`, so they can't be combined.
419468
* `sealed` implies non-extensibility, so can't be combined with `interface`
@@ -422,9 +471,13 @@ combinations don't make sense:
422471
`final`.
423472
* `base`, `interface`, and `final` all control the same two capabilities so
424473
are mutually exclusive.
474+
* `mixin` as a modifier can obviously only be applied to a class. It can be
475+
combined with any other modifiers that can be applied to a class.
425476

426477
The remaining valid combinations are:
427478

479+
<table><tr><td>
480+
428481
```
429482
class
430483
sealed class
@@ -435,14 +488,34 @@ abstract class
435488
abstract base class
436489
abstract interface class
437490
abstract final class
491+
```
492+
493+
</td><td>
494+
495+
```
496+
mixin class
497+
sealed mixin class
498+
base mixin class
499+
interface mixin class
500+
final mixin class
501+
abstract mixin class
502+
abstract base mixin class
503+
abstract interface mixin class
504+
abstract final mixin class
505+
```
506+
507+
</td><td>
438508

509+
```
439510
mixin
440511
sealed mixin
441512
base mixin
442513
interface mixin
443514
final mixin
444515
```
445516

517+
</td></tr></table>
518+
446519
The grammar is:
447520

448521
```
@@ -452,36 +525,47 @@ classDeclaration ::=
452525
'{' (metadata classMemberDeclaration)* '}'
453526
| classModifiers 'class' mixinApplicationClass
454527
455-
classModifiers ::= 'sealed' | 'abstract'? ('base' | 'interface' | 'final')?
456-
457-
mixinDeclaration ::= mixinModifiers? 'mixin' identifier typeParameters?
528+
mixinDeclaration ::= mixinModifier? 'mixin' identifier typeParameters?
458529
('on' typeNotVoidList)? interfaces?
459530
'{' (metadata classMemberDeclaration)* '}'
460531
461-
mixinModifiers ::= 'sealed' | 'base' | 'interface' | 'final'
532+
classModifiers ::= ( 'sealed' | 'abstract'? typeModifier? ) 'mixin'?
533+
mixinModifier ::= 'sealed' | typeModifier
534+
typeModifier ::= 'base' | 'interface' | 'final'
462535
```
463536

464537
### Static semantics
465538

466539
It is a compile-time error to:
467540

468541
* Extend a class marked `interface` or `final` outside of the library where it
469-
is defined.
542+
is declared.
470543

471544
* Implement a type marked `base` or `final` outside of the library where it is
472-
defined.
545+
declared.
473546

474547
* Extend or mix in a type marked `base` outside of the library where it is
475-
defined without also being marked `base` or `final`. *This ensures that a
548+
declared without also being marked `base` or `final`. *This ensures that a
476549
subtype can't escape the `base` restriction of its supertype by offering its
477550
_own_ interface that could then be implemented without inheriting the
478551
concrete implementation from the supertype.*
479552

480-
* Mix in a class marked `sealed`, `base`, `interface`, or `final`. *We want to
481-
eventually move away from classes as mixins. We don't want to break existing
482-
uses of classes as mixins but since no existing code is using these
483-
modifiers, we can prevent classes using those modifiers from also being used
484-
as mixins.*
553+
* Mix in a class not marked `mixin` outside of the library where it is
554+
declared, unless the class declaration is in a library whose language
555+
version is older than the version this feature ships in.
556+
557+
* Apply the `mixin` modifier to a class whose superclass is not `Object` or
558+
that declares a generative constructor. *The `mixin` modifier states that
559+
you intend the class to be mixed in, which is inconsistent with defining a
560+
class that can't be used as a mixin. Note that this means that the `mixin`
561+
modifier becomes a helpful reminder to ensure that you don't inadvertently
562+
break your class's ability to be used as a mixin.*
563+
564+
* Mix in a class whose superclass is not `Object` or that declares a
565+
generative constructor. *Because of the previous rule, this rule only comes
566+
into play when you use a class not marked `mixin` as a mixin within the
567+
library where it's declared. When you do that, the existing restriction
568+
still applies that the class being used as a mixin must be valid to do so.*
485569

486570
A typedef can't be used to subvert these restrictions. When extending,
487571
implementing, or mixing in a typedef, we look at the library where type the

0 commit comments

Comments
 (0)