You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dart allows you to tear off (aka. closurize) methods instead of just calling them. It does not allow you to tear off *constructors*, even though they are just as callable as methods (and for factory methods, the distinction is mainly philosophical).
6
6
@@ -14,30 +14,30 @@ This is a proposal for constructor tear-offs which introduces minimal new syntax
14
14
15
15
We allow tearing off named constructors.
16
16
17
-
An expression *e*of the form <code>*C*.*name*</code> where *C* is an identifier or qualified identifier denoting a class, and *name* is the base name of a named constructor of the class *C*, is currently allowed by the grammar, but rejected by the static semantics. It can occur as part of a constructor invocation, but cannot be an expression by itself because it has no value. We introduce a static and dynamic expression semantic for such a *named constructor tear-off expression*.
17
+
An expression *e*of the form <code>*C*.*name*</code> where *C* is an identifier or qualified identifier denoting a class, and *name* is the base name of a named constructor of the class *C*, is currently allowed by the grammar, but rejected by the static semantics. It can occur as part of a constructor invocation, but cannot be an expression by itself because it has no value. We introduce a static and dynamic expression semantic for such a *named constructor tear-off expression*.
18
18
19
-
A named constructor tear-off expression of the form <code>*C*.*name*</code> evaluates to a function value which could be created by tearing off a *corresponding constructor function*, which would be a static function defined on the class denoted by *C*
19
+
A named constructor tear-off expression of the form <code>*C*.*name*</code> evaluates to a function value which could be created by tearing off a *corresponding constructor function*, which would be a static function defined on the class denoted by *C*:
where *params* is a parameter list equivalent to the one for <code>*C*.*name*</code> (same optional/required parameters with the same names, types and default values, but where any initializing formals are replaced by normal parameters of the same type), and *args* is an argument list passing those parameters to `C.name` directly as they are received. For example, `Uri.http` evaluates to an expression which could have been created by a corresponding function literal expression:
23
+
If *C* is not generic, the <code>\<*typeParams*\></code> and <code>\<*typeArgs*\></code> are omitted. Otherwise <code>\<*typeParams*\></code> are exactly the same type parameters as those of the class declaration of *C*, and <code>\<*typeArgs*></code> applies those type parameter variables directly as type arguments to *C*.
24
+
25
+
Similarly, <code>*params*</code> is almost exactly the same parameter list as the constructor *C*.*name*, except that *initializing formals* are represented by normal parameters with the same type. All remaining properties of the parameters are the same as for the corresponding constructor parameter, including any default values, and *args* is an argument list passing those parameters to `C.name` directly as they are received. For example, `Uri.http` evaluates to an expression which could have been created by a corresponding function literal expression:
24
26
25
27
```dart
26
28
static Uri http$tearoff(String authority, String unencodedPath, [Map<String, dynamic>? queryParameters]) =>
If the class denoted by *C* is generic, the corresponding constructor function is a generic function with the same type arguments (names and bounds) as the class. For example, the corresponding constructor function for `List.filled`would be:
32
+
and a constructor of a generic class, like `List.filled`, would be:
31
33
32
-
```dart
34
+
```6dart
33
35
static List<E> filled$tearoff<E>(int count, E fill) => List<E>.filled(count, fill);
34
36
```
35
37
36
-
When tearing off the constructor of a generic class, *the function tear-off is always instantiated* so the resulting function is not generic. This works the same way as instantiated tear-off of any other function, except that it is not an option to *not* instantiate when tearing off. If type inference has no constraints on the type arguments, they will be filled in by instantiate to bounds.
37
-
38
-
The static type of the named constructor tear-off expression is the same as the static type of the corresponding (instantiated) constructor function tear-off.
38
+
When tearing off the constructor of a generic class, the result is precisely the same as if tearing off the corresponding static constructor function, including whether the expression is constant and how it canonicalizes, and whether a generic function is implicitly instantiated.
39
39
40
-
Also, similarly to function tear-offs, constructor tear-offs are potentially constant and canonicalized. Whenever the corresponding constructor function tear-off would be constant and canonicalized, the constructor tear-off itself is also constant and canonicalized.
40
+
The static type of the named constructor tear-off expression is the same as the static type of the corresponding constructor function tear-off.
41
41
42
42
### Unnamed constructor tear-off
43
43
@@ -48,65 +48,89 @@ Because of that, we introduce a *new* syntax that can be used to denote the unna
48
48
```dart
49
49
class C {
50
50
final int x;
51
-
const C.new(this.x); // declaration
51
+
const C.new(this.x); // declaration.
52
52
}
53
53
class D extend C {
54
-
D(int x) : super.new(x * 2); // super constructor reference
54
+
D(int x) : super.new(x * 2); // super constructor reference.
55
55
}
56
56
void main() {
57
-
D.new(1); // normal invocation
58
-
const C.new(1); // const invocation
59
-
var f = C.new; // tear-off
57
+
D.new(1); // normal invocation.
58
+
const C.new(1); // const invocation.
59
+
new C.new(1); // explicit new invocation.
60
+
var f = C.new; // tear-off.
60
61
f(1);
61
62
}
62
63
```
63
64
64
65
Apart from the tear-off, this code will mean exactly the same thing as the same code without the `.new`. The tear-off cannot be performed without the `.new` because that expression already means something else.
65
66
66
-
The one thing we do *not* allow is `new C.new(1)`, in an explicit constructor invocation. That'd be too much of a good thing, and we are discouraging that use of `new`. We allow `const C.new(1)` because it means something else.
67
-
68
67
*With regard to tear-offs, <code>C.new</code> works exactly as if it had been a named constructor, with a corresponding constructor function named <code>C.new$tearoff</code>.*
69
68
70
-
We probably want to support `[C.new]` as a constructor link in DartDoc as well. In `dart:mirrors`, the name of the constructor is still just `C`, not `C.new` (that's not a valid symbol, and we don't want to break existing reflection using code).
69
+
We probably want to support `[C.new]` as a constructor link in DartDoc as well. In `dart:mirrors`, the name of the constructor is still just `C`, not `C.new` (that's not a valid symbol, and we don't want to break existing reflection using code).
70
+
71
+
The grammar changes would affect the following productions by adding the `| \NEW{}` option to the name of the constructor, and a primary expression referencing the constructor by `.new`:
It would be a compile-time error to use the `.new` to denote anything which is not an "unnamed" constructor.
71
92
72
93
### Consequences
73
94
74
95
This proposal is deliberately non-breaking and backwards compatible.
75
96
76
-
It introduces new syntax for "unnamed" constructors, they are now `new`-named constructors, you can just omit the `new` in most cases, except tear-offs. This avoids conflicting with type literals when trying to tear off an unnamed constructor.
97
+
It introduces new syntax for "unnamed" constructors, they are now "`new`-named" constructors, you can just omit the `new` in most cases except tear-offs. This avoids conflicting with type literals when trying to tear off an unnamed constructor.
77
98
78
99
We technically only need the `C.new` for tear-offs, and don't need to allow it in other places, but it would be (more) inconsistent to only allow the syntax in one place. Also, the same syntax may be useful for declaring and calling generic unnamed constructors in the future.
79
100
80
-
There is no easy way to abstract over the type parameters of the class. We could make `Set<T> Function<T>() makeSet = HashSet;`tearoff as `<T>() => HashSet<T>()`, providing a generic function matching the generic class. *However*, we also want to introduce *generic constructors*, say `Map.fromIterable<T>(Iterable<T> elements, K key(T element), V value(T element))`, and tearing off that should create a generic function. Making generic tear-offs work with the *class* type parameters could interfere with this later feature. Not doing so is still a choice with consequence, we can't just allow it later if we change our minds. If `var makeFilled = List.filled;` is not generic now, it would be a breaking change to make it generic later. Adding `.new` syntax gives us a way to declare and invoke generic "unnamed" constructors, so it won't need to be `B<int><int>(…)`.
101
+
We make the tear-off of the constructor of a generic class be a generic function. *However*, we also want to introduce *generic constructors*, say `Map.fromIterable<T>(Iterable<T> elements, K key(T element), V value(T element))`, and tearing off that should also create a generic function. Making generic tear-offs work with the *class* type parameters could interfere with this later feature. The most obvious solution is to combine class and constructor type parameters when tearing off a constructor, so the tear-off function of `Map.fromIterable` could become `Map<K, V> fromIterable$tearoff<K, V, T>(…)`. The issue with that is that *making* the constructor generic would be a breaking change, it changes the number of type parameters of the tear-off `Map.fromIterable`.
81
102
82
103
Constructors with very large argument lists will create very large function closures. Example:
void Function() f = C.new; // closure of new$tearoff
94
116
```
95
117
96
-
In this case, most of the parameters are *unnecessary*, and a tear-off expression of `() => C()` would likely be sufficient. However, that would prevent canonicalization, and would be inconsistent with what we do for function-tear-off. If the implementation is just a tear-off of an implicitly defined
118
+
In this case, most of the parameters are *unnecessary*, and a tear-off expression of `() => C()` would likely be sufficient. That would prevent canonicalization, and would be inconsistent with what we do for functiontear-off. If the implementation is just a tear-off of an implicitly defined
97
119
98
120
```dart
99
121
static C new$tearOff(int? a, int? b, int? c, int? d, int? e, int? f, int? g, int? h, int? i, int? j, int? k, int? l, int? m) =>
which can be tree-shaken if the constructor is never torn off, then the overhead should be fixed.
125
+
which can be tree-shaken if the constructor is never torn off, then the overhead should be fixed. It will make it harder to tree-shake unused *parameters*, but no harder than for static functions which are also torn off.
104
126
105
127
## Possible Extensions
106
128
107
129
### Explicitly instantiated generics
108
130
109
-
We can only instantiate generic classes based on type inference. That means that `var makeIntList = List.filled;` won't work (it will have type `List<Object?> Function(int, Object?)`, you have to write out the entire type as `List<int> Function(int args, int value) = List.filled;`.
131
+
We cannot control instantiation of generic constructor functions except using the context type.
132
+
133
+
That means that `var makeIntList = List.filled;` will have type `List<E> Function<E>(int, E)`, and to specialize it to integer lists, you have to write out the entire function type as `List<int> Function(int args, int value) = List.filled;`.
110
134
111
135
If we instead allow you to write `var makeIntList = List<int>.filled;`, then you would not need the context type.
112
136
@@ -119,7 +143,18 @@ That is, we would allow the following expressions as well:
119
143
120
144
and in the semantics above, the type arguments to the corresponding constructor function uses the specified type arguments instead of inferring them.
121
145
146
+
Allowing explicit type arguments will require further language changes, possibly only changing the new `<primary>` production to:
We can go further and allow `C<typeArgs>` and `f<typeArgs>` as expressions by themselves, as an instantiated type `Type` object and an instantiated function tear-off. See issue [#123](https://github.com/dart-lang/language/issues/123). It's not *necessary* to allow `typeExpr<types>` as an expression for named constructor tear-off instantiation because there is always a <code>.*name*</code> or `.new` after the type.
155
+
122
156
## Versions
123
157
124
158
* 2.0: Initial version in this iteration. Proposed `new C` as unnamed tear-off syntax.
125
159
* 2.1: Revision. Proposed `C.new` as unnamed tear-off syntax.
0 commit comments