Skip to content

Commit b39109e

Browse files
authored
Discuss aliases with unused parameters.
1 parent c0897ec commit b39109e

File tree

1 file changed

+21
-7
lines changed

1 file changed

+21
-7
lines changed

working/0216 - constructor tearoffs/proposal.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Dart Constructor Tear-offs
22

3-
Author: [email protected]<br>Version: 2.7
3+
Author: [email protected]<br>Version: 2.8
44

55
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).
66

@@ -40,6 +40,8 @@ If *C* denotes a class declaration and *C.name* is the name of a constructor of
4040

4141
just as you can currently invoke the constructor as <code>*C*.*name*(*args*)</code>, or <code>*C*\<*typeArgs*>.*name*(*args*)</code>.
4242

43+
Expressions of the form <code>*C*\<*typeArgs*>.*name*</code> are potentially compile-time constant expressions and are compile-time constants if the type arguments are constant types.
44+
4345
_The former syntax, without type arguments, is currently allowed by the language grammar, but is rejected by the static semantics as not being a valid expression. The latter syntax is not currently grammatically an expression. Both can occur as part of a_ constructor invocation, but cannot be expressions by themselves because they have no values. We introduce a static and dynamic expression semantic for such a *named constructor tear-off expression*.
4446

4547
A named constructor tear-off expression of one of the forms above 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*, with a fresh name here represented by adding `$tearoff`:
@@ -74,13 +76,13 @@ The static type of the named constructor tear-off expression is the same as the
7476
This introduces an **ambiguity** in the grammar. If `List.filled` is a valid expression, then `List.filled(4, 4)` can both be a constructor invocation *and* a tear-off followed by a function invocation, and `List.filled<int>(4, 4)` can *only* be valid as a tear-off followed by a function invocation.
7577
This is similar to the existing possible ambiguity for an instance method invocation like `o.m(arg)`, and we resolve it the same way, by always preferring the direct invocation over doing a tear-off and a function invocation.
7678

77-
We do not allow `List.filled<int>(4, 4)` at all. We could allow it, it's syntacitcally similar to a getter invocation like `o.getter<int>(4)`, which we do handle without issues, but allowing that syntax to be a constructor tear-off could interfere with a possible later introduction of *generic constructors*. We only do constructor tear-off when the constructor reference is *not* followed by a *typeArguments* or *arguments* production. If it is followed by those, then it's a constructor *invocation*, and it's currently an error if it includes type arguments. That is, an expression of the form `List.filled<int>(4, 4)` is *invalid*, it's not interpreted as a constructor tear-off followed by a function invocation, but always as a constructor invocation&mdash;and the `List.filled` constructor is not a generic construct (no constructor is, yet). You can write `(List.filled)<int>(4, 4)` to do the tear-off or `List<int>.filled(4, 4)` to do the invocation.
79+
We do not allow `List.filled<int>(4, 4)` at all. We could allow it, it's syntactically similar to a getter invocation like `o.getter<int>(4)`, which we do handle without issues, but allowing that syntax to be a constructor tear-off could interfere with a possible later introduction of *generic constructors*. We only do constructor tear-off when the constructor reference is *not* followed by a *typeArguments* or *arguments* production. If it is followed by those, then it's a constructor *invocation*, and it's currently an error if it includes type arguments. That is, an expression of the form `List.filled<int>(4, 4)` is *invalid*, it's not interpreted as a constructor tear-off followed by a function invocation, but always as a constructor invocation&mdash;and the `List.filled` constructor is not a generic construct (no constructor is, yet). You can write `(List.filled)<int>(4, 4)` to do the tear-off or `List<int>.filled(4, 4)` to do the invocation.
7880

7981
#### Tearing off constructors from type aliases
8082

8183
*With generalized type-aliases*, it's possible to declare a class-alias like `typedef IntList = List<int>;`. We allow calling constructors on such a type alias, so we will also allow tearing off such a constructor.
8284

83-
Example class alises:
85+
Example class aliases:
8486

8587
```dart
8688
typedef IntList = List<int>; // Non-generic alias.
@@ -129,7 +131,7 @@ var f = IntList.filled; // Equivalent to `List<int>.filled` or `List.filled$tear
129131

