@@ -31,6 +31,10 @@ proposal, this proposal is non-breaking.
3131https://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
3640Dart'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
406410they declare but a lint can be turned on for users who want to be reminded if
407411they 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
413462This proposal builds on the existing sealed types proposal so the grammar
414463includes 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
426477The remaining valid combinations are:
427478
479+ <table ><tr ><td >
480+
428481```
429482class
430483sealed class
@@ -435,14 +488,34 @@ abstract class
435488abstract base class
436489abstract interface class
437490abstract 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+ ```
439510mixin
440511sealed mixin
441512base mixin
442513interface mixin
443514final mixin
444515```
445516
517+ </td ></tr ></table >
518+
446519The 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
466539It 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
486570A typedef can't be used to subvert these restrictions. When extending,
487571implementing, or mixing in a typedef, we look at the library where type the
0 commit comments