From 42b6e18aaedb436691b1d3a860e589f9fb774d7b Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sat, 27 Aug 2022 01:44:38 +0800 Subject: [PATCH 1/7] fix(es/parser): Handle `async` in `for...of` --- .../parserForOfStatement23_es2015.1.normal.js | 45 +++++-- ...arserForOfStatement23_es2015.2.minified.js | 13 +-- .../parserForOfStatement23_es5.1.normal.js | 110 ++++++++++++++++-- .../parserForOfStatement23_es5.2.minified.js | 14 +-- crates/swc_ecma_parser/src/error.rs | 4 + crates/swc_ecma_parser/src/parser/stmt.rs | 20 +++- .../tests/errors/ts1106/input.js | 2 + .../tests/errors/ts1106/input.js.stderr | 6 + 8 files changed, 167 insertions(+), 47 deletions(-) create mode 100644 crates/swc_ecma_parser/tests/errors/ts1106/input.js create mode 100644 crates/swc_ecma_parser/tests/errors/ts1106/input.js.stderr diff --git a/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.1.normal.js b/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.1.normal.js index 27e4c5c34668..02b42b6c418e 100644 --- a/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.1.normal.js +++ b/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.1.normal.js @@ -1,11 +1,34 @@ -//! -//! x Expected '=>', got 'x' -//! ,---- -//! 5 | for await (async of x) {} -//! : ^ -//! `---- -//! -//! -//!Caused by: -//! 0: failed to process input file -//! 1: Syntax Error +// @target: esnext +import _async_iterator from "@swc/helpers/src/_async_iterator.mjs"; +import _async_to_generator from "@swc/helpers/src/_async_to_generator.mjs"; +function foo(x) { + return _foo.apply(this, arguments); +} +function _foo() { + _foo = _async_to_generator(function*(x) { + var async; + { + var _iteratorAbruptCompletion = false, _didIteratorError = false, _iteratorError; + try { + for(var _iterator = _async_iterator(x), _step; _iteratorAbruptCompletion = !(_step = yield _iterator.next()).done; _iteratorAbruptCompletion = false){ + let _value = _step.value; + async = _value; + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally{ + try { + if (_iteratorAbruptCompletion && _iterator.return != null) { + yield _iterator.return(); + } + } finally{ + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + }); + return _foo.apply(this, arguments); +} diff --git a/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.2.minified.js b/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.2.minified.js index 27e4c5c34668..d43a24b89e63 100644 --- a/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.2.minified.js +++ b/crates/swc/tests/tsc-references/parserForOfStatement23_es2015.2.minified.js @@ -1,11 +1,2 @@ -//! -//! x Expected '=>', got 'x' -//! ,---- -//! 5 | for await (async of x) {} -//! : ^ -//! `---- -//! -//! -//!Caused by: -//! 0: failed to process input file -//! 1: Syntax Error +import _async_iterator from "@swc/helpers/src/_async_iterator.mjs"; +import _async_to_generator from "@swc/helpers/src/_async_to_generator.mjs"; diff --git a/crates/swc/tests/tsc-references/parserForOfStatement23_es5.1.normal.js b/crates/swc/tests/tsc-references/parserForOfStatement23_es5.1.normal.js index 27e4c5c34668..da31f476f496 100644 --- a/crates/swc/tests/tsc-references/parserForOfStatement23_es5.1.normal.js +++ b/crates/swc/tests/tsc-references/parserForOfStatement23_es5.1.normal.js @@ -1,11 +1,99 @@ -//! -//! x Expected '=>', got 'x' -//! ,---- -//! 5 | for await (async of x) {} -//! : ^ -//! `---- -//! -//! -//!Caused by: -//! 0: failed to process input file -//! 1: Syntax Error +// @target: esnext +import _async_iterator from "@swc/helpers/src/_async_iterator.mjs"; +import _async_to_generator from "@swc/helpers/src/_async_to_generator.mjs"; +import _ts_generator from "@swc/helpers/src/_ts_generator.mjs"; +function foo(x) { + return _foo.apply(this, arguments); +} +function _foo() { + _foo = _async_to_generator(function(x) { + var async, _iteratorAbruptCompletion, _didIteratorError, _iteratorError, _iterator, _step, _value, err; + return _ts_generator(this, function(_state) { + switch(_state.label){ + case 0: + _iteratorAbruptCompletion = false, _didIteratorError = false; + _state.label = 1; + case 1: + _state.trys.push([ + 1, + 6, + 7, + 12 + ]); + _iterator = _async_iterator(x); + _state.label = 2; + case 2: + return [ + 4, + _iterator.next() + ]; + case 3: + if (!(_iteratorAbruptCompletion = !(_step = _state.sent()).done)) return [ + 3, + 5 + ]; + _value = _step.value; + async = _value; + _state.label = 4; + case 4: + _iteratorAbruptCompletion = false; + return [ + 3, + 2 + ]; + case 5: + return [ + 3, + 12 + ]; + case 6: + err = _state.sent(); + _didIteratorError = true; + _iteratorError = err; + return [ + 3, + 12 + ]; + case 7: + _state.trys.push([ + 7, + , + 10, + 11 + ]); + if (!(_iteratorAbruptCompletion && _iterator.return != null)) return [ + 3, + 9 + ]; + return [ + 4, + _iterator.return() + ]; + case 8: + _state.sent(); + _state.label = 9; + case 9: + return [ + 3, + 11 + ]; + case 10: + if (_didIteratorError) { + throw _iteratorError; + } + return [ + 7 + ]; + case 11: + return [ + 7 + ]; + case 12: + return [ + 2 + ]; + } + }); + }); + return _foo.apply(this, arguments); +} diff --git a/crates/swc/tests/tsc-references/parserForOfStatement23_es5.2.minified.js b/crates/swc/tests/tsc-references/parserForOfStatement23_es5.2.minified.js index 27e4c5c34668..37d964a0b017 100644 --- a/crates/swc/tests/tsc-references/parserForOfStatement23_es5.2.minified.js +++ b/crates/swc/tests/tsc-references/parserForOfStatement23_es5.2.minified.js @@ -1,11 +1,3 @@ -//! -//! x Expected '=>', got 'x' -//! ,---- -//! 5 | for await (async of x) {} -//! : ^ -//! `---- -//! -//! -//!Caused by: -//! 0: failed to process input file -//! 1: Syntax Error +import _async_iterator from "@swc/helpers/src/_async_iterator.mjs"; +import _async_to_generator from "@swc/helpers/src/_async_to_generator.mjs"; +import _ts_generator from "@swc/helpers/src/_ts_generator.mjs"; diff --git a/crates/swc_ecma_parser/src/error.rs b/crates/swc_ecma_parser/src/error.rs index 01314c531940..05e71d418f7e 100644 --- a/crates/swc_ecma_parser/src/error.rs +++ b/crates/swc_ecma_parser/src/error.rs @@ -224,6 +224,7 @@ pub enum SyntaxError { TS1100, TS1102, TS1105, + TS1106, TS1107, TS1109, TS1110, @@ -586,6 +587,9 @@ impl SyntaxError { SyntaxError::TS1105 => "A 'break' statement can only be used within an enclosing \ iteration or switch statement" .into(), + SyntaxError::TS1106 => { + "The left-hand side of a `for...of` statement may not be `async`".into() + } SyntaxError::TS1107 => "Jump target cannot cross function boundary".into(), SyntaxError::TS1109 => "Expression expected".into(), SyntaxError::TS1114 => "Duplicate label".into(), diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index b4a35bfcaf93..2bc98a9db11a 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -1079,7 +1079,7 @@ impl<'a, I: Tokens> Parser { None }; expect!(self, '('); - let head = self.parse_for_head()?; + let head = self.parse_for_head(await_token.is_some())?; expect!(self, ')'); let ctx = Context { is_break_allowed: true, @@ -1125,7 +1125,7 @@ impl<'a, I: Tokens> Parser { }) } - fn parse_for_head(&mut self) -> PResult { + fn parse_for_head(&mut self, is_for_await: bool) -> PResult { let strict = self.ctx().strict; if is_one_of!(self, "const", "var") @@ -1171,12 +1171,26 @@ impl<'a, I: Tokens> Parser { return self.parse_normal_for_head(Some(VarDeclOrExpr::VarDecl(decl))); } + let starts_with_async_of = is!(self, "async") && peeked_is!(self, "of"); + let init = if eat_exact!(self, ';') { return self.parse_normal_for_head(None); + } else if starts_with_async_of { + let async_start = cur_pos!(self); + expect!(self, "async"); + + Box::new(Expr::Ident(Ident::new( + js_word!("async"), + span!(self, async_start), + ))) } else { self.include_in_expr(false).parse_expr_or_pat()? }; + if starts_with_async_of && !is_for_await { + self.emit_err(self.input.prev_span(), SyntaxError::TS1106); + } + // for (a of b) if is_one_of!(self, "of", "in") { let is_in = is!(self, "in"); @@ -1939,7 +1953,7 @@ export default function waitUntil(callback, options = {}) { assert_eq!(leading.borrow().len(), 1); } fn parse_for_head(str: &'static str) -> ForHead { - test_parser(str, Syntax::default(), |p| p.parse_for_head()) + test_parser(str, Syntax::default(), |p| p.parse_for_head(false)) } #[test] diff --git a/crates/swc_ecma_parser/tests/errors/ts1106/input.js b/crates/swc_ecma_parser/tests/errors/ts1106/input.js new file mode 100644 index 000000000000..4d0afc3dd0fc --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/ts1106/input.js @@ -0,0 +1,2 @@ +let async; +for (async of [1]) ; \ No newline at end of file diff --git a/crates/swc_ecma_parser/tests/errors/ts1106/input.js.stderr b/crates/swc_ecma_parser/tests/errors/ts1106/input.js.stderr new file mode 100644 index 000000000000..71c17b0e746b --- /dev/null +++ b/crates/swc_ecma_parser/tests/errors/ts1106/input.js.stderr @@ -0,0 +1,6 @@ + + x The left-hand side of a `for...of` statement may not be `async` + ,-[$DIR/tests/errors/ts1106/input.js:2:1] + 2 | for (async of [1]) ; + : ^^^^^ + `---- From fc75023e9dfdcd6ba648d234a2551a66a6d65d87 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sat, 27 Aug 2022 01:50:56 +0800 Subject: [PATCH 2/7] chore: add comments --- crates/swc_ecma_parser/src/parser/stmt.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index 2bc98a9db11a..860a18689202 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -1187,6 +1187,10 @@ impl<'a, I: Tokens> Parser { self.include_in_expr(false).parse_expr_or_pat()? }; + // ```spec + // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] + // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] + // ``` if starts_with_async_of && !is_for_await { self.emit_err(self.input.prev_span(), SyntaxError::TS1106); } From 99a12756443991489ec2883a175bdac9ad8b42e1 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sat, 27 Aug 2022 01:52:56 +0800 Subject: [PATCH 3/7] chore: update comments --- crates/swc_ecma_parser/src/parser/stmt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index 860a18689202..96009a8f6400 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -1187,7 +1187,7 @@ impl<'a, I: Tokens> Parser { self.include_in_expr(false).parse_expr_or_pat()? }; - // ```spec + // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] // ``` From bd17efb70b567bca1c614749c2dc1d34193ea688 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sat, 27 Aug 2022 10:40:27 +0800 Subject: [PATCH 4/7] chore: update --- crates/swc_ecma_parser/src/parser/stmt.rs | 26 ++++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index 96009a8f6400..e54f62fc5f40 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -1171,30 +1171,26 @@ impl<'a, I: Tokens> Parser { return self.parse_normal_for_head(Some(VarDeclOrExpr::VarDecl(decl))); } - let starts_with_async_of = is!(self, "async") && peeked_is!(self, "of"); - let init = if eat_exact!(self, ';') { return self.parse_normal_for_head(None); - } else if starts_with_async_of { + } else if is!(self, "async") && peeked_is!(self, "of") { let async_start = cur_pos!(self); expect!(self, "async"); + let span = span!(self, async_start); + + // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement + // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] + // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] + // ``` + if !is_for_await { + self.emit_err(span, SyntaxError::TS1106); + } - Box::new(Expr::Ident(Ident::new( - js_word!("async"), - span!(self, async_start), - ))) + Box::new(Expr::Ident(Ident::new(js_word!("async"), span))) } else { self.include_in_expr(false).parse_expr_or_pat()? }; - // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement - // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // ``` - if starts_with_async_of && !is_for_await { - self.emit_err(self.input.prev_span(), SyntaxError::TS1106); - } - // for (a of b) if is_one_of!(self, "of", "in") { let is_in = is!(self, "in"); From ee2aa53d6a2cd78586d7fd08778f3d165ea96d6e Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sat, 27 Aug 2022 15:13:10 +0800 Subject: [PATCH 5/7] fix: `for (async of =>` --- crates/swc_ecma_parser/src/lib.rs | 11 ++++++++ crates/swc_ecma_parser/src/parser/expr.rs | 18 +++++++++++++ crates/swc_ecma_parser/src/parser/stmt.rs | 31 ++++++++++------------- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/crates/swc_ecma_parser/src/lib.rs b/crates/swc_ecma_parser/src/lib.rs index e342462e2d0b..182cedd0f4bf 100644 --- a/crates/swc_ecma_parser/src/lib.rs +++ b/crates/swc_ecma_parser/src/lib.rs @@ -341,6 +341,9 @@ pub struct Context { module: bool, can_be_module: bool, strict: bool, + + expr_ctx: ExpressionContext, + include_in_expr: bool, /// If true, await expression is parsed, and "await" is treated as a /// keyword. @@ -389,6 +392,14 @@ pub struct Context { disallow_conditional_types: bool, } +#[derive(Debug, Clone, Copy, Default)] +struct ExpressionContext { + // TODO: + // - include_in + for_loop_init: bool, + for_await_loop_init: bool, +} + #[cfg(test)] fn with_test_sess(src: &str, f: F) -> Result where diff --git a/crates/swc_ecma_parser/src/parser/expr.rs b/crates/swc_ecma_parser/src/parser/expr.rs index 282dfe35eb33..c763f172e87c 100644 --- a/crates/swc_ecma_parser/src/parser/expr.rs +++ b/crates/swc_ecma_parser/src/parser/expr.rs @@ -390,6 +390,24 @@ impl Parser { && !self.input.had_line_break_before_cur() && is!(self, BindingIdent) { + // ```js + // for(async of + // for(async of x); + // for(async of =>{};;); + // ``` + if ctx.expr_ctx.for_loop_init && is!(self, "of") && !peeked_is!(self, "=>") { + // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement + // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] + // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] + // ``` + + if !ctx.expr_ctx.for_await_loop_init { + self.emit_err(self.input.prev_span(), SyntaxError::TS1106); + } + + return Ok(Box::new(Expr::Ident(id))); + } + let ident = self.parse_binding_ident()?; if self.input.syntax().typescript() && ident.id.sym == js_word!("as") diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index e54f62fc5f40..f17f419bbcf8 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -1079,7 +1079,12 @@ impl<'a, I: Tokens> Parser { None }; expect!(self, '('); - let head = self.parse_for_head(await_token.is_some())?; + + let mut ctx = self.ctx(); + ctx.expr_ctx.for_loop_init = true; + ctx.expr_ctx.for_await_loop_init = await_token.is_some(); + + let head = self.with_ctx(ctx).parse_for_head()?; expect!(self, ')'); let ctx = Context { is_break_allowed: true, @@ -1125,7 +1130,7 @@ impl<'a, I: Tokens> Parser { }) } - fn parse_for_head(&mut self, is_for_await: bool) -> PResult { + fn parse_for_head(&mut self) -> PResult { let strict = self.ctx().strict; if is_one_of!(self, "const", "var") @@ -1173,20 +1178,6 @@ impl<'a, I: Tokens> Parser { let init = if eat_exact!(self, ';') { return self.parse_normal_for_head(None); - } else if is!(self, "async") && peeked_is!(self, "of") { - let async_start = cur_pos!(self); - expect!(self, "async"); - let span = span!(self, async_start); - - // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement - // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // ``` - if !is_for_await { - self.emit_err(span, SyntaxError::TS1106); - } - - Box::new(Expr::Ident(Ident::new(js_word!("async"), span))) } else { self.include_in_expr(false).parse_expr_or_pat()? }; @@ -1953,7 +1944,7 @@ export default function waitUntil(callback, options = {}) { assert_eq!(leading.borrow().len(), 1); } fn parse_for_head(str: &'static str) -> ForHead { - test_parser(str, Syntax::default(), |p| p.parse_for_head(false)) + test_parser(str, Syntax::default(), |p| p.parse_for_head()) } #[test] @@ -2081,6 +2072,12 @@ export default function waitUntil(callback, options = {}) { ); } + #[test] + fn for_async_of_eqgt() { + let src = "for (async of => {};;);"; + test_parser(src, Syntax::Es(Default::default()), |p| p.parse_module()); + } + #[test] #[should_panic(expected = "await isn't allowed in non-async function")] fn await_in_function_in_module() { From d25237eedb4601c4874a0ae1d50c938cdd8a6073 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sat, 27 Aug 2022 15:29:03 +0800 Subject: [PATCH 6/7] chore: update test case --- crates/swc_ecma_parser/src/parser/stmt.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index f17f419bbcf8..ce5d33037a15 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -2073,8 +2073,14 @@ export default function waitUntil(callback, options = {}) { } #[test] - fn for_async_of_eqgt() { - let src = "for (async of => {};;);"; + fn for_of_head_lhs_async_dot() { + let src = "for (async.x of [1]) ;"; + test_parser(src, Syntax::Es(Default::default()), |p| p.parse_module()); + } + + #[test] + fn for_head_init_async_of() { + let src = "for (async of => {}; i < 10; ++i) { ++counter; }"; test_parser(src, Syntax::Es(Default::default()), |p| p.parse_module()); } From 6fde49b3bf2400e7f9cedbb81b4a3a40fd5d248d Mon Sep 17 00:00:00 2001 From: magic-akari Date: Sat, 27 Aug 2022 15:55:49 +0800 Subject: [PATCH 7/7] chore: update comment --- crates/swc_ecma_parser/src/parser/expr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/swc_ecma_parser/src/parser/expr.rs b/crates/swc_ecma_parser/src/parser/expr.rs index c763f172e87c..6ef287c248f5 100644 --- a/crates/swc_ecma_parser/src/parser/expr.rs +++ b/crates/swc_ecma_parser/src/parser/expr.rs @@ -390,6 +390,7 @@ impl Parser { && !self.input.had_line_break_before_cur() && is!(self, BindingIdent) { + // see https://github.com/tc39/ecma262/issues/2034 // ```js // for(async of // for(async of x);