Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Commit b69c5e0

Browse files
authored
Treat await as an almost-unary operator. (#711)
* Treat await as an almost unary opeator. Making pipe behave like application seems pretty unlikely. First `->` has deep binary operator behavior, including associating to the left on a->b->c, and mixes in specific ways with other operators. Instead application lives on a different level entirely with no issues of how to treat specially the right-hand-side which is enclosed in parens. The await operator is essentially a unary operator. After all it takes on argument. It is also treated as such in JS. Some expected behaviours are that `await 2 + await 3` just like `-2 + -3` is expected to mean `(await 2) + (await 3)`. The issue raised is with pipe: the desired outcome is that `await a->b` is parsed as `await a->b`. However notice that `- a->b` is parsed as `(-a)->b`. So on one side, one would like to have `await` behave like other unary operators. On the other side one wouldn't. As a compromise, this PR treats `await` as an almost-unary operator. In the sense that it binds more tightly than any "real" binary operator such as `+` and `**`, but not more than `->` and `.`. This introduces some lack of uniformity, and requires special-casing parts of parsing and printing. * cleanup * Handle binary operator inside await consistently. * More tests. * Update CHANGELOG.md
1 parent c6807e8 commit b69c5e0

File tree

9 files changed

+68
-17
lines changed

9 files changed

+68
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
- Fix issue where the JSX key type is not an optional string https://github.com/rescript-lang/syntax/pull/693
5151
- Fix issue where the JSX fragment without children build error https://github.com/rescript-lang/syntax/pull/704
5252
- Fix issue where async as an id cannot be used with application and labelled arguments https://github.com/rescript-lang/syntax/issues/707
53-
53+
- Treat await as almost-unary operator weaker than pipe so `await foo->bar` means `await (foo->bar)` https://github.com/rescript-lang/syntax/pull/711
5454

5555
#### :eyeglasses: Spec Compliance
5656

src/res_core.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3134,7 +3134,8 @@ and parseAwaitExpression p =
31343134
let awaitLoc = mkLoc p.Parser.startPos p.endPos in
31353135
let awaitAttr = makeAwaitAttr awaitLoc in
31363136
Parser.expect Await p;
3137-
let expr = parseUnaryExpr p in
3137+
let tokenPrec = Token.precedence MinusGreater in
3138+
let expr = parseBinaryExpr ~context:OrdinaryExpr p tokenPrec in
31383139
{
31393140
expr with
31403141
pexp_attributes = awaitAttr :: expr.pexp_attributes;

src/res_parens.ml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,11 @@ let flattenOperandRhs parentOperator rhs =
175175
| _ when ParsetreeViewer.isTernaryExpr rhs -> true
176176
| _ -> false
177177

178-
let lazyOrAssertOrAwaitExprRhs expr =
178+
let binaryOperatorInsideAwaitNeedsParens operator =
179+
ParsetreeViewer.operatorPrecedence operator
180+
< ParsetreeViewer.operatorPrecedence "|."
181+
182+
let lazyOrAssertOrAwaitExprRhs ?(inAwait = false) expr =
179183
let optBraces, _ = ParsetreeViewer.processBracesAttr expr in
180184
match optBraces with
181185
| Some ({Location.loc = bracesLoc}, _) -> Braced bracesLoc
@@ -186,7 +190,14 @@ let lazyOrAssertOrAwaitExprRhs expr =
186190
| _ :: _ -> true
187191
| [] -> false ->
188192
Parenthesized
189-
| expr when ParsetreeViewer.isBinaryExpression expr -> Parenthesized
193+
| {
194+
pexp_desc =
195+
Pexp_apply ({pexp_desc = Pexp_ident {txt = Longident.Lident operator}}, _);
196+
}
197+
when ParsetreeViewer.isBinaryExpression expr ->
198+
if inAwait && not (binaryOperatorInsideAwaitNeedsParens operator) then
199+
Nothing
200+
else Parenthesized
190201
| {
191202
pexp_desc =
192203
Pexp_constraint ({pexp_desc = Pexp_pack _}, {ptyp_desc = Ptyp_package _});
@@ -202,7 +213,9 @@ let lazyOrAssertOrAwaitExprRhs expr =
202213
| Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_ifthenelse _ );
203214
} ->
204215
Parenthesized
205-
| _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes ->
216+
| _
217+
when (not inAwait)
218+
&& ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes ->
206219
Parenthesized
207220
| _ -> Nothing)
208221

src/res_parens.mli

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ val subBinaryExprOperand : string -> string -> bool
1010
val rhsBinaryExprOperand : string -> Parsetree.expression -> bool
1111
val flattenOperandRhs : string -> Parsetree.expression -> bool
1212

13-
val lazyOrAssertOrAwaitExprRhs : Parsetree.expression -> kind
13+
val binaryOperatorInsideAwaitNeedsParens : string -> bool
14+
val lazyOrAssertOrAwaitExprRhs : ?inAwait:bool -> Parsetree.expression -> kind
1415

1516
val fieldExpr : Parsetree.expression -> kind
1617

