support asynchronous arrow functions (#4530)
authorAlex Lam S.L <alexlamsl@gmail.com>
Sun, 10 Jan 2021 03:34:26 +0000 (03:34 +0000)
committerGitHub <noreply@github.com>
Sun, 10 Jan 2021 03:34:26 +0000 (11:34 +0800)
lib/ast.js
lib/compress.js
lib/output.js
lib/parse.js
lib/scope.js
lib/transform.js
lib/utils.js
test/compress/awaits.js
test/reduce.js
test/ufuzz/index.js

index f0e5fef..ab2559c 100644 (file)
@@ -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: {
index 8aef5a7..f8cf75f 100644 (file)
@@ -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;
     });
index 9e704e8..ae06846 100644 (file)
@@ -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);
     }
index e44c209..4fd8a01 100644 (file)
@@ -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]) {
index f4a18ca..689100c 100644 (file)
@@ -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;
index 9b3eba8..f011cba 100644 (file)
@@ -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);
index 286266a..c3b67a6 100644 (file)
@@ -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;
index 7ae753f..8caa60c 100644 (file)
@@ -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() {
index cd00d5e..3f24b7b 100644 (file)
@@ -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) {
index 7f2a7d7..edb98aa 100644 (file)
@@ -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) + "])");