-
Notifications
You must be signed in to change notification settings - Fork 213
Class modifiers for anonymous mixin applications #2830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Nice! |
… on clause Implements the proposed spec for dart-lang/language#2830 and along the way, fixes another change with anonymous mixins erroring in on-clauses. Change-Id: I15ed7370f9099d9de89d80f5844db76f58fbd216 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/284481 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Kallen Tu <[email protected]>
I suggest we just assume this definition to be the desired behavior for now. I'll write it into the specification when I find some time. We need something like this, to ensure that The goal is that users can safely ignore the intermediate classes when thinking about their class modifiers. If there are any missing edge cases, we'll handle them from here. |
Sounds good! Implicitly adding the required class modifiers to mixin applications is probably good for the comprehensibility of the One more thing comes to mind, though: In // Library 'a.dart'.
interface mixin M {
void foo() { print('Should not be inherited outside this library'); }
}
class A extends Object with M {}
// Library 'b.dart'.
import 'a.dart';
class B extends A {} // Inherits `M.foo`, which shouldn't happen.
void main() {
B().foo();
} If we do not consider If we do consider So the point is that we'd want to consider |
I can see the point. Assume: interface class I {}
class C extends I {} If the point if The same applies to mixin applications which inherit the implementation. That does mean that the logic becomes:
More complicated than before, but it's still just a simple partial order LUB. |
I think it may be a bit easier to check the rules if they are expressed without 'otherwise'.
|
Yes. The specification changed to only be defined in terms of declarations, so the implicit intermediate classes introduced here do not need to have any modifiers. |
An anonymous mixin application class is the result of a mixin application that isn't named by a declaration.
It's every mixin application class other than the final resulting class of a
class C = S with M1, ..., Mn;
declaration, where the mixin application ofMn
toS with M1,...,M{n-1}
is the class namedC
.Proposed semantics
sealed
(and therefore necessarily in the same library),sealed
(which implies being abstract).abstract
(like all anonymous mixin application classes are today), andS
orM
is declaredfinal
(and therefore necessarily in the same library)final
.base
,base
.Background and motivation.
TL;DR: This adds modifiers to the anonymous mixin application classes which satisfy and propagate the modifiers of the superclass and mixin, which allows us to completely ignore that ananymous
We have rules for required class modifiers for subclasses that make sense for declared subclasses, but we haven't addressed anonymous mixin application classes which has no declaration of their own.
We should make sure the classes of such mixin applications satisfy the rules we have for classes, and that they behave usefully where necessary, in particular around exhaustiveness, since users can't attach modifiers to them directly.
Examples of anonymous mixin declaration classes:
These declarations introduce five new classes:
C:S&M1
:S with M1
from theC
declarationC:S&M1&M2
:C:S&M1 with M2
from theC
declaration.C
itself, hasC:S&M1&M2
as superclass, aka the "mixin application"C:S&M1&M2 with { .... }
D:S&M1
:S with M1
from theD
declaration.D
itself which is the named mixin application classD:S&M1 with M2
.The only non-mixin-application class is C.
The only non-anonymous mixin application class is the one named
D
.Being anonymous, an anonymous mixin application cannot be referenced from anywhere other than the context where it is written. The only role of an anonymous mixin application is to be the superclass of one other class, which is always in the same library as the mixin application itself.
(We let compilers combine/canonicalize equivalent mixin applications, so they'd only have one
S&M1
class. That's not technically in the language specification, but nobody cares, and you can't tell without usingdart:mirrors
, at least since we seem to actively avoid that class becoming the LUB of two subclasses.)The current semantics make every anonymous mixin application be implicitly abstract, and we preserve that (it's implied by
sealed
).We extend that to apply other modifiers as necessary and/or useful.
We mark the mixin application
final
if either superclass isfinal
(and notsealed
),and
base
if either superclass isbase
(and notfinal
orsealed
).The marker is just there to satisfy the "every subclass of a
base
orfinal
declaration must itself bebase
,final
orsealed
" rule, and to not implicitly reopen a super-class (to make the expected "implicit-reopen" lint not have to special-case anonymous mixin application classes).The propagation of
sealed
is desirable because of how exhaustiveness works.If you declare:
you'd probably expect
A
andB
to exhaustMyBase
.However, if
MyBase with _Helper
is not sealed, exhaustingMyBase
won't necessarily be taken as exhaustingMyBase with _Helper
, in which casecase A _ => ..., case B _ => ...
wont be exhaustingMyBase
.When
MyBase with _Helper
issealed
, and it has only one subclass (B
), exhaustingB
will exhaustMyBase with _Helper
too, using the same algorithm as for sealed classes with other explicitly sealed direct subclasses.We also propagate
sealed
from the mixin. It's probably more speculative, but take:This declares a sealed type with precisely two declared subclasses in the same library. Again we'd expect
M1
andM2
to exhaustM
. And again, if we don't treatA with M
, the actual immediate subclass ofM
, assealed
, exhaustingM1
won't exhaustA with M
, which means that there is a subclass ofM
which the algorithm may have to assume can have other subclasses.We could also just say that the exhaustiveness checking algorithm on sealed classes bypasses anonymous mixin application classes and go directly to the non-anonymous subclass.
That's effectively what we do by saying that the anonymous class is
sealed
, but with the added benefit that it allows us to avoid special-casing and also desugar composite mixin applications while preserving their behavior.We can, and sometimes do, treat anonymous mixin applications as if they were declared by freshly-private-named mixin application declarations of precisely one mixin, so the above would be:
All mixin applications are then of the form
class C = S with M implements I...I;
.We prefer to be able to continue doing that rewrite, and applying the
abstract
/base
/final
/sealed
modifiers as described above, rather than skipping the anonymous classes internally in the algorithm, ensures that rewriting the mixin applications into their explicit form preserves semantics precisely.@leafpetersen @kallentu @munificent @eernstg @stereotype441 @natebosch @jakemac53
EDIT: Made mixin application classes be only
base
if sufficient, to avoid reopening.The text was updated successfully, but these errors were encountered: