Skip to content

Commit 02790af

Browse files
committed
Update modules to disallow combining mixins with other types.
1 parent 41107c2 commit 02790af

File tree

1 file changed

+122
-110
lines changed

1 file changed

+122
-110
lines changed

working/modules/feature-specification.md

Lines changed: 122 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,26 @@ module.
5252

5353
## Capability controls on types
5454

55-
Dart defaults to being maximally permissive. When you define a class, it can,
56-
unless prohibited by its own structure, be used as an interface, superclass, or
57-
mixin. This is useful for consumers of the class because they are given the
58-
flexibility to do with it as they will. This flexibility comes with some
59-
downsides:
55+
There are three fundamental kinds of entities in Dart's semantics:
56+
57+
* A **class** has a set of member declarations and a superclass (which may be
58+
Object). You can use a class **construct** new instances (if not abstract)
59+
and/or you can **extend** one as a superclass.
60+
61+
* An **interface** is a set of member *signatures*.
62+
63+
* A **mixin** is a set of member declarations. Unlike a class, a mixin does
64+
*not* have a superclass. You have to apply the mixin to some concrete
65+
superclass in order to get a class that you can construct.
66+
67+
Dart's syntax somewhat obscures this. There is no dedicated syntax for declaring
68+
an interface. Until recently there was also no syntax for declaring a mixin.
69+
Instead, a class declaration can, unless prohibited by its own structure, be
70+
used as an interface, superclass, or mixin.
71+
72+
Inferring interfaces from classes and mixins a useful tool to avoid the
73+
redundancy found in Java and C# code. It provides consumers of the class maximum
74+
flexibility. But it comes at a cost:
6075

6176
* Since a class may be used as an interface, adding a method is potentially a
6277
breaking change, even if the author never intended the class's interface to
@@ -81,39 +96,24 @@ downsides:
8196

8297
[ex]: https://github.com/dart-lang/language/blob/master/working/0546-patterns/patterns-feature-specification.md#exhaustiveness-and-reachability
8398

84-
Because of these, users ask for control over the affordances a class provides
85-
([349][], [704][], [987][]). Adding modules to the language is a good
86-
opportunity to add those.
87-
88-
Note that the above reasons only come into play when *unknown code* works with
89-
a class. Thus these restrictions only apply to code using a class outside of
90-
the module where the class is declared. Inside the class's own module, you are
91-
free to use the class however you want. It's your class.
92-
93-
There are four capabilities a class may expose to outside code:
99+
Because of these, users ask for control over the affordances a declaration
100+
provides ([349][], [704][], [987][]). Modules are a natural boundary for those
101+
restrictions.
94102

95103
[349]: https://github.com/dart-lang/language/issues/349
96104
[704]: https://github.com/dart-lang/language/issues/704
97105
[987]: https://github.com/dart-lang/language/issues/987
98106

99-
* **Constructible.** – Whether a new instance can be created by calling one of
100-
its constructors.
107+
Note that the above problems only come into play when *unknown code* works with
108+
a type. Thus these restrictions only apply to code using a type outside of the
109+
module where the type is declared. Inside the types's own module, you are free
110+
to use types however you want. It's your code.
101111

102-
* **Extensible.** – Whether the class can be used as a superclass of another
103-
class in its `extends` clause.
112+
### Types of types and capabilities
104113

105-
* **Implementable.** – Whether the class exposes an interface that can be
106-
implemented.
107-
108-
* **Mix-in** – Whether the class defines a mixin can be mixed in to other
109-
classes.
110-
111-
Ideally, a class would have full control over which of these capabilities it
112-
allows and all combinations would be expressible. Dart already supports a
113-
couple of combinations: an `abstract class` cannot be constructed, and a `mixin`
114-
declaration cannot be constructed or extended.
115-
116-
An analysis of Google's corpus shows these combinations are most common:
114+
Restating the above, there are four affordances a type might offer:
115+
**construct**, **extend**, **implement**, and **mix in**. An analysis of the
116+
class declarations in Google's corpus shows these combinations are most common:
117117

