support asynchronous arrow functions (#4530)
This commit is contained in:
45
lib/ast.js
45
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: {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
55
lib/parse.js
55
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]) {
|
||||
|
||||
11
lib/scope.js
11
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) + "])");
|
||||
|
||||
Reference in New Issue
Block a user