|
| 1 | +# Dart Named Arguments Anywhere |
| 2 | + |
| 3 | +Author: [email protected], [email protected]< br>Version: 1.0< br>Specification for issue [#1072](https://github.com/dart-lang/language/issues/1072) |
| 4 | + |
| 5 | +## Motivation |
| 6 | + |
| 7 | +Dart requires named arguments to come after positional arguments in a function invocation. Named arguments can be placed in any order, because they are matched by name, not position, but are still required to be placed after positional arguments. |
| 8 | + |
| 9 | +That’s an unnecessary restriction. A compiler is perfectly capable of recognizing named arguments (they have a name followed by a `:`) and count positional arguments independently of the named arguments. |
| 10 | + |
| 11 | +Allowing named arguments to be placed anywhere in the argument list, even before positional ones, allows some APIs to be much more convenient. Example: |
| 12 | + |
| 13 | +```dart |
| 14 | +expect(something, expectAsync1((x) { |
| 15 | + something; |
| 16 | + something.more(); |
| 17 | + test(x); |
| 18 | +}, count: 2)); |
| 19 | +``` |
| 20 | + |
| 21 | +is less readable than: |
| 22 | + |
| 23 | +```dart |
| 24 | +expect(something, expectAsync1(count: 2, (x) { |
| 25 | + something; |
| 26 | + something.more(); |
| 27 | + test(x); |
| 28 | +})); |
| 29 | +``` |
| 30 | + |
| 31 | +because the `count` argument is closer to the method name it belongs to, and not separated by a longer function body. |
| 32 | + |
| 33 | +## Specification |
| 34 | + |
| 35 | +### Grammar |
| 36 | + |
| 37 | +The language grammar is changed to allow named arguments before positional arguments. |
| 38 | + |
| 39 | +The grammar: |
| 40 | + |
| 41 | +> ```latex |
| 42 | +> \begin{grammar} |
| 43 | +> <arguments> ::= `(' (<argumentList> `,'?)? `)' |
| 44 | +> |
| 45 | +> <argumentList> ::= <namedArgument> (`,' <namedArgument>)* |
| 46 | +> \alt <expressionList> (`,' <namedArgument>)* |
| 47 | +> |
| 48 | +> <namedArgument> ::= <label> <expression> |
| 49 | +> \end{grammar} |
| 50 | +> ``` |
| 51 | +
|
| 52 | +becomes |
| 53 | +
|
| 54 | +> ```latex |
| 55 | +> \begin{grammar} |
| 56 | +> <arguments> ::= `(' (<argumentList> `,'?)? `)' |
| 57 | +> |
| 58 | +> <argumentList> ::= <argument> (`,' <argument>)* |
| 59 | +> |
| 60 | +> <argument> ::= <label>? <expression> |
| 61 | +> \end{grammar} |
| 62 | +> ``` |
| 63 | +
|
| 64 | +An argument is considered *named* if it has a `<label>`, otherwise it is *positional*. |
| 65 | +
|
| 66 | +Any place the language specification assumes an argument list has positional arguments before named arguments, it should be changed to assume arguments in any order, and if necessary, be rewritten to depend on the semantics given for argument lists in the _Actual Argument Lists_ section, as modified below, instead of doing anything directly. |
| 67 | +
|
| 68 | +### Static semantics |
| 69 | +
|
| 70 | +The current language specification is underspecified since all the real complexity is in the type inference, which hasn't been formally specified yet. |
| 71 | +
|
| 72 | +To update the current specification, the section: |
| 73 | +
|
| 74 | +> ```latex |
| 75 | +> \LMHash{}% |
| 76 | +> Let $L$ be an argument list of the form |
| 77 | +> \code{($e_1 \ldots,\ e_m,\ y_{m+1}$: $e_{m+1} \ldots,\ y_{m+p}$: $e_{m+p}$)} |
| 78 | +> and assume that the static type of $e_i$ is $S_i$, $i \in 1 .. m+p$. |
| 79 | +> The \Index{static argument list type} of $L$ is then |
| 80 | +> \code{($S_1 \ldots,\ S_m,\ S_{m+1}\ y_{m+1} \ldots,\ S_{m+p}\ y_{m+p}$)}. |
| 81 | +> ``` |
| 82 | +
|
| 83 | +becomes: |
| 84 | +
|
| 85 | +> Let *L* be an arguments list of the form <code>(*p*<sub>1</sub>, …, *p*<sub>*k*</sub>)</code> with *m* positional arguments, *p*<sub>*q*<sub>1</sub></sub>, …, *p*<sub>*q*<sub>m</sub></sub> (in source order), and *n* named arguments, *p*<sub>*d*<sub>1</sub></sub>, …, *p*<sub>*d*<sub>n</sub></sub> (also in source order), so that *k = m + n*. |
| 86 | +> |
| 87 | +> A positional argument *p*<sub>*i*</sub> has the form <code>*e*<sub>*i*</sub></code> and a named argument *p*<sub>*i*</sub> has the form <code>*y*<sub>i</sub>: *e*<sub>*i*</sub></code>. |
| 88 | +> |
| 89 | +> Assume that the static type of <code>_e_<sub>*i*</sub></code> is *S*<sub>*i*</sub>, *i* ∈ {1, …, k}. |
| 90 | +> |
| 91 | +> The static argument list type of *L* is then <code>(*S*<sub>*q*<sub>1</sub></sub>, … , *S*<sub>*q*<sub>m</sub></sub>, *S*<sub>*d*<sub>1</sub></sub> *y*<sub>*d*<sub>1</sub></sub>, … , *S*<sub>*d*<sub>n</sub></sub> *y*<sub>*d*<sub>n</sub></sub>)</code> |
| 92 | +
|
| 93 | +_That is, we canonicalize the ordering for the type, and then proceed as we previously did._ |
| 94 | +
|
| 95 | +**Type inference** also needs to be updated. It must match positional arguments with positional parameters in the called function’s static type in order to provide a context type for the argument. This is done by matching named arguments against named parameters, as usual, and matching positional arguments against positional parameters *based on the number of earlier positional arguments* instead of just using the position in the argument list. Then expression types are inferred in source order like they are now. _Order is important since type inference of one expression may affect variable promotion for later expressions in the argument list._ |
| 96 | +
|
| 97 | +### Runtime semantics |
| 98 | +
|
| 99 | +Evaluation of argument expressions is in source order. Then the *Actual Argument List Evaluation* algorithm is changed to account for named arguments occurring out of order, and the position of a positional argument not necessarily being its index in the argument list. Like above, we specify the evaluation as happening in source order, the canonicalize the ordering for the result. |
| 100 | +
|
| 101 | +The section containing: |
| 102 | +
|
| 103 | +> ```latex |
| 104 | +> \LMHash{}% |
| 105 | +> Evaluation of an actual argument part of the form |
| 106 | +> |
| 107 | +> \noindent |
| 108 | +> \code{<$A_1, \ldots,\ A_r$>($a_1, \ldots,\ a_m,\ q_1$: $a_{m+1}, \ldots,\ q_l$: $a_{m+l}$)} |
| 109 | +> proceeds as follows: |
| 110 | +> |
| 111 | +> \LMHash{}% |
| 112 | +> The type arguments $A_1, \ldots, A_r$ are evaluated |
| 113 | +> in the order they appear in the program, |
| 114 | +> producing types $t_1, \ldots, t_r$. |
| 115 | +> The arguments $a_1, \ldots, a_{m+l}$ are evaluated |
| 116 | +> in the order they appear in the program, |
| 117 | +> producing objects $o_1, \ldots, o_{m+l}$. |
| 118 | +> |
| 119 | +> \commentary{% |
| 120 | +> Simply stated, an argument part consisting of $s$ type arguments, |
| 121 | +> $m$ positional arguments, and $l$ named arguments is |
| 122 | +> evaluated from left to right. |
| 123 | +> Note that the type argument list is omitted when $r = 0$ |
| 124 | +> (\ref{generics}).% |
| 125 | +> } |
| 126 | +> ``` |
| 127 | +
|
| 128 | +becomes |
| 129 | +
|
| 130 | +> Evaluation of an actual argument part of the form <code>\<*A*<sub>1</sub>, …, *A*<sub>*r*</sub>\>(*p*<sub>1</sub>, …, *p*<sub>*m*+*k*</sub>)</code> with positional arguments *p*<sub>*v*<sub>1</sub></sub>…*p*<sub>*v*<sub>*m*</sub></sub> of the form <code>*e*<sub>*v*<sub>*i*</sub></sub></code> and named arguments *p*<sub>*d*<sub>1</sub></sub>…*p*<sub>*d*<sub>*l*</sub></sub> of the form <code>*q*<sub>*d*<sub>*i*</sub></sub>: *e*<sub>*d*<sub>*i*</sub></sub></code>, proceeds as follows: |
| 131 | +> |
| 132 | +> The type arguments *A*<sub>1</sub>, …, *A*<sub>*r*</sub> are evaluated in the order they appear in the program, producing types *t*<sub>1</sub>, …, *t*<sub>*r*</sub>. |
| 133 | +> |
| 134 | +> The argument expressions *e*<sub>1</sub>, …, *e*<sub>*m*+*k*</sub> are evaluated in the order they appear in the program, producing objects *o*<sub>1</sub>, …, *o*<sub>*m*+*k*</sub>. |
| 135 | +> |
| 136 | +> _Simply stated, an argument part consisting of *r* type arguments, *m* positional arguments, and *k* named arguments (in any order) is evaluated from left to right. |
| 137 | +> Note that the type argument list is omitted when *r* = 0._ |
| 138 | +> |
| 139 | +> The evaluated argument list is then \<*t*<sub>1</sub>, …, *t*<sub>*r*</sub>>(*o*<sub>*v*<sub>1</sub></sub>, …, *o*<sub>*v*<sub>*m*</sub></sub>, *q*<sub>*d*<sub>1</sub></sub>: *o*<sub>*d*<sub>1</sub></sub>, …, *q*<sub>*d*<sub>*l*</sub></sub>: *o*<sub>*d*<sub>*l*</sub></sub>). |
| 140 | +
|
| 141 | +The semantics described above can be implemented entirely in the front-end, and the implementation can be described by the following desugaring step: |
| 142 | +
|
| 143 | +Let *e* be an invocation expression of the form <code>*e*<sub>0</sub>.*f*\<*A*<sub>1</sub>, ,*A*<sub>*r*</sub>>(*p*<sub>1</sub>, … , *p*<sub>*m*+*k*</sub>)</code> with positional arguments *p*<sub>*v*<sub>1</sub></sub>, ... , *p*<sub>*v*<sub>*m*</sub></sub> of the form <code>*e*<sub>*v*<sub>*i*</sub></sub></code> and named arguments *p*<sub>*d*<sub>1</sub></sub>, … , *p*<sub>*d*<sub>*l*</sub></sub> of the form <code>*q*<sub>*d*<sub>*i*</sub></sub>: *e*<sub>*d*<sub>*i*</sub></sub></code>, where *e*<sub>0</sub> is the receiver and *f* is the member name. In this case *e* can be desugared to an equivalent of <code>**_let_** *x* = *e*<sub>0</sub>, *x*<sub>1</sub> = *e*<sub>1</sub>, … , *x*<sub>*m*+*k*</sub> = *e*<sub>*m*+*k*</sub> **_in_** *x*.*f*\<*A*<sub>1</sub>, … , *A*<sub>*r*</sub>>(*x*<sub>*v*<sub>1</sub></sub>, … , *x*<sub>*v*<sub>*m*</sub></sub>, *q*<sub>*d*<sub>1</sub></sub>: *x*<sub>*d*<sub>1</sub></sub>, … , *q*<sub>*d*<sub>*l*</sub></sub>: *x*<sub>*d*<sub>*l*</sub></sub>)</code>. Invocations of other forms can be desugared similarly. |
| 144 | +
|
| 145 | +Hoisting of some arguments can be omitted by the implementation if that doesn't change the evaluation order. That includes, but is not necessarily limited to, trailing named arguments and constant argument expressions. If an argument list has no named arguments prior to positional arguments, no desugaring is necessary. |
| 146 | +
|
| 147 | +## Summary |
| 148 | +
|
| 149 | +We allow named arguments anywhere in the argument list, even before positional arguments. |
| 150 | +
|
| 151 | +The only real difference is that it changes evaluation order, allowing you to evaluate an argument to a named parameter before the argument to a positional argument. After evaluation, we can trivially normalize the ordering and keep our current specifications and implementations. |
| 152 | +
|
| 153 | +## Versions |
| 154 | +
|
| 155 | +1.0, 2021-11-01: Initial version |
0 commit comments