118118
```
119119
Construct 63.93% 6605
@@ -124,114 +124,126 @@ Construct + Implement 2.36% 244
124124
Mixin 1.25% 129
125125
```
126126

127-
All other combinations are less than 1% *but do occur in practice*. The latter
128-
implies that we should support all 16 combinations. To keep declarations terse,
129-
the default behavior should reflect the most common combinations and modifiers
130-
should opt for less common choices. Given the numbers above, that means classes
131-
should default to constructible, but not extensible, implementable, or
132-
mixin-able ("miscible"?).
127+
All other combinations are less than 1%. Every combination occurs in practice,
128+
though the few examples of combinations involving mixins and classes seem to be
129+
historical from the time before Dart's dedicated mixin syntax.
130+
131+
## Mixins and classes
132+
133+
Combinations of classes and interfaces make sense. Likewise, it seems natural to
134+
derive an interface from a mixin. But deriving both a class and a mixin from the
135+
same declaration has proven to be confusing.
136+
137+
A mixin, by definition, has no superclass. In order to construct or extend
138+
something, it must be a full-formed class with an inheritance chain all the way
139+
up to Object. When you derive a mixin from a class today, Dart discards the
140+
superclass and any inherited methods.
141+
142+
This is a continuing source of confusion for users, which is one reason we added
143+
dedicated `mixin` syntax. Now that we have language versioning, we can complete
144+
that transition. With this proposal, **a class declaration no longer defines an
145+
implicit mixin declaration.** The only way to create a mixin is using `mixin`.
133146

134147
### Syntax
135148

136-
Following Dart's existing syntax, we use `mixin` to allow mixing-in and
137-
`abstract` to prevent constructing. Following Java and others, we use
138-
`interface` to allow implementing. Following Swift and Kotlin, we use `open` to
139-
allow subclassing.
149+
We support all combinations of the above four capabilities, except for
150+
combinations with Mixin + Construct or Mixin + Extend.
140151

141-
Using all of those strictly as modifiers on `class` would lead to some awkard
142-
combinations (like `abstract interface` for what would just be `interface` in
143-
other languages), so the rules for combining them are a little more complex. In
144-
grammarese, the allowed combinations and modifier orders are:
152+
Following Dart's existing syntax, we use `class` to define classes, `mixin`
153+
define mixins, and `abstract` to prevent constructing. Following Java and
154+
others, we use `interface` (as a modifier here) to allow implementing. Following
155+
Swift and Kotlin, we use `open` to allow subclassing.
156+
157+
The updated grammar is:
145158

146159
```
147160
topLevelDeclaration ::=
148-
abstractClassDeclaration
149-
| classDeclaration
150-
| interfaceDeclaration
161+
classDeclaration
151162
| mixinDeclaration
152163
// existing rules...
153164
154-
abstractClassDeclaration ::= 'open'? 'abstract' 'class' // ...
155-
classDeclaration ::= 'open'? 'interface'? 'mixin'? 'class' // ...
156-
mixinDeclaration ::= 'open'? 'interface'? 'mixin' // ...
157-
interfaceDeclaration ::= 'interface' // ...
165+
classDeclaration ::= 'open'? 'interface'? 'abstract'? 'class' identifier
166+
typeParameters? superclass? interfaces?
167+
'{' (metadata classMemberDeclaration)* '}'
168+
169+
mixinDeclaration ::= 'interface'? 'mixin' identifier typeParameters?
170+
('on' typeNotVoidList)? interfaces?
171+
'{' (metadata classMemberDeclaration)* '}'
158172
```
159173

160174
That yields these combinations:
161175

