Skip to content

Commit 02c4ada

Browse files
authored
Update proposal-simple-lrhn.md
With newest decision from language meeting 2024-11-27.
1 parent 62d384f commit 02c4ada

File tree

1 file changed

+104
-57
lines changed

1 file changed

+104
-57
lines changed

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

Lines changed: 104 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Dart static access shorthand
22

3-
Author: [email protected]<br>Version: 1.1
3+
Author: [email protected]<br>Version: 1.2
44

55
You can write `.foo` instead of `ContextType.foo` when it makes sense. The rules
66
are fairly simple and easy to explain.
@@ -31,20 +31,25 @@ We also special-case the `==` and `!=` operators, but nothing else.
3131
We introduce grammar productions of the form:
3232

3333
```ebnf
34-
<primary> ::= ... -- all current productions
34+
<postfixExpression> ::= ... -- all current productions
3535
| <staticMemberShorthand>
3636
3737
<constantPattern> ::= ... -- all current productions
3838
| <staticMemberShorthand>
3939
40-
<staticMemberShorthand> ::=
40+
<staticMemberShorthand> ::= <staticMemberShorthandHead> <selector*>
41+
42+
<staticMemberShorthandHead> ::=
4143
'.' (<identifier> | 'new') -- shorthand qualified name
4244
| 'const' '.' (<identifier> | 'new') <arguments> -- shorthand object creation
4345
```
4446

45-
We also add `.` to the tokens that an expression statement cannot start with.
47+
We also add `.` to the tokens that an expression statement cannot start with. This doesn't
48+
affect starting with a double literal like `.42`, since that's a different token than a single `.`.
49+
_(Not sure this is *necessary*, but it will possibly make parser recovery easier._
50+
_So mainly disallow this as an abundance of caution.)_
4651

47-
That means you can write things like the following (with the intended meaning as
52+
That means you can write things like the following (with the intended meaning as
4853
comments, specification to achieve that below):
4954

5055
```dart
@@ -77,13 +82,20 @@ Future<String> futures = .wait([lazyString(), lazyString()]).then((list) => list
7782
```
7883

7984
This is a simple grammatical change. It allows new constructs in any place where
80-
we currently allow primary expressions, which can be followed by selector chains
81-
through the `<postfixExpression>` production `<primary> <selector>*`.
85+
we currently allow primary expressions followed by selector chains
86+
through the `<postfixExpression>` production `<primary> <selector>*`,
87+
and now also `<staticMemberShorthandHead> <selector*>`.
88+
89+
The new grammar is added as a separate production, rather than making
90+
`<staticMemberShorthandHead>` a `<primary>`, and sharing the `<selector>*`
91+
between all `<primary>`s, because the context type of the entire
92+
`<staticMemberShorthand>` is relevant and will be captured when processing
93+
that production.
8294

8395
#### Non-ambiguity
8496

85-
A `<primary>` cannot immediately follow any other complete expression. We trust
86-
that because a primary expression already contains the production
97+
A `<postfixExpression>` cannot immediately follow any other complete expression.
98+
We trust that because a primary expression already contains the production
8799
`'(' <expression> ')'` which would cause an ambiguity for `e1(e2)` since `(e2)`
88100
can also be parsed as a `<primary>`. The existing places where a `.` token
89101
occurs in the grammar are all in positions where they follow another expression
@@ -98,12 +110,13 @@ new rules are needed.
98110
Therefore the new productions introduces no new grammatical ambiguities.
99111

100112
We prevent expression statements from starting with `.` mainly out of caution.
101-
_(It’s very unlikely that an expression statement starting with static member
102-
shorthand can compile at all. If we ever allow metadata on statements, we don’t
103-
want `@foo . bar(4) ;` to be ambiguous. If we ever allow metadata on
104-
expressions, we have bigger issues.)_
113+
_(It's an unlikely expression that can start with a static member, it requires something
114+
that adds a context type on the left, `.parse(userInput) || (throw "Not true!")`
115+
or similar, which isn't particularly *useful*._
116+
_If we ever allow metadata on statements, we don’t want `@foo . bar(4);`
117+
to be ambiguous. If we ever allow metadata on expressions, we have bigger issues.)_
105118

106-
A primary expression *can* follow a `?` in a conditional expression, as in
119+
A postfix expression expression *can* follow a `?` in a conditional expression, as in
107120
`{e1 ? . id : e2}`. This is not ambiguous with `e1?.id` since we parse `?.` as a
108121
single token, and will keep doing so. It does mean that `{e1?.id:e2}` and
109122
`{e1? .id:e2}` will now both be valid and have different meanings, where the
@@ -133,41 +146,47 @@ which is not generic. We do not want this to be treated as
133146
constructor.)_
134147

