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) diff --git a/src/res_core.ml b/src/res_core.ml index 506ceea3..0d53ed16 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} @@ -237,6 +239,9 @@ let rec goToClosing closingToken state = (* Madness *) let isEs6ArrowExpression ~inTernary p = Parser.lookahead p (fun state -> + (match state.Parser.token with + | Lident "async" -> Parser.next state + | _ -> ()); match state.Parser.token with | Lident _ | Underscore -> ( Parser.next state; @@ -2031,6 +2036,16 @@ and parseOperandExpr ~context p = let expr = parseUnaryExpr p in 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 + | Await -> parseAwaitExpression p | Lazy -> Parser.next p; let expr = parseUnaryExpr p in @@ -2744,6 +2759,21 @@ 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 + 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 @@ -3099,6 +3129,28 @@ and parseExprBlock ?first p = Parser.eatBreadcrumb p; overParseConstrainedOrCoercedOrArrowExpression p blockExpr +and parseAsyncArrowExpression p = + let startPos = p.Parser.startPos in + Parser.expect (Lident "async") p; + let asyncAttr = makeAsyncAttr (mkLoc startPos p.prevEndPos) 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 + let awaitAttr = makeAwaitAttr awaitLoc 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 Parser.expect Try p; diff --git a/src/res_grammar.ml b/src/res_grammar.ml index 44f0e497..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 | 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 @@ -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 | 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 diff --git a/src/res_parens.ml b/src/res_parens.ml index 33fb0f3d..c18b7565 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) @@ -169,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 @@ -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/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 8049d6a9..c22dfb23 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" | "res.async"}, _)] as attrs; } -> let arg = (attrs, lbl, typ1) in process attrsBefore (arg :: acc) typ2 @@ -55,6 +55,30 @@ 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; attributes = List.rev acc} + | ({Location.txt = "bs"}, _) :: rest -> process async true 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 + +let hasAwaitAttribute attrs = + List.exists + (function + | {Location.txt = "res.await"}, _ -> true + | _ -> false) + attrs + let collectListExpressions expr = let rec collect acc expr = match expr.pexp_desc with @@ -168,8 +192,9 @@ let filterParsingAttrs attrs = match attr with | ( { Location.txt = - ( "ns.ternary" | "ns.braces" | "res.template" | "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 @@ -316,7 +341,8 @@ let hasAttributes attrs = match attr with | ( { Location.txt = - "bs" | "res.template" | "ns.ternary" | "ns.braces" | "ns.iflet"; + ( "bs" | "ns.braces" | "ns.iflet" | "ns.ternary" | "res.async" + | "res.await" | "res.template" ); }, _ ) -> false @@ -497,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 diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index e492010b..f1f5fa32 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -17,6 +17,17 @@ 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 -> functionAttributesInfo + +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 79a77f8e..c1d947d4 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 @@ -3093,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 @@ -3102,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 @@ -3135,8 +3137,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 ParsetreeViewer.{async; uncurried; attributes = attrs} = + ParsetreeViewer.processFunctionAttributes attrsOnArrow in let returnExpr, typConstraint = match returnExpr.pexp_desc with @@ -3156,7 +3158,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 @@ -3278,6 +3280,28 @@ 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 + 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 = match e.pexp_desc with | Pexp_apply _ | Pexp_fun _ | Pexp_newtype _ | Pexp_setfield _ @@ -3289,17 +3313,16 @@ 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 - let uncurried, attrs = - ParsetreeViewer.processUncurriedAttribute attrsOnArrow + let ParsetreeViewer.{async; uncurried; attributes = attrs} = + ParsetreeViewer.processFunctionAttributes attrsOnArrow in let returnExpr, typConstraint = match returnExpr.pexp_desc with @@ -3313,7 +3336,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 @@ -3513,13 +3536,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 = @@ -3527,14 +3550,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 @@ -3553,22 +3576,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 @@ -3671,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 @@ -4575,8 +4597,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 +4611,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 addAsync any else any (* let f = a => () *) | [ ParsetreeViewer.Parameter @@ -4604,7 +4629,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 addAsync (Doc.concat [Doc.lparen; var; Doc.rparen]) else var in printComments txtDoc cmtTbl stringLoc.loc (* let f = () => () *) @@ -4619,7 +4645,10 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint }; ] when not uncurried -> - let doc = 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 -> @@ -4628,7 +4657,10 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint | FitsOnOneLine -> true | _ -> false in - let lparen = if uncurried then Doc.text "(. " else Doc.lparen in + let maybeAsyncLparen = + 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 = Doc.concat @@ -4644,7 +4676,7 @@ and printExprFunParameters ~customLayout ~inCallback ~uncurried ~hasConstraint Doc.group (Doc.concat [ - lparen; + maybeAsyncLparen; (if shouldHug || inCallback then printedParamaters else Doc.concat 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 diff --git a/tests/parsing/grammar/expressions/async.res b/tests/parsing/grammar/expressions/async.res new file mode 100644 index 00000000..b104866d --- /dev/null +++ b/tests/parsing/grammar/expressions/async.res @@ -0,0 +1,29 @@ +let greetUser = async (userId) => { + let name = await getUserName(. userId) + "Hello " ++ name ++ "!" +} + +async () => 123 + +let fetch = { + async (. url) => browserFetch(. url) +} + +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)) +} + +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/await.res b/tests/parsing/grammar/expressions/await.res new file mode 100644 index 00000000..32a6fdf9 --- /dev/null +++ b/tests/parsing/grammar/expressions/await.res @@ -0,0 +1,26 @@ +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) +} + +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 new file mode 100644 index 00000000..7aef8ffd --- /dev/null +++ b/tests/parsing/grammar/expressions/expected/async.res.txt @@ -0,0 +1,28 @@ +let greetUser = + ((fun userId -> + ((let name = ((getUserName userId)[@res.await ][@bs ]) in + ({js|Hello |js} ^ name) ^ {js|!|js}) + [@ns.braces ])) + [@res.async ]) +;;((fun () -> 123)[@res.async ]) +let fetch = ((fun url -> ((browserFetch url)[@bs ])) + [@ns.braces ][@res.async ][@bs ]) +let fetch2 = + (((((fun url -> ((browserFetch url)[@bs ]))) + [@res.async ][@bs ]); + (((fun url -> ((browserFetch2 url)[@bs ]))) + [@res.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 ]) +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 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..8f8d9f70 --- /dev/null +++ b/tests/parsing/grammar/expressions/expected/await.res.txt @@ -0,0 +1,18 @@ +;;((wait 2)[@res.await ]) +let maybeSomeValue = + match ((fetchData url)[@res.await ]) with + | data -> Some data + | exception JsError _ -> None +let x = ((1)[@res.await ]) + 2 +let x = ((wait 1)[@res.await ]) + ((wait 2)[@res.await ]) +let () = + ((let response = ((fetch {js|/users.json|js})[@res.await ]) in + let users = ((response.json ())[@res.await ]) in + let comments = + ((((fetch {js|comment.json|js})[@res.await ]).json ()) + [@res.await ]).(0) in + Js.log2 users comments) + [@ns.braces ]) +let () = ((delay 10)[@ns.braces ][@res.await ]) +let () = ((((delay 10)[@res.await ]); ((delay 20)[@res.await ])) + [@ns.braces ]) \ No newline at end of file diff --git a/tests/printer/expr/asyncAwait.res b/tests/printer/expr/asyncAwait.res new file mode 100644 index 00000000..4265a891 --- /dev/null +++ b/tests/printer/expr/asyncAwait.res @@ -0,0 +1,83 @@ +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 + + +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} + +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 new file mode 100644 index 00000000..7a8642c4 --- /dev/null +++ b/tests/printer/expr/expected/asyncAwait.res.txt @@ -0,0 +1,105 @@ +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 + +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} + +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) +}