162176
```
163-
class // 63.93% Construct
164-
abstract class // 14.09% (none)
165-
interface // 9.77% Implement
166-
open abstract class // 6.47% Extend
167-
interface class // 2.36% Implement Construct
168-
mixin // 1.25% Mix-in
169-
open class // 0.86% Extend Construct
170-
open interface // 0.76% Implement Extend
171-
open interface class // 0.20% Implement Extend Construct
172-
open mixin // 0.14% Mix-in Extend
173-
interface mixin // 0.09% Mix-in Implement
174-
open interface mixin // 0.03% Mix-in Implement Extend
175-
mixin class // 0.02% Mix-in Construct
176-
open mixin class // 0.02% Mix-in Extend Construct
177-
interface mixin class // 0.01% Mix-in Implement Construct
178-
open interface mixin class // 0.00% Mix-in Implement Extend Construct
177+
class // 63.93% Construct
178+
abstract class // 14.09% (none)
179+
interface abstract class // 9.77% Implement
180+
open abstract class // 6.47% Extend
181+
interface class // 2.36% Implement Construct
182+
mixin // 1.25% Mix-in
183+
open class // 0.86% Extend Construct
184+
open interface abstract class // 0.76% Implement Extend
185+
open interface class // 0.20% Implement Extend Construct
186+
interface mixin // 0.09% Mix-in Implement
179187
```
180188

181-
Note that the names gradually get longer for less common combinations, so it
182-
seems this is roughly in line with keeping the common options terse.
189+
Using `abstract class` to opt out of constructing is pretty verbose, especially
190+
when combined with other modifiers. We could conceivably drop it from classes
191+
that have `interface`:
183192

184-
### Static semantics
193+
```
194+
interface abstract class -> interface
195+
open interface abstract class -> open interface
196+
```
197+
198+
But it might be confusing that a type declared using only `interface` does in
199+
fact define a class that may have concrete methods and even be constructed and
200+
used as a class inside the module. Dart users today are already used to using
201+
`abstract class` to declare interfaces, so this isn't too much of a stretch.
202+
Also, this leaves `interface` available as potential future syntax for defining
203+
a pure interface, if a need for such thing should arrive.
185204

186-
There are four "kinds" of types: `abstract class`, `class`, `interface`, and
187-
`mixin`.
205+
### Static semantics
188206

189-
The rules for using the type within its module are as permissive as possible
207+
Within a module, despite all the new modifiers, the semantics are roughly the
208+
same. The rules for using a type within its module are as permissive as possible
190209
and are based on the structure of the type itself, as in current Dart:
191210

192-
* It is a compile-time error to invoke a generative constructor of a type if
193-
the type defines or inherits any unimplemented abstract members. *You can
194-
directly construct anything internally if it wouldn't cause a problem to do
195-
so, even an interface or an abstract class.* **TODO: Even a mixin?**
211+
* It is a compile-time error to invoke a generative constructor of a class if
212+
the class defines or inherits any unimplemented abstract members. *You can
213+
directly construct abstract classes internally if it wouldn't cause a
214+
problem to do so. Mixins never have generative cosntructors.*
196215

197-
* It is a compile-time error to extend a type that has at least one factory
198-
constructor and no generative constructors.
216+
* It is a compile-time error to extend a class that has at least one factory
217+
constructor and no generative constructors. It is a compile time error to
218+
extend a mixin.
199219

200-
* It is a compile-time error to mix in a type that explicitly declares a
201-
generative constructor or has a superclass other than `Object`.
220+
* It is a compile-time error to mix in a class.
202221

203222
The rules for using types *outside* of their module are based on the
204-
capabilities the type explicitly provides:
223+
capabilities the type explicitly permits:
205224

206-
* It is a compile-time error to invoke a generative constructor of an abstract
207-
class, interface, or mixin outside of the module where the type is defined.
225+
* It is a compile-time error to invoke a generative constructor of a class
226+
marked `abstract` outside of the module where the class is defined.
208227

