Skip to content

Commit 345aca2

Browse files
authored
Update proposal-simple-lrhn.md
1 parent 60bbf6e commit 345aca2

File tree

1 file changed

+45
-13
lines changed

1 file changed

+45
-13
lines changed

working/3616 - enum value shorthand/proposal-simple-lrhn.md

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Dart static access shorthand
2-
Author: [email protected]<br>Version: 0.9
3-
2+
Author: [email protected]<br>Version: 0.2
43

54
Pitch: You can write `.foo` instead of `ContextType.foo` when it makes sense.
65

@@ -36,12 +35,23 @@ We introduce grammar productions of the form:
3635

3736
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.)_
3837

38+
That means you can write things like:
39+
40+
```dart
41+
BigInt b0 = .zero; // Context type BigInt
42+
BigInt b1 = b0 + .one; // BigInt.operator+(BigInt other)
43+
String s = .fromCharCode(42);
44+
List<int> l = .filled(10, 42); // (Will use instantiated type).
45+
```
46+
3947
This is a simple grammatical change.
4048

4149
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>`.
4250

4351
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.
4452

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+
4555
### Semantics
4656

4757
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
5262

5363
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.
5464

55-
56-
5765
#### Type inference
5866

5967
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
135143

136144
```dart
137145
Range range = .new(0, 100).translate(userDelta);
146+
BigInt p64 = .two.pow(64);
138147
```
139148

140149
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`.
141150

142151
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.
143152

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+
144162
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.
145163

146164
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
151169

152170
#### Nullable types
153171

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`.)
155173

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.
157175

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:
159177

160178
```dart
161179
void foo([Foo? foo]) {
@@ -172,17 +190,31 @@ void foo([Foo foo = const Foo(null)]) {
172190
}
173191
```
174192

175-
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.
176196

177197
#### Asynchrony and other element types
178198

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> …`.)
180210

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.)
182212

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>`.
184214

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.
186216

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.)
187218
## 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.
220+
0.1: First version, for initial comments.

0 commit comments

Comments
 (0)