You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Pitch: You can write `.foo` instead of `ContextType.foo` when it makes sense.
6
5
@@ -36,12 +35,23 @@ We introduce grammar productions of the form:
36
35
37
36
and we add `.` to the tokens that an expression statement cannot start with. _(Just to be safe. If we ever allow metadata on statements, we don’t want `@foo . bar - 4 ;` to be ambiguous. If we ever allow metadata on expressions, we have bigger issues.)_
List<int> l = .filled(10, 42); // (Will use instantiated type).
45
+
```
46
+
39
47
This is a simple grammatical change.
40
48
41
49
A primary expression cannot follow any other complete expression, something which would parse as an expression that `e.id` could be a member access on. We know that because a primary expression already contains the production `'(' <expression> ')'` which would cause an ambiguity for `e1(e2)` if `(e2)` could also be parsed as a `<primary>`.
42
50
43
51
A primary expression *can* follow a `?` in a conditional expression: `{e1?.id:e2}`. This could be ambiguous, but we handle it at tokenization time by making `?.` a single token, so there is no ambiguity here, just potentially surprising parsing if you omit a space between `?` and `.id`. It’s consistent, and a solved problem.
44
52
53
+
It avoids being ambiguous with `<postfixExpression> ::= <primary> <selector>*` by only adding the `<argumentPart>` if there is a `const` in front. (As long as we don’t introduce `'const' <expression>` as a general operator, the `const .` sequence is unique.)
54
+
45
55
### Semantics
46
56
47
57
Dart semantics, static and dynamic, does not follow the grammar precisely. For example, a static member invocation expression of the form `C.id<T1>(e2)` is treated as an atomic entity for type inference (and runtime semantics), it’s not a combination of doing a `C.id` tear-off, then a `<T1>` instantiation and then an `(e2)` invocation. The context type of that entire expression is used throughout the inference, where `(e1.id<T1>)(e2)` has `(e1.id<T1>)` in a position where it has *no* context type. _(For now, come selector based inference, it may have something, but a selector context is not a type context, and it won’t be the context type of the entire expression)._
@@ -52,8 +62,6 @@ _(It also addresses `.new<typeArgs>` and `.new<typeArgs>(args)`, but those will
52
62
53
63
The *general rule* is that any of the expression forms above, starting with <code>.id</code>, are treated exactly *as if* they were prefixed by a fresh variable, <code>*X*</code> which denotes an accessible type alias for the greatest closure of the context type scheme of the expression.
54
64
55
-
56
-
57
65
#### Type inference
58
66
59
67
In every place where the current specification specifies type inference for one of the forms <Code>*T*.*id*</code>, <code>*T*.*id*\<*typeArgs*\></code>, <code>*T*.*id*(*args*)</code>, <code>*T*.*id*\<*typeArgs*\>(*args*)</code>, <code>*T*.new</code> or <code>*T*.new(*args*)</code>, where *T* is a type clause or and identifier denoting a type declaration or a type alias declaration, we introduce a parallel “or <code>.id…</code>” clause, and then continue either with the type denoted by *T* as normal, or, for the <code>.*id*</code> clause, with the greatest closure of the context type scheme, and the *`id`* is looked up in that just as one would in the type denoted by *`T`* for *`T.id`*.
@@ -135,12 +143,22 @@ where we recognize a `<staticMemberShorthand> <selector*>` and use the context
135
143
136
144
```dart
137
145
Range range = .new(0, 100).translate(userDelta);
146
+
BigInt p64 = .two.pow(64);
138
147
```
139
148
140
149
Here the `.new(0, 100)` itself has no context type, but the entire selector chain does, and that is the type used for resolving `.new`.
141
150
142
151
This should allow more expressions to use the context. It may make it easier to get a *wrong* result, but you can do that in the first step if `.foo()` returns something of a wrong type.
143
152
153
+
It does *not* allow other operators, so the following are invalid:
154
+
155
+
```dart
156
+
BigInt m2 = -.two; // INVALID (No context type for operand of unary `-`)
157
+
BigInt m2 = .two + .one; // INVALID (No context type for first operand of `+`)
158
+
```
159
+
160
+
_(We could go further and look at the syntactically first *primary expression* in the entire expression, and apply the full context type to that, if it gets no context type otherwise. Something like `!e` gives `e` a real context type of `bool`, but `.foo + 2` and `-.foo` do not. It’s much more complicated, though.)_
161
+
144
162
An expression of the form `.foo.bar.baz` might not be considered an *assignable* expression, so you can’t do `SomeType v = .current = SomeType();`. An expression of the form `.something` should *produce* the value of the context type, that’s why it’s based on the context type, and an assignment produces the value of its right-hand side, not of the assignable expression.
145
163
146
164
On the other hand, an assignable expression being assigned to gets no context type, so that would automatically not work, and we may not need to make an exception.
@@ -151,11 +169,11 @@ All in all, it doesn’t seem like an implicit static member access can be assig
151
169
152
170
#### Nullable types
153
171
154
-
Should a nullable context type, `Foo?` look for members in `Foo`. Or in `Foo`*and*`Null`. (Which will make more sense when we get static extensions.)
172
+
Should a nullable context type, `Foo?` look for members in `Foo`. Or in `Foo`*and*`Null`. (Which will make more sense when we get static extensions which can add members on `Null`.)
155
173
156
-
It would allow `Foo x = .current ?? Foo(0);` to work, which it doesn’t today when the context type of `.current` is `Foo?`, and a union type doesn’t denote a static namespace.
174
+
It would allow `Foo x = .current ?? Foo(0);` to work, which it doesn’t today when the context type of `.current` is `Foo?`, and a union type doesn’t denote a static namespace otherwise.
157
175
158
-
It’s an option we can add later if there is big demand. Which there might be, because otherwise it makes a difference whether you declare your method as:
176
+
If we don’t allow it, it then makes a difference whether you declare your method as:
which are both completely valid ways to write the same function. It makes a difference because the latter can be called as `foo(.someFoo)` and the former cannot. I’m not sure we *want* to cause that kind of forced choices on API design.
193
+
which are both completely valid ways to write essentially the same function. It makes a difference because the latter can be called as `foo(.someFoo)` and the former cannot, and the former can be called with `null`, which is why you might want it. If we don’t allow shorthands with nullable context types, we effectively encourage people to write in the latter style, and it’s a usability pitfall to use the former with an enum type. I’m not sure we *want* to cause that kind of forced choices on API design. The shorthand shouldn’t punish you for an otherwise reasonable choice.
194
+
195
+
So leaning on allowing.
176
196
177
197
#### Asynchrony and other element types
178
198
179
-
If we say that a type is the authority on creating instances of itself, it *might* also be an authority on creating those instances *asynchronously*. With a context type of `Future<Foo>`, should we check the `Foo` declaration for a `Future<Foo>`-returning function, or just the `Future` class? If do we check `Foo`, we should probably check be both.
199
+
The nullable type is a union type. So is `FutureOr<Foo>`.
200
+
201
+
If we allow the nullable context to access members on the type (and on `Null`), should we allow static members of `Foo` (and `Future`) to be accessed with that as context type?
202
+
203
+
It’s useful. Until [#870](https://dartbug.com/language/870) gets done, the return type of a return expression in an `async` function is `FutureOr<F>` where `F` is the future-value-type of the function. If we don’t allow access, then changing `Foo foo() => .value;` to `Future<Foo> foo() async => .value;` will not work. That’s definitely going to be a surprise to users, and it’s a usability cliff. And telling them to do `Foo result = .value; return result;` instead of `return .value;` goes against everything we have so far tried to teach.
204
+
205
+
Same applies to `Future<SomeEnum> f = Future.value(.someValue);` where `Future.value` which also takes `FutureOr<SomeEnum>` as argument. That would be an argument for having a *real*`Future.valueOnly(T value) : …`, and it’s too bad the good name is taken.
206
+
207
+
If we say that a type is the authority on creating instances of itself, it *might* also be an authority on creating those instances *asynchronously*. With a context type of `Future<Foo>`, should we check the `Foo` declaration for a `Future<Foo>`-returning function, or just the `Future` class? If do we check `Foo`, we should probably check be both.
208
+
209
+
If we allow a static member of `Foo` to be accessed on `FutureOr<Foo>`, and to return a `Future<Foo>`, but do not allow that with a context type of `Future<Foo>`, it punishes people for being specific. It would *encourage* using `FutureOr<Foo>` as type instead of `Future<Foo>`, to make the API more user friendly. So, if we allow shorthand `Foo` member access on `FutureOr<Foo>`, we *may* want to allow it on `Future<Foo>` too. (But not on more specialized subtype of `Future<Foo>`, like `class MyFuture<T> implements Future<T> …`.)
180
210
181
-
This gets even further away from being simple, and it special cases the `Future` type.
211
+
This gets even further away from being simple, and it special cases the `Future` type, which isn’t *that* special as a type. (It’s not a union type. It is very special *semantically*, an asynchronous function is a completely different kind of function than a synchronous one, and `Future<Foo>` is really a way of saying “`Foo`, but later”. But the type is just another type.)
182
212
183
-
While `Future`is special, it’s not *that* special, and we could equally well have a context type of `List<Foo>`and decider to ask `Foo` for such a list. For enums, that’s even useful: `var fooSet = EnumSet<Foo>(.values)`.
213
+
If we don’t consider `Future`to be special in the language, then allowing shorthand access to `Foo` members on `Future<Foo>`can also be used, using the same arguments, for allowing it on `List<Foo>`.
184
214
185
-
So probably a “no” to this.
215
+
For enums, that’s even useful: `var fooSet = EnumSet<Foo>(.values)` which expects a `List<Foo>` as argument.
186
216
217
+
So probably a “no” to `Future<Foo>` and therefore `FutureOr<Foo>`. But it is annoying because of the implicit `FutureOr` context types. (Maybe we can special case `.foo` in returns of `async` functions only.)
187
218
## Versions
188
-
0.9: First version, for initial comments.
219
+
0.2: Updated with more examples and more arguments (in both directions) in the union type sections.
0 commit comments