Skip to content

Commit 39d55bd

Browse files
authored
Add proposed feature specification for "implicit names". (#4538)
Adds proposed feature specification for "implicit names". Has discussion of other possible syntaxes that the feature can be extended with.
1 parent 69ad01e commit 39d55bd

File tree

1 file changed

+374
-0
lines changed

1 file changed

+374
-0
lines changed
Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
# Dart Implied Parameter/Record Field Names
2+
3+
Author: lrn@google.com<br>
4+
Version: 1.0
5+
6+
## Pitch
7+
Writing the same name twice is annoying. That happens often when forwarding
8+
named arguments:
9+
```dart
10+
var subscription = stream.listen(
11+
onData,
12+
onError: onError,
13+
onDone: onDone,
14+
cancelOnError: cancelOnError,
15+
);
16+
```
17+
18+
To avoid redundant repetition, this feature will allow you to omit the
19+
argument name if it's the same name as the expression providing the value.
20+
21+
```dart
22+
var subscription = stream.listen(
23+
onData,
24+
:onError,
25+
:onDone,
26+
:cancelOnError,
27+
);
28+
```
29+
30+
Same applies to record literal fields, where we have nice syntax
31+
for destructuring, but not for re-creating:
32+
```dart
33+
typedef Color = ({int red, int green, int blue, int alpha});
34+
35+
Color colorWithAlpha(Color color, int newAlpha) {
36+
var (:red, :green, :blue, alpha: _) = color;
37+
return (red: red, green: green, blue: blue, alpha: newAlpha);
38+
}
39+
```
40+
which this feature will allow to be written as:
41+
```dart
42+
Color colorWithAlpha(Color color, int alpha) {
43+
var (:red, :green, :blue, alpha: _) = color;
44+
return (:red, :green, :blue, :alpha);
45+
}
46+
```
47+
48+
## Specification
49+
50+
The current grammar for a named argument is:
51+
```ebnf
52+
<namedArgument> ::= <label> <expression>
53+
```
54+
The [current grammar](../../accepted/3.0/records/feature-specification.md#record-expressions) for a record literal field:
55+
```ebnf
56+
recordField ::= (identifier ':' )? expression
57+
```
58+
So basically the same.
59+
60+
The new grammar is:
61+
```ebnf
62+
<namedArgument> ::= <namedExpression>
63+
64+
<recordField> ::= <namedExpression> | <expression>
65+
66+
<namedExpression> ::= <identifier>? `:' <expression>
67+
```
68+
69+
This grammar change does not introduce any new ambiguities.
70+
A named record field and a named argument can *only* occur
71+
immediately after a `(` or a `,`, as the entire content until
72+
a following `,` or `)`.
73+
Starting with a `:` at that point is not currently possible.
74+
(Even if we allow metadata annotations inside argument lists or record literals,
75+
it's unambiguous whether those annotations includes a following identifier or
76+
not.)
77+
78+
As a non-grammatical restriction, it's a **compile-time error**
79+
if a `<namedExpression>` omits the leading`<identifier>`, and the following
80+
expression is not a _single identifier expression_, as defined below.
81+
82+
An expression `e` is a _single identifier expression with identifier *I*_ if
83+
and only if it is defined as such by one of the following rules,
84+
where `s` is, inductively, a single identifier expression with identifier *I*:
85+
86+
* If `e` is a `<primary>` expression which is an `<identifier>`,
87+
it is a single identifier expression with that `<identifier>` as identifier.
88+
* `s!`: If `e` is a `<primary> <selector>*` production where `<selector>*`
89+
is the single selector `` `!' ``, and `<primary>` is `s`,
90+
then `e` is a single identifier expression with identifier *I*.
91+
* `s as T`: If `e` is a `<relationalExpression>` of the form
92+
`<bitwiseOrExpression> <typeCast>` and the `<bitwiseOrExpression>` is `s`
93+
then `e` is a single identifier expression with identifier *I*.
94+
* `(s)`: If `e` is a `<primary>` production of the form
95+
`` `(' <expression> `)' `` and the `<expression>` is `s`, then `e` is a single identifier expression with the same identifier as
96+
the `<expression>`.
97+
98+
_In short, an identifier expression is a single identifier expression,
99+
and you can then wrap it in null-assertions, parentheses, or casts,
100+
and it will still be a single identifier expression with the same identifier. The value if `id` is the same as the value of
101+
`(id! as List<num>)` &mdash; if it has a value._
102+
_The resulting expression still evaluates to the value of the original
103+
identifier, if it doesn't throw first._
104+
105+
The _name of a `<namedExpression>`_ is then:
106+
* If the named expression has a leading identifier before the colon,
107+
then that identifier.
108+
* Otherwise the following expression must be a single identifier expression
109+
with an identifier *I*, and then the name of the named expression is *I*.
110+
111+
The name of a `<namedArgument>` or a named `<recordField>` is the name of
112+
its `<namedExpression>`.
113+
114+
Where the language specification refers to a named argument's name,
115+
it now uses this definition of the name of a `<namedArgument>`.
116+
117+
Where the Record specification for a record literal refers to a field name,
118+
it now uses this definition.
119+
120+
## Semantics
121+
122+
There are no changes to static or runtime semantics, other than extending the
123+
notion of "name of a named argument" and "name of a field" to the name of the
124+
single identifier expression following an unnamed `:`.
125+
126+
After that, there is no distinction between `(name: name,)` and `(: name,)`,
127+
both syntaxes introduce a named argument or record field with name `name`
128+
and expression `name`, and the semantics is only defined in terms of those
129+
properties.
130+
131+
132+
## Examples
133+
134+
From `sdk/lib/_http/http_impl.dart` (among many others):
135+
```dart
136+
return _incoming.listen(
137+
onData,
138+
:onError,
139+
:onDone,
140+
:cancelOnError,
141+
);
142+
```
143+
144+
From `pkg/_fe_analyzer_shared/lib/src/type_inference/type_analyzer.dart`:
145+
```dart
146+
return new SwitchStatementTypeAnalysisResult(
147+
:hasDefault,
148+
:isExhaustive,
149+
:lastCaseTerminates,
150+
:requiresExhaustivenessValidation,
151+
:scrutineeType,
152+
:switchCaseCompletesNormallyErrors,
153+
:nonBooleanGuardErrors,
154+
:guardTypes,
155+
);
156+
```
157+
158+
From `pkg/dds/lib/src/dap/isolate_manager.dart`:
159+
```dart
160+
final uniqueBreakpointId = (:isolateId, :breakpointId);
161+
```
162+
163+
## Migration
164+
165+
None.
166+
167+
## Implementation
168+
169+
Experiment flag name could be `implicit-names`.
170+
171+
## Tooling
172+
173+
Analysis server can suggest removing a redundant name before a `:`.
174+
175+
There should *probably* be a lint to report when an argument name is redundant.
176+
Because if there isn't, someone will almost immediately ask for one.
177+
Whether one likes omitting the name or not is a matter of taste, so it should
178+
be an optional lint, not just a warning. It should be easy to have a fix.
179+
180+
A _rename operation_ may need to recognize if it changes a parameter
181+
name or an identifier used as argument, and insert an argument name
182+
if now necessary. That is:
183+
* If a name changes, then if any occurrences of that name is in
184+
single identifier position of an implicitly named argument,
185+
the old name must be inserted as explicit argument name.
186+
* If a named parameter name changes, then any invocation of the function
187+
which uses an implicitly named argument must have the new name
188+
inserted as explicit argument name.
189+
190+
A rename can also introduce new possibilities for removing argument names.
191+
(It may be better to not do that automatically, and rely on the user fixing
192+
the positions afterwards. Or only do it automatically if the lint is enabled.)
193+
194+
## Discussion
195+
196+
The definition of "single identifier expression" is the place this feature
197+
can be tweaked.
198+
199+
It's currently restricted to expressions where the value of the expression
200+
is always the value of evaluating a single identifier.
201+
Also, that identifier is always the *only* identifier of the expression,
202+
and can only be preceded by `(`s.
203+
204+
The identifier expression can be wrapped in casts `!` or `as T`
205+
or in parentheses, but none of those operations change the value
206+
of the expression away from the value of evaluating the identifier,
207+
only, potentially, whether it evaluates to a value at all.
208+
209+
Those are properties chosen to make it easier to read and understand
210+
a missing name, but nothing is technically necessary,
211+
we could allow any expression where we can, somehow, derive a significant
212+
identifier.
213+
The limitation to the name being the next non-`(` token should hopefully make it
214+
*very easy* to find the name.
215+
216+
Possible additions, initially or in the future, could include the following
217+
expression forms.
218+
219+
### Cascades
220+
221+
A cascade expression like `e..selector` or `e?..selector` also satisfies
222+
that the value of the expression is the value of the leading sub-expression.
223+
It could be made a single identifier expression, and since cascades are
224+
often used in argument position, that is exactly where we would *want*
225+
to use the shorter syntax.
226+
227+
The rule for single identifier expressions would add another rule:
228+
229+
> * `s..cascade`:
230+
> * If `e` a `<cascade>` of the form ``<cascade> `..' <cascadeSection>``
231+
> and the `<cascade>` is `s`,
232+
> then `e` is a single identifier expression with identifier *I*.
233+
> * If `e` a `<cascade>` of the form
234+
> ``<conditionalExpression> (`?..' | `..') <cascadeSection>``
235+
> and the `<conditionalExpression>` is `s`,
236+
> then `e` is a single identifier expression with identifier *I*.
237+
238+
The other cases do not contain any other identifier than the one that
239+
provides the value of the expression. A cascade could have any expression
240+
after the `..`, so it would be even more important for readability that
241+
the reader knows to look at the very next identifier for the name.
242+
243+
Examples of uses that could be made shorter (from `package:csslib`):
244+
```dart
245+
stylesheet = parseCss(input, errors: errors..clear());
246+
```
247+
which would become:
248+
```dart
249+
stylesheet = parseCss(input, :errors..clear());
250+
```
251+
252+
An example from Flutter:
253+
```dart
254+
TextSpan(text: offScreenText, recognizer: recognizer..onTap = () {})
255+
```
256+
which would become:
257+
```dart
258+
TextSpan(text: offScreenText, :recognizer..onTap = () {})
259+
```
260+
261+
Also, if we *ever* want to allow a prefixed identifier, making it possible
262+
to abbreviate `foo(bar: source.bar)` to `foo(:source.bar)`, then it's confusing
263+
that the very syntactically similar `(:a.b)`and `(:a..b)` mean
264+
`(b: a.b)` and `(a:..b)` respectively. Not surprising since the *value* of the
265+
expression comes from something named `b` in one case and something names `a`
266+
in the other, but does make it easier to lose track of which name goes where.
267+
_(With `(:a.b)` meaning `(b: a.b)`, it's more like the *last* identifier is
268+
the one that provides the name, and a cascade would break that, leaving the
269+
reader with no easy rule for where to find the significant identifier.)_
270+
271+
Another example simplified from actual code:
272+
```dart
273+
static Uri addQueryParameters(
274+
Uri uri,
275+
Map<String, String> queryParameters,
276+
) => uri.replace(:queryParameters..addAll(uri.queryParameters));
277+
```
278+
279+
And from a Flutter program using a null-aware cascade:
280+
```dart
281+
child: TextField(
282+
:controller?..text = initialValue,
283+
maxLines: 5,
284+
:onChanged,
285+
),
286+
```
287+
288+
### Increments
289+
290+
Expressions of the form `++id`/`--id` or `id++`/`id--` also evaluate to
291+
the value of `id`, either as it was when read for `id++`, or what it is
292+
now for `++id`, and the `id` is the first identifier of the expression.
293+
294+
As such, they are within the design parameters that are otherwise used,
295+
and could be allowed. _They'd only be valid directly on the identifier,
296+
not after wrapping with `!`, `as T` or `(...)`._
297+
298+
Increments are also often used in argument position, so it would fit
299+
in that way too.
300+
301+
I'd expect it to be less common that a *counter* has the same
302+
name as the *value*. Passing the value of a mutable variable as
303+
an argument means that the are less likely to have the *same meaning*,
304+
even if they have the same value.
305+
306+
For now, it's not included. It can easily be added if an real need
307+
is discovered.
308+
309+
### Assignments
310+
311+
An expression of the form `id1 = id2` is an expression which has the same
312+
value as the identifier, `id2`.
313+
(We don't know for sure what assigning to `id1` means,
314+
or if it can even be read, but we know that the value of the expression
315+
is the value of evaluating `id2`.)
316+
317+
Assignments are not included because that identifier is not the next identifier
318+
of the expression, and because it can easily be confusing which identifier
319+
defines the name. (And more so for more steps, like `:foo = bar = baz`.)
320+
321+
It would be *more consistent* to use the identifier `id1`.
322+
If assignment behaves *as it looks like it should*, then `id1` is a name for
323+
the value of the expression. It is also the first identifier, which makes
324+
it easy to find, and it would also open the door to `id += 2` &mdash;
325+
to generalize `id++` &mdash; or to `id ??= 42`, where the latter makes good
326+
sense in a parameter position.
327+
328+
The most generally useful choice would be that `id = e` and `id op= e`
329+
would count as `id`, since the value of the expression is the (new) value
330+
of `id`, and `id` is the first identifier of the expression.
331+
332+
Probably likely to be confusing.
333+
The expression occurs in parameter or record-field position,
334+
which means that there is *another* implicit assignment going on.
335+
336+
_If we also want to allow `:a.b` to be short for `b:a.b`, then allowing
337+
assignment puts the significant identifier in the middle: `:e1.id = e2`,
338+
which can make it hard to find.
339+
340+
### Property access
341+
342+
As alluded to above, we could allow `(:e.b)` to mean `(b: e.b)`,
343+
using the name of a final selector to represent the value it
344+
evaluates to.
345+
346+
That is consistent with using plain identifiers that refer to instance getters
347+
inside the declaring context.
348+
It would allow referring to identifiers imported with a prefix
349+
or accessing an identifier from outside of its scope just as briefly
350+
as inside its scope.
351+
352+
It would mean that the operative identifier is no longer the *first*
353+
identifier of the expression. Rather, it would be the last one,
354+
which conflicts with allowing cascades or assignments, that both
355+
add something after the identifier. A `(:a.b..c.d)` or `(:a.b=c.d)` would
356+
have the missing name somewhere in the middle of the expression,
357+
and not necessarily easy to find.
358+
359+
### Future additions.
360+
361+
Extending the "single identifier expression" to more syntaxes is non-breaking.
362+
It turns something that would be a compile-time error into something else.
363+
364+
That means that we can always add more cases later.
365+
The initially proposed expressions are only the simplest of cases,
366+
where there is only one identifier in the expression at all,
367+
so no choice of it being the first or last identifier has been made.
368+
(And increments are omitted because they are similar to assignments,
369+
and it feels like half a feature to only handle increments by themselves.)
370+
371+
## Revision history
372+
373+
* 1.0: Initial version
374+

0 commit comments

Comments
 (0)