diff --git a/accepted/future-releases/constructor-tearoffs/feature-specification.md b/accepted/future-releases/constructor-tearoffs/feature-specification.md index 789bd3d559..8c5768c86e 100644 --- a/accepted/future-releases/constructor-tearoffs/feature-specification.md +++ b/accepted/future-releases/constructor-tearoffs/feature-specification.md @@ -16,7 +16,7 @@ The goal is that you can always tear off a constructor, then invoke the torn off var v1 = C.name(args); var v2 = (C.name)(args); -// and +// and var v3 = C.name(args); var v4 = (C.name)(args); @@ -25,7 +25,7 @@ var v5 = (C.name)(args); should always give equivalent values for `v1` and `v2`, and for `v3`, `v4` and `v5`. -We also want a consistent and useful *identity* and *equality* of the torn off functions, with the tear-off expression being a constant expression where possible. It should match what we already do for static function tear-off where that makes sense. +We also want a consistent and useful *identity* and *equality* of the torn off functions, with the tear-off expression being a constant expression where possible. It should match what we already do for static function tear-off where that makes sense. ## Proposal @@ -55,9 +55,9 @@ If *C* denotes a class declaration (it's an identifier or qualified identifier w just as you can currently invoke the constructor as *C*.*name*(*args*), or *C*\<*typeArgs*>.*name*(*args*). -Expressions of the form *C*\<*typeArgs*>.*name* are potentially compile-time constant expressions and are compile-time constants if the type arguments are constant types (and *C*.*name* actually denotes a constructor). +_These expressions can be constant, as specified in the section about constant expressions._ -_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 when denoting a constructor. 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 semantics for such a *named constructor tear-off expression*, which makes them valid expressions._ +_The syntax without type arguments is currently allowed by the language grammar, but is rejected by the static semantics as not being a valid expression when denoting a constructor. The syntax with type arguments 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 semantics for such a *named constructor tear-off expression*, which makes them valid expressions._ 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`: @@ -65,12 +65,12 @@ A named constructor tear-off expression of one of the forms above evaluates to a If *C* is not generic, then \<*typeParams*\> and \<*typeArgs*\> are omitted. Otherwise \<*typeParams*\> are exactly the same type parameters as those of the class declaration of *C* (including bounds), and \<*typeArgs*> applies those type parameter variables directly as type arguments to *C*. -Similarly, *params* is *almost* exactly the same parameter list as the constructor *C*.*name*, with the one exception that *initializing formals* are represented by normal parameters with the same name and 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. +Similarly, *params* is *almost* exactly the same parameter list as the constructor *C*.*name*, with the one exception that *initializing formals* are represented by normal parameters with the same name and 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 tearing off a corresponding static function declaration: ```dart -static Uri http$tearoff(String authority, String unencodedPath, [Map? queryParameters]) => +static Uri http$tearoff(String authority, String unencodedPath, [Map? queryParameters]) => Uri.http(authority, unencodedPath, queryParameters); ``` @@ -118,11 +118,11 @@ where *args* passes the parameters *params* directly to as arguments to *C.name* For the example aliases above, the constructor functions corresponding to `List.filled` would be: ```dart -List IntList$filled$tearoff(int length, int value) => +List IntList$filled$tearoff(int length, int value) => List.filled(length, value); -List NumList$filled$tearoff(int length, T value) => +List NumList$filled$tearoff(int length, T value) => List.filled(length, value); -List MyList$filled$tearoff(int length, T value) => +List MyList$filled$tearoff(int length, T value) => List.filled(length, value); ``` @@ -157,7 +157,7 @@ Example: ```dart // Equivalent to `List.filled` or `List.filled$tearoff` -var makeIntList = NumList.filled; +var makeIntList = NumList.filled; // Same as `List.filled` after inference. List Function(int, double) makeDoubleList = NumList.filled; ``` @@ -177,7 +177,7 @@ In this example, `Ignore2.filled` is treated exactly like `List.fille **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.** -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—except when the alias is a *proper rename*, as defined below. +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—except when the alias is a *proper rename*, as defined below. Example: @@ -192,7 +192,7 @@ Example: ```dart typedef ListList = List>; // Corresponding factory function -List> ListList$filled$tearoff(int length, List value) => +List> ListList$filled$tearoff(int length, List value) => List>.filled(length, value); var f = ListList.filled; // Equivalent to `= ListList$filled$tearoff;` ``` @@ -230,17 +230,17 @@ Example : ```dart var f = MyList.filled; // Equivalent to `List.filled` or `List.filled$tearoff` -// Instantiated type aliases use the aliased type, +// Instantiated type aliases use the aliased type, // and are constant and canonicalized when the type is constant. print(identical(MyList.filled, NumList.filled)); // true print(identical(MyList.filled, List.filled)); // true -print(identical(NumList.filled, List.filled)); // true +print(identical(NumList.filled, List.filled)); // true // Non-instantiated type aliases have their own generic function. print(identical(MyList.filled, MyList.filled)); // true print(identical(NumList.filled, NumList.filled)); // true print(identical(MyList.filled, NumList.filled)); // false -print(identical(MyList.filled, List.filled)); // true (proper rename!) +print(identical(MyList.filled, List.filled)); // true (proper rename!) // Implicitly instantiated tear-off. List Function(int, int) myList = MyList.filled; @@ -262,14 +262,14 @@ class C { // Proper rename, different, but equivalent, bound. typedef A = C; void main() { - // Static type : C Function() + // Static type : C Function() // Runtime type: C Function() var cf = C.name; // Static type : C Function() // Runtime type: C Function() var af = A.name; var co = (cf as dynamic)(); - var ao = (af as dynamic)(); + var ao = (af as dynamic)(); // Dynamic instantiate to bounds uses actual bounds. print(co.runtimeType); // C print(ao.runtimeType); // C @@ -321,7 +321,7 @@ It's still not allowed to have two constructor declarations with the same name, ### Explicitly instantiated classes and functions -The above named constructor tear-off feature allows you to explicitly instantiate a constructor tear-off as `List.filled`. We do not have a similar ability to explicitly instantiate function tear-offs. Currently you have to provide a context type and rely on *implicit instantiation* if you want to tear off an instantiated version of a generic function. +The above named constructor tear-off feature allows you to explicitly instantiate a constructor tear-off as `List.filled`. We do not have a similar ability to explicitly instantiate function tear-offs. Currently you have to provide a context type and rely on *implicit instantiation* if you want to tear off an instantiated version of a generic function. We can also use type aliases to define instantiated interface types, but we cannot do the same thing in-line. @@ -357,14 +357,14 @@ For an expression of the form *e*\<*typeArgs*>, which is not follow * If *e* denotes a generic instance method (*e* has the form *r*.*name* and *r* has a static type for which *name* is a generic interface method), then *e*\<*typeArgs*> performs an explicitly instantiated method tear-off, which works just like the current implicitly instantiated method tear-off except that the types are provided instead of inferred. * If *e* has a static type which is a generic callable object type (a non-function type with a generic method named `call`), then *e*\<*typeArgs*> is equivalent to the instantiated method-tear off *e*\.call<*typeArgs*>. * Otherwise, if *e* has a static type which is a generic function type, then *e*\<*typeArgs*> is equivalent to the instantiated method-tear off *e*\.call<*typeArgs*>. -* Otherwise the expression is a compile-time error. +* Otherwise the expression is a compile-time error. * This includes *e* having the static type `dynamic` or `Function`. We do not support implicit or explicit instantiation of functions where we do not know the number and bounds of the type parameters at compile-time. * It also includes *e* denoting a constructor. _(We reserve this syntax for denoting instantiation of generic constructors, should the language add [generic constructors](https://github.com/dart-lang/language/issues/647) in the future. Instead just write (*C*.*name*)\<*typeArgs*\> or *C*\.*name*.)_ Cascades can contain explicitly instantiated tearoffs, because they can contain any selector and instantiation is now a selector, e.g., `receiver..foo()..instanceMethod..bar`. _Note that this example is allowed for consistency, but it will compute a value and discard it. Instantiation without immediate invocation is expected to be primarily used in places where the value of that instantiation will be stored for later use, and using it in a cascade is outside of that usage pattern. One example where it could be useful would be as a receiver for an extension method on function types, like `receiver..foo()..bar.apply(argList)`. The first selector of a cascade section must still be one of `..identifier` or `..[index]`, it cannot be `..` any more than it can be `..(argumentList)`._ ```dart -class A { +class A { List m(X x) => [x]; } @@ -373,7 +373,7 @@ extension FunctionApplier on Function { print(Function.apply(this, positionalArguments, const {})); } -void main() { +void main() { A() ..m.applyAndPrint([2]) ..m.applyAndPrint(['three']); @@ -382,7 +382,7 @@ void main() { The static type of the explicitly instantiated tear-offs are the same as if the type parameter had been inferred, but no longer depends on the context type. Missing type arguments in implicit instantiation expressions can now be considered "filled in" by type inference, as if they had been written explicitly, just as for other inferred type arguments. -The static type of the instantiated type literal is `Type`. This feature also satisfies issue [#123](https://github.com/dart-lang/language/issues/123). +The static type of the instantiated type literal is `Type`. This feature also satisfies issue [#123](https://github.com/dart-lang/language/issues/123). As mentioned above, we **do not allow** *dynamic* explicit instantiation. If an expression `e` has type `dynamic` (or `Never` or `Function` ), then e\ is a **compile-time error**. It's not possible to do implicit instantiation without knowing the member signature to some extent, and we also don't allow explicit instantiation. _(Possible alternative: Allow it, and handle it all at run-time, including any errors from having the wrong number or types of arguments, or just not existing at all. We won't do this for now.)_ @@ -391,7 +391,7 @@ We **now allow** both implicit and explicit instantiation of *callable objects* Previously, the following code was invalid: ```dart -class Id { +class Id { T call(T value) => value; } int Function(int) intId = Id(); @@ -413,7 +413,7 @@ Also, we allow explicitly instantiating a callable object: var intId = Id(); ``` -is also type-inferred to the same initialization. +is also type-inferred to the same initialization. **That is**, given an expression of the form *e*\<*typeArgs*>, if *e* has a static type which is a callable object, the expression is equivalent to *e*\.call<*typeArgs*>. Since no object with an interface type can otherwise support type-instantiation, this coercion turns an error into useful code, and allows a typed callable object to be consistently treated like a function object equivalent to its `call` method. @@ -429,7 +429,7 @@ f(x.a-d); // f((x.a)-d) or f((x.a < b), (c > -d])) The `x.a` can be an explicitly instantiated generic function tear-off or an explicitly instantiated type literal named using a prefix, which is new. While neither type objects nor functions declare `operator-` or `operator[]`, such could be added using extension methods. -We will disambiguate such situations *heuristically* based on the token following the `>` that matches the `<` we are ambiguous about. In the existing ambiguity we treat `(` as a sign that the `<` starts a generic invocation. We extend the number of tokens which, when following a potential type argument list, makes us choose to parse the previous tokens as that type argument list. +We will disambiguate such situations *heuristically* based on the token following the `>` that matches the `<` we are ambiguous about. In the existing ambiguity we treat `(` as a sign that the `<` starts a generic invocation. We extend the number of tokens which, when following a potential type argument list, makes us choose to parse the previous tokens as that type argument list. There is a number of tokens which very consistently *end* an expression, and we include all those: @@ -437,7 +437,7 @@ There is a number of tokens which very consistently *end* an expression, and we Then we include tokens which we *predict* will continue a generic instantiation: -> `(` `.` `==` `!=` +> `(` `.` `==` `!=` The first six are tokens which cannot possibly start an expression, and therefore cannot occur after a greater-than infix operator. The last four tokens can continue an expression, and of those only `(` can also start an expression, and we already decided how to disambiguate that). @@ -452,18 +452,18 @@ Grammatically, we restrict the productions for the less-than operator and the ty > | `>' > | `<=' > | `<' NEGATIVE_LOOKAHEAD( `>' ( | )) -> +> > ::= > '!' > | assignableSelector > | argumentPart > | typeArguments LOOKAHEAD( | ) -> +> > ::= `(' | `.' | `==' | `!=' > ::= `)' | `]' | `}' | `;' | `:' | `,' > ``` -That is, if a `<` occurs where it could potentially be either a type arguments list or a less than operator, absent any knowledge of the rest of the program, the parser can first try to parse it as a type argument list, then look at the following token, and if that token is one of the ones listed above, it *must* be a type argument list, because the relational less-than operator cannot possibly match due to its negative lookahead on exactly the thing that was just matched. +That is, if a `<` occurs where it could potentially be either a type arguments list or a less than operator, absent any knowledge of the rest of the program, the parser can first try to parse it as a type argument list, then look at the following token, and if that token is one of the ones listed above, it *must* be a type argument list, because the relational less-than operator cannot possibly match due to its negative lookahead on exactly the thing that was just matched. If the next token is not one of those characters, then the compiler can backtrack and try parsing as a relational less-than operator because the type arguments selector cannot possibly match. (If the compiler can somehow peek at the token following the matching `>` of a type arguments list before parsing the list, then it can potentially skip parsing as a type argument list entirely if the following token is not one of the chosen ones. It will have to try parsing as a type argument list for the cases where *that* part could fail to match, and thereby satisfy the negative lookahead of the relational operator, like `f(2 < 3, 4 > (5))` where the `2` and `3` are not valid type productions. @@ -498,9 +498,9 @@ A mixin application introduces *forwarding constructors* for accessible supercla Example: ```dart -class A { - A.named(); - A(); +class A { + A.named(); + A(); } mixin M {} class B = A with M; @@ -517,22 +517,25 @@ The grammar changes necessary for these changes are provided separately (as [cha ### Constant expressions -We add the following to the set of expressions that are potentially constant and constant: +We add the following to the set of expressions that are potentially constant or constant: + +If *e* is a potentially constant expression derived from \ \* and *T**1*..*T**k* derived from \ is a list of potentially constant type expressions, then *e*\<*T**1*..*T**k*> is a potentially constant expression. +If moreover *e* is a constant expression whose static type is a function type *F* or *e* is a type literal, and *T**1*..*T**k* is a list of constant type expressions, then *e*\<*T**1*..*T**k*> is a constant expression. + +*It follows that *F* is a generic function type taking *k* type arguments, and *T**1*..*T**k* satisfy the bounds of *F*, and similarly for the type literal, because otherwise the program would have a compile-time error.* -If `e` is a potentially constant expression, `T1..Tk` is derived from ``, and `e` is derived from ` *`, then `e` is a potentially constant expression. -If moreover `e` is a constant expression whose static type is a function type `F`, or `e` is a type literal, and `T1..Tk` is a list of constant type expressions, then `e` is a constant expression. +Assume that *C*.*name* denotes a constructor, and *T**1*..*T**k* is derived from \, for some *k* >= 0. +*C*\<*T**1*..*T**k*>.*name* is then a potentially constant expression if *T**j* is a potentially constant type expression for each *j*. +It is further a constant expression if *T**j* is a constant type expression for each *j*. -*It follows that `F` is a generic function type taking `k` type arguments, and `T1..Tk` satisfy the bounds of `F`, and similarly for the type literal, because otherwise the program would have a compile-time error.* +_In particular, *C*.*name*, which is the case where *k* == 0, is always constant when *C* is a non-generic class, and it may or may not be constant if it is generic and the type arguments are inferred._ -The following cases are specified elsewhere in this document: +If *T* denotes a type variable then *T* is a potentially constant type expression, and a potentially constant expression. -*Section 'Named constructor tearoffs': -This section says that expressions of the form *C*\<*typeArgs*>.*name* can be potentially constant and constant expressions. -Also, the constantness of named constructor tearoffs follows the constantness of the tearoffs of the corresponding constructor functions.* +_This is just a simpler way to write something which is already supported: If we wish to use a type variable as an expression in a constant constructor initializer list, we can define typedef F\ = X; and express the same thing as F\._ -*Section 'Tearing off constructors from type aliases': -This section contains several rules about constant expressions: About non-generic type aliases; about generic type aliases that are applied to some actual type arguments; about generic type aliases that are 'proper renames' and that do not receive any actual type arguments; and about generic type aliases that are not 'proper renames' and do not receive any actual type arguments. -In general, their constantness follows the constantness of specific corresponding constructor functions.* +*Section 'Tearing off constructors from type aliases' specifies several additional cases, all saying that a tearoff from a type alias is constant if and only if the corresponding constructor function tearoff is constant. +This is specified about non-generic type aliases; about generic type aliases that are applied to some actual type arguments; about generic type aliases that are 'proper renames' and that do not receive any actual type arguments; and about generic type aliases that are not 'proper renames' and do not receive any actual type arguments.* ## Summary @@ -552,7 +555,7 @@ We allow `TypeName.new` and `TypeName.new` everywhere we allow a refer class C { final T x; const C.new(this.x); // Same as: `const C(this.x);` - C.other(T x) : this.new(x); // Same as: `: this(x)` + C.other(T x) : this.new(x); // Same as: `: this(x)` factory C.d(T x) = D.new; // same as: `= D;` } @@ -615,8 +618,8 @@ extension Ext on C { } class D extends C with M { void method() { - var f4 = super.inst; // works like (int $) => super.inst($) - var f4TypeName = super.inst.runtimeType.toString(); + var f4 = super.inst; // works like (int $) => super.inst($) + var f4TypeName = super.inst.runtimeType.toString(); } } void main() { @@ -624,7 +627,7 @@ void main() { var t1 = List; // Type object for `List`. var t2 = ListList; // Type object for `List>`. - // Instantiated function tear-offs. + // Instantiated function tear-offs. T local(T value) => value; const f1 = top; // int Function(int), works like (int $) => top($); const f2 = C.stat; // int Function(int), works like (int $) => C.stat($); @@ -632,7 +635,7 @@ void main() { var d = D(); var f4 = d.inst; // int Function(int), works like (int $) => c.inst($); var f5 = d.minst; // int Function(int), works like (int $) => c.minst($); - var f6 = d.einst; // int Function(int), works like (int $) => Ext(c).einst($); + var f6 = d.einst; // int Function(int), works like (int $) => Ext(c).einst($); var typeName = List.toString(); var functionTypeName = local.runtimeType.toString(); } @@ -660,7 +663,7 @@ That makes a type instantiation expression of the form *e*\<*typeArgs*> C(a: a, b: b, c: c, d: d, e: e, f: f, g: g, h: h, j: j, l: l, m: m); } -... +... void Function() f = C.new; // closure of new$tearoff ``` @@ -730,4 +733,5 @@ In this case, most of the parameters are *unnecessary*, and a tear-off expressio * 2.12: Mention abstract classes. * 2.13: Add `is` and `as` disambiguation tokens. * 2.14: Remove many disambiguation tokens. Allow instantiating function *objects* and *callable objects*. Mention forwarding constructors from mixin applications. -* 2.15: Add section about constants and specify new rules about potentially constant and constant expressions of the form `e`. +* 2.15: Add section about constants and specify new rules about potentially constant and constant expressions of the form e\1..Tk>. +* 2.16: Add one more kind of potential constant, type parameters.