@@ -31,6 +31,10 @@ proposal, this proposal is non-breaking.
31
31
https://github.com/dart-lang/language/issues/2595
32
32
[ leaf proposal ] : https://github.com/dart-lang/language/issues/2595
33
33
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
+
34
38
## Motivation
35
39
36
40
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
406
410
they declare but a lint can be turned on for users who want to be reminded if
407
411
they re-add a capability in a subtype.
408
412
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.
410
459
411
460
## Syntax
412
461
413
462
This proposal builds on the existing sealed types proposal so the grammar
414
463
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:
417
466
418
467
* ` sealed ` implies ` abstract ` , so they can't be combined.
419
468
* ` sealed ` implies non-extensibility, so can't be combined with ` interface `
@@ -422,9 +471,13 @@ combinations don't make sense:
422
471
` final ` .
423
472
* ` base ` , ` interface ` , and ` final ` all control the same two capabilities so
424
473
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.
425
476
426
477
The remaining valid combinations are:
427
478
479
+ <table ><tr ><td >
480
+
428
481
```
429
482
class
430
483
sealed class
@@ -435,14 +488,34 @@ abstract class
435
488
abstract base class
436
489
abstract interface class
437
490
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 >
438
508
509
+ ```
439
510
mixin
440
511
sealed mixin
441
512
base mixin
442
513
interface mixin
443
514
final mixin
444
515
```
445
516
517
+ </td ></tr ></table >
518
+
446
519
The grammar is:
447
520
448
521
```
@@ -452,36 +525,47 @@ classDeclaration ::=
452
525
'{' (metadata classMemberDeclaration)* '}'
453
526
| classModifiers 'class' mixinApplicationClass
454
527
455
- classModifiers ::= 'sealed' | 'abstract'? ('base' | 'interface' | 'final')?
456
-
457
- mixinDeclaration ::= mixinModifiers? 'mixin' identifier typeParameters?
528
+ mixinDeclaration ::= mixinModifier? 'mixin' identifier typeParameters?
458
529
('on' typeNotVoidList)? interfaces?
459
530
'{' (metadata classMemberDeclaration)* '}'
460
531
461
- mixinModifiers ::= 'sealed' | 'base' | 'interface' | 'final'
532
+ classModifiers ::= ( 'sealed' | 'abstract'? typeModifier? ) 'mixin'?
533
+ mixinModifier ::= 'sealed' | typeModifier
534
+ typeModifier ::= 'base' | 'interface' | 'final'
462
535
```
463
536
464
537
### Static semantics
465
538
466
539
It is a compile-time error to:
467
540
468
541
* Extend a class marked ` interface ` or ` final ` outside of the library where it
469
- is defined .
542
+ is declared .
470
543
471
544
* Implement a type marked ` base ` or ` final ` outside of the library where it is
472
- defined .
545
+ declared .
473
546
474
547
* 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
476
549
subtype can't escape the ` base ` restriction of its supertype by offering its
477
550
_ own_ interface that could then be implemented without inheriting the
478
551
concrete implementation from the supertype.*
479
552
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.*
485
569
486
570
A typedef can't be used to subvert these restrictions. When extending,
487
571
implementing, or mixing in a typedef, we look at the library where type the
0 commit comments