209-
* It is a compile-time error for a type to appear in an `extends` clause
210-
outside of the module where the type is defined unless the type is marked
228+
* It is a compile-time error for a class to appear in an `extends` clause
229+
outside of the module where the class is defined unless the class is marked
211230
`open`.
212231

213232
* It is a compile-time error for a type to appear in an `implements` clause
214-
outside of the module where the type is defined unless the type is an
215-
interface or is marked `interface`.
233+
outside of the module where the type is defined unless the type is marked
234+
`interface`.
216235

217-
* It is a compile-time error for a type to appear in a `with` clause outside
218-
of the module where the type is defined unless the type is a mixin or is
219-
marked `mixin`.
236+
* It is a compile-time error for a class to appear in a `with` clause.
220237

221238
We also want to make sure the type structurally supports any capability it
222239
claims to offer. This helps package maintainers catch mistakes where they
223240
inadvertently break a capability that the type offers.
224241

225242
* It is a compile-time error if a non-abstract class contains an abstract
226-
method or inherits an abstract method no corresponding implementation.
243+
method or inherits an abstract method with no corresponding implementation.
227244
*Since a non-abstract class declares that code outside the module can
228-
construct it, this rule ensures that it is safe to do so.*
229-
230-
*All other kinds of types -- abstract classes, interfaces, and mixins -- may
231-
contain both abstract and non-abstract members. Even interfaces can contain
232-
non-abstract members. This is because while an interface can't be
233-
constructed or extended outside of the module, it can be internally if it
234-
has no abstract members.*
245+
construct it, this rule ensures that it is safe to do so. Abstract classes
246+
and mixins may contain both abstract and non-abstract members.*
235247

236248
* It is a compile-time error if a public-named type marked `class` does not
237249
have a public-named constructor. *The constructor can be a default or
@@ -244,13 +256,10 @@ inadvertently break a capability that the type offers.
244256

245257
**TODO: Is this too much of a restriction?**
246258

247-
* It is a compile-time error if a public-named type marked `mixin` defines
248-
any constructors. **TODO: Is this restriction correct?**
249-
250259
* It is a compile-time error if a class C marked `interface` has a superclass
251260
D which is not also marked `interface`, unless C and D are declared in the
252-
same module. *In other words, someone can't extend a class with no interface
253-
that they don't control and then retroactively give it an interface by way
261+
same module. *In other words, someone can't extend a class with an interface
262+
that they don't control and then retroactively expose its interface by way
254263
of a subclass. This ensures that if you declare a class C with no interface,
255264
then any object of type C will reliably be an instance of your actual class
256265
C or some other type you control.*
@@ -271,24 +280,27 @@ particular:
271280
* If the class has at least one generative constructor (which may be default),
272281
it is treated as implicitly marked `open`.
273282

274-
* If the class has at least one generative constructor (which may be default)
275-
and is not marked `abstract` it is treated as implicitly marked `class`.
276-
277283
* If the class has no non-default generative constructors, and `Object` as
278-
superclass, it is treated as implicitly marked `mixin`.
284+
superclass, it continues to expose an implicit mixin.
279285

280286
[language versioning]: https://dart.dev/guides/language/evolution#language-versioning
281287

282288
When updating a library to the language version that supports modules, you'll
283289
want to decide what capabilities to offer, or just place all the modifiers you
284-
can to preserve the class's current behavior.
290+
need to preserve the class's current behavior.
291+
292+
Migrating a class that is used both as a class and a mixin is harder. For that,
293+
you will have to migrate it to two separate declarations and give one of them
294+
a different name. Fortunately, classes used this way are very rare.
285295

286296
**TODO: Investigate tooling to automatically migrate.**
287297

288298
## Capability controls on members
289299

290300
**TODO: Do we want 'final' non-overridable members?**
291301

302+
**TODO: Protected?**
303+
292304
## Implicit modules and legacy code
293305

294306
**TODO**

0 commit comments

Comments
 (0)