Skip to content

Commit

Permalink
Fix transpilation of nested async arrow functions.
Browse files Browse the repository at this point in the history
Also use `const` instead of `let` and simplify the logic a bit.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=158856990
  • Loading branch information
brad4d authored and Tyler Breisacher committed Jun 13, 2017
1 parent ad26a8c commit 42143c3
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 17 deletions.
23 changes: 10 additions & 13 deletions src/com/google/javascript/jscomp/RewriteAsyncFunctions.java
Expand Up @@ -58,41 +58,38 @@ public final class RewriteAsyncFunctions implements NodeTraversal.Callback, HotS
private static final class LexicalContext { private static final class LexicalContext {
final Optional<Node> function; // absent for top level final Optional<Node> function; // absent for top level
final LexicalContext thisAndArgumentsContext; final LexicalContext thisAndArgumentsContext;
final Optional<LexicalContext> enclosingAsyncContext;
boolean asyncThisReplacementWasDone = false; boolean asyncThisReplacementWasDone = false;
boolean asyncArgumentsReplacementWasDone = false; boolean asyncArgumentsReplacementWasDone = false;


/** Creates root-level context. */ /** Creates root-level context. */
LexicalContext() { LexicalContext() {
this.function = Optional.absent(); this.function = Optional.absent();
this.thisAndArgumentsContext = this; this.thisAndArgumentsContext = this;
this.enclosingAsyncContext = Optional.absent();
} }


LexicalContext(LexicalContext outer, Node function) { LexicalContext(LexicalContext outer, Node function) {
this.function = Optional.of(function); this.function = Optional.of(function);
// An arrow function shares 'this' and 'arguments' with its outer scope. // An arrow function shares 'this' and 'arguments' with its outer scope.
this.thisAndArgumentsContext = this.thisAndArgumentsContext =
function.isArrowFunction() ? outer.thisAndArgumentsContext : this; function.isArrowFunction() ? outer.thisAndArgumentsContext : this;
this.enclosingAsyncContext =
function.isAsyncFunction() ? Optional.of(this) : outer.enclosingAsyncContext;
} }


boolean isAsyncFunction() { boolean isAsyncContext() {
return function.isPresent() && function.get().isAsyncFunction(); return function.isPresent() && function.get().isAsyncFunction();
} }


boolean mustReplaceThisAndArguments() { boolean mustReplaceThisAndArguments() {
return enclosingAsyncContext.isPresent() return thisAndArgumentsContext.isAsyncContext();
&& enclosingAsyncContext.get().thisAndArgumentsContext == thisAndArgumentsContext;
} }


void recordAsyncThisReplacementWasDone() { void recordAsyncThisReplacementWasDone() {
enclosingAsyncContext.get().asyncThisReplacementWasDone = true; checkState(thisAndArgumentsContext.isAsyncContext());
thisAndArgumentsContext.asyncThisReplacementWasDone = true;
} }


void recordAsyncArgumentsReplacementWasDone() { void recordAsyncArgumentsReplacementWasDone() {
enclosingAsyncContext.get().asyncArgumentsReplacementWasDone = true; checkState(thisAndArgumentsContext.isAsyncContext());
thisAndArgumentsContext.asyncArgumentsReplacementWasDone = true;
} }
} }


