1
- TODO: link
1
+ # Wildcards
2
+
3
+ Author: Bob Nystrom
4
+
5
+ Status: In-progress
6
+
7
+ Version 1.0
2
8
3
9
Pattern matching brings a new way to declare variables. Inside patterns, any
4
10
variable whose name is ` _ ` is considered a "wildcard". It behaves like a
5
- variable syntactically, but it doesn't actually create a variable with that
6
- name. That means you can use ` _ ` multiple times in a pattern without a name
7
- collision, and you can't use it as an expression to access the matched value.
11
+ variable syntactically, but doesn't actually create a variable with that name.
12
+ That means you can use ` _ ` multiple times in a pattern without a name collision.
8
13
9
14
This proposal extends that non-binding behavior to other variables in Dart
10
15
programs named ` _ ` .
@@ -19,7 +24,7 @@ The name `_` is a well-established convention in Dart for a couple of uses:
19
24
var hundredPenguins = List.generate(100, (_) => 'penguin');
20
25
```
21
26
22
- * **Used method override parameters.** When a method overrides an inherited
27
+ * **Unused method override parameters.** When a method overrides an inherited
23
28
method, it must accept the same required parameters. If it doesn't use one,
24
29
sometimes it gets named `_`. (In practice, it's more common to just use the
25
30
same name as the inherited parameter.)
@@ -47,9 +52,9 @@ The name `_` is a well-established convention in Dart for a couple of uses:
47
52
Since `_` *is* a binding name in Dart today, you can only use this name once in
48
53
any given scope. If you want to ignore multiple callback parameters, the
49
54
convention is to use a series of underscores for each name: `_`, `__`, `___`,
50
- etc. In practice, these are all "wildcard" names.
55
+ etc.
51
56
52
- Making `_` actually non-binding solves a few problems:
57
+ Making `_` non-binding outside of patterns solves a few problems:
53
58
54
59
* It makes other variable declarations consistent with how variable
55
60
declarations in patterns behave.
@@ -59,45 +64,258 @@ Making `_` actually non-binding solves a few problems:
59
64
60
65
* It prevents code from using a variable that wasn't intended to be used.
61
66
62
- At the same time, we want to *not* prevent the idiom of using `_` as the name
63
- of the canonical private constructor. This means making `_` non-binding for
64
- *variables*, but not for *members*. Getters and setters make the line between
65
- those somewhat fuzzy.
67
+ At the same time, we want to support the idiom of using `_` as the name of the
68
+ canonical private constructor, so we don't want *all* declarations named `_`
69
+ to be non-binding.
70
+
71
+ It seems that the natural line to draw is between declarations that are local
72
+ to a block scope versus those that are top-level declarations or members where
73
+ library privacy comes into play.
66
74
67
75
## Proposal
68
76
69
- TODO: should silence unused variable warnings
77
+ A *local declaration* is any of:
70
78
71
- ## Breaking change
79
+ * Function parameters. This includes top-level functions, local functions,
80
+ function expressions ("lambdas"), instance methods, static methods,
81
+ constructors, etc. It includes all parameter kinds: simple, field formals,
82
+ and function-typed formals, etc.:
83
+
84
+ ```dart
85
+ Foo(_, this._, super._, void _(), {_}) {}
72
86
73
- This is a breaking change to code that declares variables named `_` and actually
74
- uses them.
87
+ list.where((_) => true);
88
+ ```
89
+
90
+ * Local variable declaration statement variables.
91
+
92
+ ```dart
93
+ main() {
94
+ var _ = 1;
95
+ int _ = 2;
96
+ }
97
+ ```
75
98
76
- TODO: stats
99
+ * For loop variable declarations.
77
100
78
- ## Semantics
101
+ ```dart
102
+ for (_ = 0;;) {}
103
+ for (_ in list) {}
104
+ ```
105
+
106
+ * Catch clause parameters.
107
+
108
+ ```dart
109
+ try {
110
+ throw '!';
111
+ } catch (_) {
112
+ print('oops');
113
+ }
114
+ ```
115
+
116
+ * Generic type and generic function type parameters.
117
+
118
+ ```dart
119
+ class T<_> {}
120
+
121
+ takeGenericCallback(<_>() => true);
122
+ ```
123
+
124
+ A local declaration whose name is `_` does not bind anything to that name. This
125
+ means you can have multiple local declarations named `_` in the same namespace
126
+ without a collision error. The initializer, if there is one, is still executed,
127
+ but the value is not accessible.
128
+
129
+ Other declarations: top-level variables, top-level function names, type names,
130
+ member names, etc. are unchanged. They can be named `_` as they are today.
79
131
80
132
We do not change how identifier *expressions* behave. Members can be named `_`
81
133
and you can access them from inside the class where the member is declared
82
- without any leading `this.`. If the member is a method tear-off, it's possible
83
- for a `_` expression to be meaningful:
134
+ without any leading `this.`:
135
+
136
+ ```dart
137
+ class C {
138
+ var _ = 'bound';
139
+
140
+ test() {
141
+ print(_); // Prints "bounnd".
142
+ }
143
+ }
144
+ ```
145
+
146
+ Likewise with a top-level named ` _ ` :
147
+
148
+ ``` dart
149
+ var _ = 'ok';
150
+
151
+ main() {
152
+ print(_); // Prints "ok".
153
+ }
154
+ ```
155
+
156
+ It's just that a local declaration named ` _ ` doesn't bind that name to anything.
157
+
158
+ There are a few interesting corners and refinements:
159
+
160
+ ### Assignment
161
+
162
+ The behavior of assignment expressions is unchanged. In a pattern assignment,
163
+ ` _ ` is always a wildcard. This is valid:
164
+
165
+ ``` dart
166
+ int a;
167
+ (_, a) = (1, 2);
168
+ ```
169
+
170
+ But in a non-pattern assignment, ` _ ` is treated as a normal identifier. If it
171
+ resolves to something assignable (which now must mean a member or top-level
172
+ declaration), the assignment is valid. Otherwise it's an error:
173
+
174
+ ``` dart
175
+ main() {
176
+ _ = 1; // Error.
177
+ }
178
+
179
+ class C {
180
+ var _;
181
+
182
+ test() {
183
+ _ = 2; // OK.
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### Wildcards do not shadow
189
+
190
+ Here is an interesting example:
84
191
85
192
``` dart
86
193
class C {
87
- static C _() => C() ;
194
+ var _ = 'field' ;
88
195
89
- static C test() {
90
- var tearOff = _; // <-- Valid.
91
- return tearOff();
196
+ test() {
197
+ var _ = 'local';
198
+
199
+ _ = 'assign';
92
200
}
93
201
}
94
202
```
95
203
96
- todo: should we allow instance or static fields? no: will be inconsistent if
97
- later allow patterns there
204
+ This program is valid and assigns to the * field* , not the local. This code is
205
+ quite confusing. In practice, we expect reasonable users will not name fields
206
+ ` _ ` and thus not run into this problem.
207
+
208
+ ### Initializing formals
209
+
210
+ An initializing formal named ` _ ` does still initialize a field named ` _ ` (and
211
+ you can still have a field with that name):
212
+
213
+ ``` dart
214
+ class C {
215
+ var _;
216
+
217
+ C(this._); // OK.
218
+ }
219
+ ```
220
+
221
+ But no * parameter* with that name is bound, which means ` _ ` can't be accessed
222
+ inside the initializer list. In the body is fine, since that refers to the
223
+ field, not the parameter:
224
+
225
+ ``` dart
226
+ class C {
227
+ var _;
228
+ var other;
229
+
230
+ C(this._)
231
+ : other = _ { // <-- Error. No "_" in scope.
232
+ print(_); // OK. Prints the field.
233
+ }
234
+ }
235
+ ```
236
+
237
+ Even though the parameters no longer collide, it is still an error to have two
238
+ initializing formals named ` _ ` :
239
+
240
+ ``` dart
241
+ class C {
242
+ var _;
243
+ C(this._, this._); // Error.
244
+ }
245
+ ```
246
+
247
+ ### Unused variable warnings
248
+
249
+ Dart tools currently warn if you have an unused local declaration. If the
250
+ declaration is named ` _ ` , it now * can't* be used, so the tools should stop
251
+ showing the warning for that name.
252
+
253
+ ### Multiple underscore lint and quick fix
254
+
255
+ Since ` _ ` is binding today, when you need more than one, the convention to avoid
256
+ collisions is to use a series of underscores. With this proposal, that
257
+ convention is no longer needed.
258
+
259
+ It would be helpful if the linter would suggest that variables whose name is a
260
+ series of more than one ` _ ` be renamed to just ` _ ` now that it won't collide.
261
+ Likewise, a quick fix could perform that change automatically.
262
+
263
+ ## Breaking change
264
+
265
+ This is a breaking change to code with parameters or other local declarations
266
+ named ` _ ` that actually uses them. Fortunately, code doing that is rare.
267
+
268
+ I wrote [ a script] [ scrape ] to examine every identifier whose name consists only
269
+ of underscores in a large corpus of pub packages, Flutter widgets, and open
270
+ source Flutter applications (18,695,158 lines in 102,015 files):
271
+
272
+ [ scrape ] : https://gist.github.com/munificent/e03728874aae4d16a9760f207aecb16c
273
+
274
+ ```
275
+ -- Declaration (33074 total) --
276
+ 21786 ( 65.870%): Parameter name
277
+ 8093 ( 24.469%): Constructor name
278
+ 2913 ( 8.808%): Catch parameter
279
+ 236 ( 0.714%): Local variable
280
+ 31 ( 0.094%): Loop variable
281
+ 3 ( 0.009%): Extension name
282
+ 2 ( 0.006%): For loop variable
283
+ 2 ( 0.006%): Static field
284
+ 2 ( 0.006%): Type parameter
285
+ 2 ( 0.006%): Instance field
286
+ 2 ( 0.006%): Method name
287
+ 1 ( 0.003%): Enum value name
288
+ 1 ( 0.003%): Function name
289
+
290
+ -- Use (17356 total) --
291
+ 13289 ( 76.567%): Private constructor invocation =========
292
+ 2640 ( 15.211%): Private superclass constructor invocation ==
293
+ 807 ( 4.650%): Identifier expression =
294
+ 522 ( 3.008%): Redirection to private constructor =
295
+ 48 ( 0.277%): Factory constructor redirecting to private name =
296
+ 46 ( 0.265%): Assignment target =
297
+ 3 ( 0.017%): Field initializer =
298
+ 1 ( 0.006%): Type annotation =
299
+ ```
300
+
301
+ As expected, most declarations named ` _ ` (or longer) are parameters and
302
+ constructor names, the two main idioms. Catch clause parameters are fairly
303
+ common too. All other declarations named ` _ ` are extremely rare, less than 1% of
304
+ the total when put together.
305
+
306
+ When code * uses* a declaration named ` _ ` (or longer), over 95% are private
307
+ constructor calls, as expected. But there are a relatively small number of uses
308
+ where a variable named ` _ ` is being accessed. My simple syntactic analysis can't
309
+ distinguish between whether those are accessing local variables (in which case
310
+ they will break) or instance or top-level members (in which case they're OK).
311
+ From skimming some of the examples, it does look like some users sometimes name
312
+ lambda parameters ` _ ` and then use the parameter in the body.
98
313
99
- todo: should we allow getters and setters? no: inconsistent with vars.
314
+ Fixing these is easy: just rename the variable and its uses to something other
315
+ than ` _ ` . Since this proposal doesn't affect the behavior of externally visible
316
+ members, these fixes can always be done locally without changing a library's
317
+ public API.
100
318
101
- todo: should we allow methods? yes: constructors and static methods mostly
102
- interchangeable. allowing static methods without instance methods would be
103
- weird .
319
+ However, this * is * a breaking change. If this ships in the same version as
320
+ pattern matching, we can gate it behind a language version and only break code
321
+ when it upgrades to that version .
0 commit comments