1
1
# Declaring Constructors
2
2
3
- Author: Bob Nystrom (based on ideas by Lasse, Nate, et al)
3
+ Author: Bob Nystrom (based on ideas by Erik, Lasse, Nate, et al)
4
4
5
5
Status: In-progress
6
6
7
- Version 0.1 (see [ CHANGELOG] ( #CHANGELOG ) at end)
7
+ Version 0.2 (see [ CHANGELOG] ( #CHANGELOG ) at end)
8
8
9
9
Experiment flag: declaring-constructors
10
10
@@ -222,9 +222,9 @@ But for the 1/3 of fields that are mutable, primary constructors avoid the `var`
222
222
that this proposal requires.
223
223
224
224
There are real brevity advantages to primary constructors. But that brevity
225
- comes with trade-offs. A primary constructor can't have a body, initializer
226
- list, of explicit superclass constructor call. If you need any of those, you're
227
- back to needing a regular in-body constructor.
225
+ comes with trade-offs. A primary constructor can't have a body and some versions
226
+ of the proposal prohibit initializer lists, or explicit superclass constructor
227
+ calls. If you need those, you're back to needing a regular in-body constructor.
228
228
229
229
A declaring constructor like in this proposal * is* an in-body constructor, so
230
230
it has none of those limitations. A class author can * always* make one of a
@@ -236,10 +236,16 @@ proposal because they have a body, explicit initializer list, etc. (On the other
236
236
hand, the fact that ~ 77% of constructors could still be primary constructors and
237
237
be even * more* terse than this proposal is a point in favor of that proposal.)
238
238
239
- I like to think of this feature as syntactic sugar for * instance fields* , not
240
- constructors. A class whose constructor initializes 80 fields will find either
241
- of these proposals 80 times more useful than a class with just one field. If
242
- you look at a corpus and count fields, the numbers are a little different.
239
+ I like to think of this feature as more about * instance fields and parameters*
240
+ than constructors themselves (beyond the tiny sweetness of ` this ` instead of a
241
+ class name). By that I mean that the brevity scales with the number of * fields*
242
+ that use it, not the number of constructors. A class whose constructor
243
+ initializes 80 fields will find either of these proposals 80 times more useful
244
+ than a class with just one field.
245
+
246
+ So when analyzing a corpus, it makes sense to look at * fields* that will use the
247
+ feature instead of * constructors* . If you look at a corpus and count fields, the
248
+ numbers are a little different.
243
249
244
250
In the same corpus, a little more than half (~ 53%) of all instance fields could
245
251
be implicitly declared using a primary constructor. Around ~ 65% could be
@@ -253,11 +259,79 @@ with factory constructors, redirecting constructors, initializing formals, and
253
259
super parameters, the amount of constructor-related syntactic sugar is getting a
254
260
little silly.
255
261
262
+ ### Private field parameters and initializing formals
263
+
264
+ While we're touching constructors, we have the opportunity to fix a
265
+ long-standing annoyance. Initializing formals are common and well-loved, but
266
+ they fail when you want to initialize a * private* field using a * named*
267
+ parameter. The field's name starts with ` _ ` , which isn't allowed for a named
268
+ parameter. Instead, you are forced to write explicit initializers like:
269
+
270
+ ``` dart
271
+ class House {
272
+ int? _windows;
273
+ int? _bedrooms;
274
+ int? _swimmingPools;
275
+
276
+ House({
277
+ int? windows,
278
+ int? bedrooms,
279
+ int? swimmingPools,
280
+ }) : _windows = windows,
281
+ _bedrooms = bedrooms,
282
+ _swimmingPools = swimmingPools;
283
+ }
284
+ ```
285
+
286
+ Note also that the author is forced to type annotate the parameters as well
287
+ since they are no longer inferred from the initialized field.
288
+
289
+ When I last [ analyzed a corpus] [ corpus private ] , 17% of all field initializers
290
+ in initializer lists were doing nothing but shaving off a ` _ ` . There is an
291
+ obvious intended semantics here: simply remove the ` _ ` from the named parameter
292
+ but keep it for the initialized field. Likewise, for declaring constructors,
293
+ the induced field keeps the ` _ ` while the parameter name loses it. That turns
294
+ the above example into:
295
+
296
+ [ corpus private ] : https://github.com/dart-lang/language/blob/db9f63185707c4c89a69118e842e4cc6e0e59cc3/resources/instance-initialization-analysis.md
297
+
298
+ ``` dart
299
+ class House {
300
+ int? _windows;
301
+ int? _bedrooms;
302
+ int? _swimmingPools;
303
+
304
+ House({this._windows, this._bedrooms, this._swimmingPools});
305
+ }
306
+ ```
307
+
308
+ And when combined with a declaring constructor:
309
+
310
+ ``` dart
311
+ class House {
312
+ this({
313
+ var int? _windows,
314
+ var int? _bedrooms,
315
+ var int? _swimmingPools,
316
+ });
317
+ }
318
+ ```
319
+
320
+ While this is a tiny sprinkle of syntactic sugar, it has a deeper value.
321
+ Initializing formals and declaring constructors are so much more concise that
322
+ users will want to use them whenever they can. But if they don't support private
323
+ fields and named parameters, then we are incentivizing users to make instance
324
+ fields public that they might otherwise prefer to keep private.
325
+
326
+ It's a well-established software engineering principle to minimize public state,
327
+ so we don't want the language to discourage users from encapsulating fields.
328
+
256
329
## Syntax
257
330
258
331
The syntax changes are small—just using ` this ` instead of the class name in a
259
- constructor. But weaving it into the grammar is a little complicated because
260
- some kinds of constructors can't be declaring:
332
+ constructor and allowing ` var ` on a parameter along with a type. But weaving it
333
+ into the grammar is a little complicated because some kinds of constructors
334
+ can't be declaring:
261
335
262
336
```
263
337
classMemberDeclaration ::=
@@ -278,11 +352,44 @@ the parameter. A declaring constructor can be `const` or not. It can't be
278
352
redirecting. It also can't be ` external ` since an external constructor has no
279
353
implicit initializer list or body where the fields could be initialized.*
280
354
355
+ We also need to allow ` var ` before a simple formal parameter while also allowing
356
+ a type annotation. (Today, ` var ` is allowed, but only in place of a type, like
357
+ how variables are declared.) We redefine ` simpleFormalParameter ` to:
358
+
359
+ ```
360
+ simpleFormalParameter ::=
361
+ 'covariant'? ('final' | 'var')? type? identifier
362
+ ```
363
+
364
+ * The ` simpleFormalParameter ` rule was previously defined in terms of the same
365
+ rules used for variable declarations. That meant that the grammar for parameters
366
+ allowed ` late ` and ` const ` . Those are then disallowed by the specification
367
+ outside of the grammar. Here, we eliminate the need for that extra-grammatical
368
+ restriction by defining a grammar specifically for simple formal parameters that
369
+ only includes what they allow.*
370
+
371
+ * We could allow ` late ` on field parameters and have that apply to the instance
372
+ field (but not the parameter since parameters are always initialized). That
373
+ could be useful in theory if there are other generative constructors that don't
374
+ initialize the field. But to avoid confusion, we simply don't allow it. If a
375
+ user wants a ` late ` instance field, they can always declare it outside of the
376
+ declaring constructor.*
377
+
378
+ It is a compile-time error for a ` simpleFormalParameter ` to have both ` var `
379
+ and a type outside of a declaring constructor.
380
+
381
+ * This keeps the normal parameter list grammar consistent with other variable
382
+ declarations. We could allow ` var int x ` as a parameter outside of a declaring
383
+ constructor, but doing so would be confusing because it looks like a field
384
+ parameter but isn't. Ideally, we would also disallow ` final ` on parameters
385
+ outside of declaring constructors, but doing so is a breaking change.*
386
+
281
387
## Static semantics
282
388
283
389
This feature is just syntactic sugar for things the user can already express,
284
390
so there are no interesting new semantics.
285
391
392
+ ### Declaring constructors
286
393
287
394
Given a ` declaringConstructor ` D in class C:
288
395
@@ -291,16 +398,28 @@ Given a `declaringConstructor` D in class C:
291
398
* If P has a ` final ` or ` var ` modifier, it is a ** field parameter** :
292
399
293
400
* Implicitly declare an instance field F on the surrounding class with
294
- the same name and type as P. * If P has no type, it implicitly has
295
- type ` dynamic ` , as does F.*
401
+ the same name and type as P. * If P has no type annotation , it
402
+ implicitly has type ` dynamic ` , as does F.*
296
403
297
404
* If P is ` final ` , then the instance field is also ` final ` .
298
405
299
- * Any doc comments and metadata annotations on P are also copied to F.
300
- * For example, a user could mark the parameter ` @override ` if the
301
- the implicitly declared field overrides an inherited getter.*
406
+ * Any doc comments and metadata annotations on P are also applied to
407
+ F. * For example, a user could mark the parameter ` @override ` if the
408
+ the implicitly declared field overrides an inherited getter. If a
409
+ user wants to document the instance field induced by a field
410
+ parameter, they can do so by putting a doc comment on the
411
+ parameter.*
412
+
413
+ * P now behaves like an initializing formal that initializes F.
414
+ Concretely:*
302
415
303
- * P comes an initializing formal that initializes F.
416
+ * A final local variable with the same name and type as P is
417
+ introduced into the formal parameter initializer scope * (the scope
418
+ that the initializer list has access to but the constructor body
419
+ does not)* .
420
+
421
+ * Parameter P is * not* bound in the formal parameter scope of D.
422
+ * Inside the body, references to P's name refer to F, not P.*
304
423
305
424
* Note that a declaring constructor doesn't have to have any field
306
425
parameters. A user still may want to use the feature just to use ` this `
@@ -320,7 +439,7 @@ It is a compile-time error if:
320
439
* The implicitly declared fields would lead to an erroneous class. * For
321
440
example if the class has a ` const ` constructor but one of the field
322
441
parameters induces a non-` final ` field, or an induced field collides with
323
- another member of the same name.
442
+ another member of the same name.*
324
443
325
444
* An implicitly declared field is also explicitly initialized in the declaring
326
445
constructor's initializer list. * This is really just a restatement of the
@@ -329,12 +448,79 @@ It is a compile-time error if:
329
448
330
449
* A field parameter is named ` _ ` . * We could allow this but... why?*
331
450
451
+ ### Private field parameters and initializing formals
452
+
453
+ An identifier is a * private name* if it starts with an underscore (` _ ` ),
454
+ otherwise it's a * public name* .
455
+
456
+ A private name may have a * corresponding public name* . If the characters of the
457
+ identifier with the leading underscore removed form a valid identifier and a
458
+ public name, then that is the private name's corresponding public name. * For
459
+ example, the corresponding public name of ` _foo ` is ` foo ` .* If removing the
460
+ underscore does not leave something which is is a valid identifier * (as in ` _ `
461
+ or ` _2x ` )* or leaves another private name * (as in ` __x ` )* , then the private name
462
+ has no corresponding public name.
463
+
464
+ The private declared name, * p* , of an initializing formal or field parameter in
465
+ constructor C has a corresponding * non-conflicting public name* if it has a
466
+ corresponding public name, * n* , and no other parameter of the same constructor
467
+ declaration has either of the names * p* or * n* as declared name. * In other
468
+ words, if removing the ` _ ` leads to a collision with another parameter, then
469
+ there is a conflict.*
470
+
471
+ Given an initializing formal or field parameter with private name * p* :
472
+
473
+ * If * p* has a non-conflicting public name * n* , then:
474
+
475
+ * The name of the parameter in C is * n* . * If the parameter is named, this
476
+ then avoids the compile-time error that would otherwise be reported for
477
+ a private named parameter.*
478
+
479
+ * The local variable in the initializer list scope of C is * p* . * Inside
480
+ the body of the constructor, uses of * p* refer to the field, not the
481
+ parameter.*
482
+
483
+ * If the parameter is an initializing formal, then it initializes a
484
+ corresponding field with name * p* .
485
+
486
+ * Else the field parameter induces an instance field with name * p* .
487
+
488
+ * Any generated API documentation for the parameter should also use * n* .*
489
+
490
+ * Else (there is no non-conflicting public name), the name of the parameter is
491
+ left alone and also used for the initialized or induced field. * If the
492
+ parameter is named, this is a compile-time error.*
493
+
494
+ * For example:*
495
+
496
+ ``` dart
497
+ class Id {
498
+ late final int _region = 0;
499
+
500
+ this({this._region, final int _value}) : assert(_region > 0 && _value > 0);
501
+
502
+ @override
503
+ String toString() => 'Id($_region, $_value)';
504
+ }
505
+
506
+ main() {
507
+ print(Id(region: 1, value: 2)); // Prints "Id(1, 2)".
508
+ }
509
+ ```
510
+
332
511
## Runtime semantics
333
512
334
- There are no runtime semantics for this feature.
513
+ The runtime semantics for field parameters inside a declaring constructor are
514
+ the same as for initializing formals:
515
+
516
+ Executing a field parameter with name * id* causes the instance variable * id*
517
+ of the immediately surrounding class to be assigned the value of the
518
+ corresponding actual argument.
335
519
336
520
## Compatibility
337
521
522
+ ### Declaring constructors
523
+
338
524
The identifier ` this ` is already a reserved word that can't appear at this
339
525
point in the grammar, so this is a non-breaking change that doesn't affect any
340
526
existing code.
@@ -363,10 +549,19 @@ a *very* small number of authors use.)
363
549
So while the potential for confusion is there, I think it's unlikely to be a
364
550
problem in practice.
365
551
552
+ ### Private field parameters and initializing formals
553
+
554
+ Any existing initializing formals with private names must be positional since
555
+ it's a compile-time error to have a private named parameter. Since those
556
+ arguments are passed positionally, the change to give the parameter a public
557
+ name has no effect on any callsites.
558
+
559
+ Generated documentation may change, but that should be harmless.
560
+
366
561
### Language versioning
367
562
368
- Even this change it's non-breaking, it is language versioned and can only be
369
- used in libraries whose language version is at or later than the version this
563
+ Even though this change is non-breaking, it is language versioned and can only
564
+ be used in libraries whose language version is at or later than the version this
370
565
feature ships in. This is mainly to ensure that users don't inadvertently try to
371
566
use this feature in packages whose SDK constraint allows older Dart SDK versions
372
567
that don't support the feature.
@@ -409,8 +604,32 @@ become a declaring one:
409
604
declaring? Probably the one with the most initializing formals, but what do
410
605
you do in case there's a tie?
411
606
607
+ ### Lint for ` final ` parameters
608
+
609
+ This proposal retcons the ` final ` modifier on parameters to mean something
610
+ specific in a declaring constructor. Outside of a declaring constructor the
611
+ modifier is allowed but has a different effect: it simply makes the parameter
612
+ itself non-assignable.
613
+
614
+ If declaring constructors become popular, then users will likely start to see
615
+ ` final ` before a parameter and read it as a field parameter. They will then be
616
+ confused if the parameter isn't actually in a declaring constructor and isn't
617
+ a field parameter.
618
+
619
+ Given that non-assignable parameters aren't actually that * useful* , it may be
620
+ worth discouraging users from marking a parameter with ` final ` unless it is a
621
+ field parameter. This seems like a good candidate for a lint.
622
+
412
623
## Changelog
413
624
625
+ ### 0.2
626
+
627
+ - Apply review feedback from Lasse.
628
+ - Add section for inferring public parameter names from private ones.
629
+ - Update ` simpleFormalParameter ` grammar to allow ` var ` followed by a type.
630
+ - Add lint for using ` final ` on parameters.
631
+ - Specify the semantics not in terms of field parameters.
632
+
414
633
### 0.1
415
634
416
635
- Initial draft.
0 commit comments