135148
The *general rule* is that any of the expression forms above, starting with
136-
<code>.id</code>, are treated exactly *as if* they were prefixed by a fresh
137-
identifier <code>*X*</code> which denotes an accessible type alias for the
138-
greatest closure of the context type scheme of the following primary and
139-
selector chain.
149+
<code>.id</code>, are treated exactly *as if* they were preceded by a fresh
150+
prefixed identifier <code>*_p.C*</code> which denotes the declaration of the type of the
151+
context type scheme of the entire `<staticMemberShorthand>`.
140152

141153
#### Type inference
142154

143155
First, when inferring types for a `<postfixExpression>` of the form
144-
`<staticMemberShorthand> <selector>*` with context type scheme *C*, then, if the
145-
`<staticMemberShorthand>` has not yet been assigned a *shorthand context*,
146-
assign *C* as its shorthand context. Then continue as normal. _This assigns the
156+
`<staticMemberShorthand>` with context type scheme *C*, then assign *C* as
157+
the shorthand context of the leading `<staticMemberShorthandHead>`.
158+
Then continue inferring a type for the entire `<staticMemberShorthand>`
159+
recursively on the chain of selectors of the `<selector*>`,
160+
in the same way as for a `<primary> <selector>*`. _This assigns the
147161
context type scheme of the entire, maximal selector chain to the static member
148-
shorthand, and does not change that when recursing on shorter prefixes._
162+
shorthand head, moving it past any intermediate `<selector>`s._
149163

150-
_The effect will be that `.id…` will behave exactly like `T.id…` where `T`
164+
_The intended effect will be that `.id…` will behave exactly like `T.id…` where `T`
151165
denotes the declaration of the context type._
152166

153-
**Definition:** If a shorthand context type schema has the form `C` or `C<...>`,
167+
**Definition (Declaration denoted by a context type scheme):**
168+
If a shorthand context type schema has the form `C` or `C<...>`,
154169
and `C` is a type introduced by the type declaration *D*, then the shorthand
155170
context *denotes the type declaration* *D*. If a shorthand context `S` denotes a
156-
type declaration *D*, then so does a shorthand context `S?`. Otherwise, a
157-
shorthand context does not denote any declaration.
171+
type declaration *D*, then so does a shorthand context of `S?` and `FutureOr<S>`.
172+
Otherwise, a shorthand context does not denote any declaration.
158173

159-
_This effectively derives a *declaration* from the context type scheme of the
174+
_This effectively derives a single declaration from the context type scheme of the
160175
surrounding `<postfixExpression>`. It allows a nullable context type to denote
161176
the same as its non-`Null` type, so that you can use a static member shorthand
162177
as an argument for optional parameters, or in other contexts where we change a
163-
type to nullable just to allow omitting things ._
178+
type to nullable just to allow omitting things , and it allows a `FutureOr<T>` to_
179+
_denoted the same declarations as `T` mainly to allow shorthands in returns_
180+
_of `async` function_._
164181

