From 21c01e6f24cc6e1193bb2ffec1f9cf02fc182ef0 Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 4 Jul 2022 08:47:55 +0200 Subject: [PATCH 01/22] Implement parsing of `await` expressions. operand-expr += `await` unary-expr --- src/res_core.ml | 10 ++++++++++ src/res_grammar.ml | 2 +- src/res_token.ml | 9 ++++++--- tests/parsing/grammar/expressions/await.res | 17 +++++++++++++++++ .../grammar/expressions/expected/await.res.txt | 14 ++++++++++++++ 5 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/parsing/grammar/expressions/await.res create mode 100644 tests/parsing/grammar/expressions/expected/await.res.txt diff --git a/src/res_core.ml b/src/res_core.ml index 506ceea3..74a1a323 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -2031,6 +2031,16 @@ and parseOperandExpr ~context p = let expr = parseUnaryExpr p in let loc = mkLoc startPos p.prevEndPos in Ast_helper.Exp.assert_ ~loc expr + | Await -> + let awaitLoc = mkLoc startPos p.endPos in + let awaitAttr = (Location.mkloc "await" awaitLoc, Parsetree.PStr []) in + Parser.next p; + let expr = parseUnaryExpr p in + { + expr with + pexp_attributes = awaitAttr :: expr.pexp_attributes; + pexp_loc = {expr.pexp_loc with loc_start = awaitLoc.loc_start}; + } | Lazy -> Parser.next p; let expr = parseUnaryExpr p in diff --git a/src/res_grammar.ml b/src/res_grammar.ml index 44f0e497..44db244c 100644 --- a/src/res_grammar.ml +++ b/src/res_grammar.ml @@ -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 | Lazy | Try | Await -> true | _ -> false diff --git a/src/res_token.ml b/src/res_token.ml index 2c4f8f26..aa94a8ce 100644 --- a/src/res_token.ml +++ b/src/res_token.ml @@ -1,6 +1,7 @@ module Comment = Res_comment type t = + | Await | Open | True | False @@ -111,6 +112,7 @@ let precedence = function | _ -> 0 let toString = function + | Await -> "await" | Open -> "open" | True -> "true" | False -> "false" @@ -207,6 +209,7 @@ let toString = function | ModuleComment (_loc, s) -> "ModuleComment " ^ s let keywordTable = function + | "await" -> Await | "and" -> And | "as" -> As | "assert" -> Assert @@ -238,9 +241,9 @@ let keywordTable = function [@@raises Not_found] let isKeyword = function - | And | As | Assert | Constraint | Else | Exception | External | False | For - | If | In | Include | Land | Lazy | Let | List | Lor | Module | Mutable | Of - | Open | Private | Rec | Switch | True | Try | Typ | When | While -> + | Await | And | As | Assert | Constraint | Else | Exception | External | False + | For | If | In | Include | Land | Lazy | Let | List | Lor | Module | Mutable + | Of | Open | Private | Rec | Switch | True | Try | Typ | When | While -> true | _ -> false diff --git a/tests/parsing/grammar/expressions/await.res b/tests/parsing/grammar/expressions/await.res new file mode 100644 index 00000000..9167efa8 --- /dev/null +++ b/tests/parsing/grammar/expressions/await.res @@ -0,0 +1,17 @@ +await wait(2) + +let maybeSomeValue = switch await fetchData(url) { +| data => Some(data) +| exception JsError(_) => None +} + +let x = await 1 + 2 + +let x = await wait(1) + await wait(2) + +let () = { + let response = await fetch("/users.json"); // get users list + let users = await response.json(); // parse JSON + let comments = (await (await fetch("comment.json")).json())[0]; + Js.log2(users, comments) +} \ No newline at end of file diff --git a/tests/parsing/grammar/expressions/expected/await.res.txt b/tests/parsing/grammar/expressions/expected/await.res.txt new file mode 100644 index 00000000..a1ffed40 --- /dev/null +++ b/tests/parsing/grammar/expressions/expected/await.res.txt @@ -0,0 +1,14 @@ +;;((wait 2)[@await ]) +let maybeSomeValue = + match ((fetchData url)[@await ]) with + | data -> Some data + | exception JsError _ -> None +let x = ((1)[@await ]) + 2 +let x = ((wait 1)[@await ]) + ((wait 2)[@await ]) +let () = + ((let response = ((fetch {js|/users.json|js})[@await ]) in + let users = ((response.json ())[@await ]) in + let comments = + ((((fetch {js|comment.json|js})[@await ]).json ())[@await ]).(0) in + Js.log2 users comments) + [@ns.braces ]) \ No newline at end of file From 599573871eb02c5821a3d422a022a59eb971fb2b Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 4 Jul 2022 09:19:38 +0200 Subject: [PATCH 02/22] Implement parsing of `async` expressions operand-expr += `async` es6-arrow-expression --- src/res_core.ml | 11 +++++++++++ src/res_grammar.ml | 2 +- src/res_token.ml | 10 +++++++--- tests/parsing/grammar/expressions/async.res | 10 ++++++++++ .../grammar/expressions/expected/async.res.txt | 9 +++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 tests/parsing/grammar/expressions/async.res create mode 100644 tests/parsing/grammar/expressions/expected/async.res.txt diff --git a/src/res_core.ml b/src/res_core.ml index 74a1a323..7631cd00 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -2031,6 +2031,17 @@ and parseOperandExpr ~context p = let expr = parseUnaryExpr p in let loc = mkLoc startPos p.prevEndPos in Ast_helper.Exp.assert_ ~loc expr + | Async -> + let asyncAttr = + (Location.mkloc "async" (mkLoc startPos p.endPos), Parsetree.PStr []) + in + Parser.next p; + let expr = parseEs6ArrowExpression p in + { + expr with + pexp_attributes = asyncAttr :: expr.pexp_attributes; + pexp_loc = {expr.pexp_loc with loc_start = startPos}; + } | Await -> let awaitLoc = mkLoc startPos p.endPos in let awaitAttr = (Location.mkloc "await" awaitLoc, Parsetree.PStr []) in diff --git a/src/res_grammar.ml b/src/res_grammar.ml index 44db244c..61fd2936 100644 --- a/src/res_grammar.ml +++ b/src/res_grammar.ml @@ -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 | Await -> + | Switch | While | For | Assert | Lazy | Try | Async | Await -> true | _ -> false diff --git a/src/res_token.ml b/src/res_token.ml index aa94a8ce..31145188 100644 --- a/src/res_token.ml +++ b/src/res_token.ml @@ -1,6 +1,7 @@ module Comment = Res_comment type t = + | Async | Await | Open | True @@ -112,6 +113,7 @@ let precedence = function | _ -> 0 let toString = function + | Async -> "async" | Await -> "await" | Open -> "open" | True -> "true" @@ -209,6 +211,7 @@ let toString = function | ModuleComment (_loc, s) -> "ModuleComment " ^ s let keywordTable = function + | "async" -> Async | "await" -> Await | "and" -> And | "as" -> As @@ -241,9 +244,10 @@ let keywordTable = function [@@raises Not_found] let isKeyword = function - | Await | And | As | Assert | Constraint | Else | Exception | External | False - | For | If | In | Include | Land | Lazy | Let | List | Lor | Module | Mutable - | Of | Open | Private | Rec | Switch | True | Try | Typ | When | While -> + | Async | Await | And | As | Assert | Constraint | Else | Exception | External + | False | For | If | In | Include | Land | Lazy | Let | List | Lor | Module + | Mutable | Of | Open | Private | Rec | Switch | True | Try | Typ | When + | While -> true | _ -> false diff --git a/tests/parsing/grammar/expressions/async.res b/tests/parsing/grammar/expressions/async.res new file mode 100644 index 00000000..f066771b --- /dev/null +++ b/tests/parsing/grammar/expressions/async.res @@ -0,0 +1,10 @@ +let greetUser = async (userId) => { + let name = await getUserName(. userId) + "Hello " ++ name ++ "!" +} + +async () => 123 + +let fetch = { + async (. url) => browserFetch(. url) +} \ No newline at end of file diff --git a/tests/parsing/grammar/expressions/expected/async.res.txt b/tests/parsing/grammar/expressions/expected/async.res.txt new file mode 100644 index 00000000..425f2345 --- /dev/null +++ b/tests/parsing/grammar/expressions/expected/async.res.txt @@ -0,0 +1,9 @@ +let greetUser = + ((fun userId -> + ((let name = ((getUserName userId)[@await ][@bs ]) in + ({js|Hello |js} ^ name) ^ {js|!|js}) + [@ns.braces ])) + [@async ]) +;;((fun () -> 123)[@async ]) +let fetch = ((fun url -> ((browserFetch url)[@bs ])) + [@ns.braces ][@async ][@bs ]) \ No newline at end of file From 454d5b6bca6d8671cb81a75e158e936d88d55b0a Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 4 Jul 2022 09:43:51 +0200 Subject: [PATCH 03/22] Make "await". and "async" non-keywords. Existing code using async/await as identifiers in e.g. record field names will keep working. --- src/res_core.ml | 68 +++++++++++++------ src/res_grammar.ml | 2 +- src/res_token.ml | 13 +--- tests/parsing/grammar/expressions/async.res | 5 ++ tests/parsing/grammar/expressions/await.res | 9 +++ .../expressions/expected/async.res.txt | 8 ++- .../expressions/expected/await.res.txt | 4 +- 7 files changed, 75 insertions(+), 34 deletions(-) diff --git a/src/res_core.ml b/src/res_core.ml index 7631cd00..3883ef53 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -2031,27 +2031,8 @@ and parseOperandExpr ~context p = let expr = parseUnaryExpr p in let loc = mkLoc startPos p.prevEndPos in Ast_helper.Exp.assert_ ~loc expr - | Async -> - let asyncAttr = - (Location.mkloc "async" (mkLoc startPos p.endPos), Parsetree.PStr []) - in - Parser.next p; - let expr = parseEs6ArrowExpression p in - { - expr with - pexp_attributes = asyncAttr :: expr.pexp_attributes; - pexp_loc = {expr.pexp_loc with loc_start = startPos}; - } - | Await -> - let awaitLoc = mkLoc startPos p.endPos in - let awaitAttr = (Location.mkloc "await" awaitLoc, Parsetree.PStr []) in - Parser.next p; - let expr = parseUnaryExpr p in - { - expr with - pexp_attributes = awaitAttr :: expr.pexp_attributes; - pexp_loc = {expr.pexp_loc with loc_start = awaitLoc.loc_start}; - } + | Lident "async" -> parseAsyncExpression p + | Lident "await" -> parseAwaitExpression p | Lazy -> Parser.next p; let expr = parseUnaryExpr p in @@ -2765,6 +2746,20 @@ and parseBracedOrRecordExpr p = let expr = parseRecordExpr ~startPos [] p in Parser.expect Rbrace p; expr + | Lident "async" -> + let expr = parseAsyncExpression p in + let expr = parseExprBlock ~first:expr p in + Parser.expect Rbrace p; + let loc = mkLoc startPos p.prevEndPos in + let braces = makeBracesAttr loc in + {expr with pexp_attributes = braces :: expr.pexp_attributes} + | Lident "await" -> + let expr = parseAwaitExpression p in + let expr = parseExprBlock ~first:expr p in + Parser.expect Rbrace p; + let loc = mkLoc startPos p.prevEndPos in + let braces = makeBracesAttr loc in + {expr with pexp_attributes = braces :: expr.pexp_attributes} | Uident _ | Lident _ -> ( let startToken = p.token in let valueOrConstructor = parseValueOrConstructor p in @@ -3120,6 +3115,37 @@ and parseExprBlock ?first p = Parser.eatBreadcrumb p; overParseConstrainedOrCoercedOrArrowExpression p blockExpr +and parseAsyncExpression p = + let startPos = p.Parser.startPos in + match p.token with + | Lident "async" -> + let asyncAttr = + (Location.mkloc "async" (mkLoc startPos p.endPos), Parsetree.PStr []) + in + Parser.next p; + let expr = parseEs6ArrowExpression p in + { + expr with + pexp_attributes = asyncAttr :: expr.pexp_attributes; + pexp_loc = {expr.pexp_loc with loc_start = startPos}; + } + | _ -> assert false + +and parseAwaitExpression p = + let startPos = p.Parser.startPos in + match p.token with + | Lident "await" -> + let awaitLoc = mkLoc startPos p.endPos in + let awaitAttr = (Location.mkloc "await" awaitLoc, Parsetree.PStr []) in + Parser.next p; + let expr = parseUnaryExpr p in + { + expr with + pexp_attributes = awaitAttr :: expr.pexp_attributes; + pexp_loc = {expr.pexp_loc with loc_start = awaitLoc.loc_start}; + } + | _ -> assert false + and parseTryExpression p = let startPos = p.Parser.startPos in Parser.expect Try p; diff --git a/src/res_grammar.ml b/src/res_grammar.ml index 61fd2936..44f0e497 100644 --- a/src/res_grammar.ml +++ b/src/res_grammar.ml @@ -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 | Async | Await -> + | Switch | While | For | Assert | Lazy | Try -> true | _ -> false diff --git a/src/res_token.ml b/src/res_token.ml index 31145188..2c4f8f26 100644 --- a/src/res_token.ml +++ b/src/res_token.ml @@ -1,8 +1,6 @@ module Comment = Res_comment type t = - | Async - | Await | Open | True | False @@ -113,8 +111,6 @@ let precedence = function | _ -> 0 let toString = function - | Async -> "async" - | Await -> "await" | Open -> "open" | True -> "true" | False -> "false" @@ -211,8 +207,6 @@ let toString = function | ModuleComment (_loc, s) -> "ModuleComment " ^ s let keywordTable = function - | "async" -> Async - | "await" -> Await | "and" -> And | "as" -> As | "assert" -> Assert @@ -244,10 +238,9 @@ let keywordTable = function [@@raises Not_found] let isKeyword = function - | Async | Await | And | As | Assert | Constraint | Else | Exception | External - | False | For | If | In | Include | Land | Lazy | Let | List | Lor | Module - | Mutable | Of | Open | Private | Rec | Switch | True | Try | Typ | When - | While -> + | And | As | Assert | Constraint | Else | Exception | External | False | For + | If | In | Include | Land | Lazy | Let | List | Lor | Module | Mutable | Of + | Open | Private | Rec | Switch | True | Try | Typ | When | While -> true | _ -> false diff --git a/tests/parsing/grammar/expressions/async.res b/tests/parsing/grammar/expressions/async.res index f066771b..0fcd97d9 100644 --- a/tests/parsing/grammar/expressions/async.res +++ b/tests/parsing/grammar/expressions/async.res @@ -7,4 +7,9 @@ async () => 123 let fetch = { async (. url) => browserFetch(. url) +} + +let fetch2 = { + async (. url) => browserFetch(. url) + async (. url) => browserFetch2(. url) } \ No newline at end of file diff --git a/tests/parsing/grammar/expressions/await.res b/tests/parsing/grammar/expressions/await.res index 9167efa8..32a6fdf9 100644 --- a/tests/parsing/grammar/expressions/await.res +++ b/tests/parsing/grammar/expressions/await.res @@ -14,4 +14,13 @@ let () = { let users = await response.json(); // parse JSON let comments = (await (await fetch("comment.json")).json())[0]; Js.log2(users, comments) +} + +let () = { + await delay(10) +} + +let () = { + await delay(10) + await delay(20) } \ No newline at end of file diff --git a/tests/parsing/grammar/expressions/expected/async.res.txt b/tests/parsing/grammar/expressions/expected/async.res.txt index 425f2345..cc38ead8 100644 --- a/tests/parsing/grammar/expressions/expected/async.res.txt +++ b/tests/parsing/grammar/expressions/expected/async.res.txt @@ -6,4 +6,10 @@ let greetUser = [@async ]) ;;((fun () -> 123)[@async ]) let fetch = ((fun url -> ((browserFetch url)[@bs ])) - [@ns.braces ][@async ][@bs ]) \ No newline at end of file + [@ns.braces ][@async ][@bs ]) +let fetch2 = + (((((fun url -> ((browserFetch url)[@bs ]))) + [@async ][@bs ]); + (((fun url -> ((browserFetch2 url)[@bs ]))) + [@async ][@bs ])) + [@ns.braces ]) \ No newline at end of file diff --git a/tests/parsing/grammar/expressions/expected/await.res.txt b/tests/parsing/grammar/expressions/expected/await.res.txt index a1ffed40..0ecccfa2 100644 --- a/tests/parsing/grammar/expressions/expected/await.res.txt +++ b/tests/parsing/grammar/expressions/expected/await.res.txt @@ -11,4 +11,6 @@ let () = let comments = ((((fetch {js|comment.json|js})[@await ]).json ())[@await ]).(0) in Js.log2 users comments) - [@ns.braces ]) \ No newline at end of file + [@ns.braces ]) +let () = ((delay 10)[@ns.braces ][@await ]) +let () = ((((delay 10)[@await ]); ((delay 20)[@await ]))[@ns.braces ]) \ No newline at end of file From 686fa533862f4b2a1720038abe8298bd688d1e26 Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 4 Jul 2022 11:02:55 +0200 Subject: [PATCH 04/22] Implement printer support for async arrow expressions --- src/res_parsetree_viewer.ml | 15 +++++++- src/res_parsetree_viewer.mli | 5 +++ src/res_printer.ml | 38 ++++++++++++------- test.res | 2 + tests/printer/expr/asyncAwait.res | 13 +++++++ .../printer/expr/expected/asyncAwait.res.txt | 13 +++++++ 6 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 test.res create mode 100644 tests/printer/expr/asyncAwait.res create mode 100644 tests/printer/expr/expected/asyncAwait.res.txt diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index 8049d6a9..b10fabcc 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -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" | "async"}, _)] as attrs; } -> let arg = (attrs, lbl, typ1) in process attrsBefore (arg :: acc) typ2 @@ -55,6 +55,16 @@ let processUncurriedAttribute attrs = in process false [] attrs +let processFunctionAttributes attrs = + let rec process async uncurried acc attrs = + match attrs with + | [] -> (async, uncurried, List.rev acc) + | ({Location.txt = "bs"}, _) :: rest -> process async true acc rest + | ({Location.txt = "async"}, _) :: rest -> process true uncurried acc rest + | attr :: rest -> process async uncurried (attr :: acc) rest + in + process false false [] attrs + let collectListExpressions expr = let rec collect acc expr = match expr.pexp_desc with @@ -316,7 +326,8 @@ let hasAttributes attrs = match attr with | ( { Location.txt = - "bs" | "res.template" | "ns.ternary" | "ns.braces" | "ns.iflet"; + ( "bs" | "async" | "res.template" | "ns.ternary" | "ns.braces" + | "ns.iflet" ); }, _ ) -> false diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index e492010b..1f2a6202 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -17,6 +17,11 @@ val functorType : val processUncurriedAttribute : Parsetree.attributes -> bool * Parsetree.attributes +(* determines whether a function is async and/or uncurried based on the given attributes *) +val processFunctionAttributes : + Parsetree.attributes -> + bool (* async *) * bool (* uncurried *) * Parsetree.attributes + type ifConditionKind = | If of Parsetree.expression | IfLet of Parsetree.pattern * Parsetree.expression diff --git a/src/res_printer.ml b/src/res_printer.ml index 79a77f8e..86307995 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -3135,8 +3135,8 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = cmtTbl | Pexp_fun _ | Pexp_newtype _ -> let attrsOnArrow, parameters, returnExpr = ParsetreeViewer.funExpr e in - let uncurried, attrs = - ParsetreeViewer.processUncurriedAttribute attrsOnArrow + let async, uncurried, attrs = + ParsetreeViewer.processFunctionAttributes attrsOnArrow in let returnExpr, typConstraint = match returnExpr.pexp_desc with @@ -3156,7 +3156,7 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = in let parametersDoc = printExprFunParameters ~customLayout ~inCallback:NoCallback ~uncurried - ~hasConstraint parameters cmtTbl + ~async ~hasConstraint parameters cmtTbl in let returnExprDoc = let optBraces, _ = ParsetreeViewer.processBracesAttr returnExpr in @@ -3298,8 +3298,8 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = and printPexpFun ~customLayout ~inCallback e cmtTbl = let attrsOnArrow, parameters, returnExpr = ParsetreeViewer.funExpr e in - let uncurried, attrs = - ParsetreeViewer.processUncurriedAttribute attrsOnArrow + let async, uncurried, attrs = + ParsetreeViewer.processFunctionAttributes attrsOnArrow in let returnExpr, typConstraint = match returnExpr.pexp_desc with @@ -3313,7 +3313,7 @@ and printPexpFun ~customLayout ~inCallback e cmtTbl = | _ -> (returnExpr, None) in let parametersDoc = - printExprFunParameters ~customLayout ~inCallback ~uncurried + printExprFunParameters ~customLayout ~inCallback ~async ~uncurried ~hasConstraint: (match typConstraint with | Some _ -> true @@ -4575,8 +4575,8 @@ and printCase ~customLayout (case : Parsetree.case) cmtTbl = in Doc.group (Doc.concat [Doc.text "| "; content]) -and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint - parameters cmtTbl = +and printExprFunParameters ~customLayout ~inCallback ~async ~uncurried + ~hasConstraint parameters cmtTbl = match parameters with (* let f = _ => () *) | [ @@ -4589,8 +4589,11 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint }; ] when not uncurried -> - let doc = if hasConstraint then Doc.text "(_)" else Doc.text "_" in - printComments doc cmtTbl ppat_loc + let any = + let doc = if hasConstraint then Doc.text "(_)" else Doc.text "_" in + printComments doc cmtTbl ppat_loc + in + if async then Doc.concat [Doc.text "async "; any] else any (* let f = a => () *) | [ ParsetreeViewer.Parameter @@ -4604,7 +4607,8 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint when not uncurried -> let txtDoc = let var = printIdentLike stringLoc.txt in - if hasConstraint then addParens var else var + let var = if hasConstraint then addParens var else var in + if async then Doc.concat [Doc.text "async ("; var; Doc.rparen] else var in printComments txtDoc cmtTbl stringLoc.loc (* let f = () => () *) @@ -4619,7 +4623,7 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint }; ] when not uncurried -> - let doc = Doc.text "()" in + let doc = if async then Doc.text "async ()" else Doc.text "()" in printComments doc cmtTbl loc (* let f = (~greeting, ~from as hometown, ~x=?) => () *) | parameters -> @@ -4628,7 +4632,13 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint | FitsOnOneLine -> true | _ -> false in - let lparen = if uncurried then Doc.text "(. " else Doc.lparen in + let maybeAsyncLparen = + match (async, uncurried) with + | true, true -> Doc.text "async (. " + | true, false -> Doc.text "async (" + | false, true -> Doc.text "(. " + | false, false -> Doc.lparen + in let shouldHug = ParsetreeViewer.parametersShouldHug parameters in let printedParamaters = Doc.concat @@ -4644,7 +4654,7 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint Doc.group (Doc.concat [ - lparen; + maybeAsyncLparen; (if shouldHug || inCallback then printedParamaters else Doc.concat diff --git a/test.res b/test.res new file mode 100644 index 00000000..47eb5207 --- /dev/null +++ b/test.res @@ -0,0 +1,2 @@ +let f = (.) => () +let f = async () => delay(20) diff --git a/tests/printer/expr/asyncAwait.res b/tests/printer/expr/asyncAwait.res new file mode 100644 index 00000000..e33f4031 --- /dev/null +++ b/tests/printer/expr/asyncAwait.res @@ -0,0 +1,13 @@ +let sequentialAwait = async () => { + let result1 = await paused("first") + nodeJsAssert.equal(result1, "first") + + let result2 = await paused("second") + nodeJsAssert.equal(result2, "second") +} + +let f = async () => () +let f = async (.) => () +let f = async f => f() +let f = async (a, b) => a + b +let f = async (. a, b) => a + b \ No newline at end of file diff --git a/tests/printer/expr/expected/asyncAwait.res.txt b/tests/printer/expr/expected/asyncAwait.res.txt new file mode 100644 index 00000000..fcdf41a2 --- /dev/null +++ b/tests/printer/expr/expected/asyncAwait.res.txt @@ -0,0 +1,13 @@ +let sequentialAwait = async () => { + let result1 = @await paused("first") + nodeJsAssert.equal(result1, "first") + + let result2 = @await paused("second") + nodeJsAssert.equal(result2, "second") +} + +let f = async () => () +let f = async (. ()) => () +let f = async (f) => f() +let f = async (a, b) => a + b +let f = async (. a, b) => a + b From 5c91bc4b4d8e7a5bc153e8478d166384234d9008 Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 4 Jul 2022 11:17:44 +0200 Subject: [PATCH 05/22] Implement printer support for await expressions --- src/res_parsetree_viewer.ml | 11 +++++++++-- src/res_parsetree_viewer.mli | 2 ++ src/res_printer.ml | 12 ++++++++---- tests/printer/expr/asyncAwait.res | 8 +++++++- tests/printer/expr/expected/asyncAwait.res.txt | 9 +++++++-- 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index b10fabcc..9e3c55e2 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -65,6 +65,13 @@ let processFunctionAttributes attrs = in process false false [] attrs +let hasAwaitAttribute attrs = + List.exists + (function + | {Location.txt = "await"}, _ -> true + | _ -> false) + attrs + let collectListExpressions expr = let rec collect acc expr = match expr.pexp_desc with @@ -178,8 +185,8 @@ let filterParsingAttrs attrs = match attr with | ( { Location.txt = - ( "ns.ternary" | "ns.braces" | "res.template" | "bs" | "ns.iflet" - | "ns.namedArgLoc" | "ns.optional" ); + ( "ns.ternary" | "ns.braces" | "res.template" | "await" | "bs" + | "ns.iflet" | "ns.namedArgLoc" | "ns.optional" ); }, _ ) -> false diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index 1f2a6202..b063c82d 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -22,6 +22,8 @@ val processFunctionAttributes : Parsetree.attributes -> bool (* async *) * bool (* uncurried *) * Parsetree.attributes +val hasAwaitAttribute : Parsetree.attributes -> bool + type ifConditionKind = | If of Parsetree.expression | IfLet of Parsetree.pattern * Parsetree.expression diff --git a/src/res_printer.ml b/src/res_printer.ml index 86307995..7397226f 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -3278,6 +3278,11 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = | Pexp_poly _ -> Doc.text "Pexp_poly not impemented in printer" | Pexp_object _ -> Doc.text "Pexp_object not impemented in printer" in + let exprWithAwait = + if ParsetreeViewer.hasAwaitAttribute e.pexp_attributes then + Doc.concat [Doc.text "await "; printedExpression] + else printedExpression + in let shouldPrintItsOwnAttributes = match e.pexp_desc with | Pexp_apply _ | Pexp_fun _ | Pexp_newtype _ | Pexp_setfield _ @@ -3289,12 +3294,11 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = | _ -> false in match e.pexp_attributes with - | [] -> printedExpression + | [] -> exprWithAwait | attrs when not shouldPrintItsOwnAttributes -> Doc.group - (Doc.concat - [printAttributes ~customLayout attrs cmtTbl; printedExpression]) - | _ -> printedExpression + (Doc.concat [printAttributes ~customLayout attrs cmtTbl; exprWithAwait]) + | _ -> exprWithAwait and printPexpFun ~customLayout ~inCallback e cmtTbl = let attrsOnArrow, parameters, returnExpr = ParsetreeViewer.funExpr e in diff --git a/tests/printer/expr/asyncAwait.res b/tests/printer/expr/asyncAwait.res index e33f4031..ec00cede 100644 --- a/tests/printer/expr/asyncAwait.res +++ b/tests/printer/expr/asyncAwait.res @@ -10,4 +10,10 @@ let f = async () => () let f = async (.) => () let f = async f => f() let f = async (a, b) => a + b -let f = async (. a, b) => a + b \ No newline at end of file +let f = async (. a, b) => a + b + + +let maybeSomeValue = switch await fetchData(url) { +| data => Some(data) +| exception JsError(_) => None +} \ No newline at end of file diff --git a/tests/printer/expr/expected/asyncAwait.res.txt b/tests/printer/expr/expected/asyncAwait.res.txt index fcdf41a2..16c93a17 100644 --- a/tests/printer/expr/expected/asyncAwait.res.txt +++ b/tests/printer/expr/expected/asyncAwait.res.txt @@ -1,8 +1,8 @@ let sequentialAwait = async () => { - let result1 = @await paused("first") + let result1 = await paused("first") nodeJsAssert.equal(result1, "first") - let result2 = @await paused("second") + let result2 = await paused("second") nodeJsAssert.equal(result2, "second") } @@ -11,3 +11,8 @@ let f = async (. ()) => () let f = async (f) => f() let f = async (a, b) => a + b let f = async (. a, b) => a + b + +let maybeSomeValue = switch await fetchData(url) { +| data => Some(data) +| exception JsError(_) => None +} From 03db38953c8d8ba71de84e1cef92bb6db2ccba9d Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 4 Jul 2022 11:33:12 +0200 Subject: [PATCH 06/22] Implement printing of parens (precedence) for await expressions --- src/res_parens.ml | 14 ++++++++++++++ tests/printer/expr/asyncAwait.res | 15 ++++++++++++++- tests/printer/expr/expected/asyncAwait.res.txt | 13 +++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/res_parens.ml b/src/res_parens.ml index 33fb0f3d..b0e1739c 100644 --- a/src/res_parens.ml +++ b/src/res_parens.ml @@ -45,6 +45,8 @@ let callExpr expr = | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_ifthenelse _ ); } -> Parenthesized + | _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> + Parenthesized | _ -> Nothing) let structureExpr expr = @@ -96,6 +98,8 @@ let unaryExprOperand expr = | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_ifthenelse _ ); } -> Parenthesized + | _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> + Parenthesized | _ -> Nothing) let binaryExprOperand ~isLhs expr = @@ -120,6 +124,8 @@ let binaryExprOperand ~isLhs expr = | expr when ParsetreeViewer.isBinaryExpression expr -> Parenthesized | expr when ParsetreeViewer.isTernaryExpr expr -> Parenthesized | {pexp_desc = Pexp_lazy _ | Pexp_assert _} when isLhs -> Parenthesized + | _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> + Parenthesized | {Parsetree.pexp_attributes = attrs} -> if ParsetreeViewer.hasPrintableAttributes attrs then Parenthesized else Nothing) @@ -196,6 +202,8 @@ let lazyOrAssertExprRhs expr = | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_ifthenelse _ ); } -> Parenthesized + | _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> + Parenthesized | _ -> Nothing) let isNegativeConstant constant = @@ -240,6 +248,8 @@ let fieldExpr expr = | Pexp_ifthenelse _ ); } -> Parenthesized + | _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> + Parenthesized | _ -> Nothing) let setFieldExprRhs expr = @@ -302,6 +312,8 @@ let jsxPropExpr expr = } when startsWithMinus x -> Parenthesized + | _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> + Parenthesized | { Parsetree.pexp_desc = ( Pexp_ident _ | Pexp_constant _ | Pexp_field _ | Pexp_construct _ @@ -338,6 +350,8 @@ let jsxChildExpr expr = } when startsWithMinus x -> Parenthesized + | _ when ParsetreeViewer.hasAwaitAttribute expr.pexp_attributes -> + Parenthesized | { Parsetree.pexp_desc = ( Pexp_ident _ | Pexp_constant _ | Pexp_field _ | Pexp_construct _ diff --git a/tests/printer/expr/asyncAwait.res b/tests/printer/expr/asyncAwait.res index ec00cede..91323fb3 100644 --- a/tests/printer/expr/asyncAwait.res +++ b/tests/printer/expr/asyncAwait.res @@ -16,4 +16,17 @@ let f = async (. a, b) => a + b let maybeSomeValue = switch await fetchData(url) { | data => Some(data) | exception JsError(_) => None -} \ No newline at end of file +} + +(await f)(a, b) +-(await f) +await 1 + await 2 + +lazy (await f()) +assert (await f()) + +(await f).json() + +user.data = await fetch() + +{await weirdReactSuspenseApi} \ No newline at end of file diff --git a/tests/printer/expr/expected/asyncAwait.res.txt b/tests/printer/expr/expected/asyncAwait.res.txt index 16c93a17..c70693e0 100644 --- a/tests/printer/expr/expected/asyncAwait.res.txt +++ b/tests/printer/expr/expected/asyncAwait.res.txt @@ -16,3 +16,16 @@ let maybeSomeValue = switch await fetchData(url) { | data => Some(data) | exception JsError(_) => None } + +(await f)(a, b) +-(await f) +(await 1) + (await 2) + +lazy (await f()) +assert (await f()) + +(await f).json() + +user.data = await fetch() + + {await weirdReactSuspenseApi} From 9e711a4ac21fdc17820019d7571a6e06d273fef3 Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 4 Jul 2022 13:18:55 +0200 Subject: [PATCH 07/22] Make sure the "async" identifier can be used without being interpreted as async arrow function --- src/res_core.ml | 16 ++++++++++++---- tests/parsing/grammar/expressions/async.res | 12 ++++++++++++ .../grammar/expressions/expected/async.res.txt | 8 ++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/res_core.ml b/src/res_core.ml index 3883ef53..a9c7eb7c 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -237,6 +237,11 @@ let rec goToClosing closingToken state = (* Madness *) let isEs6ArrowExpression ~inTernary p = Parser.lookahead p (fun state -> + let () = + match state.Parser.token with + | Lident "async" -> Parser.next state + | _ -> () + in match state.Parser.token with | Lident _ | Underscore -> ( Parser.next state; @@ -2031,7 +2036,10 @@ and parseOperandExpr ~context p = let expr = parseUnaryExpr p in let loc = mkLoc startPos p.prevEndPos in Ast_helper.Exp.assert_ ~loc expr - | Lident "async" -> parseAsyncExpression p + | Lident "async" + when isEs6ArrowExpression ~inTernary:(context = TernaryTrueBranchExpr) p + -> + parseAsyncArrowExpression p | Lident "await" -> parseAwaitExpression p | Lazy -> Parser.next p; @@ -2746,8 +2754,8 @@ and parseBracedOrRecordExpr p = let expr = parseRecordExpr ~startPos [] p in Parser.expect Rbrace p; expr - | Lident "async" -> - let expr = parseAsyncExpression p in + | Lident "async" when isEs6ArrowExpression ~inTernary:false p -> + let expr = parseAsyncArrowExpression p in let expr = parseExprBlock ~first:expr p in Parser.expect Rbrace p; let loc = mkLoc startPos p.prevEndPos in @@ -3115,7 +3123,7 @@ and parseExprBlock ?first p = Parser.eatBreadcrumb p; overParseConstrainedOrCoercedOrArrowExpression p blockExpr -and parseAsyncExpression p = +and parseAsyncArrowExpression p = let startPos = p.Parser.startPos in match p.token with | Lident "async" -> diff --git a/tests/parsing/grammar/expressions/async.res b/tests/parsing/grammar/expressions/async.res index 0fcd97d9..f40b0b69 100644 --- a/tests/parsing/grammar/expressions/async.res +++ b/tests/parsing/grammar/expressions/async.res @@ -12,4 +12,16 @@ let fetch = { let fetch2 = { async (. url) => browserFetch(. url) async (. url) => browserFetch2(. url) +} + +// don't parse async es6 arrow +let async = { + let f = async() + ()->async + async() + async.async + + {async: async[async]} + + result->async->mapAsync(a => doStuff(a)) } \ No newline at end of file diff --git a/tests/parsing/grammar/expressions/expected/async.res.txt b/tests/parsing/grammar/expressions/expected/async.res.txt index cc38ead8..80e28065 100644 --- a/tests/parsing/grammar/expressions/expected/async.res.txt +++ b/tests/parsing/grammar/expressions/expected/async.res.txt @@ -12,4 +12,12 @@ let fetch2 = [@async ][@bs ]); (((fun url -> ((browserFetch2 url)[@bs ]))) [@async ][@bs ])) + [@ns.braces ]) +let async = + ((let f = async () in + () |. async; + async (); + async.async; + { async = (async.(async)) }; + (result |. async) |. (mapAsync (fun a -> doStuff a))) [@ns.braces ]) \ No newline at end of file From d0b7cb14484af78c6d94d07f22acf95e6eab3e00 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 6 Jul 2022 11:29:30 +0200 Subject: [PATCH 08/22] Make `await` a keyword again. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 https://github.com/cristianoc/rescript-compiler-experiments/pull/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. --- src/res_core.ml | 31 ++++++++++--------------------- src/res_grammar.ml | 8 ++++---- src/res_token.ml | 9 ++++++--- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/res_core.ml b/src/res_core.ml index a9c7eb7c..2c69ef19 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -2040,7 +2040,7 @@ and parseOperandExpr ~context p = when isEs6ArrowExpression ~inTernary:(context = TernaryTrueBranchExpr) p -> parseAsyncArrowExpression p - | Lident "await" -> parseAwaitExpression p + | Await -> parseAwaitExpression p | Lazy -> Parser.next p; let expr = parseUnaryExpr p in @@ -2761,13 +2761,6 @@ and parseBracedOrRecordExpr p = let loc = mkLoc startPos p.prevEndPos in let braces = makeBracesAttr loc in {expr with pexp_attributes = braces :: expr.pexp_attributes} - | Lident "await" -> - let expr = parseAwaitExpression p in - let expr = parseExprBlock ~first:expr p in - Parser.expect Rbrace p; - let loc = mkLoc startPos p.prevEndPos in - let braces = makeBracesAttr loc in - {expr with pexp_attributes = braces :: expr.pexp_attributes} | Uident _ | Lident _ -> ( let startToken = p.token in let valueOrConstructor = parseValueOrConstructor p in @@ -3140,19 +3133,15 @@ and parseAsyncArrowExpression p = | _ -> assert false and parseAwaitExpression p = - let startPos = p.Parser.startPos in - match p.token with - | Lident "await" -> - let awaitLoc = mkLoc startPos p.endPos in - let awaitAttr = (Location.mkloc "await" awaitLoc, Parsetree.PStr []) in - Parser.next p; - let expr = parseUnaryExpr p in - { - expr with - pexp_attributes = awaitAttr :: expr.pexp_attributes; - pexp_loc = {expr.pexp_loc with loc_start = awaitLoc.loc_start}; - } - | _ -> assert false + let awaitLoc = mkLoc p.Parser.startPos p.endPos in + let awaitAttr = (Location.mkloc "await" awaitLoc, Parsetree.PStr []) in + Parser.expect Await p; + let expr = parseUnaryExpr p in + { + expr with + pexp_attributes = awaitAttr :: expr.pexp_attributes; + pexp_loc = {expr.pexp_loc with loc_start = awaitLoc.loc_start}; + } and parseTryExpression p = let startPos = p.Parser.startPos in diff --git a/src/res_grammar.ml b/src/res_grammar.ml index 44f0e497..d1348c60 100644 --- a/src/res_grammar.ml +++ b/src/res_grammar.ml @@ -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 -> true | _ -> false @@ -257,9 +257,9 @@ let isJsxChildStart = isAtomicExprStart let isBlockExprStart = function | Token.At | Hash | Percent | Minus | MinusDot | Plus | PlusDot | Bang | True | False | Float _ | Int _ | String _ | Codepoint _ | Lident _ | Uident _ - | Lparen | List | Lbracket | Lbrace | Forwardslash | Assert | Lazy | If | For - | While | Switch | Open | Module | Exception | Let | LessThan | Backtick | Try - | Underscore -> + | Lparen | List | Lbracket | Lbrace | Forwardslash | Assert | Await | Lazy + | If | For | While | Switch | Open | Module | Exception | Let | LessThan + | Backtick | Try | Underscore -> true | _ -> false diff --git a/src/res_token.ml b/src/res_token.ml index 2c4f8f26..a2dceeca 100644 --- a/src/res_token.ml +++ b/src/res_token.ml @@ -1,6 +1,7 @@ module Comment = Res_comment type t = + | Await | Open | True | False @@ -111,6 +112,7 @@ let precedence = function | _ -> 0 let toString = function + | Await -> "await" | Open -> "open" | True -> "true" | False -> "false" @@ -210,6 +212,7 @@ let keywordTable = function | "and" -> And | "as" -> As | "assert" -> Assert + | "await" -> Await | "constraint" -> Constraint | "else" -> Else | "exception" -> Exception @@ -238,9 +241,9 @@ let keywordTable = function [@@raises Not_found] let isKeyword = function - | And | As | Assert | Constraint | Else | Exception | External | False | For - | If | In | Include | Land | Lazy | Let | List | Lor | Module | Mutable | Of - | Open | Private | Rec | Switch | True | Try | Typ | When | While -> + | Await | And | As | Assert | Constraint | Else | Exception | External | False + | For | If | In | Include | Land | Lazy | Let | List | Lor | Module | Mutable + | Of | Open | Private | Rec | Switch | True | Try | Typ | When | While -> true | _ -> false From 0283e8df806bc2c754abe945a572018d93da9a2c Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 6 Jul 2022 11:35:42 +0200 Subject: [PATCH 09/22] Remove `assert false` branch from async arrow expression parsing --- src/res_core.ml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/res_core.ml b/src/res_core.ml index 2c69ef19..9f5082d3 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -3118,19 +3118,16 @@ and parseExprBlock ?first p = and parseAsyncArrowExpression p = let startPos = p.Parser.startPos in - match p.token with - | Lident "async" -> - let asyncAttr = - (Location.mkloc "async" (mkLoc startPos p.endPos), Parsetree.PStr []) - in - Parser.next p; - let expr = parseEs6ArrowExpression p in - { - expr with - pexp_attributes = asyncAttr :: expr.pexp_attributes; - pexp_loc = {expr.pexp_loc with loc_start = startPos}; - } - | _ -> assert false + Parser.expect (Lident "async") p; + let asyncAttr = + (Location.mkloc "async" (mkLoc startPos p.prevEndPos), Parsetree.PStr []) + in + let expr = parseEs6ArrowExpression p in + { + expr with + pexp_attributes = asyncAttr :: expr.pexp_attributes; + pexp_loc = {expr.pexp_loc with loc_start = startPos}; + } and parseAwaitExpression p = let awaitLoc = mkLoc p.Parser.startPos p.endPos in From dc85c2192f02758c42b0505d35720278efe92f8a Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 6 Jul 2022 13:55:39 +0200 Subject: [PATCH 10/22] Make `processFunctionAttributes` return a record instead of tuple --- src/res_parsetree_viewer.ml | 8 +++++++- src/res_parsetree_viewer.mli | 10 +++++++--- src/res_printer.ml | 4 ++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index 9e3c55e2..4050e731 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -55,10 +55,16 @@ let processUncurriedAttribute attrs = in process false [] attrs +type functionAttributesInfo = { + async: bool; + uncurried: bool; + attributes: Parsetree.attributes; +} + let processFunctionAttributes attrs = let rec process async uncurried acc attrs = match attrs with - | [] -> (async, uncurried, List.rev acc) + | [] -> {async; uncurried; attributes = List.rev acc} | ({Location.txt = "bs"}, _) :: rest -> process async true acc rest | ({Location.txt = "async"}, _) :: rest -> process true uncurried acc rest | attr :: rest -> process async uncurried (attr :: acc) rest diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index b063c82d..f1f5fa32 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -17,10 +17,14 @@ val functorType : val processUncurriedAttribute : Parsetree.attributes -> bool * Parsetree.attributes +type functionAttributesInfo = { + async: bool; + uncurried: bool; + attributes: Parsetree.attributes; +} + (* determines whether a function is async and/or uncurried based on the given attributes *) -val processFunctionAttributes : - Parsetree.attributes -> - bool (* async *) * bool (* uncurried *) * Parsetree.attributes +val processFunctionAttributes : Parsetree.attributes -> functionAttributesInfo val hasAwaitAttribute : Parsetree.attributes -> bool diff --git a/src/res_printer.ml b/src/res_printer.ml index 7397226f..d8f4ee94 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -3135,7 +3135,7 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = cmtTbl | Pexp_fun _ | Pexp_newtype _ -> let attrsOnArrow, parameters, returnExpr = ParsetreeViewer.funExpr e in - let async, uncurried, attrs = + let ParsetreeViewer.{async; uncurried; attributes = attrs} = ParsetreeViewer.processFunctionAttributes attrsOnArrow in let returnExpr, typConstraint = @@ -3302,7 +3302,7 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = and printPexpFun ~customLayout ~inCallback e cmtTbl = let attrsOnArrow, parameters, returnExpr = ParsetreeViewer.funExpr e in - let async, uncurried, attrs = + let ParsetreeViewer.{async; uncurried; attributes = attrs} = ParsetreeViewer.processFunctionAttributes attrsOnArrow in let returnExpr, typConstraint = From 66defd5c8a1162461a3c6d4d1cc9db780bb454a7 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 07:23:32 +0200 Subject: [PATCH 11/22] Refactor code style "async" token lookahead See https://github.com/rescript-lang/syntax/pull/600#discussion_r914861732 --- src/res_core.ml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/res_core.ml b/src/res_core.ml index 9f5082d3..b814c07c 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -237,11 +237,9 @@ let rec goToClosing closingToken state = (* Madness *) let isEs6ArrowExpression ~inTernary p = Parser.lookahead p (fun state -> - let () = - match state.Parser.token with - | Lident "async" -> Parser.next state - | _ -> () - in + (match state.Parser.token with + | Lident "async" -> Parser.next state + | _ -> ()); match state.Parser.token with | Lident _ | Underscore -> ( Parser.next state; From ff69dfa1678c584aaa72ca807bdd4090843c0d65 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 08:17:08 +0200 Subject: [PATCH 12/22] Rename @await/@async to @res.await/@res.async --- src/res_core.ml | 8 ++++---- src/res_parsetree_viewer.ml | 16 ++++++++------- .../expressions/expected/async.res.txt | 12 +++++------ .../expressions/expected/await.res.txt | 20 ++++++++++--------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/res_core.ml b/src/res_core.ml index b814c07c..ff37555f 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -161,6 +161,8 @@ let uncurryAttr = (Location.mknoloc "bs", Parsetree.PStr []) let ternaryAttr = (Location.mknoloc "ns.ternary", Parsetree.PStr []) let ifLetAttr = (Location.mknoloc "ns.iflet", Parsetree.PStr []) let optionalAttr = (Location.mknoloc "ns.optional", Parsetree.PStr []) +let makeAwaitAttr loc = (Location.mkloc "res.await" loc, Parsetree.PStr []) +let makeAsyncAttr loc = (Location.mkloc "res.async" loc, Parsetree.PStr []) let makeExpressionOptional ~optional (e : Parsetree.expression) = if optional then {e with pexp_attributes = optionalAttr :: e.pexp_attributes} @@ -3117,9 +3119,7 @@ and parseExprBlock ?first p = and parseAsyncArrowExpression p = let startPos = p.Parser.startPos in Parser.expect (Lident "async") p; - let asyncAttr = - (Location.mkloc "async" (mkLoc startPos p.prevEndPos), Parsetree.PStr []) - in + let asyncAttr = makeAsyncAttr (mkLoc startPos p.prevEndPos) in let expr = parseEs6ArrowExpression p in { expr with @@ -3129,7 +3129,7 @@ and parseAsyncArrowExpression p = and parseAwaitExpression p = let awaitLoc = mkLoc p.Parser.startPos p.endPos in - let awaitAttr = (Location.mkloc "await" awaitLoc, Parsetree.PStr []) in + let awaitAttr = makeAwaitAttr awaitLoc in Parser.expect Await p; let expr = parseUnaryExpr p in { diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index 4050e731..bbd96291 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -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" | "async"}, _)] as attrs; + ptyp_attributes = [({txt = "bs" | "res.async"}, _)] as attrs; } -> let arg = (attrs, lbl, typ1) in process attrsBefore (arg :: acc) typ2 @@ -66,7 +66,8 @@ let processFunctionAttributes attrs = match attrs with | [] -> {async; uncurried; attributes = List.rev acc} | ({Location.txt = "bs"}, _) :: rest -> process async true acc rest - | ({Location.txt = "async"}, _) :: rest -> process true uncurried acc rest + | ({Location.txt = "res.async"}, _) :: rest -> + process true uncurried acc rest | attr :: rest -> process async uncurried (attr :: acc) rest in process false false [] attrs @@ -74,7 +75,7 @@ let processFunctionAttributes attrs = let hasAwaitAttribute attrs = List.exists (function - | {Location.txt = "await"}, _ -> true + | {Location.txt = "res.await"}, _ -> true | _ -> false) attrs @@ -191,8 +192,9 @@ let filterParsingAttrs attrs = match attr with | ( { Location.txt = - ( "ns.ternary" | "ns.braces" | "res.template" | "await" | "bs" - | "ns.iflet" | "ns.namedArgLoc" | "ns.optional" ); + ( "ns.ternary" | "ns.braces" | "res.template" | "res.await" + | "res.async" | "bs" | "ns.iflet" | "ns.namedArgLoc" + | "ns.optional" ); }, _ ) -> false @@ -339,8 +341,8 @@ let hasAttributes attrs = match attr with | ( { Location.txt = - ( "bs" | "async" | "res.template" | "ns.ternary" | "ns.braces" - | "ns.iflet" ); + ( "bs" | "res.async" | "res.await" | "res.template" | "ns.ternary" + | "ns.braces" | "ns.iflet" ); }, _ ) -> false diff --git a/tests/parsing/grammar/expressions/expected/async.res.txt b/tests/parsing/grammar/expressions/expected/async.res.txt index 80e28065..ea2c26e7 100644 --- a/tests/parsing/grammar/expressions/expected/async.res.txt +++ b/tests/parsing/grammar/expressions/expected/async.res.txt @@ -1,17 +1,17 @@ let greetUser = ((fun userId -> - ((let name = ((getUserName userId)[@await ][@bs ]) in + ((let name = ((getUserName userId)[@res.await ][@bs ]) in ({js|Hello |js} ^ name) ^ {js|!|js}) [@ns.braces ])) - [@async ]) -;;((fun () -> 123)[@async ]) + [@res.async ]) +;;((fun () -> 123)[@res.async ]) let fetch = ((fun url -> ((browserFetch url)[@bs ])) - [@ns.braces ][@async ][@bs ]) + [@ns.braces ][@res.async ][@bs ]) let fetch2 = (((((fun url -> ((browserFetch url)[@bs ]))) - [@async ][@bs ]); + [@res.async ][@bs ]); (((fun url -> ((browserFetch2 url)[@bs ]))) - [@async ][@bs ])) + [@res.async ][@bs ])) [@ns.braces ]) let async = ((let f = async () in diff --git a/tests/parsing/grammar/expressions/expected/await.res.txt b/tests/parsing/grammar/expressions/expected/await.res.txt index 0ecccfa2..8f8d9f70 100644 --- a/tests/parsing/grammar/expressions/expected/await.res.txt +++ b/tests/parsing/grammar/expressions/expected/await.res.txt @@ -1,16 +1,18 @@ -;;((wait 2)[@await ]) +;;((wait 2)[@res.await ]) let maybeSomeValue = - match ((fetchData url)[@await ]) with + match ((fetchData url)[@res.await ]) with | data -> Some data | exception JsError _ -> None -let x = ((1)[@await ]) + 2 -let x = ((wait 1)[@await ]) + ((wait 2)[@await ]) +let x = ((1)[@res.await ]) + 2 +let x = ((wait 1)[@res.await ]) + ((wait 2)[@res.await ]) let () = - ((let response = ((fetch {js|/users.json|js})[@await ]) in - let users = ((response.json ())[@await ]) in + ((let response = ((fetch {js|/users.json|js})[@res.await ]) in + let users = ((response.json ())[@res.await ]) in let comments = - ((((fetch {js|comment.json|js})[@await ]).json ())[@await ]).(0) in + ((((fetch {js|comment.json|js})[@res.await ]).json ()) + [@res.await ]).(0) in Js.log2 users comments) [@ns.braces ]) -let () = ((delay 10)[@ns.braces ][@await ]) -let () = ((((delay 10)[@await ]); ((delay 20)[@await ]))[@ns.braces ]) \ No newline at end of file +let () = ((delay 10)[@ns.braces ][@res.await ]) +let () = ((((delay 10)[@res.await ]); ((delay 20)[@res.await ])) + [@ns.braces ]) \ No newline at end of file From 3d5af745bc601d083e595ab1de54cb0ba33b6c59 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 08:23:29 +0200 Subject: [PATCH 13/22] Alphabetically sort variant cases in `isBlockExprStart` --- src/res_grammar.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/res_grammar.ml b/src/res_grammar.ml index d1348c60..074869da 100644 --- a/src/res_grammar.ml +++ b/src/res_grammar.ml @@ -255,11 +255,11 @@ let isAttributeStart = function let isJsxChildStart = isAtomicExprStart let isBlockExprStart = function - | Token.At | Hash | Percent | Minus | MinusDot | Plus | PlusDot | Bang | True - | False | Float _ | Int _ | String _ | Codepoint _ | Lident _ | Uident _ - | Lparen | List | Lbracket | Lbrace | Forwardslash | Assert | Await | Lazy - | If | For | While | Switch | Open | Module | Exception | Let | LessThan - | Backtick | Try | Underscore -> + | Token.Assert | At | Await | Backtick | Bang | Codepoint _ | Exception + | False | Float _ | For | Forwardslash | Hash | If | Int _ | Lazy | Lbrace + | Lbracket | LessThan | Let | Lident _ | List | Lparen | Minus | MinusDot + | Module | Open | Percent | Plus | PlusDot | String _ | Switch | True | Try + | Uident _ | Underscore | While -> true | _ -> false From 702d5865267824675dd316fce4e0579ca2b72484 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 08:44:20 +0200 Subject: [PATCH 14/22] Sort variant cases `isExprStart` alphabetically --- src/res_grammar.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/res_grammar.ml b/src/res_grammar.ml index 074869da..cba9b4bd 100644 --- a/src/res_grammar.ml +++ b/src/res_grammar.ml @@ -147,11 +147,11 @@ let isAtomicTypExprStart = function | _ -> false let isExprStart = function - | Token.True | False | Int _ | String _ | Float _ | Codepoint _ | Backtick - | Underscore (* _ => doThings() *) - | Uident _ | Lident _ | Hash | Lparen | List | Module | Lbracket | Lbrace - | LessThan | Minus | MinusDot | Plus | PlusDot | Bang | Percent | At | If - | Switch | While | For | Assert | Await | Lazy | Try -> + | Token.Assert | At | Await | Backtick | Bang | Codepoint _ | False | Float _ + | For | Hash | If | Int _ | Lazy | Lbrace | Lbracket | LessThan | Lident _ + | List | Lparen | Minus | MinusDot | Module | Percent | Plus | PlusDot + | String _ | Switch | True | Try | Uident _ | Underscore (* _ => doThings() *) + | While -> true | _ -> false From 046d1e31f2bc5872fb6b6c3831b989b96ee508d6 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 08:57:08 +0200 Subject: [PATCH 15/22] Extract printing of "async " in a helper --- src/res_printer.ml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/res_printer.ml b/src/res_printer.ml index d8f4ee94..fc0b09a8 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -79,6 +79,8 @@ let addBraces doc = Doc.rbrace; ]) +let addAsync doc = Doc.concat [Doc.text "async "; doc] + let getFirstLeadingComment tbl loc = match Hashtbl.find tbl.CommentTable.leading loc with | comment :: _ -> Some comment @@ -4597,7 +4599,7 @@ and printExprFunParameters ~customLayout ~inCallback ~async ~uncurried let doc = if hasConstraint then Doc.text "(_)" else Doc.text "_" in printComments doc cmtTbl ppat_loc in - if async then Doc.concat [Doc.text "async "; any] else any + if async then addAsync any else any (* let f = a => () *) | [ ParsetreeViewer.Parameter @@ -4612,7 +4614,7 @@ and printExprFunParameters ~customLayout ~inCallback ~async ~uncurried let txtDoc = let var = printIdentLike stringLoc.txt in let var = if hasConstraint then addParens var else var in - if async then Doc.concat [Doc.text "async ("; var; Doc.rparen] else var + if async then addAsync (Doc.concat [Doc.lparen; var; Doc.rparen]) else var in printComments txtDoc cmtTbl stringLoc.loc (* let f = () => () *) @@ -4627,7 +4629,10 @@ and printExprFunParameters ~customLayout ~inCallback ~async ~uncurried }; ] when not uncurried -> - let doc = if async then Doc.text "async ()" else Doc.text "()" in + let doc = + let lparenRparen = Doc.text "()" in + if async then addAsync lparenRparen else lparenRparen + in printComments doc cmtTbl loc (* let f = (~greeting, ~from as hometown, ~x=?) => () *) | parameters -> @@ -4637,11 +4642,8 @@ and printExprFunParameters ~customLayout ~inCallback ~async ~uncurried | _ -> false in let maybeAsyncLparen = - match (async, uncurried) with - | true, true -> Doc.text "async (. " - | true, false -> Doc.text "async (" - | false, true -> Doc.text "(. " - | false, false -> Doc.lparen + let lparen = if uncurried then Doc.text "(. " else Doc.lparen in + if async then addAsync lparen else lparen in let shouldHug = ParsetreeViewer.parametersShouldHug parameters in let printedParamaters = From 41844e658e9f5dd1db2a9adceaf74bd08ee3721a Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 09:11:00 +0200 Subject: [PATCH 16/22] Document ternary colon vs async arrow expression colon --- src/res_core.ml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/res_core.ml b/src/res_core.ml index ff37555f..0279ca03 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -2037,6 +2037,11 @@ and parseOperandExpr ~context p = let loc = mkLoc startPos p.prevEndPos in Ast_helper.Exp.assert_ ~loc expr | Lident "async" + (* we need to be careful when we're in a ternary true branch: + `condition ? ternary-true-branch : false-branch` + Arrow expressions could be of the form: `async (): int => stuff()` + But if we're in a ternary, the `:` of the ternary takes precedence + *) when isEs6ArrowExpression ~inTernary:(context = TernaryTrueBranchExpr) p -> parseAsyncArrowExpression p From fcd3c326a0a328f51558ed3778376e728836e64d Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 09:16:44 +0200 Subject: [PATCH 17/22] Add extra test case for async arrow expressions in ternary true branches --- tests/parsing/grammar/expressions/async.res | 4 +++- tests/parsing/grammar/expressions/expected/async.res.txt | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/parsing/grammar/expressions/async.res b/tests/parsing/grammar/expressions/async.res index f40b0b69..b104866d 100644 --- a/tests/parsing/grammar/expressions/async.res +++ b/tests/parsing/grammar/expressions/async.res @@ -24,4 +24,6 @@ let async = { {async: async[async]} result->async->mapAsync(a => doStuff(a)) -} \ No newline at end of file +} + +let f = isPositive ? (async (a, b) : int => a + b) : async (c, d) : int => c - d \ No newline at end of file diff --git a/tests/parsing/grammar/expressions/expected/async.res.txt b/tests/parsing/grammar/expressions/expected/async.res.txt index ea2c26e7..7aef8ffd 100644 --- a/tests/parsing/grammar/expressions/expected/async.res.txt +++ b/tests/parsing/grammar/expressions/expected/async.res.txt @@ -20,4 +20,9 @@ let async = async.async; { async = (async.(async)) }; (result |. async) |. (mapAsync (fun a -> doStuff a))) - [@ns.braces ]) \ No newline at end of file + [@ns.braces ]) +let f = + ((if isPositive + then ((fun a -> fun b -> (a + b : int))[@res.async ]) + else (((fun c -> fun d -> (c - d : int)))[@res.async ])) + [@ns.ternary ]) \ No newline at end of file From d20b14975a643861b67eb6481c5d3f7136737a88 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 14:38:45 +0200 Subject: [PATCH 18/22] Sort attribute variant cases on `hasAttributes` and `filterParsingAttrs` --- src/res_parsetree_viewer.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index bbd96291..e54a7d59 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -192,9 +192,9 @@ let filterParsingAttrs attrs = match attr with | ( { Location.txt = - ( "ns.ternary" | "ns.braces" | "res.template" | "res.await" - | "res.async" | "bs" | "ns.iflet" | "ns.namedArgLoc" - | "ns.optional" ); + ( "bs" | "ns.braces" | "ns.iflet" | "ns.namedArgLoc" + | "ns.optional" | "ns.ternary" | "res.async" | "res.await" + | "res.template" ); }, _ ) -> false @@ -341,8 +341,8 @@ let hasAttributes attrs = match attr with | ( { Location.txt = - ( "bs" | "res.async" | "res.await" | "res.template" | "ns.ternary" - | "ns.braces" | "ns.iflet" ); + ( "bs" | "ns.braces" | "ns.iflet" | "ns.ternary" | "res.async" + | "res.await" | "res.template" ); }, _ ) -> false From 3b17a08bea26c6fa62e39e8a6f6f1b21e3e8c40f Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 14:42:57 +0200 Subject: [PATCH 19/22] Document Lident "async" branch in `parseBracedOrRecordExpr`. --- src/res_core.ml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/res_core.ml b/src/res_core.ml index 0279ca03..0d53ed16 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -2759,6 +2759,14 @@ and parseBracedOrRecordExpr p = let expr = parseRecordExpr ~startPos [] p in Parser.expect Rbrace p; expr + (* + The branch below takes care of the "braced" expression {async}. + The big reason that we need all these branches is that {x} isn't a record with a punned field x, but a braced expression… There's lots of "ambiguity" between a record with a single punned field and a braced expression… + What is {x}? + 1) record {x: x} + 2) expression x which happens to wrapped in braces + Due to historical reasons, we always follow 2 + *) | Lident "async" when isEs6ArrowExpression ~inTernary:false p -> let expr = parseAsyncArrowExpression p in let expr = parseExprBlock ~first:expr p in From bff364ed32dcbf48d8c62069f2e03b4262903e69 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 15:23:05 +0200 Subject: [PATCH 20/22] Fix printing of await expression inside binary expressions --- src/res_parsetree_viewer.ml | 6 ++--- src/res_parsetree_viewer.mli | 1 - src/res_printer.ml | 23 +++++++++++-------- test.res | 2 -- tests/printer/expr/asyncAwait.res | 7 +++++- .../printer/expr/expected/asyncAwait.res.txt | 5 ++++ 6 files changed, 26 insertions(+), 18 deletions(-) delete mode 100644 test.res diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index e54a7d59..8de27c34 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -523,8 +523,8 @@ let isPrintableAttribute attr = match attr with | ( { Location.txt = - ( "bs" | "res.template" | "ns.ternary" | "ns.braces" | "ns.iflet" - | "JSX" ); + ( "bs" | "ns.iflet" | "ns.braces" | "JSX" | "res.async" | "res.await" + | "res.template" | "ns.ternary" ); }, _ ) -> false @@ -532,8 +532,6 @@ let isPrintableAttribute attr = let hasPrintableAttributes attrs = List.exists isPrintableAttribute attrs -let filterPrintableAttributes attrs = List.filter isPrintableAttribute attrs - let partitionPrintableAttributes attrs = List.partition isPrintableAttribute attrs diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index f1f5fa32..656780a1 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -99,7 +99,6 @@ val hasOptionalAttribute : Parsetree.attributes -> bool val shouldIndentBinaryExpr : Parsetree.expression -> bool val shouldInlineRhsBinaryExpr : Parsetree.expression -> bool val hasPrintableAttributes : Parsetree.attributes -> bool -val filterPrintableAttributes : Parsetree.attributes -> Parsetree.attributes val partitionPrintableAttributes : Parsetree.attributes -> Parsetree.attributes * Parsetree.attributes diff --git a/src/res_printer.ml b/src/res_printer.ml index fc0b09a8..c5b53f46 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -3519,13 +3519,13 @@ and printBinaryExpression ~customLayout (expr : Parsetree.expression) cmtTbl = then let leftPrinted = flatten ~isLhs:true left operator in let rightPrinted = - let _, rightAttrs = + let rightPrinteableAttrs, rightInternalAttrs = ParsetreeViewer.partitionPrintableAttributes right.pexp_attributes in let doc = printExpressionWithComments ~customLayout - {right with pexp_attributes = rightAttrs} + {right with pexp_attributes = rightInternalAttrs} cmtTbl in let doc = @@ -3533,14 +3533,14 @@ and printBinaryExpression ~customLayout (expr : Parsetree.expression) cmtTbl = Doc.concat [Doc.lparen; doc; Doc.rparen] else doc in - let printableAttrs = - ParsetreeViewer.filterPrintableAttributes right.pexp_attributes - in let doc = Doc.concat - [printAttributes ~customLayout printableAttrs cmtTbl; doc] + [ + printAttributes ~customLayout rightPrinteableAttrs cmtTbl; + doc; + ] in - match printableAttrs with + match rightPrinteableAttrs with | [] -> doc | _ -> addParens doc in @@ -3559,22 +3559,25 @@ and printBinaryExpression ~customLayout (expr : Parsetree.expression) cmtTbl = in printComments doc cmtTbl expr.pexp_loc else + let printeableAttrs, internalAttrs = + ParsetreeViewer.partitionPrintableAttributes expr.pexp_attributes + in let doc = printExpressionWithComments ~customLayout - {expr with pexp_attributes = []} + {expr with pexp_attributes = internalAttrs} cmtTbl in let doc = if Parens.subBinaryExprOperand parentOperator operator - || expr.pexp_attributes <> [] + || printeableAttrs <> [] && (ParsetreeViewer.isBinaryExpression expr || ParsetreeViewer.isTernaryExpr expr) then Doc.concat [Doc.lparen; doc; Doc.rparen] else doc in Doc.concat - [printAttributes ~customLayout expr.pexp_attributes cmtTbl; doc] + [printAttributes ~customLayout printeableAttrs cmtTbl; doc] | _ -> assert false else match expr.pexp_desc with diff --git a/test.res b/test.res deleted file mode 100644 index 47eb5207..00000000 --- a/test.res +++ /dev/null @@ -1,2 +0,0 @@ -let f = (.) => () -let f = async () => delay(20) diff --git a/tests/printer/expr/asyncAwait.res b/tests/printer/expr/asyncAwait.res index 91323fb3..5155786d 100644 --- a/tests/printer/expr/asyncAwait.res +++ b/tests/printer/expr/asyncAwait.res @@ -29,4 +29,9 @@ assert (await f()) user.data = await fetch() -{await weirdReactSuspenseApi} \ No newline at end of file +{await weirdReactSuspenseApi} + +let inBinaryExpression = await x->Js.Promise.resolve + 1 +let inBinaryExpression = await x->Js.Promise.resolve + await y->Js.Promise.resolve + +let () = await (await fetch(url))->(await resolve) diff --git a/tests/printer/expr/expected/asyncAwait.res.txt b/tests/printer/expr/expected/asyncAwait.res.txt index c70693e0..d2872215 100644 --- a/tests/printer/expr/expected/asyncAwait.res.txt +++ b/tests/printer/expr/expected/asyncAwait.res.txt @@ -29,3 +29,8 @@ assert (await f()) user.data = await fetch() {await weirdReactSuspenseApi} + +let inBinaryExpression = (await x)->Js.Promise.resolve + 1 +let inBinaryExpression = (await x)->Js.Promise.resolve + (await y)->Js.Promise.resolve + +let () = (await fetch(url))->(await resolve) From c15dee02cdea76070dd9538c60f9aef044759df2 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 7 Jul 2022 18:12:41 +0200 Subject: [PATCH 21/22] Implement printing of parens for rhs of await expression correctly. --- src/res_parens.ml | 2 +- src/res_parens.mli | 2 +- src/res_parsetree_viewer.ml | 2 + src/res_parsetree_viewer.mli | 1 + src/res_printer.ml | 29 +++++--- tests/printer/expr/asyncAwait.res | 46 +++++++++++++ .../printer/expr/expected/asyncAwait.res.txt | 69 +++++++++++++++++++ 7 files changed, 141 insertions(+), 10 deletions(-) diff --git a/src/res_parens.ml b/src/res_parens.ml index b0e1739c..c18b7565 100644 --- a/src/res_parens.ml +++ b/src/res_parens.ml @@ -175,7 +175,7 @@ let flattenOperandRhs parentOperator rhs = | _ when ParsetreeViewer.isTernaryExpr rhs -> true | _ -> false -let lazyOrAssertExprRhs expr = +let lazyOrAssertOrAwaitExprRhs expr = let optBraces, _ = ParsetreeViewer.processBracesAttr expr in match optBraces with | Some ({Location.loc = bracesLoc}, _) -> Braced bracesLoc diff --git a/src/res_parens.mli b/src/res_parens.mli index 04cca4b8..cedf98e1 100644 --- a/src/res_parens.mli +++ b/src/res_parens.mli @@ -10,7 +10,7 @@ val subBinaryExprOperand : string -> string -> bool val rhsBinaryExprOperand : string -> Parsetree.expression -> bool val flattenOperandRhs : string -> Parsetree.expression -> bool -val lazyOrAssertExprRhs : Parsetree.expression -> kind +val lazyOrAssertOrAwaitExprRhs : Parsetree.expression -> kind val fieldExpr : Parsetree.expression -> kind diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index 8de27c34..c22dfb23 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -532,6 +532,8 @@ let isPrintableAttribute attr = let hasPrintableAttributes attrs = List.exists isPrintableAttribute attrs +let filterPrintableAttributes attrs = List.filter isPrintableAttribute attrs + let partitionPrintableAttributes attrs = List.partition isPrintableAttribute attrs diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index 656780a1..f1f5fa32 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -99,6 +99,7 @@ val hasOptionalAttribute : Parsetree.attributes -> bool val shouldIndentBinaryExpr : Parsetree.expression -> bool val shouldInlineRhsBinaryExpr : Parsetree.expression -> bool val hasPrintableAttributes : Parsetree.attributes -> bool +val filterPrintableAttributes : Parsetree.attributes -> Parsetree.attributes val partitionPrintableAttributes : Parsetree.attributes -> Parsetree.attributes * Parsetree.attributes diff --git a/src/res_printer.ml b/src/res_printer.ml index c5b53f46..c1d947d4 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -3095,7 +3095,7 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = | Pexp_assert expr -> let rhs = let doc = printExpressionWithComments ~customLayout expr cmtTbl in - match Parens.lazyOrAssertExprRhs expr with + match Parens.lazyOrAssertOrAwaitExprRhs expr with | Parens.Parenthesized -> addParens doc | Braced braces -> printBraces doc expr braces | Nothing -> doc @@ -3104,7 +3104,7 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = | Pexp_lazy expr -> let rhs = let doc = printExpressionWithComments ~customLayout expr cmtTbl in - match Parens.lazyOrAssertExprRhs expr with + match Parens.lazyOrAssertOrAwaitExprRhs expr with | Parens.Parenthesized -> addParens doc | Braced braces -> printBraces doc expr braces | Nothing -> doc @@ -3282,7 +3282,24 @@ and printExpression ~customLayout (e : Parsetree.expression) cmtTbl = in let exprWithAwait = if ParsetreeViewer.hasAwaitAttribute e.pexp_attributes then - Doc.concat [Doc.text "await "; printedExpression] + let rhs = + match + Parens.lazyOrAssertOrAwaitExprRhs + { + e with + pexp_attributes = + List.filter + (function + | {Location.txt = "res.await" | "ns.braces"}, _ -> false + | _ -> true) + e.pexp_attributes; + } + with + | Parens.Parenthesized -> addParens printedExpression + | Braced braces -> printBraces printedExpression e braces + | Nothing -> printedExpression + in + Doc.concat [Doc.text "await "; rhs] else printedExpression in let shouldPrintItsOwnAttributes = @@ -3680,11 +3697,7 @@ and printBinaryExpression ~customLayout (expr : Parsetree.expression) cmtTbl = { expr with pexp_attributes = - List.filter - (fun attr -> - match attr with - | {Location.txt = "ns.braces"}, _ -> false - | _ -> true) + ParsetreeViewer.filterPrintableAttributes expr.pexp_attributes; } with diff --git a/tests/printer/expr/asyncAwait.res b/tests/printer/expr/asyncAwait.res index 5155786d..4265a891 100644 --- a/tests/printer/expr/asyncAwait.res +++ b/tests/printer/expr/asyncAwait.res @@ -34,4 +34,50 @@ user.data = await fetch() let inBinaryExpression = await x->Js.Promise.resolve + 1 let inBinaryExpression = await x->Js.Promise.resolve + await y->Js.Promise.resolve +let withCallback = async (. ()) => { + async (. x) => await (x->Js.promise.resolve) + 1 +} + let () = await (await fetch(url))->(await resolve) + +let _ = await (1 + 1) +let _ = await 1 + 1 +let _ = await (-1) +let _ = await - 1 +let _ = await !ref +let _ = await f +let _ = await %extension +let _ = await "test" +let _ = await ((a, b) => a + b) +let _ = await (async (a, b) => a + b) +let _ = await (switch x { | A => () | B => ()}) +let _ = await [1, 2, 3] +let _ = await (1, 2, 3) +let _ = await {name: "Steve", age: 32} +let _ = await (user.name = "Steve") +let _ = await (if 20 { true } else {false}) +let _ = await (condition() ? true : false) +let _ = await f(x) +let _ = await f(. x) +let _ = await (f(x) : Js.Promise.t) +let _ = await (while true { infiniteLoop() }) +let _ = await (try ok() catch { | _ => logError() }) +let _ = await (for i in 0 to 10 { sideEffect()}) +let _ = await (lazy x) +let _ = await (assert x) +let _ = await promises[0] +let _ = await promises["resolved"] +let _ = await (promises["resolved"] = sideEffect()) +let _ = await (@attr expr) +let _ = await module(Foo) +let _ = await module(Foo: Bar) +let _ = await Promise +let _ = await Promise(status) + +// braces are being dropped, because the ast only has space to record braces surrounding the await +let _ = await {x} +// here braces stay, because of precedence +let _ = await { + let x = 1 + Js.Promise.resolve(x) +} \ No newline at end of file diff --git a/tests/printer/expr/expected/asyncAwait.res.txt b/tests/printer/expr/expected/asyncAwait.res.txt index d2872215..7a8642c4 100644 --- a/tests/printer/expr/expected/asyncAwait.res.txt +++ b/tests/printer/expr/expected/asyncAwait.res.txt @@ -33,4 +33,73 @@ user.data = await fetch() let inBinaryExpression = (await x)->Js.Promise.resolve + 1 let inBinaryExpression = (await x)->Js.Promise.resolve + (await y)->Js.Promise.resolve +let withCallback = async (. ()) => { + async (. x) => await (x->Js.promise.resolve) + 1 +} + let () = (await fetch(url))->(await resolve) + +let _ = await (1 + 1) +let _ = (await 1) + 1 +let _ = await -1 +let _ = await -1 +let _ = await !ref +let _ = await f +let _ = await %extension +let _ = await "test" +let _ = await ((a, b) => a + b) +let _ = await (async (a, b) => a + b) +let _ = await ( + switch x { + | A => () + | B => () + } +) +let _ = await [1, 2, 3] +let _ = await (1, 2, 3) +let _ = await {name: "Steve", age: 32} +let _ = await (user.name = "Steve") +let _ = await ( + if 20 { + true + } else { + false + } +) +let _ = await (condition() ? true : false) +let _ = await f(x) +let _ = await f(. x) +let _ = (await (f(x): Js.Promise.t)) +let _ = await ( + while true { + infiniteLoop() + } +) +let _ = await ( + try ok() catch { + | _ => logError() + } +) +let _ = await ( + for i in 0 to 10 { + sideEffect() + } +) +let _ = await (lazy x) +let _ = await (assert x) +let _ = await promises[0] +let _ = await promises["resolved"] +let _ = await promises["resolved"] = sideEffect() +let _ = @attr await (expr) +let _ = await module(Foo) +let _ = await module(Foo: Bar) +let _ = await Promise +let _ = await Promise(status) + +// braces are being dropped, because the ast only has space to record braces surrounding the await +let _ = await x +// here braces stay, because of precedence +let _ = await { + let x = 1 + Js.Promise.resolve(x) +} From 5e9b9141ca54fea9ba1e329f480108fc2e8cca51 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Mon, 18 Jul 2022 10:02:37 +0200 Subject: [PATCH 22/22] Update CHANGELOG.md --- CHANGELOG.md | 59 ++++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370864cb..6b06cb75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,34 +1,39 @@ -## Unreleased +## Master -* Fix printing for inline nullary functor types [#477](https://github.com/rescript-lang/syntax/pull/477) -* Fix stripping of quotes for empty poly variants [#474](https://github.com/rescript-lang/syntax/pull/474) -* Implement syntax for arity zero vs arity one in uncurried application in [#139](https://github.com/rescript-lang/syntax/pull/139) -* Fix parsing of first class module exprs as part of binary/ternary expr in [#256](https://github.com/rescript-lang/syntax/pull/256) -* Fix formatter hanging on deeply nested function calls [#261](https://github.com/rescript-lang/syntax/issues/261) -* Remove parsing of "import" and "export" which was never officially supported. +#### :rocket: New Feature -## ReScript 9.0.0 +- Add surface syntax for `async`/`await` https://github.com/rescript-lang/syntax/pull/600 + +## ReScript 10.0 -* Fix parsing of poly-var typexpr consisting of one tag-spec-first in [#254](https://github.com/rescript-lang/syntax/pull/254) -* Implement new syntax for guards on pattern match cases in [#248](https://github.com/rescript-lang/syntax/pull/248) -* Implement intelligent breaking for poly-var type expressions in [#246](https://github.com/rescript-lang/syntax/pull/246) -* Improve indentation of fast pipe chain in let binding in [#244](https://github.com/rescript-lang/syntax/pull/244) -* Improve printing of non-callback arguments in call expressions with callback in [#241](https://github.com/rescript-lang/syntax/pull/241/files) -* Fix printing of constrained expressions in rhs of js objects [#240](https://github.com/rescript-lang/syntax/pull/240) -* Improve printing of trailing comments under lhs of "pipe" expression in [#329](https://github.com/rescript-lang/syntax/pull/239/files) -* Improve printing of jsx children and props with leading line comment in [#236](https://github.com/rescript-lang/syntax/pull/236) -* Improve conversion of quoted strings from Reason in [#238](https://github.com/rescript-lang/syntax/pull/238) -* Print attributes/extension without bs prefix where possible in [#230](https://github.com/rescript-lang/syntax/pull/230) -* Cleanup gentype attribute printing [fe05e1051aa94b16f6993ddc5ba9651f89e86907](https://github.com/rescript-lang/syntax/commit/fe05e1051aa94b16f6993ddc5ba9651f89e86907) -* Implement light weight syntax for poly-variants [f84c5760b3f743f65e934195c87fc06bf88bff75](https://github.com/rescript-lang/syntax/commit/f84c5760b3f743f65e934195c87fc06bf88bff75) -* Fix bug in fast pipe conversion from Reason. [3d5f2daba5418b821c577ba03e2de1afb0dd66de](https://github.com/rescript-lang/syntax/commit/3d5f2daba5418b821c577ba03e2de1afb0dd66de) -* Improve parsed AST when tilde is missing in arrow expr parameters. [e52a0c89ac39b578a2062ef15fae2be625962e1f](https://github.com/rescript-lang/syntax/commit/e52a0c89ac39b578a2062ef15fae2be625962e1f) -* Improve parser diagnostics for missing tilde in labeled parameters. [a0d7689d5d2bfc31dc251e966ac33a3001200171](https://github.com/rescript-lang/syntax/commit/a0d7689d5d2bfc31dc251e966ac33a3001200171) -* Improve printing of uncurried application with a huggable expression in [c8767215186982e171fe9f9101d518150a65f0d7](https://github.com/rescript-lang/syntax/commit/c8767215186982e171fe9f9101d518150a65f0d7) -* Improve printing of uncurried arrow typexpr outcome printer in [4d953b668cf47358deccb8b730566f24de25b9ee](https://github.com/rescript-lang/syntax/commit/4d953b668cf47358deccb8b730566f24de25b9ee) -* Remove support for nativeint syntax in [72d9b7034fc28f317672c94994b322bee520acca](https://github.com/rescript-lang/syntax/commit/72d9b7034fc28f317672c94994b322bee520acca) -* Improve printing of poly variant typexprs with attributes in [bf6561b](https://github.com/rescript-lang/syntax/commit/bf6561bb5d84557b8b6cbbcd40078c39526af4af) +- Fix printing for inline nullary functor types [#477](https://github.com/rescript-lang/syntax/pull/477) +- Fix stripping of quotes for empty poly variants [#474](https://github.com/rescript-lang/syntax/pull/474) +- Implement syntax for arity zero vs arity one in uncurried application in [#139](https://github.com/rescript-lang/syntax/pull/139) +- Fix parsing of first class module exprs as part of binary/ternary expr in [#256](https://github.com/rescript-lang/syntax/pull/256) +- Fix formatter hanging on deeply nested function calls [#261](https://github.com/rescript-lang/syntax/issues/261) +- Remove parsing of "import" and "export" which was never officially supported. + +## ReScript 9.0.0 +- Fix parsing of poly-var typexpr consisting of one tag-spec-first in [#254](https://github.com/rescript-lang/syntax/pull/254) +- Implement new syntax for guards on pattern match cases in [#248](https://github.com/rescript-lang/syntax/pull/248) +- Implement intelligent breaking for poly-var type expressions in [#246](https://github.com/rescript-lang/syntax/pull/246) +- Improve indentation of fast pipe chain in let binding in [#244](https://github.com/rescript-lang/syntax/pull/244) +- Improve printing of non-callback arguments in call expressions with callback in [#241](https://github.com/rescript-lang/syntax/pull/241/files) +- Fix printing of constrained expressions in rhs of js objects [#240](https://github.com/rescript-lang/syntax/pull/240) +- Improve printing of trailing comments under lhs of "pipe" expression in [#329](https://github.com/rescript-lang/syntax/pull/239/files) +- Improve printing of jsx children and props with leading line comment in [#236](https://github.com/rescript-lang/syntax/pull/236) +- Improve conversion of quoted strings from Reason in [#238](https://github.com/rescript-lang/syntax/pull/238) +- Print attributes/extension without bs prefix where possible in [#230](https://github.com/rescript-lang/syntax/pull/230) +- Cleanup gentype attribute printing [fe05e1051aa94b16f6993ddc5ba9651f89e86907](https://github.com/rescript-lang/syntax/commit/fe05e1051aa94b16f6993ddc5ba9651f89e86907) +- Implement light weight syntax for poly-variants [f84c5760b3f743f65e934195c87fc06bf88bff75](https://github.com/rescript-lang/syntax/commit/f84c5760b3f743f65e934195c87fc06bf88bff75) +- Fix bug in fast pipe conversion from Reason. [3d5f2daba5418b821c577ba03e2de1afb0dd66de](https://github.com/rescript-lang/syntax/commit/3d5f2daba5418b821c577ba03e2de1afb0dd66de) +- Improve parsed AST when tilde is missing in arrow expr parameters. [e52a0c89ac39b578a2062ef15fae2be625962e1f](https://github.com/rescript-lang/syntax/commit/e52a0c89ac39b578a2062ef15fae2be625962e1f) +- Improve parser diagnostics for missing tilde in labeled parameters. [a0d7689d5d2bfc31dc251e966ac33a3001200171](https://github.com/rescript-lang/syntax/commit/a0d7689d5d2bfc31dc251e966ac33a3001200171) +- Improve printing of uncurried application with a huggable expression in [c8767215186982e171fe9f9101d518150a65f0d7](https://github.com/rescript-lang/syntax/commit/c8767215186982e171fe9f9101d518150a65f0d7) +- Improve printing of uncurried arrow typexpr outcome printer in [4d953b668cf47358deccb8b730566f24de25b9ee](https://github.com/rescript-lang/syntax/commit/4d953b668cf47358deccb8b730566f24de25b9ee) +- Remove support for nativeint syntax in [72d9b7034fc28f317672c94994b322bee520acca](https://github.com/rescript-lang/syntax/commit/72d9b7034fc28f317672c94994b322bee520acca) +- Improve printing of poly variant typexprs with attributes in [bf6561b](https://github.com/rescript-lang/syntax/commit/bf6561bb5d84557b8b6cbbcd40078c39526af4af) ## ReScript 8.4.2 (December 11, 2020)