From 1d72c1c1f77d979014d6dece3ba78c850068ad51 Mon Sep 17 00:00:00 2001 From: Marshall Roch Date: Wed, 4 Jan 2023 14:54:36 -0800 Subject: [PATCH] [parser] error on `for (async of [])` Summary: Fixes https://github.com/facebook/flow/issues/8651 Reviewed By: SamChou19815 Differential Revision: D42187354 fbshipit-source-id: 042aa651ce1f05aaac35d83730f76b55556ee323 --- src/parser/statement_parser.ml | 24 ++++++++++ .../flow/for_of_loops_invalid/for_async_of.js | 1 + .../for_async_of.tree.json | 39 +++++++++++++++ .../flow/statement/for/for_async_of_arrow.js | 1 + .../for/for_async_of_arrow.tree.json | 48 +++++++++++++++++++ .../flow/statement/for/for_async_of_parens.js | 1 + .../for/for_async_of_parens.tree.json | 35 ++++++++++++++ .../__tests__/js_layout_generator_test.ml | 6 +++ .../output/js_layout_generator.ml | 12 +++++ 9 files changed, 167 insertions(+) create mode 100644 src/parser/test/flow/for_of_loops_invalid/for_async_of.js create mode 100644 src/parser/test/flow/for_of_loops_invalid/for_async_of.tree.json create mode 100644 src/parser/test/flow/statement/for/for_async_of_arrow.js create mode 100644 src/parser/test/flow/statement/for/for_async_of_arrow.tree.json create mode 100644 src/parser/test/flow/statement/for/for_async_of_parens.js create mode 100644 src/parser/test/flow/statement/for/for_async_of_parens.tree.json diff --git a/src/parser/statement_parser.ml b/src/parser/statement_parser.ml index 6cee435cbaa..71d9f9b604d 100644 --- a/src/parser/statement_parser.ml +++ b/src/parser/statement_parser.ml @@ -368,6 +368,11 @@ module Statement let leading = leading @ Peek.comments env in Expect.token env T_LPAREN; let comments = Flow_ast_utils.mk_comments_opt ~leading () in + let init_starts_with_async = + match Peek.token env with + | T_ASYNC -> true + | _ -> false + in let (init, errs) = let env = env |> with_no_in true in match Peek.token env with @@ -429,6 +434,25 @@ module Statement | Some (For_expression expr) -> (* #sec-for-in-and-for-of-statements-static-semantics-early-errors *) let patt = Pattern_cover.as_pattern ~err:Parse_error.InvalidLHSInForOf env expr in + (match (init_starts_with_async, patt) with + | ( true, + ( _, + Pattern.Identifier + { + Pattern.Identifier.name = + (id_loc, { Identifier.name = "async"; comments = _ }); + annot = _; + optional = _; + } + ) + ) -> + (* #prod-nLtPS4oB - `for (async of ...)` is forbidden because it is + ambiguous whether it's a for-of with an `async` identifier, or a + regular for loop with an async arrow function with a param named + `of`. We can backtrack, so we know it's a for-of, but the spec + still disallows it. *) + error_at env (id_loc, Parse_error.InvalidLHSInForOf) + | _ -> ()); Statement.ForOf.LeftPattern patt | None -> assert false in diff --git a/src/parser/test/flow/for_of_loops_invalid/for_async_of.js b/src/parser/test/flow/for_of_loops_invalid/for_async_of.js new file mode 100644 index 00000000000..8327cdf036e --- /dev/null +++ b/src/parser/test/flow/for_of_loops_invalid/for_async_of.js @@ -0,0 +1 @@ +for (async of []); diff --git a/src/parser/test/flow/for_of_loops_invalid/for_async_of.tree.json b/src/parser/test/flow/for_of_loops_invalid/for_async_of.tree.json new file mode 100644 index 00000000000..62797b7150f --- /dev/null +++ b/src/parser/test/flow/for_of_loops_invalid/for_async_of.tree.json @@ -0,0 +1,39 @@ +{ + "errors":[ + { + "loc":{"source":null,"start":{"line":1,"column":5},"end":{"line":1,"column":10}}, + "message":"Invalid left-hand side in for-of" + } + ], + "type":"Program", + "loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":18}}, + "range":[0,18], + "body":[ + { + "type":"ForOfStatement", + "loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":18}}, + "range":[0,18], + "left":{ + "type":"Identifier", + "loc":{"source":null,"start":{"line":1,"column":5},"end":{"line":1,"column":10}}, + "range":[5,10], + "name":"async", + "typeAnnotation":null, + "optional":false + }, + "right":{ + "type":"ArrayExpression", + "loc":{"source":null,"start":{"line":1,"column":14},"end":{"line":1,"column":16}}, + "range":[14,16], + "elements":[] + }, + "body":{ + "type":"EmptyStatement", + "loc":{"source":null,"start":{"line":1,"column":17},"end":{"line":1,"column":18}}, + "range":[17,18] + }, + "await":false + } + ], + "comments":[] +} diff --git a/src/parser/test/flow/statement/for/for_async_of_arrow.js b/src/parser/test/flow/statement/for/for_async_of_arrow.js new file mode 100644 index 00000000000..24b0e2c995e --- /dev/null +++ b/src/parser/test/flow/statement/for/for_async_of_arrow.js @@ -0,0 +1 @@ +for (async of => {};;); diff --git a/src/parser/test/flow/statement/for/for_async_of_arrow.tree.json b/src/parser/test/flow/statement/for/for_async_of_arrow.tree.json new file mode 100644 index 00000000000..fedc57c6ea0 --- /dev/null +++ b/src/parser/test/flow/statement/for/for_async_of_arrow.tree.json @@ -0,0 +1,48 @@ +{ + "type":"Program", + "loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":23}}, + "range":[0,23], + "body":[ + { + "type":"ForStatement", + "loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":23}}, + "range":[0,23], + "init":{ + "type":"ArrowFunctionExpression", + "loc":{"source":null,"start":{"line":1,"column":5},"end":{"line":1,"column":19}}, + "range":[5,19], + "id":null, + "params":[ + { + "type":"Identifier", + "loc":{"source":null,"start":{"line":1,"column":11},"end":{"line":1,"column":13}}, + "range":[11,13], + "name":"of", + "typeAnnotation":null, + "optional":false + } + ], + "body":{ + "type":"BlockStatement", + "loc":{"source":null,"start":{"line":1,"column":17},"end":{"line":1,"column":19}}, + "range":[17,19], + "body":[] + }, + "async":true, + "generator":false, + "predicate":null, + "expression":false, + "returnType":null, + "typeParameters":null + }, + "test":null, + "update":null, + "body":{ + "type":"EmptyStatement", + "loc":{"source":null,"start":{"line":1,"column":22},"end":{"line":1,"column":23}}, + "range":[22,23] + } + } + ], + "comments":[] +} diff --git a/src/parser/test/flow/statement/for/for_async_of_parens.js b/src/parser/test/flow/statement/for/for_async_of_parens.js new file mode 100644 index 00000000000..c39edd188e6 --- /dev/null +++ b/src/parser/test/flow/statement/for/for_async_of_parens.js @@ -0,0 +1 @@ +for ((async) of y); diff --git a/src/parser/test/flow/statement/for/for_async_of_parens.tree.json b/src/parser/test/flow/statement/for/for_async_of_parens.tree.json new file mode 100644 index 00000000000..055ec98dfe9 --- /dev/null +++ b/src/parser/test/flow/statement/for/for_async_of_parens.tree.json @@ -0,0 +1,35 @@ +{ + "type":"Program", + "loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "range":[0,19], + "body":[ + { + "type":"ForOfStatement", + "loc":{"source":null,"start":{"line":1,"column":0},"end":{"line":1,"column":19}}, + "range":[0,19], + "left":{ + "type":"Identifier", + "loc":{"source":null,"start":{"line":1,"column":6},"end":{"line":1,"column":11}}, + "range":[6,11], + "name":"async", + "typeAnnotation":null, + "optional":false + }, + "right":{ + "type":"Identifier", + "loc":{"source":null,"start":{"line":1,"column":16},"end":{"line":1,"column":17}}, + "range":[16,17], + "name":"y", + "typeAnnotation":null, + "optional":false + }, + "body":{ + "type":"EmptyStatement", + "loc":{"source":null,"start":{"line":1,"column":18},"end":{"line":1,"column":19}}, + "range":[18,19] + }, + "await":false + } + ], + "comments":[] +} diff --git a/src/parser_utils/output/__tests__/js_layout_generator_test.ml b/src/parser_utils/output/__tests__/js_layout_generator_test.ml index 5c52d8e8852..7fbd083a1d9 100644 --- a/src/parser_utils/output/__tests__/js_layout_generator_test.ml +++ b/src/parser_utils/output/__tests__/js_layout_generator_test.ml @@ -1798,6 +1798,12 @@ let tests = assert_statement_string ~ctxt "for(let{x,y}of z);"; assert_statement_string ~ctxt ~pretty:true "for (let {x, y} of z);" ); + ( "forof_async" >:: fun ctxt -> + (* the parens are required *) + assert_statement_string ~ctxt "for((async)of y);"; + assert_statement_string ~ctxt ~pretty:true "for ((async) of y);"; + assert_statement_string ~ctxt ~pretty:true "for ((/* needs parens */ async) of y);" + ); ( "yield_expressions" >:: fun ctxt -> assert_expression_string ~ctxt "function* f(){yield}"; assert_expression_string ~ctxt "function* f(){yield a}"; diff --git a/src/parser_utils/output/js_layout_generator.ml b/src/parser_utils/output/js_layout_generator.ml index b6ec45e6aa5..6bcc45fec83 100644 --- a/src/parser_utils/output/js_layout_generator.ml +++ b/src/parser_utils/output/js_layout_generator.ml @@ -872,6 +872,18 @@ and statement ?(pretty_semicolon = false) ~opts (root_stmt : (Loc.t, Loc.t) Ast. begin match left with | S.ForOf.LeftDeclaration decl -> variable_declaration ~opts decl + | S.ForOf.LeftPattern + ( ( _, + Ast.Pattern.Identifier + { + Ast.Pattern.Identifier.name = + (_, { Ast.Identifier.name = "async"; _ }); + annot = Ast.Type.Missing _; + _; + } + ) as patt + ) -> + wrap_in_parens (pattern ~opts patt) | S.ForOf.LeftPattern patt -> pattern ~opts patt end; Atom "of";