130132
**Tearing off a constructor from an instantiated generic alias for a class is equivalent to tearing off the constructor from the aliased class (which is instantiated by the instantiated alias if the class is generic, which it probably is since otherwise the alias doesn't use its type parameter).**
131133

132-
A *generic* type alias is not an alias for *one* type, but for a family of types. If the type alias is *instantiated* (implicitly or explicitly) before the tear-off, then the result is still the same as tearing the constructor off the aliased type directly, using the corresponding constructor function of the class, and it's constant and canonicalized if the type arguments are constant.
134+
A *generic* type alias is not an alias for *one* type, but for a family of types. If the type alias is *instantiated* (implicitly or explicitly) before the tear-off, then the result is still the same as tearing the constructor off the aliased type directly, using the corresponding constructor function of the class, and it's constant and canonicalized if the instantiating type arguments are compile-time constant types.
133135

134136
Example:
135137

@@ -140,7 +142,18 @@ List<double> Function(int, double) makeDoubleList = NumList.filled; // Same as `
140142

141143
Here `NumList<int>` is a single type (`List<int>`), and the tear-off happens from that type.
142144

143-
**If the generic alias is not a proper rename for the class it alises, then tearing off a constructor from the uninstantiated alias is equivalent to tearing off the corresponding constructor function of the alias, which is a generic function. The result always a generic function, and is always a compile-time constant.**
145+
Notice that whether an alias expansion is constant depends on the parameters, not the result. Example:
146+
147+
```dart
148+
typedef Ignore2<T, S> = List<T>;
149+
void foo<X>() {
150+
var c = Ignore2<int, X>.filled; // Aka List<int>.filled, but is *not constant*.
151+
}
152+
```
153+
154+
In this example, `Ignore2<int, X>.filled` is treated exactly like `List<Y>.filled` where `Y` happens to be bound to `int` when the expression is evaluated. There is no canonicalization. Such a situation, where a type alias has parameters it does not use, is expected to be extremely rare.
155+
156+
**If the generic alias is not a proper rename for the class it aliases, then tearing off a constructor from the uninstantiated alias is equivalent to tearing off the corresponding constructor function of the alias, which is a generic function. The result always a generic function, and is always a compile-time constant.**
144157

145158
If the generic alias is *not* instantiated before the constructor is torn off, then the tear-off abstracts over the type parameters *of the alias*, and tearing off a constructor works equivalently to tearing off the corresponding constructor function *of the alias* (where the generics match the type alias, not the underlying class). This is where we use the corresponding constructor function of the alias&mdash;except when the alias is a *proper rename*, as defined below.
146159

@@ -172,7 +185,7 @@ A type alias of the form <code>typedef *A*\<*X*<sub>1</sub> extends *P*<sub>1</s
172185
* *n* = *m*.
173186
* *P*<sub>*i*</sub>[*X*<sub>1</sub>&mapsto;*Y*<sub>1</sub>, &hellip; *X*<sub>*n*</sub>&mapsto;*Y*<sub>*n*</sub>] and *Q*<sub>*i*</sub> are mutual subtypes for all 1 &le; *i* &le; *n*.
174187

175-
That is, an alias is not a proper rename if it accepts different type parameters than the class it alises, whether it be the bounds, the order, or even the number of type parameters.
188+
That is, an alias is not a proper rename if it accepts different type parameters than the class it aliases, whether it be the bounds, the order, or even the number of type parameters.
176189

177190
Example:
178191

@@ -502,4 +515,5 @@ In this case, most of the parameters are *unnecessary*, and a tear-off expressio
502515
* 2.4: Only allow tear-offs of declarations and instance methods, not arbitrary functions. Specify disambiguation strategy for parsing ambiguities.
503516
* 2.5: Elaborate on instance member tear-offs.
504517
* 2.6: Elaborate on constructor name clashes and alias tear-off identity.
505-
* 2.7: State that we do not allow implicit `.call` member instantiations on callable objects.
518+
* 2.7: State that we do not allow implicit `.call` member instantiations on callable objects.
519+
* 2.8: State that unused type arguments of a type alias still affect whether they are constant.

0 commit comments

Comments
 (0)