src/res_printer.ml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3330,13 +3330,13 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl =
33303330
if ParsetreeViewer.hasAwaitAttribute e.pexp_attributes then
33313331
let rhs =
33323332
match
3333-
Parens.lazyOrAssertOrAwaitExprRhs
3333+
Parens.lazyOrAssertOrAwaitExprRhs ~inAwait:true
33343334
{
33353335
e with
33363336
pexp_attributes =
33373337
List.filter
33383338
(function
3339-
| {Location.txt = "res.await" | "ns.braces"}, _ -> false
3339+
| {Location.txt = "ns.braces"}, _ -> false
33403340
| _ -> true)
33413341
e.pexp_attributes;
33423342
}
@@ -3612,13 +3612,18 @@ and printBinaryExpression ~customLayout (expr : Parsetree.expression) cmtTbl =
36123612
in
36133613
let doc =
36143614
if isAwait then
3615+
let parens =
3616+
Res_parens.binaryOperatorInsideAwaitNeedsParens operator
3617+
in
36153618
Doc.concat
36163619
[
3617-
Doc.text "await ";
36183620
Doc.lparen;
3621+
Doc.text "await ";
3622+
(if parens then Doc.lparen else Doc.nil);
36193623
leftPrinted;
36203624
printBinaryOperator ~inlineRhs:false operator;
36213625
rightPrinted;
3626+
(if parens then Doc.rparen else Doc.nil);
36223627
Doc.rparen;
36233628
]
36243629
else

tests/parsing/grammar/expressions/async.res

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,9 @@ let async = {
2929
let f = isPositive ? (async (a, b) : int => a + b) : async (c, d) : int => c - d
3030

3131
let foo = async(~a=34)
32-
let bar = async(~a)=>a+1
32+
let bar = async(~a)=>a+1
33+
34+
let ex1 = await 3 + await 4
35+
let ex2 = await 3 ** await 4
36+
let ex3 = await foo->bar(~arg)
37+
let ex4 = await foo.bar.baz

tests/parsing/grammar/expressions/expected/async.res.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@ let f =
2727
else (((fun c -> fun d -> (c - d : int)))[@res.async ]))
2828
[@ns.ternary ])
2929
let foo = async ~a:((34)[@ns.namedArgLoc ])
30-
let bar = ((fun ~a:((a)[@ns.namedArgLoc ]) -> a + 1)[@res.async ])
30+
let bar = ((fun ~a:((a)[@ns.namedArgLoc ]) -> a + 1)[@res.async ])
31+
let ex1 = ((3)[@res.await ]) + ((4)[@res.await ])
32+
let ex2 = ((3)[@res.await ]) ** ((4)[@res.await ])
33+
let ex3 = ((foo |. (bar ~arg:((arg)[@ns.namedArgLoc ])))[@res.await ])
34+
let ex4 = (((foo.bar).baz)[@res.await ])

tests/printer/expr/asyncAwait.res

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ let f13 = @a @b (~x) => 3
9999

100100
let aw = (await (server->start))->foo
101101
let aw = (@foo (server->start))->foo
102+
let aw = (await (3**4))->foo
102103

103104
let foo = async(~a=34)
104-
let bar = async(~a)=>a+1
105+
let bar = async(~a)=>a+1
106+
107+
let a1 = await 3 + await 4
108+
let a2 = await 3 ** await 4
109+
let a3 = await foo->bar(~arg)
110+
let a4 = await foo.bar.baz
111+
112+
let b1 = await (3+4)
113+
let b2 = await (3**4)
114+
let b3 = await (foo->bar(~arg))
115+
let b4 = await (foo.bar.baz)

tests/printer/expr/expected/asyncAwait.res.txt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ user.data = await fetch()
3030

3131
<Navbar promise={await gc()}> {await weirdReactSuspenseApi} </Navbar>
3232

33-
let inBinaryExpression = (await x)->Js.Promise.resolve + 1
34-
let inBinaryExpression = (await x)->Js.Promise.resolve + (await y)->Js.Promise.resolve
33+
let inBinaryExpression = await x->Js.Promise.resolve + 1
34+
let inBinaryExpression = await x->Js.Promise.resolve + await y->Js.Promise.resolve
3535

3636
let withCallback = async (. ()) => {
37-
async (. x) => await (x->Js.promise.resolve) + 1
37+
async (. x) => await x->Js.promise.resolve + 1
3838
}
3939

40-
let () = (await fetch(url))->(await resolve)
40+
let () = await (await fetch(url))->(await resolve)
4141

4242
let _ = await (1 + 1)
4343
let _ = (await 1) + 1
@@ -119,8 +119,19 @@ let f11 = (. ~x) => (. ~y) => 3
119119
let f12 = @a x => 3
120120
let f13 = (@a @b ~x) => 3
121121

122-
let aw = await (server->start)->foo
122+
let aw = (await server->start)->foo
123123
let aw = @foo (server->start)->foo
124+
let aw = (await (3 ** 4))->foo
124125

125126
let foo = async(~a=34)
126127
let bar = async (~a) => a + 1
128+
129+
let a1 = (await 3) + (await 4)
130+
let a2 = (await 3) ** (await 4)
131+
let a3 = await foo->bar(~arg)
132+
let a4 = await foo.bar.baz
133+
134+
let b1 = await (3 + 4)
135+
let b2 = await (3 ** 4)
136+
let b3 = await foo->bar(~arg)
137+
let b4 = await foo.bar.baz

0 commit comments

Comments
 (0)