Skip to content

Commit

Permalink
Transpile for-await-of
Browse files Browse the repository at this point in the history
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=207351322
  • Loading branch information
MatthewMerrill authored and tjgq committed Aug 6, 2018
1 parent 4fde8fb commit d5aa5b4
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/com/google/javascript/jscomp/AstValidator.java
Expand Up @@ -1192,6 +1192,7 @@ private void validateForOf(Node n) {

private void validateForAwaitOf(Node n) {
validateFeature(Feature.FOR_AWAIT_OF, n);
validateFeature(Feature.ASYNC_FUNCTIONS, n);
validateNodeType(Token.FOR_AWAIT_OF, n);
validateChildCount(n);
validateVarOrAssignmentTarget(n.getFirstChild());
Expand Down
49 changes: 32 additions & 17 deletions src/com/google/javascript/jscomp/Es6InjectRuntimeLibraries.java
Expand Up @@ -85,6 +85,10 @@ public void process(Node externs, Node root) {
compiler.ensureLibraryInjected("es6/async_generator_wrapper", /* force= */ false);
}

if (used.contains(Feature.FOR_AWAIT_OF)) {
compiler.ensureLibraryInjected("es6/util/makeiterator", /* force= */ false);
}

// TODO(johnlenz): remove this. Symbol should be handled like the other polyfills.
TranspilationPasses.processTranspile(compiler, root, requiredForFeatures, this);
}
Expand Down Expand Up @@ -148,23 +152,34 @@ private void initSymbolBefore(Node n) {

// TODO(tbreisacher): Do this for all well-known symbols.
private void visitGetprop(NodeTraversal t, Node n) {
if (n.matchesQualifiedName("Symbol.iterator")) {
if (isGlobalSymbol(t, n.getFirstChild())) {
compiler.ensureLibraryInjected("es6/symbol", false);
Node statement = NodeUtil.getEnclosingStatement(n);
Node init =
IR.exprResult(IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbolIterator")));
statement.getParent().addChildBefore(init.useSourceInfoFromForTree(statement), statement);
compiler.reportChangeToEnclosingScope(init);
}
} else if (n.matchesQualifiedName("Symbol.asyncIterator")) {
if (isGlobalSymbol(t, n.getFirstChild())) {
compiler.ensureLibraryInjected("es6/symbol", false);
Node statement = NodeUtil.getEnclosingStatement(n);
Node init =
IR.exprResult(IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbolAsyncIterator")));
statement.getParent().addChildBefore(init.useSourceInfoFromForTree(statement), statement);
compiler.reportChangeToEnclosingScope(init);
Node receiverNode = n.getFirstChild();
String propName = receiverNode.getNext().getString();
if (isGlobalSymbol(t, receiverNode)) {
compiler.ensureLibraryInjected("es6/symbol", false);
Node statement = NodeUtil.getEnclosingStatement(n);
switch (propName) {
case "iterator":
{
Node init =
IR.exprResult(IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbolIterator")))
.useSourceInfoFromForTree(statement);
statement.getParent().addChildBefore(init, statement);
compiler.reportChangeToEnclosingScope(init);
break;
}
case "asyncIterator":
{
Node init =
IR.exprResult(
IR.call(NodeUtil.newQName(compiler, "$jscomp.initSymbolAsyncIterator")))
.useSourceInfoFromForTree(statement);
statement.getParent().addChildBefore(init, statement);
compiler.reportChangeToEnclosingScope(init);
break;
}
default:
// TODO(bradfordcsmith): Should we warn for unrecognized symbol names?
break;
}
}
}
Expand Down
102 changes: 95 additions & 7 deletions src/com/google/javascript/jscomp/RewriteAsyncIteration.java
Expand Up @@ -17,6 +17,7 @@

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.rhino.IR.getprop;

import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
Expand Down Expand Up @@ -49,10 +50,6 @@
*/
public final class RewriteAsyncIteration implements NodeTraversal.Callback, HotSwapCompilerPass {

static final DiagnosticType CANNOT_CONVERT_ASYNC_ITERATION_YET =
DiagnosticType.error(
"JSC_CANNOT_CONVERT_ASYNC_ITERATION_YET", "Cannot convert async iteration yet.");

private static final FeatureSet transpiledFeatures =
FeatureSet.BARE_MINIMUM.with(Feature.ASYNC_GENERATORS, Feature.FOR_AWAIT_OF);

Expand All @@ -69,6 +66,10 @@ public final class RewriteAsyncIteration implements NodeTraversal.Callback, HotS
private static final String ACTION_ENUM_YIELD_STAR =
"$jscomp.AsyncGeneratorWrapper$ActionEnum.YIELD_STAR";

private static final String FOR_AWAIT_ITERATOR_TEMP_NAME = "$jscomp$forAwait$tempIterator";
private static final String FOR_AWAIT_RESULT_TEMP_NAME = "$jscomp$forAwait$tempResult";
private int nextForAwaitId = 0;

/** Indicates the type of function currently being parsed. */
private enum FunctionFlavor {
NORMAL(false, false),
Expand Down Expand Up @@ -98,7 +99,7 @@ static FunctionFlavor fromFunctionNode(Node functionNode) {
}
}

public RewriteAsyncIteration(AbstractCompiler compiler) {
RewriteAsyncIteration(AbstractCompiler compiler) {
this.compiler = compiler;
this.functionFlavorStack = new ArrayDeque<>();
}
Expand Down Expand Up @@ -139,9 +140,9 @@ public void visit(NodeTraversal t, Node n, Node parent) {
}
}

// TODO(mattmm): Implement for-await-of
if (n.isForAwaitOf()) {
compiler.report(JSError.make(CANNOT_CONVERT_ASYNC_ITERATION_YET));
replaceForAwaitOf(n);
NodeUtil.addFeatureToScript(t.getCurrentFile(), Feature.CONST_DECLARATIONS);
}

if (n.isFunction()) {
Expand Down Expand Up @@ -272,4 +273,91 @@ private void convertYieldOfAsyncGenerator(Node yieldNode) {
yieldNode.addChildToFront(newActionRecord);
yieldNode.removeProp(Node.YIELD_ALL);
}

/**
* for await (lhs of rhs) { block(); }
*
* <p>...becomes...
*
* <pre>{@code
* for (const tmpIterator = makeAsyncIterator(rhs);;) {
* const tmpRes = await tmpIterator.next();
* if (tmpRes.done) {
* break;
* }
* lhs = $tmpRes.value;
* {
* block(); // Wrapped in a block in case block re-declares lhs variable.
* }
* }
* }</pre>
*
* @param forAwaitOf
*/
private void replaceForAwaitOf(Node forAwaitOf) {
int forAwaitId = nextForAwaitId++;
String iteratorTempName = FOR_AWAIT_ITERATOR_TEMP_NAME + forAwaitId;
String resultTempName = FOR_AWAIT_RESULT_TEMP_NAME + forAwaitId;

checkState(forAwaitOf.getParent() != null, "Cannot replace parentless for-await-of");

Node lhs = forAwaitOf.removeFirstChild();
Node rhs = forAwaitOf.removeFirstChild();
Node originalBody = forAwaitOf.removeFirstChild();

Node initializer =
IR.constNode(
IR.name(iteratorTempName),
NodeUtil.newCallNode(NodeUtil.newQName(compiler, "$jscomp.makeAsyncIterator"), rhs))
.useSourceInfoIfMissingFromForTree(rhs);

// const tmpRes = await tmpIterator.next()
Node resultDeclaration =
IR.constNode(IR.name(resultTempName), constructAwaitNextResult(iteratorTempName));
Node breakIfDone =
IR.ifNode(getprop(IR.name(resultTempName), "done"), IR.block(IR.breakNode()));

// Assignment statement to be moved from lhs into body of new for-loop
Node lhsAssignment;
if (lhs.isValidAssignmentTarget()) {
// In case of "for await (x of _)" just assign into the lhs.
lhsAssignment = IR.exprResult(IR.assign(lhs, getprop(IR.name(resultTempName), "value")));
} else if (NodeUtil.isNameDeclaration(lhs)) {
// In case of "for await (let x of _)" add a rhs to the let, becoming "let x = res.value"
lhs.getFirstChild().addChildToBack(getprop(IR.name(resultTempName), "value"));
lhsAssignment = lhs;
} else {
throw new AssertionError("unexpected for-await-of lhs");
}
lhsAssignment.useSourceInfoIfMissingFromForTree(lhs);

Node newForLoop =
IR.forNode(
initializer,
IR.empty(),
IR.empty(),
IR.block(resultDeclaration, breakIfDone, lhsAssignment, ensureBlock(originalBody)));
forAwaitOf.replaceWith(newForLoop);
newForLoop.useSourceInfoIfMissingFromForTree(forAwaitOf);
compiler.reportChangeToEnclosingScope(newForLoop);
}

private Node ensureBlock(Node possiblyBlock) {
return possiblyBlock.isBlock()
? possiblyBlock
: IR.block(possiblyBlock).useSourceInfoFrom(possiblyBlock);
}

private Node constructAwaitNextResult(String iteratorTempName) {
if (functionFlavorStack.peek() != FunctionFlavor.ASYNCHRONOUS_GENERATOR) {
return IR.await(NodeUtil.newCallNode(getprop(IR.name(iteratorTempName), "next")));
} else {
// We are in an AsyncGenerator and must instead yield an "await" ActionRecord
return IR.yield(
IR.newNode(
NodeUtil.newQName(compiler, ACTION_RECORD_NAME),
NodeUtil.newQName(compiler, ACTION_ENUM_AWAIT),
NodeUtil.newCallNode(getprop(IR.name(iteratorTempName), "next"))));
}
}
}
2 changes: 1 addition & 1 deletion src/com/google/javascript/rhino/IR.java
Expand Up @@ -277,7 +277,7 @@ public static Node forIn(Node target, Node cond, Node body) {
}

public static Node forNode(Node init, Node cond, Node incr, Node body) {
checkState(init.isVar() || mayBeExpressionOrEmpty(init));
checkState(init.isVar() || init.isLet() || init.isConst() || mayBeExpressionOrEmpty(init));
checkState(mayBeExpressionOrEmpty(cond));
checkState(mayBeExpressionOrEmpty(incr));
checkState(body.isBlock());
Expand Down
14 changes: 7 additions & 7 deletions test/com/google/javascript/jscomp/AstValidatorTest.java
Expand Up @@ -117,12 +117,12 @@ public void testForOf() {

public void testForAwaitOf() {
setAcceptedLanguage(LanguageMode.ECMASCRIPT_NEXT);
valid("for await(var a of b);");
valid("for await(let a of b);");
valid("for await(const a of b);");
valid("for await(a of b);");
valid("for await(a of []);");
valid("for await(a of {});");
valid("async () => { for await(var a of b); }");
valid("async () => { for await(let a of b); }");
valid("async () => { for await(const a of b); }");
valid("async () => { for await(a of b); }");
valid("async () => { for await(a of []); }");
valid("async () => { for await(a of {}); }");
}

public void testQuestionableForIn() {
Expand Down Expand Up @@ -464,7 +464,7 @@ public void testFeatureValidation_forOf() {
}

public void testFeatureValidation_forAwaitOf() {
testFeatureValidation("for await (const a of b) {}", Feature.FOR_AWAIT_OF);
testFeatureValidation("async () => { for await (const a of b) {} }", Feature.FOR_AWAIT_OF);
}

public void testFeatureValidation_generatorFunctions() {
Expand Down
17 changes: 15 additions & 2 deletions test/com/google/javascript/jscomp/IntegrationTest.java
Expand Up @@ -5366,8 +5366,21 @@ public void testAsyncIter() {

test(
options,
"for await (a of foo()) {}",
RewriteAsyncIteration.CANNOT_CONVERT_ASYNC_ITERATION_YET);
lines("async function abc() { for await (a of foo()) { bar(); } }"),
lines(
"async function abc() {",
" for (const $jscomp$forAwait$tempIterator0 = $jscomp.makeAsyncIterator(foo());;) {",
" const $jscomp$forAwait$tempResult0 =",
" await $jscomp$forAwait$tempIterator0.next();",
" if ($jscomp$forAwait$tempResult0.done) {",
" break;",
" }",
" a = $jscomp$forAwait$tempResult0.value;",
" {",
" bar();",
" }",
" }",
"}"));
}

public void testDestructuringRest() {
Expand Down

0 comments on commit d5aa5b4

Please sign in to comment.