1
1
# Class modifiers
2
2
3
- Author: Bob Nystrom
3
+ Author: Bob Nystrom, Lasse Nielsen
4
4
5
5
Status: Accepted
6
6
7
- Version 1.3
7
+ Version 1.4
8
8
9
9
Experiment flag: class-modifiers
10
10
@@ -24,7 +24,7 @@ Informally, the new syntax is:
24
24
25
25
* ` interface ` : As a modifier on a class or mixin, allows the type to be
26
26
implemented but not extended or mixed in. In other words, it takes away
27
- being used as a subclass through extension or mixing in .
27
+ being able to inherit from the type .
28
28
29
29
* ` final ` : As a modifier on a class or mixin, prohibits extending,
30
30
implementing, or mixing in.
@@ -420,6 +420,51 @@ This proposal takes the last option where types have exactly the restrictions
420
420
they declare but a lint can be turned on for users who want to be reminded if
421
421
they re-add a capability in a subtype.
422
422
423
+ ### Inherited restrictions
424
+
425
+ Allowing you to ignore restrictions on your own types allows some useful
426
+ architectural patterns, but it's important that doing so doesn't let you ignore
427
+ restrictions on types from *other* libraries because then you could break the
428
+ invariants the library expects. In particular, consider:
429
+
430
+ ```dart
431
+ // lib_a.dart
432
+ base class A {
433
+ void _private() {
434
+ print('Got it.');
435
+ }
436
+ }
437
+
438
+ callPrivateMethod(A a) {
439
+ a._private();
440
+ }
441
+ ```
442
+
443
+ This library declares a class and marks it ` base ` to ensure that every instance
444
+ of ` A ` in the program must be an ` A ` or a class that inherits from it. That in
445
+ turn ensures that the call to ` _private() ` in ` callPrivateMethod() ` is always
446
+ safe.
447
+
448
+ Now consider:
449
+
450
+ ```
451
+ // lib_b.dart
452
+ import 'lib_a.dart';
453
+
454
+ base class B extends A {} // OK: Inheriting.
455
+
456
+ class C implements B {} // OK: Ignoring restriction on own type B.
457
+ ```
458
+
459
+ These two class declarations each seem to be fine. But put together, the result
460
+ is a class ` C ` that is a subtype of ` A ` but doesn't inherit from it and doesn't
461
+ have the ` _private() ` method that lib_a.dart expects.
462
+
463
+ So we want to allow libraries to ignore restrictions on their own types, but we
464
+ need to be careful that doing so doesn't break invariants in * other* libraries.
465
+ In practice, this means that when a class opts out of being implemented using
466
+ ` base ` or ` final ` , then that particularl restriction can't be ignored.
467
+
423
468
## Mixin classes
424
469
425
470
In line with Dart's permissive default nature, Dart allows any class declaration
@@ -458,7 +503,8 @@ This proposal builds on the existing sealed types proposal so the grammar
458
503
includes those changes. The full set of modifiers that can appear before a class
459
504
or mixin declaration are ` abstract ` , ` sealed ` , ` base ` , ` interface ` , ` final ` , and ` mixin ` .
460
505
461
- * The modifiers do not apply to other declarations. This includes ` enum ` declarations.
506
+ * The modifiers do not apply to other declarations like ` enum ` , ` typedef ` , or
507
+ ` extension ` .*
462
508
463
509
Many combinations don't make sense:
464
510
@@ -530,62 +576,142 @@ mixinModifier ::= 'sealed' | 'base' | 'interface' | 'final'
530
576
531
577
## Static semantics
532
578
579
+ A pair of definitions:
580
+
581
+ * A * pre-feature library* is a library whose language version is lower than
582
+ the version this feature is released in.
583
+
584
+ * A * post-feature library* is a library whose language version is at or above
585
+ the version this feature is released in.
586
+
587
+ ### Basic restrictions
588
+
533
589
It is a compile-time error to:
534
590
535
- * Extend a class marked ` interface ` , ` final ` or ` sealed ` outside of the library
536
- where it is declared.
537
-
538
- * Implement the interface of a class or mixin marked ` base ` , ` final ` or ` sealed `
539
- outside of the library where it is declared.
540
-
541
- * Mix in a mixin or mixin class marked ` interface ` , ` final ` or ` sealed ` outside
542
- of the library where it is declared.
591
+ * Extend a class marked ` interface ` , ` final ` or ` sealed ` outside of the
592
+ library where it is declared.
543
593
544
- * Extend a class marked ` base ` outside of the library where it is declared
545
- unless the extending class is marked ` base ` or ` final ` . * This ensures that a
546
- subtype can't escape the ` base ` restriction of its supertype by offering its
547
- _ own_ interface that could then be implemented without inheriting the
548
- concrete implementation from the supertype.* <!-- Needs to account for `sealed` -->
594
+ * Implement the interface of a class or mixin marked ` base ` , ` final ` or
595
+ ` sealed ` outside of the library where it is declared.
549
596
550
- * Mix in a mixin or mixin class marked ` base ` outside of the library where it
551
- is declared unless the class mixing it in is marked ` base ` or ` final ` . * As
552
- with the previous rule, ensures you can't get a backdoor interface on a
553
- mixin that doesn't want to expose one.*
554
-
555
- * Apply ` mixin ` to a class whose superclass is not ` Object ` or that declares a
556
- _ non-trivial generative constructor_ .
557
- * Such a class can have an ` extends ` clause of the form ` extends Object ` ,
558
- or no ` extends ` clause. It cannot have any ` with ` clause.*
597
+ * Mix in a mixin or mixin class marked ` interface ` , ` final ` or ` sealed `
598
+ outside of the library where it is declared.
599
+
600
+ There is no direct restriction on types allowed in ` on ` clauses of mixin
601
+ declarations.
602
+
603
+ A typedef can't be used to subvert these restrictions or any of the restrictions
604
+ below. When extending, implementing, or mixing in a typedef, we look at the
605
+ library where class or mixin the typedef resolves to is defined to determine if
606
+ the behavior is allowed. * Note that the library where the _ typedef_ is defined
607
+ does not come into play. Typedefs cannot be marked with any of the new
608
+ modifiers.*
609
+
610
+ ### Disallowing implementation
611
+
612
+ It is a compile-time error if a subtype of a declaration is marked ` base ` or
613
+ ` final ` is not marked ` base ` , ` final ` , or ` sealed ` . This restriction applies to
614
+ both direct and indirect subtypes and along all paths that introduce subtypes:
615
+ ` implements ` clauses, ` extends ` clauses, ` with ` clauses, and ` on ` clauses. This
616
+ restriction applies even to types within the same library.
617
+
618
+ * Once the ability to use as an interface is removed, it cannot be reintroduced
619
+ in a subtype. If a class is marked ` base ` or ` final ` , you may still implement
620
+ the class's interface inside the same library, but the implementing class must
621
+ again be marked ` base ` , ` final ` , or ` sealed ` to avoid it exposing an
622
+ implementable interface.*
623
+
624
+ Further, while you can ignore some restrictions on declarations within the same
625
+ library, you can't use that to ignore restrictions inherited from other
626
+ libraries.
559
627
560
- A _ trivial generative constructor _ is a non-redirecting generative constructor
561
- which has
628
+ We say a class or mixin declaration ` D ` * can't be implemented locally * if it
629
+ extends, mixes in, implements, or has as on type any declaration ` S ` where:
562
630
563
- * an empty parameter list,
564
- * no initializer list (no ` : ... ` ),
565
- * no constructor body (only ` ; ` ),
566
- * and is not marker ` external ` .
631
+ * ` S ` is from another library than ` D ` , and ` S ` has the modifier ` base ` ,
632
+ ` final ` or ` sealed ` , or
567
633
568
- Any other generative constructor is non-trivial.
569
- A trivial generative constructor may be ` const ` and may be a named constructor.
634
+ * ` S ` is from the same library as ` D ` , and ` S ` can't be implemented locally.
570
635
571
- * Declaring a trivial generative constructor allows the class to be used as
572
- both a mixin and as a superclass, even if it also declares other factory constructors
573
- which suppress the default constructor.*
636
+ Otherwise, ` D ` can be implemented locally. It is a compile-time error if:
574
637
575
- * Mix in a class not marked ` mixin ` which has a superclass other than ` Object ` .
638
+ * A class or mixin declaration ` D ` can't be implemented locally, and ` D ` is
639
+ not marked ` base ` , ` final ` or ` sealed ` .
576
640
577
- * Mix in a class not marked ` mixin ` declared in a library with a language version including
578
- this feature, if the mixin application is not in the same library,
579
- or if the class has any non-trivial generative constructor .
641
+ * A class or mixin declaration ` D ` implements the interface of a class or
642
+ mixin declaration ` S ` , declared in the same library as ` D ` , and ` S ` can't be
643
+ implemented locally .
580
644
581
- * Mix in a class not marked ` mixin ` from a library with a language version older than
582
- the version this feature ships in, if the class declares any generative constructor.
645
+ ### Mixin restrictions
583
646
584
- A typedef can't be used to subvert these restrictions. When extending,
585
- implementing, or mixing in a typedef, we look at the library where class or
586
- mixin the typedef resolves to is defined to determine if the behavior is
587
- allowed. * Note that the library where the _ typedef_ is defined does not come
588
- into play. Typedefs cannot be marked with any of the new modifiers.*
647
+ There are a few changes around mixins to support ` mixin class ` and disallow
648
+ using normal ` class ` declarations as mixins while dealing with language
649
+ versioning and backwards compatibility.
650
+
651
+ Currently, a class may only be used as a mixin if it has a default constructor.
652
+ This prevents the class from defining a ` const ` constructor or any factory
653
+ constructors. We loosen this somewhat. Define a * trivial generative constructor*
654
+ to be a generative constructor that:
655
+
656
+ * Is not a redirecting constructor,
657
+
658
+ * declares no parameters,
659
+
660
+ * has no initializer list (no ` : ... ` part),
661
+
662
+ * has no body (only ` ; ` ), and
663
+
664
+ * is not ` external ` . * An ` external ` constructor is considered to have an
665
+ externally provided initializer list and/or body.*
666
+
667
+ A trivial constructor may be named or unnamed, and ` const ` or non-` const ` . A
668
+ * non-trivial generative constructor* is a generative constructor which is not a
669
+ trivial generative constructor.
670
+
671
+ It's a compile-time error if:
672
+
673
+ * A ` mixin class ` declaration has a superclass other than ` Object ` . * The
674
+ declaration is limited to an ` extends Object ` clause or no ` extends ` clause,
675
+ and no ` with ` clauses. The class grammar prohibits ` on ` clauses.*
676
+
677
+ * A ` mixin class ` declaration declares any non-trivial generative constructor.
678
+ * It may declare no constructors, in which case it gets a default
679
+ constructor, or it can declare factory constructors and/or trivial
680
+ generative constructors.*
681
+
682
+ These rules ensure that when you mark a ` class ` with ` mixin ` that it * can* be
683
+ used as one.
684
+
685
+ A class not marked ` mixin ` can still be used as a mixins when the class's
686
+ declaration is in a pre-feature library. Post-feature libraries can also mix in
687
+ their own classes that aren't marked ` mixin ` as long as the class supports it.
688
+ Specifically:
689
+
690
+ It's a compile-time error to for a declaration in library ` L ` to mix in a
691
+ non-` mixin ` class declaration ` D ` from library ` K ` if any of:
692
+
693
+ * The superclass of ` D ` is not ` Object ` ,
694
+
695
+ * ` K ` is a pre-feature library, and ` D ` declares any constructors, or
696
+
697
+ * For pre-feature libraries, we can't tell if the intent of ` class ` was "just
698
+ a class" or "both a class and a mixin". For compatibility, we assume the
699
+ latter, even if the class is being used as a mixin in a post-feature library
700
+ and where it does happen to be possible to distinguish those two intents.*
701
+
702
+ * ` K ` is a post-feature library, and any of:
703
+
704
+ * ` K ` is not the same library as ` L ` ,
705
+
706
+ * ` D ` is a mixin-application class declaration (has the form `class D =
707
+ ...;`), or
708
+
709
+ * ` D ` declares any non-trivial generative constructors.
710
+
711
+ * When a class is in a library where it possible to distinguish between
712
+ whether the class is intended to use it as a mixin or not, the author is
713
+ obligated to document the intent, and that intent applies to all other
714
+ libraries regardless of their version.*
589
715
590
716
### ` @reopen ` lint
591
717
@@ -616,44 +742,26 @@ The changes in this proposal are guarded by a language version. This makes the
616
742
restriction on not allowing classes to be used as mixins by default
617
743
non-breaking.
618
744
619
- Let ` n ` be the language version this proposal ships in. Then:
620
-
621
- * ` base ` , ` interface ` , ` final ` , ` sealed ` and ` mixin ` can only be applied to classes and
622
- mixins in libraries whose language version is ` >= n ` .
745
+ * ` base ` , ` interface ` , ` final ` , ` sealed ` and ` mixin ` can only be applied to
746
+ classes and mixins in post-feature libraries.
623
747
624
748
* When the ` base ` , ` interface ` , ` final ` , ` mixin ` , or ` sealed ` modifiers are
625
749
placed on a class or mixin, the resulting restrictions apply to all other
626
- libraries, even libraries whose version is ` < n ` .
750
+ libraries, even pre-feature libraries .
627
751
628
752
* In other words, we gate being able to _ author_ the restrictions to
629
- libraries on version ` n ` . But once a type has those restrictions, they apply
753
+ post-feature libraries . But once a type has those restrictions, they apply
630
754
to all other libraries, regardless of the versions of those libraries.
631
755
"Ignorance of the law is no defense."*
632
756
633
- ** TODO:** Decide if we want to carve out an exception to this rule for the
634
- SDK core libraries.
757
+ * We would like to add modifiers to some classes in platform (i.e. ` dart: ` )
758
+ libraries when this feature ships. But we would also like to not immediately
759
+ break existing code. To avoid forcing users to immediately migrate,
760
+ declarations in pre-feature libraries can ignore modifiers on some
761
+ declarations in platform libraries. Instead, users will only have to abide
762
+ by those restrictions when they upgrade their library's language version.
635
763
636
- * A class declaration in a library whose language version is ` < n ` can be used
637
- as a mixin as long as the class meets the existing mixin restrictions
638
- (superclass is ` Object ` , declares no generative constructor). This is is
639
- true even if the library where the class is being used as a mixin is `>=
640
- n`.
641
-
642
- * For libraries whose version is ` < n ` , we can't tell if the intent of
643
- ` class ` was "just a class" or "both a class and a mixin". For compatibility,
644
- we assume the latter, even if the class is being used as a mixin in a
645
- library whose version is ` >= n ` and where it does happen to be possible to
646
- distinguish those two intents.*
647
-
648
- * A class declaration in a library whose version is ` >= n ` must be explicitly
649
- marked ` mixin class ` to allow the class to be used as a mixin from another
650
- library. This is true even if the library where the class is being used as
651
- a mixin is ` < n ` .
652
-
653
- * When a class is in a library where it possible to distinguish between
654
- whether the class is intended to use it as a mixin or not, the author is
655
- obliged to document the intent, and that intent applies to all other libraries
656
- regardless of their version.*
764
+ This is a special case behavior only available to platform libraries.
657
765
658
766
### Compatibility
659
767
@@ -665,12 +773,18 @@ needed.
665
773
666
774
## Changelog
667
775
776
+ 1.4
777
+
778
+ - Update rules to close loopholes on classes that don't want to expose
779
+ interfaces.
780
+
668
781
1.3
669
782
670
783
- Specify and update restrictions on ` mixin class ` declarations to allow
671
784
trivial generative constructors.
672
- - Specify that "mixin application" class declarations (` class C = S with M ` ) cannot
673
- be ` mixin class ` declaration, but can use other modifiers
785
+
786
+ - Specify that "mixin application" class declarations (` class C = S with M ` )
787
+ cannot be ` mixin class ` declaration, but can use other modifiers
674
788
675
789
1.2
676
790
0 commit comments