165182
**Constant shorthand**: When inferring types for a `const .id(arguments)` or
166183
`const .new(arguments)` with context type schema *C*, let *D* be the declaration
167184
denoted by the shorthand context assigned to the `<staticMemberShorthand>`. Then
168-
proceed with type inference as if `.id`/`.new` was preceded by an identifier
185+
proceed with type inference as if `.id`/`.new` was preceded by an identifier `D`
169186
denoting the declaration *D*. It’s a compile-time error if the shorthand context
170-
does not denote a class, mixin, enum or extension type declaration.
187+
does not denote a class, mixin, enum or extension type declaration, or if
188+
`D.id`/`D.new` does not denote a constant constructor. _(And since `mixin`s cannot
189+
have constructors, it won't be a `mixin` declaration.)_
171190

172191
**Non-constant shorthand**: When inferring types for constructs containing the
173192
non-`const` production, in every place where the current specification specifies
@@ -183,6 +202,11 @@ assigned to the leading `<staticMemberShorthand>`. It’s a compile-time error i
183202
the shorthand context does not denote a class, mixin, enum or extension type
184203
declaration.
185204

205+
_If no selectors were recursed past getting to this point, or only `!` selectors, then
206+
this expression may have an actual context type. If it was followed by "real" selectors,_
207+
_like `.parse(input).abs()`, then the recognized expression, `.parse(input)`_
208+
_here, likely has no context type._
209+
186210
Expression forms `.new<typeArgs>` or `.new<typeArgs>(args)` will always be
187211
compile-time errors. (The grammar allows them, because it allows any selector to
188212
follow a static member shorthand, but that static member shorthand must denote a
@@ -205,8 +229,9 @@ be inferred as `List<int>`. In most normal use cases it doesn’t matter, becaus
205229
the context type will fill in the missing type variables, but if the
206230
construction is followed by more selectors, it loses that context type. _It also
207231
means that the meaning of `.id`/`.new` is *always* the same, it doesn’t matter
208-
whether it’s a constructor or a static member, it’s always preceded by the name
209-
of the declaration denoted by the context.
232+
whether it’s a constructor or a static member, it’s always implicitly preceded by
233+
the raw name of the declaration denoted by the context, and any instantiation
234+
in the context is ignored.
210235

211236
The following uses are *not* allowed because they have no shorthand context that
212237
denotes an allowed type declaration:
@@ -219,33 +244,27 @@ dynamic v3 = .parse("42"); // Context `_`.
219244
FutureOr<int> v4 = .parse("42"); // Context `FutureOr<int>` is structural type.
220245
```
221246

247+
Since `!` does propagate a context, `int x = (.tryParse(input))!;` does work,
248+
with a context type scheme of `int?`, which is enough to allow `.tryParse`.
249+
222250
#### Special case for `==`
223251

224-
For `==`, we special-case when the right operand is a static member shorthand.
252+
For `==`, we special-case when the right operand is (precisely!) a static
253+
member shorthand.
225254

226255
If an expression has the form `e1 == e2` or `e1 != e2`, or a pattern has the
227-
form `== e2`, where the static type of `e1` is *S1* and the function signature
228-
of `operator ==` of `S1` is <code>*R* Function(*T*)</code>, *then* before doing
229-
type inference of `e2` as part of that expression or pattern:
230-
231-
* If `e2` has the form `<staticMemberShorthand> <selector>*` and
232-
<code>*T*</code> is a supertype of `Object`,
233-
234-
* Then assign *T* as the shorthand context of `e2`.
235-
236-
_If the parameter type of the `==` operator of the type of `e1` is,
237-
unexpectedly, a proper subtype of `Object` (so it's declared `covariant`), it's
238-
assumed that that is the kind of object it should be compared to. Otherwise we
239-
assume the right-hand side should have the same type as the left-hand side, most
240-
likely an enum value._
256+
form `== e2`, where the static type of `e1` is *S1* , and *e2* is precisely a
257+
`<staticMemberShorthand>` expression, then assign the type *S1* as the
258+
shorthand context of the `<staticMemberShorthandHead>` of *e2* before inferring
259+
its static type the same way as above.
241260

242261
This special-casing is only against an immediate static member shorthand.
243262
It does not change the *context type* of the second operand, so it would not
244263
work with, for example, `Endian.host == wantBig ? .big : .little`.
245-
Here the second operand is not a `<staticMemberShorthand> <selector>*`,
246-
so it won't have a shorthand context set, and the parameter type of
247-
`Endian.operator==` is `Object`, so that is the context type of the
248-
second operand.
264+
Here the second operand is not a `<staticMemberShorthand>`,
265+
so it won't have a shorthand context set, and the context type of the
266+
second operand of `==` is the empty context. (It's neither the static type of
267+
the first operand, or the parameter type of the first operand's `operator==`.)
249268

250269
Examples of allowed comparisons:
251270

@@ -260,11 +279,19 @@ Not allowed:
260279
// NOT ALLOWED, ALL `.id`S ARE ERRORS
261280
if (.host == Endian.host) notOk!; // Dart `==` is not symmetric.
262281
if (Endian.host == preferLittle ? .little : .big) notOk!; // RHS not shorthand.
263-
if ((Endian.host as Object) == .little) notOk!; // Context type `Object`.
282+
if ((Endian.host as Object) == .little) notOk!; // Assigned shorthand context type `Object`.
264283
```
265284
_We could consider generally changing the context type of the second operand to
266285
the static type of the LHS, an aspirational context type, if the parameter type
267-
is not useful._
286+
is not useful. For now, that's kept as a possible future improvement._
287+
288+
If a pattern has the form `'==' <staticMemberShorthand>`, then the matched value type
289+
is assigned as the shorthand context of the leading `<staticMemberShorthandHead>`,
290+
and the type is inferred in the same way. It's also a compile-time error if the resulting
291+
expression is not constant, which limits the possible forms.
292+
293+
If a `<staticMemberShorthand>` occurs in a constant context for any other reason,
294+
it must also be a constant expression.
268295

269296
#### Runtime semantics
270297

@@ -275,9 +302,8 @@ to constructors, and use those as runtime type arguments to the class, we infer
275302
the entire target of the member access and use that at runtime._
276303

277304
In every case where we inserted a type inference clause, we resolved the
278-
reference to a static member in order to use its type for static type inference.
279-
The runtime semantics then say that it invokes the member found before, and it
280-
works for the `.id…` variant too.
305+
reference to a static member or constructor in order to use its type for static type inference.
306+
The runtime semantics then say that it invokes the member found before.
281307

282308
#### Patterns
283309

@@ -310,7 +336,7 @@ constant getter or constant constructor invocation. _(There is no chance of a
310336
method or constructor tear-off having the correct type for the context, but if
311337
the context type is not enforced for some reason, like being lost in an **Up**
312338
computation, it’s technically possible to tear off a static method as a constant
313-
expression. It’s unlikely to succeed dynamic type tests at runtime.)_
339+
expression. It’s unlikely to succeed at dynamic type tests at runtime.)_
314340

315341
An expression without a leading `const` is a potential constant and constant
316342
expression if the corresponding explicit static access would be one. Being a
@@ -323,6 +349,10 @@ Symbol symbol = const .new("orange"); // => const Symbol.new("Orange")
323349
Endian endian = .big; // => Endian.big.
324350
```
325351

352+
The only selector which can follow a constant `.id`/`.new` or
353+
`const .id(args)`/`const .new(args) ` `<staticMemberAccessHead>` and
354+
still be a constant expression is the `!` selector.
355+
326356
## New complications and concerns
327357

328358
### Delayed resolution
@@ -508,9 +538,26 @@ probably to `FutureOr<Foo>` too. But it is annoying because of the implicit
508538
while still allowing a `FutureOr<F>` to be returned, as an aspirational
509539
context type.)
510540

541+
We've decided to allow members of `X` to be accessed on `FutureOr<X>`, but
542+
not members of `Future`. Primarily to allow people to return values from
543+
`async` functions, where we don't *want* to encourage returning `Future`s.
544+
511545
## Versions
512546

547+
1.2: "Final" decisions:
548+
549+
* `==` only special-cases second operand, and only if it's precisely a
550+
shorthand expression. There is no change to the actual context type,
551+
and no recognition of nested shorthands. You can do `e == .foo`,
552+
and that's it. Does not depend on parameter type of LHS's `operator==`.
553+
* The static namespace denoted by `S` is also the namespace denoted
554+
by `S?` and `FutureOr<S>`, nothing more and nothing less.
555+
* Made grammar be `<postfixExpression>` and not share `<selector>*`
556+
with `<primary>`s. Shouldn't change anything, but makes it clear at which
557+
grammar production the context type is captured.
558+
513559
1.1: Makes `==` only special-case second operand.
560+
514561
* Keeps context type for second operand, set its shorthand context instead.
515562
* Remember to mention `== e` pattern.
516563
* Clean-up and reflow.

0 commit comments

Comments
 (0)