From: Alex Lam S.L Date: Sun, 10 Jan 2021 03:34:26 +0000 (+0000) Subject: support asynchronous arrow functions (#4530) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=ba54d074d8f434148a4f9ab5f59fcb70ff08dda9;p=UglifyJS.git support asynchronous arrow functions (#4530) --- diff --git a/lib/ast.js b/lib/ast.js index f0e5fefb..ab2559c2 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -567,8 +567,20 @@ var AST_Accessor = DEFNODE("Accessor", null, { }, }, AST_Lambda); +function is_arrow(node) { + return node instanceof AST_AsyncArrow || node instanceof AST_Arrow; +} + function is_function(node) { - return node instanceof AST_Arrow || node instanceof AST_AsyncFunction || node instanceof AST_Function; + return is_arrow(node) || node instanceof AST_AsyncFunction || node instanceof AST_Function; +} + +function walk_lambda(node, tw) { + if (is_arrow(node) && node.value) { + node.value.walk(tw); + } else { + walk_body(node, tw); + } } var AST_Arrow = DEFNODE("Arrow", "inlined value", { @@ -601,9 +613,38 @@ var AST_Arrow = DEFNODE("Arrow", "inlined value", { }, AST_Lambda); function is_async(node) { - return node instanceof AST_AsyncDefun || node instanceof AST_AsyncFunction; + return node instanceof AST_AsyncArrow || node instanceof AST_AsyncDefun || node instanceof AST_AsyncFunction; } +var AST_AsyncArrow = DEFNODE("AsyncArrow", "inlined value", { + $documentation: "An asynchronous arrow function expression", + $propdoc: { + value: "[AST_Node?] simple return expression, or null if using function body.", + }, + walk: function(visitor) { + var node = this; + visitor.visit(node, function() { + node.argnames.forEach(function(argname) { + argname.walk(visitor); + }); + if (node.rest) node.rest.walk(visitor); + if (node.value) { + node.value.walk(visitor); + } else { + walk_body(node, visitor); + } + }); + }, + _validate: function() { + if (this.name != null) throw new Error("name must be null"); + if (this.uses_arguments) throw new Error("uses_arguments must be false"); + if (this.value != null) { + must_be_expression(this, "value"); + if (this.body.length) throw new Error("body must be empty if value exists"); + } + }, +}, AST_Lambda); + var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined name", { $documentation: "An asynchronous function expression", $propdoc: { diff --git a/lib/compress.js b/lib/compress.js index 8aef5a71..f8cf75f1 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -749,11 +749,7 @@ merge(Compressor.prototype, { } }); }); - if (fn instanceof AST_Arrow && fn.value) { - fn.value.walk(tw); - } else { - walk_body(fn, tw); - } + walk_lambda(fn, tw); var safe_ids = tw.safe_ids; pop(tw); walk_defuns(tw, fn); @@ -1893,7 +1889,7 @@ merge(Compressor.prototype, { return !(argname instanceof AST_Destructured); })) { abort = true; - } else if (fn instanceof AST_Arrow && fn.value) { + } else if (is_arrow(fn) && fn.value) { fn.value.transform(scanner); } else for (var i = 0; !abort && i < fn.body.length; i++) { var stat = fn.body[i]; @@ -3765,12 +3761,15 @@ merge(Compressor.prototype, { if (!(stat instanceof AST_Directive)) return stat; } } - AST_Arrow.DEFMETHOD("first_statement", function() { + + function arrow_first_statement() { if (this.value) return make_node(AST_Return, this.value, { value: this.value }); return skip_directives(this.body); - }); + } + AST_Arrow.DEFMETHOD("first_statement", arrow_first_statement); + AST_AsyncArrow.DEFMETHOD("first_statement", arrow_first_statement); AST_Lambda.DEFMETHOD("first_statement", function() { return skip_directives(this.body); }); @@ -4384,6 +4383,9 @@ merge(Compressor.prototype, { def(AST_Arrow, function() { return basic_negation(this); }); + def(AST_AsyncArrow, function() { + return basic_negation(this); + }); def(AST_AsyncFunction, function() { return basic_negation(this); }); @@ -4783,7 +4785,7 @@ merge(Compressor.prototype, { return true; } if (node instanceof AST_This) { - if (scopes.length == 0 && self instanceof AST_Arrow) result = false; + if (scopes.length == 0 && is_arrow(self)) result = false; return true; } })); @@ -4869,7 +4871,7 @@ merge(Compressor.prototype, { return trim_block(self); }); - OPT(AST_Arrow, function(self, compressor) { + function opt_arrow(self, compressor) { if (!compressor.option("arrows")) return self; var body = tighten_body(self.value ? [ self.first_statement() ] : self.body, compressor); switch (body.length) { @@ -4886,7 +4888,9 @@ merge(Compressor.prototype, { break; } return self; - }); + } + OPT(AST_Arrow, opt_arrow); + OPT(AST_AsyncArrow, opt_arrow); OPT(AST_Function, function(self, compressor) { self.body = tighten_body(self.body, compressor); @@ -5116,11 +5120,7 @@ merge(Compressor.prototype, { }); if (node.rest) node.rest.mark_symbol(marker, scanner); } - if (node instanceof AST_Arrow && node.value) { - node.value.walk(tw); - } else { - walk_body(node, tw); - } + walk_lambda(node, tw); pop(); return true; } @@ -6735,6 +6735,7 @@ merge(Compressor.prototype, { } return this; }); + def(AST_AsyncArrow, return_null); def(AST_AsyncFunction, return_null); def(AST_Await, function(compressor) { if (!compressor.option("awaits")) return this; @@ -8226,7 +8227,7 @@ merge(Compressor.prototype, { && can_drop && all(fn.body, is_empty) && (fn !== exp || fn_name_unused(fn, compressor)) - && !(fn instanceof AST_Arrow && fn.value)) { + && !(is_arrow(fn) && fn.value)) { return make_sequence(self, convert_args()).optimize(compressor); } } @@ -8334,7 +8335,7 @@ merge(Compressor.prototype, { if (!safe) return true; if (node instanceof AST_Scope) { if (node === fn) return; - if (node instanceof AST_Arrow) { + if (is_arrow(node)) { for (var i = 0; safe && i < node.argnames.length; i++) node.argnames[i].walk(tw); } else if (is_defun(node) && node.name.name == "await") { safe = false; @@ -10562,7 +10563,7 @@ merge(Compressor.prototype, { while (p = compressor.parent(i++)) { if (p instanceof AST_Lambda) { if (p instanceof AST_Accessor) return; - if (p instanceof AST_Arrow) continue; + if (is_arrow(p)) continue; fn_parent = compressor.parent(i); return p; } @@ -10571,13 +10572,14 @@ merge(Compressor.prototype, { }); AST_Arrow.DEFMETHOD("contains_this", return_false); + AST_AsyncArrow.DEFMETHOD("contains_this", return_false); AST_Scope.DEFMETHOD("contains_this", function() { var result; var self = this; self.walk(new TreeWalker(function(node) { if (result) return true; if (node instanceof AST_This) return result = true; - if (node !== self && node instanceof AST_Scope && !(node instanceof AST_Arrow)) return true; + if (node !== self && node instanceof AST_Scope && !is_arrow(node)) return true; })); return result; }); diff --git a/lib/output.js b/lib/output.js index 9e704e8c..ae068462 100644 --- a/lib/output.js +++ b/lib/output.js @@ -692,7 +692,7 @@ function OutputStream(options) { // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ] return p instanceof AST_Array // () => (foo, bar) - || p instanceof AST_Arrow && p.value === this + || is_arrow(p) && p.value === this // await (foo, bar) || p instanceof AST_Await // 1 + (2, 3) + 4 ==> 8 @@ -813,6 +813,9 @@ function OutputStream(options) { // ({ p: a } = o); if (this.left instanceof AST_DestructuredObject) return needs_parens_obj(output); }); + PARENS(AST_AsyncArrow, function(output) { + return needs_parens_assign_cond(this, output); + }); PARENS(AST_Conditional, function(output) { return needs_parens_assign_cond(this, output); }); @@ -1005,8 +1008,7 @@ function OutputStream(options) { } }); } - DEFPRINT(AST_Arrow, function(output) { - var self = this; + function print_arrow(self, output) { if (self.argnames.length == 1 && self.argnames[0] instanceof AST_SymbolFunarg && !self.rest) { self.argnames[0].print(output); } else { @@ -1020,6 +1022,14 @@ function OutputStream(options) { } else { print_braced(self, output, true); } + } + DEFPRINT(AST_Arrow, function(output) { + print_arrow(this, output); + }); + DEFPRINT(AST_AsyncArrow, function(output) { + output.print("async"); + output.space(); + print_arrow(this, output); }); function print_lambda(self, output) { if (self.name) { @@ -1207,7 +1217,7 @@ function OutputStream(options) { if (noin) node.walk(new TreeWalker(function(node) { if (parens) return true; if (node instanceof AST_Binary && node.operator == "in") return parens = true; - if (node instanceof AST_Scope && !(node instanceof AST_Arrow && node.value)) return true; + if (node instanceof AST_Scope && !(is_arrow(node) && node.value)) return true; })); node.print(output, parens); } diff --git a/lib/parse.js b/lib/parse.js index e44c2090..4fd8a019 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -796,13 +796,14 @@ function parse($TEXT, options) { next(); return function_(AST_AsyncDefun); } + break; case "await": if (S.in_async) return simple_statement(); - default: - return is_token(peek(), "punc", ":") - ? labeled_statement() - : simple_statement(); + break; } + return is_token(peek(), "punc", ":") + ? labeled_statement() + : simple_statement(); case "punc": switch (S.token.value) { @@ -1094,16 +1095,19 @@ function parse($TEXT, options) { end: node.end, }); } + if (node instanceof AST_SymbolFunarg) return node; if (node instanceof AST_SymbolRef) return new AST_SymbolFunarg(node); token_error(node.start, "Invalid arrow parameter"); } - function arrow(exprs, start) { + function arrow(exprs, start, async) { var was_async = S.in_async; - S.in_async = false; + S.in_async = async; var was_funarg = S.in_funarg; S.in_funarg = S.in_function; var argnames = exprs.map(to_funarg); + var rest = exprs.rest || null; + if (rest) rest = to_funarg(rest); S.in_funarg = was_funarg; expect("=>"); var body, value; @@ -1129,10 +1133,10 @@ function parse($TEXT, options) { S.in_loop = loop; S.labels = labels; S.in_async = was_async; - return new AST_Arrow({ + return new (async ? AST_AsyncArrow : AST_Arrow)({ start: start, argnames: argnames, - rest: exprs.rest || null, + rest: rest, body: body, value: value, end: prev(), @@ -1431,16 +1435,9 @@ function parse($TEXT, options) { } unexpected(); } - var ctor; - if (is("name", "async") && is_token(peek(), "keyword", "function")) { + if (is("keyword", "function")) { next(); - ctor = AST_AsyncFunction; - } else if (is("keyword", "function")) { - ctor = AST_Function; - } - if (ctor) { - next(); - var func = function_(ctor); + var func = function_(AST_Function); func.start = start; func.end = prev(); return subscripts(func, allow_calls); @@ -1448,6 +1445,30 @@ function parse($TEXT, options) { if (is("name")) { var sym = _make_symbol(AST_SymbolRef, start); next(); + if (sym.name == "async") { + if (is("keyword", "function")) { + next(); + var func = function_(AST_AsyncFunction); + func.start = start; + func.end = prev(); + return subscripts(func, allow_calls); + } + if (is("name")) { + start = S.token; + sym = _make_symbol(AST_SymbolRef, start); + next(); + return arrow([ sym ], start, true); + } + if (is("punc", "(")) { + var call = subscripts(sym, allow_calls); + if (!is("punc", "=>")) return call; + var args = call.args; + if (args[args.length - 1] instanceof AST_Spread) { + args.rest = args.pop().expression; + } + return arrow(args, start, true); + } + } return is("punc", "=>") ? arrow([ sym ], start) : subscripts(sym, allow_calls); } if (ATOMIC_START_TOKEN[S.token.type]) { diff --git a/lib/scope.js b/lib/scope.js index f4a18ca7..689100cf 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -223,11 +223,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { }); if (node.rest) node.rest.walk(tw); in_arg.pop(); - if (node instanceof AST_Arrow && node.value) { - node.value.walk(tw); - } else { - walk_body(node, tw); - } + walk_lambda(node, tw); return true; } if (node instanceof AST_LoopControl) { @@ -328,7 +324,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { function is_arguments(sym) { return sym.orig[0] instanceof AST_SymbolFunarg && !(sym.orig[1] instanceof AST_SymbolFunarg || sym.orig[2] instanceof AST_SymbolFunarg) - && !(sym.scope instanceof AST_Arrow); + && !is_arrow(sym.scope); } function redefine(node, scope) { @@ -395,6 +391,9 @@ AST_Scope.DEFMETHOD("init_vars", function(parent_scope) { AST_Arrow.DEFMETHOD("init_vars", function(parent_scope) { init_scope_vars(this, parent_scope); }); +AST_AsyncArrow.DEFMETHOD("init_vars", function(parent_scope) { + init_scope_vars(this, parent_scope); +}); AST_Lambda.DEFMETHOD("init_vars", function(parent_scope) { init_scope_vars(this, parent_scope); this.uses_arguments = false; diff --git a/lib/transform.js b/lib/transform.js index 9b3eba86..f011cba9 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -136,7 +136,7 @@ TreeTransformer.prototype = new TreeWalker; if (self.rest) self.rest = self.rest.transform(tw); self.body = do_list(self.body, tw); }); - DEF(AST_Arrow, function(self, tw) { + function transform_arrow(self, tw) { self.argnames = do_list(self.argnames, tw); if (self.rest) self.rest = self.rest.transform(tw); if (self.value) { @@ -144,7 +144,9 @@ TreeTransformer.prototype = new TreeWalker; } else { self.body = do_list(self.body, tw); } - }); + } + DEF(AST_Arrow, transform_arrow); + DEF(AST_AsyncArrow, transform_arrow); DEF(AST_Call, function(self, tw) { self.expression = self.expression.transform(tw); self.args = do_list(self.args, tw); diff --git a/lib/utils.js b/lib/utils.js index 286266a2..c3b67a6f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -241,7 +241,7 @@ function HOP(obj, prop) { function first_in_statement(stack, arrow) { var node = stack.parent(-1); for (var i = 0, p; p = stack.parent(i++); node = p) { - if (p instanceof AST_Arrow) { + if (is_arrow(p)) { return arrow && p.value === node; } else if (p instanceof AST_Binary) { if (p.left === node) continue; diff --git a/test/compress/awaits.js b/test/compress/awaits.js index 7ae753fb..8caa60c0 100644 --- a/test/compress/awaits.js +++ b/test/compress/awaits.js @@ -1,3 +1,27 @@ +async_arrow: { + input: { + (async a => console.log(a))("PASS"); + console.log(typeof (async () => 42)()); + } + expect_exact: '(async a=>console.log(a))("PASS");console.log(typeof(async()=>42)());' + expect_stdout: [ + "PASS", + "object", + ] + node_version: ">=8" +} + +async_label: { + input: { + (async function() { + async: console.log("PASS"); + })(); + } + expect_exact: '(async function(){async:console.log("PASS")})();' + expect_stdout: "PASS" + node_version: ">=8" +} + await_await: { input: { (async function() { diff --git a/test/reduce.js b/test/reduce.js index cd00d5e9..3f24b7b4 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -667,7 +667,10 @@ function is_timed_out(result) { function is_statement(node) { return node instanceof U.AST_Statement - && !(node instanceof U.AST_Arrow || node instanceof U.AST_AsyncFunction || node instanceof U.AST_Function); + && !(node instanceof U.AST_Arrow + || node instanceof U.AST_AsyncArrow + || node instanceof U.AST_AsyncFunction + || node instanceof U.AST_Function); } function merge_sequence(array, node) { diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 7f2a7d72..edb98aa7 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -1053,7 +1053,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { var s = []; switch (rng(5)) { case 0: - if (SUPPORT.arrow && !async && !name && rng(2)) { + if (SUPPORT.arrow && !name && rng(2)) { var args, suffix; (rng(2) ? createBlockVariables : function() { arguments[3](); @@ -1067,16 +1067,17 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { } else { params = createParams(save_async, NO_DUPLICATE); } + params = (async ? "async (" : "(") + params + ") => "; if (defns) { s.push( - "((" + params + ") => {", + "(" + params + "{", strictMode(), defns(), _createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) ); suffix = "})"; } else { - s.push("((" + params + ") => "); + s.push("(" + params); switch (rng(10)) { case 0: s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])");