From 7730a6ea5af2206764fa728b627bb63927b281c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Sun, 6 Jun 2021 22:16:59 +0900 Subject: [PATCH] fix(es/transforms): Fix bugs (#1795) swc_ecma_transforms_compat: - `async_to_generator`: Handle await in async generators correctly. (#1752) swc_ecma_transforms_module: - Don't panic on double import from one module. (#1757) --- .../tsc/generatedContextualTyping/1/input.ts | 3 + .../tsc/generatedContextualTyping/1/output.ts | 14 ++ ecmascript/transforms/compat/Cargo.toml | 2 +- .../compat/src/es2017/async_to_generator.rs | 35 +++- .../compat/tests/es2017_async_to_generator.rs | 28 +++ ecmascript/transforms/module/Cargo.toml | 2 +- ecmascript/transforms/module/src/util.rs | 8 + .../transforms/module/tests/common_js.rs | 17 ++ tests/fixture/issue-1752/case1/input/.swcrc | 8 + tests/fixture/issue-1752/case1/input/index.js | 20 ++ .../fixture/issue-1752/case1/output/index.js | 184 ++++++++++++++++++ tests/fixture/issue-1757/case1/input/.swcrc | 5 + tests/fixture/issue-1757/case1/input/index.js | 4 + .../fixture/issue-1757/case1/output/index.js | 3 + 14 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/input.ts create mode 100644 ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/output.ts create mode 100644 tests/fixture/issue-1752/case1/input/.swcrc create mode 100644 tests/fixture/issue-1752/case1/input/index.js create mode 100644 tests/fixture/issue-1752/case1/output/index.js create mode 100644 tests/fixture/issue-1757/case1/input/.swcrc create mode 100644 tests/fixture/issue-1757/case1/input/index.js create mode 100644 tests/fixture/issue-1757/case1/output/index.js diff --git a/ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/input.ts b/ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/input.ts new file mode 100644 index 000000000000..65e4eb68dc67 --- /dev/null +++ b/ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/input.ts @@ -0,0 +1,3 @@ +class x87 { constructor(parm: () => Base[] = function named() { return [d1, d2] }) { } } + +class x90 { constructor(parm: { (): Base[]; } = function named() { return [d1, d2] }) { } } diff --git a/ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/output.ts b/ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/output.ts new file mode 100644 index 000000000000..ffb4f1bb93e7 --- /dev/null +++ b/ecmascript/transforms/base/tests/ts-resolver/tsc/generatedContextualTyping/1/output.ts @@ -0,0 +1,14 @@ +class x87 { + constructor(parm__2: () => Base[] = function named__3() { + return [d1, d2]; + }){ + } +} +class x90 { + constructor(parm__4: { + () : Base[]; + } = function named__5() { + return [d1, d2]; + }){ + } +} diff --git a/ecmascript/transforms/compat/Cargo.toml b/ecmascript/transforms/compat/Cargo.toml index 5d0f114c66c2..4e66bdd0d972 100644 --- a/ecmascript/transforms/compat/Cargo.toml +++ b/ecmascript/transforms/compat/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_ecma_transforms_compat" repository = "https://github.com/swc-project/swc.git" -version = "0.17.9" +version = "0.17.10" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/ecmascript/transforms/compat/src/es2017/async_to_generator.rs b/ecmascript/transforms/compat/src/es2017/async_to_generator.rs index 0c3c0e36bad3..2497032d24c6 100644 --- a/ecmascript/transforms/compat/src/es2017/async_to_generator.rs +++ b/ecmascript/transforms/compat/src/es2017/async_to_generator.rs @@ -866,7 +866,9 @@ impl Actual { /// /// `_asyncToGenerator(function*() {})` from `async function() {}`; fn make_fn_ref(mut expr: FnExpr, should_not_bind_this: bool) -> Expr { - expr.function.body = expr.function.body.fold_with(&mut AsyncFnBodyHandler); + expr.function.body = expr.function.body.fold_with(&mut AsyncFnBodyHandler { + is_async_generator: expr.function.is_generator, + }); assert!(expr.function.is_async); expr.function.is_async = false; @@ -901,7 +903,9 @@ fn make_fn_ref(mut expr: FnExpr, should_not_bind_this: bool) -> Expr { }) } -struct AsyncFnBodyHandler; +struct AsyncFnBodyHandler { + is_async_generator: bool, +} macro_rules! noop { ($name:ident, $T:path) => { @@ -924,11 +928,28 @@ impl Fold for AsyncFnBodyHandler { let expr = expr.fold_children_with(self); match expr { - Expr::Await(AwaitExpr { span, arg }) => Expr::Yield(YieldExpr { - span, - delegate: false, - arg: Some(arg), - }), + Expr::Await(AwaitExpr { span, arg }) => { + if self.is_async_generator { + let callee = helper!(await_async_generator, "awaitAsyncGenerator"); + let arg = Box::new(Expr::Call(CallExpr { + span, + callee, + args: vec![arg.as_arg()], + type_args: Default::default(), + })); + Expr::Yield(YieldExpr { + span, + delegate: false, + arg: Some(arg), + }) + } else { + Expr::Yield(YieldExpr { + span, + delegate: false, + arg: Some(arg), + }) + } + } _ => expr, } } diff --git a/ecmascript/transforms/compat/tests/es2017_async_to_generator.rs b/ecmascript/transforms/compat/tests/es2017_async_to_generator.rs index 430ce2f28e75..13f2f971c9b6 100644 --- a/ecmascript/transforms/compat/tests/es2017_async_to_generator.rs +++ b/ecmascript/transforms/compat/tests/es2017_async_to_generator.rs @@ -2612,3 +2612,31 @@ test!( } " ); + +test_exec!( + syntax(), + |_| async_to_generator(), + issue_1752_1, + " + async function* generate() { + const results = await Promise.all([ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ]) + for (const result of results) { + console.log(`yield ${result}`) + yield result + } + } + + async function printValues() { + const iterator = generate() + for await (const value of iterator) { + console.log(`iterator value: ${value}`) + } + } + + printValues() + " +); diff --git a/ecmascript/transforms/module/Cargo.toml b/ecmascript/transforms/module/Cargo.toml index 60075bf2c1df..667f0e884efc 100644 --- a/ecmascript/transforms/module/Cargo.toml +++ b/ecmascript/transforms/module/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_ecma_transforms_module" repository = "https://github.com/swc-project/swc.git" -version = "0.17.1" +version = "0.17.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/ecmascript/transforms/module/src/util.rs b/ecmascript/transforms/module/src/util.rs index 3a48708e81ca..160274231556 100644 --- a/ecmascript/transforms/module/src/util.rs +++ b/ecmascript/transforms/module/src/util.rs @@ -318,6 +318,14 @@ impl Scope { } else { self.imports .entry(import.src.value.clone()) + .and_modify(|opt| { + if opt.is_none() { + *opt = Some(( + local_name_for_src(&import.src.value), + import.src.span.apply_mark(Mark::fresh(Mark::root())), + )); + } + }) .or_insert_with(|| { Some(( local_name_for_src(&import.src.value), diff --git a/ecmascript/transforms/module/tests/common_js.rs b/ecmascript/transforms/module/tests/common_js.rs index 600847fb6df1..b40feafbaad2 100644 --- a/ecmascript/transforms/module/tests/common_js.rs +++ b/ecmascript/transforms/module/tests/common_js.rs @@ -4765,3 +4765,20 @@ test!( }); " ); + +test!( + syntax(), + |_| tr(Default::default()), + issue_1757_1, + " + import 'testlibrary'; + import { aFunc } from 'testlibrary'; + + console.log('aFunc: ', aFunc(1,2)); + ", + " + 'use strict'; + var _testlibrary = require('testlibrary'); + console.log('aFunc: ', (0, _testlibrary).aFunc(1, 2)); + " +); diff --git a/tests/fixture/issue-1752/case1/input/.swcrc b/tests/fixture/issue-1752/case1/input/.swcrc new file mode 100644 index 000000000000..135ad181fe05 --- /dev/null +++ b/tests/fixture/issue-1752/case1/input/.swcrc @@ -0,0 +1,8 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript" + }, + "target": "es2017" + } +} diff --git a/tests/fixture/issue-1752/case1/input/index.js b/tests/fixture/issue-1752/case1/input/index.js new file mode 100644 index 000000000000..0eb076d41dca --- /dev/null +++ b/tests/fixture/issue-1752/case1/input/index.js @@ -0,0 +1,20 @@ +async function* generate() { + const results = await Promise.all([ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ]) + for (const result of results) { + console.log(`yield ${result}`) + yield result + } +} + +async function printValues() { + const iterator = generate() + for await (const value of iterator) { + console.log(`iterator value: ${value}`) + } +} + +printValues() \ No newline at end of file diff --git a/tests/fixture/issue-1752/case1/output/index.js b/tests/fixture/issue-1752/case1/output/index.js new file mode 100644 index 000000000000..e3664fb41dd2 --- /dev/null +++ b/tests/fixture/issue-1752/case1/output/index.js @@ -0,0 +1,184 @@ +function AsyncGenerator(gen) { + var front, back; + function send(key, arg) { + return new Promise(function(resolve, reject) { + var request = { + key: key, + arg: arg, + resolve: resolve, + reject: reject, + next: null + }; + if (back) { + back = back.next = request; + } else { + front = back = request; + resume(key, arg); + } + }); + } + function resume(key, arg) { + try { + var result = gen[key](arg); + var value = result.value; + var wrappedAwait = value instanceof _AwaitValue; + Promise.resolve(wrappedAwait ? value.wrapped : value).then(function(arg) { + if (wrappedAwait) { + resume("next", arg); + return; + } + settle(result.done ? "return" : "normal", arg); + }, function(err) { + resume("throw", err); + }); + } catch (err) { + settle("throw", err); + } + } + function settle(type, value) { + switch(type){ + case "return": + front.resolve({ + value: value, + done: true + }); + break; + case "throw": + front.reject(value); + break; + default: + front.resolve({ + value: value, + done: false + }); + break; + } + front = front.next; + if (front) { + resume(front.key, front.arg); + } else { + back = null; + } + } + this._invoke = send; + if (typeof gen.return !== "function") { + this.return = undefined; + } +} +if (typeof Symbol === "function" && Symbol.asyncIterator) { + AsyncGenerator.prototype[Symbol.asyncIterator] = function() { + return this; + }; +} +AsyncGenerator.prototype.next = function(arg) { + return this._invoke("next", arg); +}; +AsyncGenerator.prototype.throw = function(arg) { + return this._invoke("throw", arg); +}; +AsyncGenerator.prototype.return = function(arg) { + return this._invoke("return", arg); +}; +function _asyncIterator(iterable) { + var method; + if (typeof Symbol === "function") { + if (Symbol.asyncIterator) { + method = iterable[Symbol.asyncIterator]; + if (method != null) return method.call(iterable); + } + if (Symbol.iterator) { + method = iterable[Symbol.iterator]; + if (method != null) return method.call(iterable); + } + } + throw new TypeError("Object is not async iterable"); +} +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } + if (info.done) { + resolve(value); + } else { + Promise.resolve(value).then(_next, _throw); + } +} +function _asyncToGenerator(fn) { + return function() { + var self = this, args = arguments; + return new Promise(function(resolve, reject) { + var gen = fn.apply(self, args); + function _next(value) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); + } + function _throw(err) { + asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); + } + _next(undefined); + }); + }; +} +function _awaitAsyncGenerator(value) { + return new _AwaitValue(value); +} +function _AwaitValue(value) { + this.wrapped = value; +} +function _wrapAsyncGenerator(fn) { + return function() { + return new AsyncGenerator(fn.apply(this, arguments)); + }; +} +function _generate() { + _generate = _wrapAsyncGenerator(function*() { + const results = yield _awaitAsyncGenerator(Promise.all([ + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + ])); + for (const result of results){ + console.log(`yield ${result}`); + yield result; + } + }); + return _generate.apply(this, arguments); +} +function generate() { + return _generate.apply(this, arguments); +} +function _printValues() { + _printValues = _asyncToGenerator(function*() { + const iterator = generate(); + { + var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError; + try { + for(var _iterator = _asyncIterator(iterator), _step, _value; _step = yield _iterator.next(), _iteratorNormalCompletion = _step.done, _value = yield _step.value, !_iteratorNormalCompletion; _iteratorNormalCompletion = true){ + const value = _value; + console.log(`iterator value: ${value}`); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally{ + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + yield _iteratorError.return(); + } + } finally{ + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + }); + return _printValues.apply(this, arguments); +} +function printValues() { + return _printValues.apply(this, arguments); +} +printValues(); diff --git a/tests/fixture/issue-1757/case1/input/.swcrc b/tests/fixture/issue-1757/case1/input/.swcrc new file mode 100644 index 000000000000..536ca3af1e66 --- /dev/null +++ b/tests/fixture/issue-1757/case1/input/.swcrc @@ -0,0 +1,5 @@ +{ + "module": { + "type": "commonjs" + } +} diff --git a/tests/fixture/issue-1757/case1/input/index.js b/tests/fixture/issue-1757/case1/input/index.js new file mode 100644 index 000000000000..00321bda3db6 --- /dev/null +++ b/tests/fixture/issue-1757/case1/input/index.js @@ -0,0 +1,4 @@ +import "testlibrary"; +import { aFunc } from "testlibrary"; + +console.log("aFunc: ", aFunc(1, 2)); \ No newline at end of file diff --git a/tests/fixture/issue-1757/case1/output/index.js b/tests/fixture/issue-1757/case1/output/index.js new file mode 100644 index 000000000000..3bbe62b8e7f0 --- /dev/null +++ b/tests/fixture/issue-1757/case1/output/index.js @@ -0,0 +1,3 @@ +"use strict"; +var _testlibrary = require("testlibrary"); +console.log("aFunc: ", (0, _testlibrary).aFunc(1, 2));