|
| 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>)` — 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` — |
| 325 | +to generalize `id++` — 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