Expand Down Expand Up @@ -141,7 +138,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
} }
switch (n.getToken()) { switch (n.getToken()) {
case FUNCTION: case FUNCTION:
if (context.isAsyncFunction()) { if (context.isAsyncContext()) {
convertAsyncFunction(context); convertAsyncFunction(context);
} }
break; break;
Expand All @@ -161,7 +158,7 @@ public void visit(NodeTraversal t, Node n, Node parent) {
break; break;


case AWAIT: case AWAIT:
checkState(context.isAsyncFunction(), "await found within non-async function body"); checkState(context.isAsyncContext(), "await found within non-async function body");
checkState(n.hasOneChild(), "await should have 1 operand, but has %s", n.getChildCount()); checkState(n.hasOneChild(), "await should have 1 operand, but has %s", n.getChildCount());
// Awaits become yields in the converted async function's inner generator function. // Awaits become yields in the converted async function's inner generator function.
parent.replaceChild(n, IR.yield(n.removeFirstChild()).useSourceInfoIfMissingFrom(n)); parent.replaceChild(n, IR.yield(n.removeFirstChild()).useSourceInfoIfMissingFrom(n));
Expand All @@ -180,10 +177,10 @@ private void convertAsyncFunction(LexicalContext functionContext) {
originalFunction.replaceChild(originalBody, newBody); originalFunction.replaceChild(originalBody, newBody);


if (functionContext.asyncThisReplacementWasDone) { if (functionContext.asyncThisReplacementWasDone) {
newBody.addChildToBack(IR.let(IR.name(ASYNC_THIS), IR.thisNode())); newBody.addChildToBack(IR.constNode(IR.name(ASYNC_THIS), IR.thisNode()));
} }
if (functionContext.asyncArgumentsReplacementWasDone) { if (functionContext.asyncArgumentsReplacementWasDone) {
newBody.addChildToBack(IR.let(IR.name(ASYNC_ARGUMENTS), IR.name("arguments"))); newBody.addChildToBack(IR.constNode(IR.name(ASYNC_ARGUMENTS), IR.name("arguments")));
} }


// Normalize arrow function short body to block body // Normalize arrow function short body to block body
Expand Down
35 changes: 31 additions & 4 deletions test/com/google/javascript/jscomp/RewriteAsyncFunctionsTest.java
Expand Up @@ -84,7 +84,7 @@ public void testArgumentsReplacement_asyncFunction() {
"async function f(a, b, ...rest) { return arguments.length; }", "async function f(a, b, ...rest) { return arguments.length; }",
LINE_JOINER.join( LINE_JOINER.join(
"function f(a, b, ...rest) {", "function f(a, b, ...rest) {",
" let $jscomp$async$arguments = arguments;", " const $jscomp$async$arguments = arguments;",
" function* $jscomp$async$generator() {", " function* $jscomp$async$generator() {",
" return $jscomp$async$arguments.length;", // arguments replaced " return $jscomp$async$arguments.length;", // arguments replaced
" }", " }",
Expand All @@ -102,7 +102,7 @@ public void testArgumentsReplacement_asyncClosure() {
LINE_JOINER.join( LINE_JOINER.join(
"function outer() {", "function outer() {",
" function f() {", " function f() {",
" let $jscomp$async$arguments = arguments;", " const $jscomp$async$arguments = arguments;",
" function* $jscomp$async$generator() {", " function* $jscomp$async$generator() {",
" return $jscomp$async$arguments.length;", // arguments replaced " return $jscomp$async$arguments.length;", // arguments replaced
" }", " }",
Expand All @@ -123,7 +123,7 @@ public void testArgumentsReplacement_normalClosureInAsync() {
"}"), "}"),
LINE_JOINER.join( LINE_JOINER.join(
"function a() {", "function a() {",
" let $jscomp$async$arguments = arguments;", " const $jscomp$async$arguments = arguments;",
" function* $jscomp$async$generator() {", " function* $jscomp$async$generator() {",
" function inner() {", " function inner() {",
" return arguments.length;", // unchanged " return arguments.length;", // unchanged
Expand All @@ -140,7 +140,7 @@ public void testClassMethod() {
LINE_JOINER.join( LINE_JOINER.join(
"class A {", "class A {",
" f() {", " f() {",
" let $jscomp$async$this = this;", " const $jscomp$async$this = this;",
" function* $jscomp$async$generator() {", " function* $jscomp$async$generator() {",
" return $jscomp$async$this.x;", // this replaced " return $jscomp$async$this.x;", // this replaced
" }", " }",
Expand All @@ -149,6 +149,33 @@ public void testClassMethod() {
"}")); "}"));
} }


public void testClassMethodWithAsyncArrow() {
test(
LINE_JOINER.join(
"class A {",
" async f() {",
" let g = async () => { console.log(this); };",
" g();",
" }",
"}"),
LINE_JOINER.join(
"class A {",
" f() {",
" const $jscomp$async$this = this;",
" function *$jscomp$async$generator() {",
" let g = () => {",
" function *$jscomp$async$generator() {",
" console.log($jscomp$async$this);",
" }",
" return $jscomp.executeAsyncGenerator($jscomp$async$generator());",
" };",
" g();",
" }",
" return $jscomp.executeAsyncGenerator($jscomp$async$generator());",
" }",
"}"));
}

public void testArrowFunctionExpressionBody() { public void testArrowFunctionExpressionBody() {
test( test(
"let f = async () => 1;", "let f = async () => 1;",
Expand Down
Expand Up @@ -144,6 +144,24 @@ testSuite({
}); });
}, },


testMemberFunctionUsingThisInAsyncArrowFunction() {
class C {
constructor() {
this.value = 0;
}

async delayedIncrementAndReturnThis() {
const nestedArrow = async () => { this.value++; return this; };
return await nestedArrow();
}
}
const c = new C();
return c.delayedIncrementAndReturnThis().then(result => {
assertEquals(c, result);
assertEquals(1, c.value);
});
},

testArgumentsHandledCorrectly() { testArgumentsHandledCorrectly() {
const expected1 = {}; const expected1 = {};
const expected2 = 2; const expected2 = 2;
Expand Down

0 comments on commit 42143c3

Please sign in to comment.