-
Notifications
You must be signed in to change notification settings - Fork 38
Done, but do not merge yet: Async/Await syntax. #600
Conversation
Do we need to make |
We don't, will remove |
I'm wondering how we should go about the implementation of let await = 1 // OK ✅
async function wait() {
let await = 1 // NOT OK ❌
} The plot twist is that the newer browsers (chrome or node) support top-level await. (By implicitly wrapping everything in an async function?) let await = 1 // NOT OK, with top level await ❌ This makes me think:
let await = 1
let x = await // Ok, now it gets tricky. Does this start an "await"-expression?
= await fetch(url) // could be this
= await() // could be this
= await await() // or even this
= await // or we might just be assigning the identifier `await` to `x`
Seems like we can infer the following rules for an
But this "breaks down" in the case of let x = await - 1
// could be a binary expression: "identifier" MINUS "1"
// could also be an await expression with a unary expression: `await` `(-1)` These whitespace sensitive rules kinda feel super arbitrary. Which makes me think that introducing |
Indeed looks like a case where not-a-keyword costs way more than it gives. |
a8f3611
to
1d109fb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there roundtrip tests for this? Can't remember what tests are auto included in roundtrip.
This look great!
Only got style comments.
src/res_grammar.ml
Outdated
@@ -151,7 +151,7 @@ let isExprStart = function | |||
| Underscore (* _ => doThings() *) | |||
| Uident _ | Lident _ | Hash | Lparen | List | Module | Lbracket | Lbrace | |||
| LessThan | Minus | MinusDot | Plus | PlusDot | Bang | Percent | At | If | |||
| Switch | While | For | Assert | Lazy | Try -> | |||
| Switch | While | For | Assert | Await | Lazy | Try -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder: in the vscode extension, the grammar needs to be taught about the keyword await
.
Also, semantic highlighting needs to be taught about the id async
, when encountered as attribute. The attribute's location needs to be used to emit the symbol.
@@ -338,6 +350,8 @@ let jsxChildExpr expr = | |||
} | |||
when startsWithMinus x -> | |||
Parenthesized | |||
| _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a lot of these. Should there be a generic concept of which this is an instance so all these calls are moved to a single place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you give an example of what you mean by this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can move to a helper function, though the difference is minimal. Just easier to make sure all the instances are updated at once if something changes in future.
Another thing I was wondering is: this case seems to often go where there is also ParsetreeViewer.filterParsingAttrs attrs
. But not always, so not sure what value this observation has.
But I guess in the cases where ParsetreeViewer.filterParsingAttrs attrs
is checked, this await check could at least go right after it. Assuming that no cases in between should take precedence.
See here: cristianoc/rescript-compiler-experiments@6101661 Once this is stable we can continue and make it point to this syntax commit. |
Yes, printer tests are roundtripped |
@@ -338,6 +350,8 @@ let jsxChildExpr expr = | |||
} | |||
when startsWithMinus x -> | |||
Parenthesized | |||
| _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can move to a helper function, though the difference is minimal. Just easier to make sure all the instances are updated at once if something changes in future.
Another thing I was wondering is: this case seems to often go where there is also ParsetreeViewer.filterParsingAttrs attrs
. But not always, so not sure what value this observation has.
But I guess in the cases where ParsetreeViewer.filterParsingAttrs attrs
is checked, this await check could at least go right after it. Assuming that no cases in between should take precedence.
@@ -11,7 +11,7 @@ let arrowType ct = | |||
process attrsBefore (arg :: acc) typ2 | |||
| { | |||
ptyp_desc = Ptyp_arrow ((Nolabel as lbl), typ1, typ2); | |||
ptyp_attributes = [({txt = "bs"}, _)] as attrs; | |||
ptyp_attributes = [({txt = "bs" | "res.async"}, _)] as attrs; | |||
} -> | |||
let arg = (attrs, lbl, typ1) in | |||
process attrsBefore (arg :: acc) typ2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is there a separate case for process attributes?
Why not always process attributes and if nothing of interest is found then it's a no-op.
And the following match case disappears.
Sweet. Let me sync this up to the async compiler PR. |
Bingo. Everything works now, as far as I can see. |
@IwanKaramazow could you rebase on master? Then we can merge. |
operand-expr += `await` unary-expr
operand-expr += `async` es6-arrow-expression
Existing code using async/await as identifiers in e.g. record field names will keep working.
…d as async arrow function
Reasoning: In JavaScript `await` is a keyword *only* inside async functions. ```javascript let await = 1 // OK ✅ async function wait() { let await = 1 // NOT OK ❌ } ``` The plot twist is that the newer browsers (chrome or node) support top-level await. (By implicitly wrapping everything in an async function?) ```javascript let await = 1 // NOT OK, with top level await ❌ ``` This makes me think: * We can replicate JavaScript parser; inside async function expressions, use of `await` as identifier will result into a syntax error - Downside: this feels like a responsibility for another part of the compiler, not the parser? This is already implemented in cristianoc/rescript-compiler-experiments#1, so we would also be doing double work. - Other downside: if we ever allow top-level await, then we just implemented the above for nothing. * Allow `await` as a "non-keyword" everywhere with some "tricks". ```javascript let await = 1 let x = await // Ok, now it gets tricky. Does this start an "await"-expression? = await fetch(url) // could be this = await() // could be this = await await() // or even this = await // or we might just be assigning the identifier `await` to `x` ``` Seems like we can infer the following rules for an `await expression`: - space after `await` - next token is on the same line as `await` - next token indicates the start of a unary expression But this "breaks down" in the case of ```javascript let x = await - 1 // could be a binary expression: "identifier" MINUS "1" // could also be an await expression with a unary expression: `await` `(-1)` ``` These whitespace sensitive rules kinda feel super arbitrary. Which makes me think that introducing `await` as a keyword is an ok compromise.
da8161b
to
c15dee0
Compare
@cristianoc done |
Async/await surface syntax cf. https://forum.rescript-lang.org/t/ann-async-await-is-coming-to-rescript/3488
async
is a "non-keyword" for backwards compatibility.await
has been implemented as normal keyword. Turned out to be too difficult to move forward otherwise: Done, but do not merge yet: Async/Await syntax. #600 (comment)