All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.Es6RewriteGenerators Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2014 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.Es6ToEs3Util.createType;
import static com.google.javascript.jscomp.Es6ToEs3Util.makeIterator;
import static com.google.javascript.jscomp.Es6ToEs3Util.withType;

import com.google.common.base.Joiner;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.AbstractCompiler.MostRecentTypechecker;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.FunctionTypeI;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfoBuilder;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.ObjectTypeI;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TypeI;
import com.google.javascript.rhino.TypeIRegistry;
import com.google.javascript.rhino.jstype.JSTypeNative;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;

/**
 * Converts ES6 generator functions to valid ES3 code. This pass runs after all ES6 features
 * except for yield and generators have been transpiled.
 *
 * @author [email protected] (Matthew Loring)
 */
public final class Es6RewriteGenerators
    extends NodeTraversal.AbstractPostOrderCallback implements HotSwapCompilerPass {

  static final String GENERATOR_PRELOAD_FUNCTION_NAME = "$jscomp$generator$function$name";

  // Name of the variable that holds the state at which the generator
  // should resume execution after a call to yield or return.
  // The beginning state is 0 and the end state is -1.
  private static final String GENERATOR_STATE = "$jscomp$generator$state";
  private static final String GENERATOR_DO_WHILE_INITIAL = "$jscomp$generator$first$do";
  private static final String GENERATOR_YIELD_ALL_NAME = "$jscomp$generator$yield$all";
  private static final String GENERATOR_YIELD_ALL_ENTRY = "$jscomp$generator$yield$entry";
  private static final String GENERATOR_ARGUMENTS = "$jscomp$generator$arguments";
  private static final String GENERATOR_THIS = "$jscomp$generator$this";
  private static final String GENERATOR_ACTION_ARG = "$jscomp$generator$action$arg";
  private static final double GENERATOR_ACTION_NEXT = 0;
  private static final double GENERATOR_ACTION_THROW = 1;
  private static final String GENERATOR_NEXT_ARG = "$jscomp$generator$next$arg";
  private static final String GENERATOR_THROW_ARG = "$jscomp$generator$throw$arg";
  private static final String GENERATOR_SWITCH_ENTERED = "$jscomp$generator$switch$entered";
  private static final String GENERATOR_SWITCH_VAL = "$jscomp$generator$switch$val";
  private static final String GENERATOR_FINALLY_JUMP = "$jscomp$generator$finally";
  private static final String GENERATOR_ERROR = "$jscomp$generator$global$error";
  private static final String GENERATOR_FOR_IN_ARRAY = "$jscomp$generator$forin$array";
  private static final String GENERATOR_FOR_IN_VAR = "$jscomp$generator$forin$var";
  private static final String GENERATOR_FOR_IN_ITER = "$jscomp$generator$forin$iter";
  private static final String GENERATOR_LOOP_GUARD = "$jscomp$generator$loop$guard";

  private final AbstractCompiler compiler;
  private static final FeatureSet transpiledFeatures =
      FeatureSet.BARE_MINIMUM.with(Feature.GENERATORS);

  // Maintains a stack of numbers which identify the cases which mark the end of loops. These
  // are used to manage jump destinations for break and continue statements.
  private final List currentLoopContext;

  private final List currentExceptionContext;

  private static int generatorCaseCount;

  private final Supplier generatorCounter;

  // Current case statement onto which translated statements from the
  // body of a generator will be appended.
  private Node enclosingBlock;

  // Destination for vars defined in the body of a generator.
  private Node hoistRoot;

  // Body of the generator function currently being translated.
  private Node originalGeneratorBody;

  // Current statement being translated.
  private Node currentStatement;

  private boolean hasTranslatedTry;

  // Whether we should preserve type information during transpilation.
  private final boolean addTypes;

  private final TypeIRegistry registry;

  private final TypeI unknownType;
  private final TypeI undefinedType;
  private final TypeI stringType;
  private final TypeI booleanType;
  private final TypeI falseType;
  private final TypeI trueType;
  private final TypeI numberType;

  public Es6RewriteGenerators(AbstractCompiler compiler) {
    checkNotNull(compiler);
    this.compiler = compiler;
    this.currentLoopContext = new ArrayList<>();
    this.currentExceptionContext = new ArrayList<>();
    generatorCounter = compiler.getUniqueNameIdSupplier();
    this.addTypes = MostRecentTypechecker.NTI.equals(compiler.getMostRecentTypechecker());
    this.registry = compiler.getTypeIRegistry();
    this.unknownType = createType(addTypes, registry, JSTypeNative.UNKNOWN_TYPE);
    this.undefinedType = createType(addTypes, registry, JSTypeNative.VOID_TYPE);
    this.stringType = createType(addTypes, registry, JSTypeNative.STRING_TYPE);
    this.booleanType = createType(addTypes, registry, JSTypeNative.BOOLEAN_TYPE);
    this.falseType = createType(addTypes, registry, JSTypeNative.FALSE_TYPE);
    this.trueType = createType(addTypes, registry, JSTypeNative.TRUE_TYPE);
    this.numberType = createType(addTypes, registry, JSTypeNative.NUMBER_TYPE);
  }

  @Override
  public void process(Node externs, Node root) {
    // Report change only if the generator function is preloaded. See #cleanUpGeneratorSkeleton.
    boolean reportChange = getPreloadedGeneratorFunc(compiler.getJsRoot()) != null;
    TranspilationPasses.processTranspile(
        compiler, root, transpiledFeatures, new DecomposeYields(compiler), this);
    cleanUpGeneratorSkeleton(reportChange);
  }

  @Override
  public void hotSwapScript(Node scriptRoot, Node originalRoot) {
    TranspilationPasses.hotSwapTranspile(
        compiler, scriptRoot, transpiledFeatures, new DecomposeYields(compiler), this);
  }

  @Override
  public void visit(NodeTraversal t, Node n, Node parent) {
    switch (n.getToken()) {
      case FUNCTION:
        if (n.isGeneratorFunction()) {
          generatorCaseCount = 0;
          visitGenerator(n, parent);
        }
        break;
      case NAME:
        Node enclosing = NodeUtil.getEnclosingFunction(n);
        if (enclosing != null
            && enclosing.isGeneratorFunction()
            && n.matchesQualifiedName("arguments")) {
          n.setString(GENERATOR_ARGUMENTS);
        }
        break;
      case THIS:
        enclosing = NodeUtil.getEnclosingFunction(n);
        if (enclosing != null && enclosing.isGeneratorFunction()) {
          n.replaceWith(withType(IR.name(GENERATOR_THIS), n.getTypeI()));
        }
        break;
      case YIELD:
        if (n.isYieldAll()) {
          visitYieldAll(t, n, parent);
        } else if (!parent.isExprResult()) {
          visitYieldExpr(t, n, parent);
        } else {
          visitYieldThrows(t, parent, parent.getParent());
        }
        break;
      default:
        break;
    }
  }

  private void visitYieldThrows(NodeTraversal t, Node n, Node parent) {
    Node ifThrows =
        IR.ifNode(
            withBooleanType(
                IR.eq(
                    withNumberType(IR.name(GENERATOR_ACTION_ARG)),
                    withNumberType(IR.number(GENERATOR_ACTION_THROW)))),
            IR.block(IR.throwNode(withUnknownType(IR.name(GENERATOR_THROW_ARG)))));
    parent.addChildAfter(ifThrows, n);
    t.reportCodeChange();
  }

  /**
   * Translates expressions using the new yield-for syntax.
   *
   * 

Sample translation: * *

   * var i = yield * gen();
   * 
* *

Is rewritten to: * *

   * var $jscomp$generator$yield$all = gen();
   * var $jscomp$generator$yield$entry;
   * while (!($jscomp$generator$yield$entry =
   *     $jscomp$generator$yield$all.next($jscomp$generator$next$arg)).done) {
   *   yield $jscomp$generator$yield$entry.value;
   * }
   * var i = $jscomp$generator$yield$entry.value;
   * 
*/ private void visitYieldAll(NodeTraversal t, Node n, Node parent) { ObjectTypeI yieldAllType = null; TypeI typeParam = unknownType; if (addTypes) { yieldAllType = n.getFirstChild().getTypeI().autobox().toMaybeObjectType(); typeParam = yieldAllType.getTemplateTypes().get(0); } TypeI iteratorType = createGenericType(JSTypeNative.ITERATOR_TYPE, typeParam); TypeI iteratorNextType = addTypes ? iteratorType.toMaybeObjectType().getPropertyType("next") : null; TypeI iIterableResultType = createGenericType(JSTypeNative.I_ITERABLE_RESULT_TYPE, typeParam); TypeI iIterableResultDoneType = addTypes ? iIterableResultType.toMaybeObjectType().getPropertyType("done") : null; TypeI iIterableResultValueType = addTypes ? iIterableResultType.toMaybeObjectType().getPropertyType("value") : null; Node enclosingStatement = NodeUtil.getEnclosingStatement(n); Node iterator = makeIterator(compiler, n.removeFirstChild()); if (addTypes) { TypeI jscompType = t.getScope().getVar("$jscomp").getNode().getTypeI(); TypeI makeIteratorType = jscompType.toMaybeObjectType().getPropertyType("makeIterator"); iterator.getFirstChild().setTypeI(makeIteratorType); iterator.getFirstFirstChild().setTypeI(jscompType); } Node generator = IR.var( withType(IR.name(GENERATOR_YIELD_ALL_NAME), iteratorType), withType(iterator, iteratorType)); Node entryDecl = IR.var(withType(IR.name(GENERATOR_YIELD_ALL_ENTRY), iIterableResultType)); Node assignIterResult = withType( IR.assign( withType(IR.name(GENERATOR_YIELD_ALL_ENTRY), iIterableResultType), withType( IR.call( withType( IR.getprop( withType(IR.name(GENERATOR_YIELD_ALL_NAME), iteratorType), withStringType(IR.string("next"))), iteratorNextType), withUnknownType(IR.name(GENERATOR_NEXT_ARG))), iIterableResultType)), iIterableResultType); Node loopCondition = withBooleanType( IR.not( withType( IR.getprop(assignIterResult, withStringType(IR.string("done"))), iIterableResultDoneType))); Node elemValue = withType( IR.getprop( withType(IR.name(GENERATOR_YIELD_ALL_ENTRY), iIterableResultType), withStringType(IR.string("value"))), iIterableResultValueType); Node yieldStatement = IR.exprResult(withUnknownType(IR.yield(elemValue.cloneTree()))); Node loop = IR.whileNode(loopCondition, IR.block(yieldStatement)); enclosingStatement.getParent().addChildBefore(generator, enclosingStatement); enclosingStatement.getParent().addChildBefore(entryDecl, enclosingStatement); enclosingStatement.getParent().addChildBefore(loop, enclosingStatement); if (parent.isExprResult()) { parent.detach(); } else { parent.replaceChild(n, elemValue); } visitYieldThrows(t, yieldStatement, yieldStatement.getParent()); t.reportCodeChange(); } private void visitYieldExpr(NodeTraversal t, Node n, Node parent) { Node enclosingStatement = NodeUtil.getEnclosingStatement(n); Node yieldStatement = IR.exprResult( n.hasChildren() ? withType(IR.yield(n.removeFirstChild()), n.getTypeI()) : withType(IR.yield(), n.getTypeI())); Node yieldResult = withUnknownType(IR.name(GENERATOR_NEXT_ARG + generatorCounter.get())); Node yieldResultDecl = IR.var(yieldResult.cloneTree(), withUnknownType(IR.name(GENERATOR_NEXT_ARG))); parent.replaceChild(n, yieldResult); enclosingStatement.getParent().addChildBefore(yieldStatement, enclosingStatement); enclosingStatement.getParent().addChildBefore(yieldResultDecl, enclosingStatement); visitYieldThrows(t, yieldStatement, yieldStatement.getParent()); t.reportCodeChange(); } private void visitGenerator(Node n, Node parent) { Es6ToEs3Util.preloadEs6Symbol(compiler); hasTranslatedTry = false; Node genBlock = preloadGeneratorSkeleton(compiler, false).getLastChild().cloneTree(); generatorCaseCount++; originalGeneratorBody = n.getLastChild(); n.replaceChild(originalGeneratorBody, genBlock); NodeUtil.markNewScopesChanged(genBlock, compiler); n.setIsGeneratorFunction(false); TypeI generatorFuncType = n.getTypeI(); TypeI generatorReturnType = addTypes ? generatorFuncType.toMaybeFunctionType().getReturnType() : null; TypeI yieldType = unknownType; if (addTypes) { if (generatorReturnType.isGenericObjectType()) { yieldType = generatorReturnType.autobox().toMaybeObjectType().getTemplateTypes().get(0); } addTypesToGeneratorSkeleton(genBlock, yieldType); } // TODO(mattloring): remove this suppression once we can optimize the switch statement to // remove unused cases. JSDocInfoBuilder builder = JSDocInfoBuilder.maybeCopyFrom(n.getJSDocInfo()); // TODO(mattloring): copy existing suppressions. builder.recordSuppressions(ImmutableSet.of("uselessCode")); JSDocInfo info = builder.build(); n.setJSDocInfo(info); // Set state to the default after the body of the function has completed. originalGeneratorBody.addChildToBack( IR.exprResult( withNumberType( IR.assign( withNumberType(IR.name(GENERATOR_STATE)), withNumberType(IR.number(-1)))))); enclosingBlock = getUnique(genBlock, Token.CASE).getLastChild(); hoistRoot = genBlock.getFirstChild(); if (NodeUtil.isNameReferenced(originalGeneratorBody, GENERATOR_ARGUMENTS)) { hoistRoot .getParent() .addChildAfter( IR.var( withUnknownType(IR.name(GENERATOR_ARGUMENTS)), withUnknownType(IR.name("arguments"))), hoistRoot); } if (NodeUtil.isNameReferenced(originalGeneratorBody, GENERATOR_THIS)) { hoistRoot .getParent() .addChildAfter( IR.var(withUnknownType(IR.name(GENERATOR_THIS)), withUnknownType(IR.thisNode())), hoistRoot); } while (originalGeneratorBody.hasChildren()) { currentStatement = originalGeneratorBody.removeFirstChild(); boolean advanceCase = translateStatementInOriginalBody(); if (advanceCase) { int caseNumber; if (currentStatement.isGeneratorMarker()) { caseNumber = (int) currentStatement.getFirstChild().getDouble(); } else { caseNumber = generatorCaseCount; generatorCaseCount++; } Node oldCase = enclosingBlock.getParent(); Node newCase = withBooleanType(IR.caseNode(withNumberType(IR.number(caseNumber)), IR.block())); enclosingBlock = newCase.getLastChild(); if (oldCase.isTry()) { oldCase = oldCase.getGrandparent(); if (!currentExceptionContext.isEmpty()) { Node newTry = IR.tryCatch(IR.block(), currentExceptionContext.get(0).catchBlock.cloneTree()); newCase.getLastChild().addChildToBack(newTry); enclosingBlock = newCase.getLastChild().getLastChild().getFirstChild(); } } oldCase.getParent().addChildAfter(newCase, oldCase); } } parent.useSourceInfoIfMissingFromForTree(parent); compiler.reportChangeToEnclosingScope(genBlock); } /** Returns {@code true} if a new case node should be added */ private boolean translateStatementInOriginalBody() { if (currentStatement.isVar()) { visitVar(); return false; } else if (currentStatement.isGeneratorMarker()) { visitGeneratorMarker(); return true; } else if (currentStatement.isFunction()) { visitFunctionStatement(); return false; } else if (currentStatement.isNormalBlock()) { visitBlock(); return false; } else if (controlCanExit(currentStatement)) { switch (currentStatement.getToken()) { case WHILE: case DO: case FOR: visitLoop(null); return false; case FOR_IN: visitForIn(); return false; case LABEL: visitLabel(); return false; case SWITCH: visitSwitch(); return false; case IF: if (!currentStatement.isGeneratorSafe()) { visitIf(); return false; } break; case TRY: visitTry(); return false; case EXPR_RESULT: if (currentStatement.getFirstChild().isYield()) { visitYieldExprResult(); return true; } break; case RETURN: visitReturn(); return false; case CONTINUE: visitContinue(); return false; case BREAK: if (!currentStatement.isGeneratorSafe()) { visitBreak(); return false; } break; case THROW: visitThrow(); return false; default: // We never want to copy over an untranslated statement for which control exits. throw new RuntimeException( "Untranslatable control-exiting statement in generator function: " + currentStatement.getToken()); } } // In the default case, add the statement to the current case block unchanged. enclosingBlock.addChildToBack(currentStatement); return false; } private void visitFunctionStatement() { hoistRoot.getParent().addChildAfter(currentStatement, hoistRoot); } private void visitTry() { Node tryBody = currentStatement.getFirstChild(); Node caughtError; Node catchBody; Node catchBlock = tryBody.getNext(); if (catchBlock.hasChildren()) { // There is a catch block caughtError = catchBlock.getFirstChild().removeFirstChild(); catchBody = catchBlock.getFirstChild().removeFirstChild(); } else { caughtError = withUnknownType(IR.name(GENERATOR_ERROR + "temp")); catchBody = IR.block(IR.throwNode(caughtError.cloneTree())); catchBody.getFirstChild().setGeneratorSafe(true); } Node finallyBody = catchBlock.getNext(); int catchStartState = generatorCaseCount++; Node catchStart = makeGeneratorMarker(catchStartState); Node errorNameGenerated = withUnknownType(IR.name("$jscomp$generator$" + caughtError.getString())); originalGeneratorBody.addChildToFront(catchStart); originalGeneratorBody.addChildAfter(catchBody, catchStart); Node assignError = withUnknownType( IR.assign(withUnknownType(IR.name(GENERATOR_ERROR)), errorNameGenerated.cloneTree())); Node newCatchBody = IR.block(IR.exprResult(assignError), createStateUpdate(catchStartState), createSafeBreak()); Node newCatch = IR.catchNode(errorNameGenerated, newCatchBody); currentExceptionContext.add(0, new ExceptionContext(catchStartState, newCatch)); if (finallyBody != null) { Node finallyName = withNumberType(IR.name(GENERATOR_FINALLY_JUMP + generatorCounter.get())); int finallyStartState = generatorCaseCount++; Node finallyStart = makeGeneratorMarker(finallyStartState); int finallyEndState = generatorCaseCount++; Node finallyEnd = makeGeneratorMarker(finallyEndState); NodeTraversal.traverseEs6( compiler, tryBody, new ControlExitsCheck(finallyName, finallyStartState)); NodeTraversal.traverseEs6( compiler, catchBody, new ControlExitsCheck(finallyName, finallyStartState)); originalGeneratorBody.addChildToFront(tryBody.detach()); originalGeneratorBody.addChildAfter(finallyStart, catchBody); originalGeneratorBody.addChildAfter(finallyBody.detach(), finallyStart); originalGeneratorBody.addChildAfter(finallyEnd, finallyBody); originalGeneratorBody.addChildToFront(IR.var(finallyName.cloneTree())); finallyBody.addChildToBack( IR.exprResult( withNumberType( IR.assign(withNumberType(IR.name(GENERATOR_STATE)), finallyName.cloneTree())))); finallyBody.addChildToBack(createSafeBreak()); tryBody.addChildToBack( IR.exprResult( withNumberType( IR.assign(finallyName.cloneTree(), withNumberType(IR.number(finallyEndState)))))); tryBody.addChildToBack(createStateUpdate(finallyStartState)); tryBody.addChildToBack(createSafeBreak()); catchBody.addChildToBack( IR.exprResult( withNumberType( IR.assign(finallyName.cloneTree(), withNumberType(IR.number(finallyEndState)))))); } else { int catchEndState = generatorCaseCount++; Node catchEnd = makeGeneratorMarker(catchEndState); originalGeneratorBody.addChildAfter(catchEnd, catchBody); tryBody.addChildToBack(createStateUpdate(catchEndState)); tryBody.addChildToBack(createSafeBreak()); originalGeneratorBody.addChildToFront(tryBody.detach()); } catchBody.addChildToFront(IR.var(caughtError, withUnknownType(IR.name(GENERATOR_ERROR)))); if (enclosingBlock.getParent().isTry()) { enclosingBlock = enclosingBlock.getGrandparent(); } enclosingBlock.addChildToBack(IR.tryCatch(IR.block(), newCatch)); enclosingBlock = enclosingBlock.getLastChild().getFirstChild(); if (!hasTranslatedTry) { hasTranslatedTry = true; hoistRoot .getParent() .addChildAfter(IR.var(withUnknownType(IR.name(GENERATOR_ERROR))), hoistRoot); } } private void visitContinue() { checkState(currentLoopContext.get(0).continueCase != -1); int continueCase; if (currentStatement.hasChildren()) { continueCase = getLoopContext(currentStatement.removeFirstChild().getString()).continueCase; } else { continueCase = currentLoopContext.get(0).continueCase; } enclosingBlock.addChildToBack(createStateUpdate(continueCase)); enclosingBlock.addChildToBack(createSafeBreak()); } private void visitThrow() { enclosingBlock.addChildToBack(createStateUpdate(-1)); enclosingBlock.addChildToBack(currentStatement); } private void visitBreak() { int breakCase; if (currentStatement.hasChildren()) { LoopContext loop = getLoopContext(currentStatement.removeFirstChild().getString()); if (loop == null) { compiler.report( JSError.make( currentStatement, Es6ToEs3Util.CANNOT_CONVERT_YET, "Breaking to a label that is not a loop")); return; } breakCase = loop.breakCase; } else { breakCase = currentLoopContext.get(0).breakCase; } enclosingBlock.addChildToBack(createStateUpdate(breakCase)); enclosingBlock.addChildToBack(createSafeBreak()); } private void visitLabel() { Node labelName = currentStatement.removeFirstChild(); Node child = currentStatement.removeFirstChild(); if (NodeUtil.isLoopStructure(child)) { currentStatement = child; visitLoop(labelName.getString()); } else { originalGeneratorBody.addChildToFront(child); } } /** * Pops the loop information off of our stack if we reach the marker cooresponding * to the end of the current loop. */ private void visitGeneratorMarker() { if (!currentLoopContext.isEmpty() && currentLoopContext.get(0).breakCase == currentStatement.getFirstChild().getDouble()) { currentLoopContext.remove(0); } if (!currentExceptionContext.isEmpty() && currentExceptionContext.get(0).catchStartCase == currentStatement.getFirstChild().getDouble()) { currentExceptionContext.remove(0); } } /** * Uses a case statement to jump over the body if the condition of the * if statement is false. Additionally, lift the body of the {@code if} * statement to the top level. */ private void visitIf() { Node condition = currentStatement.removeFirstChild(); Node ifBody = currentStatement.removeFirstChild(); boolean hasElse = currentStatement.hasChildren(); int ifEndState = generatorCaseCount++; Node invertedConditional = IR.ifNode( withBooleanType(IR.not(condition)), IR.block(createStateUpdate(ifEndState), createSafeBreak())); invertedConditional.setGeneratorSafe(true); Node endIf = makeGeneratorMarker(ifEndState); originalGeneratorBody.addChildToFront(invertedConditional); originalGeneratorBody.addChildAfter(ifBody, invertedConditional); originalGeneratorBody.addChildAfter(endIf, ifBody); if (hasElse) { Node elseBlock = currentStatement.removeFirstChild(); int elseEndState = generatorCaseCount++; Node endElse = makeGeneratorMarker(elseEndState); ifBody.addChildToBack(createStateUpdate(elseEndState)); ifBody.addChildToBack(createSafeBreak()); originalGeneratorBody.addChildAfter(elseBlock, endIf); originalGeneratorBody.addChildAfter(endElse, elseBlock); } } /** * Translates switch statements into a series of if statements. * *

Sample translation: *

   * switch (i) {
   *   case 1:
   *     s;
   *   case 2:
   *     t;
   *   ...
   * }
   * 
* *

Is eventually rewritten to: * *

   * $jscomp$generator$switch$entered0 = false;
   * if ($jscomp$generator$switch$entered0 || i == 1) {
   *   $jscomp$generator$switch$entered0 = true;
   *   s;
   * }
   * if ($jscomp$generator$switch$entered0 || i == 2) {
   *   $jscomp$generator$switch$entered0 = true;
   *   t;
   * }
   * ...
   *
   * 
*/ private void visitSwitch() { Node didEnter = withBooleanType(IR.name(GENERATOR_SWITCH_ENTERED + generatorCounter.get())); Node didEnterDecl = IR.var(didEnter.cloneTree(), withFalseType(IR.falseNode())); Node switchVal = withType( IR.name(GENERATOR_SWITCH_VAL + generatorCounter.get()), currentStatement.getFirstChild().getTypeI()); Node switchValDecl = IR.var(switchVal.cloneTree(), currentStatement.removeFirstChild()); originalGeneratorBody.addChildToFront(didEnterDecl); originalGeneratorBody.addChildAfter(switchValDecl, didEnterDecl); Node insertionPoint = switchValDecl; while (currentStatement.hasChildren()) { Node currCase = currentStatement.removeFirstChild(); Node equivBlock; currCase .getLastChild() .addChildToFront( IR.exprResult( withBooleanType(IR.assign(didEnter.cloneTree(), withTrueType(IR.trueNode()))))); if (currCase.isDefaultCase()) { if (currentStatement.hasChildren()) { compiler.report( JSError.make( currentStatement, Es6ToEs3Util.CANNOT_CONVERT_YET, "Default case as intermediate case")); } equivBlock = IR.block(currCase.removeFirstChild()); } else { equivBlock = IR.ifNode( withBooleanType( IR.or( didEnter.cloneTree(), withBooleanType( IR.sheq(switchVal.cloneTree(), currCase.removeFirstChild())))), currCase.removeFirstChild()); } originalGeneratorBody.addChildAfter(equivBlock, insertionPoint); insertionPoint = equivBlock; } int breakTarget = generatorCaseCount++; int cont = currentLoopContext.isEmpty() ? -1 : currentLoopContext.get(0).continueCase; currentLoopContext.add(0, new LoopContext(breakTarget, cont, null)); Node breakCase = makeGeneratorMarker(breakTarget); originalGeneratorBody.addChildAfter(breakCase, insertionPoint); } /** * Lifts all children to the body of the original generator to flatten the block. */ private void visitBlock() { if (!currentStatement.hasChildren()) { return; } Node insertionPoint = currentStatement.removeFirstChild(); originalGeneratorBody.addChildToFront(insertionPoint); for (Node child = currentStatement.removeFirstChild(); child != null; child = currentStatement.removeFirstChild()) { originalGeneratorBody.addChildAfter(child, insertionPoint); insertionPoint = child; } } /** * Translates for in loops to a for in loop which produces an array of * values iterated over followed by a plain for loop which performs the logic * contained in the body of the original for in. * *

Sample translation: *

   * for (i in j) {
   *   s;
   * }
   * 
* *

Is eventually rewritten to: * *

   * $jscomp$arr = [];
   * $jscomp$iter = j;
   * for (i in $jscomp$iter) {
   *   $jscomp$arr.push(i);
   * }
   * for ($jscomp$var = 0; $jscomp$var < $jscomp$arr.length; $jscomp$var++) {
   *   i = $jscomp$arr[$jscomp$var];
   *   if (!(i in $jscomp$iter)) {
   *     continue;
   *   }
   *   s;
   * }
   * 
*/ private void visitForIn() { Node variable = currentStatement.removeFirstChild(); Node iterable = currentStatement.removeFirstChild(); Node body = currentStatement.removeFirstChild(); TypeI iterableType = iterable.getTypeI(); TypeI typeParam = unknownType; if (addTypes) { typeParam = iterableType.autobox().toMaybeObjectType().getTemplateTypes().get(0); } TypeI arrayType = createGenericType(JSTypeNative.ARRAY_TYPE, typeParam); String loopId = generatorCounter.get(); Node arrayName = withType(IR.name(GENERATOR_FOR_IN_ARRAY + loopId), arrayType); Node varName = withNumberType(IR.name(GENERATOR_FOR_IN_VAR + loopId)); Node iterableName = withType(IR.name(GENERATOR_FOR_IN_ITER + loopId), iterableType); if (variable.isVar()) { variable = variable.removeFirstChild(); } body.addChildToFront( IR.ifNode( withBooleanType(IR.not(IR.in(variable.cloneTree(), iterableName.cloneTree()))), IR.block(IR.continueNode()))); body.addChildToFront( IR.var(variable.cloneTree(), IR.getelem(arrayName.cloneTree(), varName.cloneTree()))); hoistRoot.getParent().addChildAfter(IR.var(arrayName.cloneTree()), hoistRoot); hoistRoot.getParent().addChildAfter(IR.var(varName.cloneTree()), hoistRoot); hoistRoot.getParent().addChildAfter(IR.var(iterableName.cloneTree()), hoistRoot); Node arrayDef = IR.exprResult( withType( IR.assign(arrayName.cloneTree(), withType(IR.arraylit(), arrayType)), arrayType)); Node iterDef = IR.exprResult(withType(IR.assign(iterableName.cloneTree(), iterable), iterableType)); Node newForIn = IR.forIn( variable.cloneTree(), iterableName, IR.block( IR.exprResult( withNumberType( IR.call(IR.getprop(arrayName.cloneTree(), IR.string("push")), variable))))); Node newFor = IR.forNode( withNumberType(IR.assign(varName.cloneTree(), withNumberType(IR.number(0)))), withBooleanType( IR.lt( varName.cloneTree(), withNumberType(IR.getprop(arrayName, IR.string("length"))))), withNumberType(IR.inc(varName, true)), body); enclosingBlock.addChildToBack(arrayDef); enclosingBlock.addChildToBack(iterDef); enclosingBlock.addChildToBack(newForIn); originalGeneratorBody.addChildToFront(newFor); } /** * Translates loops to a case statement followed by an if statement * containing the loop body. The if statement finishes by * jumping back to the initial case statement to enter the loop again. * In the case of for and do loops, initialization and post loop statements are inserted * before and after the if statement. Below is a sample translation for a while loop: * *

Sample translation: *

   * while (b) {
   *   s;
   * }
   * 
* *

Is eventually rewritten to: *

   * case n:
   *   if (b) {
   *     s;
   *     state = n;
   *     break;
   *   }
   * 
*/ private void visitLoop(String label) { Node initializer; Node guard; Node incr; Node body; if (currentStatement.isWhile()) { guard = currentStatement.removeFirstChild(); body = currentStatement.removeFirstChild(); initializer = IR.empty(); incr = IR.empty(); } else if (currentStatement.isVanillaFor()) { initializer = currentStatement.removeFirstChild(); if (initializer.isAssign()) { initializer = IR.exprResult(initializer); } guard = currentStatement.removeFirstChild(); incr = currentStatement.removeFirstChild(); body = currentStatement.removeFirstChild(); } else { checkState(currentStatement.isDo()); initializer = IR.empty(); incr = withBooleanType( IR.assign( withBooleanType(IR.name(GENERATOR_DO_WHILE_INITIAL)), withFalseType(IR.falseNode()))); body = currentStatement.removeFirstChild(); guard = currentStatement.removeFirstChild(); } Node condition; Node prestatement; if (guard.isNormalBlock()) { prestatement = guard.removeFirstChild(); condition = guard.removeFirstChild(); } else { prestatement = IR.block(); condition = guard; } int loopBeginState = generatorCaseCount++; int continueState = loopBeginState; if (!incr.isEmpty()) { continueState = generatorCaseCount++; Node continueCase = makeGeneratorMarker(continueState); body.addChildToBack(continueCase); body.addChildToBack(incr.isNormalBlock() ? incr : IR.exprResult(incr)); } currentLoopContext.add(0, new LoopContext(generatorCaseCount, continueState, label)); Node beginCase = makeGeneratorMarker(loopBeginState); Node conditionalBranch = IR.ifNode(condition.isEmpty() ? withTrueType(IR.trueNode()) : condition, body); Node setStateLoopStart = createStateUpdate(loopBeginState); Node breakToStart = createSafeBreak(); originalGeneratorBody.addChildToFront(conditionalBranch); if (!prestatement.isEmpty()) { originalGeneratorBody.addChildToFront(prestatement); } originalGeneratorBody.addChildToFront(beginCase); if (!initializer.isEmpty()) { originalGeneratorBody.addChildToFront(initializer); } body.addChildToBack(setStateLoopStart); body.addChildToBack(breakToStart); } /** * Hoists {@code var} statements into the closure containing the iterator * to preserve their state across * multiple calls to next(). */ private void visitVar() { Node name = currentStatement.removeFirstChild(); while (name != null) { if (name.hasChildren()) { enclosingBlock.addChildToBack( IR.exprResult(withType(IR.assign(name, name.removeFirstChild()), name.getTypeI()))); } hoistRoot.getParent().addChildAfter(IR.var(name.cloneTree()), hoistRoot); // name now refers to "generated" assignment which is not visible to end user. Don't index it. name.makeNonIndexable(); name = currentStatement.removeFirstChild(); } } /** * Translates {@code yield} to set the state so that execution resume at the next statement * when the function is next called and then returns an iterator result with * the desired value. */ private void visitYieldExprResult() { enclosingBlock.addChildToBack(createStateUpdate()); Node yield = currentStatement.getFirstChild(); Node value = yield.hasChildren() ? yield.removeFirstChild() : withUndefinedType(IR.name("undefined")); enclosingBlock.addChildToBack(IR.returnNode(createIteratorResult(value, false))); } /** * Translates {@code return} statements to set the state to done before returning the * desired value. */ private void visitReturn() { enclosingBlock.addChildToBack(createStateUpdate(-1)); enclosingBlock.addChildToBack( IR.returnNode( createIteratorResult( currentStatement.hasChildren() ? currentStatement.removeFirstChild() : withUndefinedType(IR.name("undefined")), true))); } private Node createStateUpdate() { return IR.exprResult( withNumberType( IR.assign( withNumberType(IR.name(GENERATOR_STATE)), withNumberType(IR.number(generatorCaseCount))))); } private Node createStateUpdate(int state) { return IR.exprResult( withNumberType( IR.assign(withNumberType(IR.name(GENERATOR_STATE)), withNumberType(IR.number(state))))); } private Node createIteratorResult(Node value, boolean done) { TypeI iIterableResultType = createGenericType(JSTypeNative.I_ITERABLE_RESULT_TYPE, value.getTypeI()); return withType( IR.objectlit( IR.propdef(IR.stringKey("value"), value), IR.propdef( IR.stringKey("done"), done ? withTrueType(IR.trueNode()) : withFalseType(IR.falseNode()))), iIterableResultType); } private static Node createSafeBreak() { Node breakNode = IR.breakNode(); breakNode.setGeneratorSafe(true); return breakNode; } private Node createFinallyJumpBlock(Node finallyName, int finallyStartState) { int jumpPoint = generatorCaseCount++; Node setReturnState = IR.exprResult( withNumberType( IR.assign(finallyName.cloneTree(), withNumberType(IR.number(jumpPoint))))); Node toFinally = createStateUpdate(finallyStartState); Node returnPoint = makeGeneratorMarker(jumpPoint); Node returnBlock = IR.block(setReturnState, toFinally, createSafeBreak()); returnBlock.addChildToBack(returnPoint); return returnBlock; } private LoopContext getLoopContext(String label) { for (LoopContext context : currentLoopContext) { if (label.equals(context.label)) { return context; } } return null; } private boolean controlCanExit(Node n) { ControlExitsCheck exits = new ControlExitsCheck(); NodeTraversal.traverseEs6(compiler, n, exits); return exits.didExit(); } /** * Finds the only child of the {@code node} of the given type. */ private Node getUnique(Node node, Token type) { List matches = new ArrayList<>(); insertAll(node, type, matches); checkState(matches.size() == 1, matches); return matches.get(0); } /** * Adds all children of the {@code node} of the given type to given list. */ private void insertAll(Node node, Token type, List matchingNodes) { if (node.getToken() == type) { matchingNodes.add(node); } for (Node c = node.getFirstChild(); c != null; c = c.getNext()) { insertAll(c, type, matchingNodes); } } /** * Decomposes expressions with yields inside of them to equivalent * sequence of expressions in which all non-statement yields are * of the form: * *
   *   var name = yield expr;
   * 
* *

For example, change the following code: *

   *   return x || yield y;
   * 
*

Into: *

   *  var temp$$0;
   *  if (temp$$0 = x); else temp$$0 = yield y;
   *  return temp$$0;
   * 
* * This uses the {@link ExpressionDecomposer} class */ private final class DecomposeYields extends NodeTraversal.AbstractPreOrderCallback { private final AbstractCompiler compiler; private final ExpressionDecomposer decomposer; DecomposeYields(AbstractCompiler compiler) { this.compiler = compiler; Set consts = new HashSet<>(); decomposer = new ExpressionDecomposer( compiler, compiler.getUniqueNameIdSupplier(), consts, Scope.createGlobalScope(new Node(Token.SCRIPT)), compiler.getOptions().allowMethodCallDecomposing()); } @Override public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { case YIELD: visitYieldExpression(t, n); break; case DO: case FOR: case WHILE: visitLoop(t, n); break; case CASE: if (controlCanExit(n.getFirstChild())) { compiler.report( JSError.make( n, Es6ToEs3Util.CANNOT_CONVERT_YET, "Case statements that contain yields")); return false; } break; default: break; } return true; } private void visitYieldExpression(NodeTraversal t, Node n) { if (n.getParent().isExprResult()) { return; } if (decomposer.canExposeExpression(n) != ExpressionDecomposer.DecompositionType.UNDECOMPOSABLE) { decomposer.exposeExpression(n); t.reportCodeChange(); } else { String link = "https://github.com/google/closure-compiler/wiki/FAQ" + "#i-get-an-undecomposable-expression-error-for-my-yield-or-await-expression" + "-what-do-i-do"; String suggestion = "Please rewrite the yield or await as a separate statement."; String message = "Undecomposable expression: " + suggestion + "\nSee " + link; compiler.report(JSError.make(n, Es6ToEs3Util.CANNOT_CONVERT, message)); } } private void visitLoop(NodeTraversal t, Node n) { Node enclosingFunc = NodeUtil.getEnclosingFunction(n); if (enclosingFunc == null || !enclosingFunc.isGeneratorFunction() || n.isForIn()) { return; } Node enclosingBlock = NodeUtil.getEnclosingBlock(n); Node guard = null; Node incr = null; switch (n.getToken()) { case FOR: guard = n.getSecondChild(); incr = guard.getNext(); break; case WHILE: guard = n.getFirstChild(); incr = IR.empty(); break; case DO: guard = n.getLastChild(); if (!guard.isEmpty()) { Node firstEntry = IR.name(GENERATOR_DO_WHILE_INITIAL); enclosingBlock.addChildToFront( IR.var(firstEntry.cloneTree(), withTrueType(IR.trueNode()))); guard = withBooleanType(IR.or(firstEntry, n.getLastChild().detach())); n.addChildToBack(guard); } incr = IR.empty(); break; default: break; } if (!controlCanExit(guard) && !controlCanExit(incr)) { return; } Node guardName = IR.name(GENERATOR_LOOP_GUARD + generatorCounter.get()); if (!guard.isEmpty()) { Node container = new Node(Token.BLOCK); n.replaceChild(guard, container); container.addChildToFront( IR.block( IR.exprResult( withType( IR.assign(guardName.cloneTree(), guard.cloneTree()), guard.getTypeI())))); container.addChildToBack(guardName.cloneTree()); } if (!incr.isEmpty()) { n.addChildBefore(IR.block(IR.exprResult(incr.detach())), n.getLastChild()); } enclosingBlock.addChildToFront(IR.var(guardName)); t.reportCodeChange(); } } private Node makeGeneratorMarker(int i) { Node n = IR.exprResult(withNumberType(IR.number(i))); n.setGeneratorMarker(true); return n; } private final class ControlExitsCheck implements NodeTraversal.Callback { int continueCatchers; int breakCatchers; int throwCatchers; List labels = new ArrayList<>(); boolean exited; boolean addJumps; private Node finallyName; private int finallyStartState; ControlExitsCheck(Node finallyName, int finallyStartState) { this.finallyName = finallyName; this.finallyStartState = finallyStartState; addJumps = true; } ControlExitsCheck() { addJumps = false; } @Override public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) { switch (n.getToken()) { case FUNCTION: return false; case LABEL: labels.add(0, n.getFirstChild().getString()); break; case DO: case WHILE: case FOR: case FOR_IN: continueCatchers++; breakCatchers++; break; case SWITCH: breakCatchers++; break; case BLOCK: parent = n.getParent(); if (parent != null && parent.isTry() && parent.getFirstChild() == n && n.getNext().hasChildren()) { throwCatchers++; } break; case BREAK: if (!n.isGeneratorSafe() && ((breakCatchers == 0 && !n.hasChildren()) || (n.hasChildren() && !labels.contains(n.getFirstChild().getString())))) { exited = true; if (addJumps) { parent.addChildBefore(createFinallyJumpBlock(finallyName, finallyStartState), n); } } break; case CONTINUE: if (continueCatchers == 0 || (n.hasChildren() && !labels.contains(n.getFirstChild().getString()))) { exited = true; if (addJumps) { parent.addChildBefore(createFinallyJumpBlock(finallyName, finallyStartState), n); } } break; case THROW: if (throwCatchers == 0) { exited = true; if (addJumps && !n.isGeneratorSafe()) { parent.addChildBefore(createFinallyJumpBlock(finallyName, finallyStartState), n); } } break; case RETURN: exited = true; if (addJumps) { parent.addChildBefore(createFinallyJumpBlock(finallyName, finallyStartState), n); } break; case YIELD: exited = true; break; default: break; } return true; } @Override public void visit(NodeTraversal t, Node n, Node parent) { switch (n.getToken()) { case LABEL: labels.remove(0); break; case DO: case WHILE: case FOR: case FOR_IN: continueCatchers--; breakCatchers--; break; case SWITCH: breakCatchers--; break; case BLOCK: parent = n.getParent(); if (parent != null && parent.isTry() && parent.getFirstChild() == n && n.getNext().hasChildren()) { throwCatchers--; } break; default: break; } } public boolean didExit() { return exited; } } private static final class LoopContext { int breakCase; int continueCase; String label; LoopContext(int breakCase, int continueCase, String label) { this.breakCase = breakCase; this.continueCase = continueCase; this.label = label; } } private static final class ExceptionContext { int catchStartCase; Node catchBlock; ExceptionContext(int catchStartCase, Node catchBlock) { this.catchStartCase = catchStartCase; this.catchBlock = catchBlock; } } /** * Preloads the skeleton AST function that is needed for generators, * reports change to enclosing scope, and returns it. * If the skeleton is already preloaded, does not do anything, just returns the node. */ static Node preloadGeneratorSkeletonAndReportChange(AbstractCompiler compiler) { return preloadGeneratorSkeleton(compiler, true); } /** * Preloads the skeleton AST function that is needed for generators and returns it. * If the skeleton is already preloaded, does not do anything, just returns the node. * reportChange tells the function whether to report a code change in the enclosing scope. * * Because validity checks happen between passes, we need to report the change if the generator * was preloaded in the {@link EarlyEs6ToEs3Converter} class. * However, if the generator was preloaded in this {@link Es6RewriteGenerators} class, we do not * want to report the change since it will be removed by {@link #cleanUpGeneratorSkeleton} */ private static Node preloadGeneratorSkeleton(AbstractCompiler compiler, boolean reportChange) { Node root = compiler.getJsRoot(); Node generatorFunc = getPreloadedGeneratorFunc(root); if (generatorFunc != null) { return generatorFunc; } Node genFunc = compiler.parseSyntheticCode(Joiner.on('\n').join( "function " + GENERATOR_PRELOAD_FUNCTION_NAME + "() {", " var " + GENERATOR_STATE + " = 0;", " function $jscomp$generator$impl(", " " + GENERATOR_ACTION_ARG + ",", " " + GENERATOR_NEXT_ARG + ",", " " + GENERATOR_THROW_ARG + ") {", " while (1) switch (" + GENERATOR_STATE + ") {", " case 0:", " default:", " return {value: undefined, done: true};", " }", " }", // TODO(tbreisacher): Remove this cast if we start returning an actual // Generator object. " var iterator = /** @type {!Generator} */ ({", " next: function(arg) {", " return $jscomp$generator$impl(" + GENERATOR_ACTION_NEXT + ", arg, undefined);", " },", " throw: function(arg) {", " return $jscomp$generator$impl(" + GENERATOR_ACTION_THROW + ", undefined, arg);", " },", // TODO(tbreisacher): Implement Generator.return: // http://www.ecma-international.org/ecma-262/6.0/#sec-generator.prototype.return " return: function(arg) { throw Error('Not yet implemented'); },", " });", " $jscomp.initSymbolIterator();", " /** @this {!Generator} */", " iterator[Symbol.iterator] = function() { return this; };", " return iterator;", "}")) .getFirstChild() // function .detach(); root.getFirstChild().addChildToFront(genFunc); if (reportChange) { NodeUtil.markNewScopesChanged(genFunc, compiler); compiler.reportChangeToEnclosingScope(genFunc); } return genFunc; } /** * Add types to key nodes in the generator AST created by {@link #preloadGeneratorSkeleton} For * example, changes {@code Generator} to {@code Generator}, where yieldType is the * inferred yield type of the original user-defined generator function. */ private void addTypesToGeneratorSkeleton(Node genBlock, TypeI yieldType) { TypeI generatorType = createGenericType(JSTypeNative.GENERATOR_TYPE, yieldType); TypeI iIterableResultType = createGenericType(JSTypeNative.I_ITERABLE_RESULT_TYPE, yieldType); // Add type to the generator implementation function node. Node impl = genBlock.getSecondChild(); checkState(impl.isFunction()); FunctionTypeI implFuncType = impl.getTypeI().toMaybeFunctionType(); implFuncType = implFuncType.toBuilder().withReturnType(iIterableResultType).build(); impl.setTypeI(implFuncType); impl.getFirstChild().setTypeI(implFuncType); Node objectLit = impl.getChildAtIndex(2) .getFirstChild() .getSecondChild() .getFirstChild() .getChildAtIndex(2) .getFirstFirstChild() // RETURN node in default case .getFirstChild(); checkState(objectLit.isObjectLit()); objectLit.setTypeI(iIterableResultType); objectLit.getFirstChild().setTypeI(iIterableResultType); // Add type to the var iterator = {next: function (...) {...}, throw: ..., return: ... } node Node iteratorVar = impl.getNext(); checkState(iteratorVar.isVar()); iteratorVar.getFirstChild().setTypeI(generatorType); iteratorVar.getFirstFirstChild().setTypeI(generatorType); iteratorVar.getFirstFirstChild().getFirstChild().setTypeI(generatorType); Node next = iteratorVar.getFirstFirstChild().getFirstFirstChild(); // String key "next" checkState(next.isStringKey()); FunctionTypeI nextFunctionType = next.getTypeI().toMaybeFunctionType(); nextFunctionType = nextFunctionType.toBuilder().withReturnType(iIterableResultType).build(); next.setTypeI(nextFunctionType); next.getFirstChild().setTypeI(nextFunctionType); // CALL node of function $jscomp$generator$impl within RETURN node in function of "next" Node call = next.getFirstChild().getChildAtIndex(2).getFirstFirstChild(); checkState(call.isCall()); call.setTypeI(iIterableResultType); Node genImplName = call.getFirstChild(); checkState(genImplName.isName()); FunctionTypeI genImplType = genImplName.getTypeI().toMaybeFunctionType(); genImplType = genImplType.toBuilder().withReturnType(iIterableResultType).build(); genImplName.setTypeI(genImplType); // Add type to the iterator[Symbol.iterator] = function () { return this; } node Node exprResult = iteratorVar.getNext().getNext(); checkState(exprResult.isExprResult()); FunctionTypeI funcType = exprResult.getFirstChild().getTypeI().toMaybeFunctionType(); // Set function type to be function(this:Generator):Generator funcType = funcType.toBuilder().withReturnType(iIterableResultType).build(); exprResult.getFirstChild().setTypeI(funcType); exprResult.getFirstFirstChild().setTypeI(funcType); exprResult.getFirstFirstChild().getFirstChild().setTypeI(generatorType); exprResult.getFirstChild().getSecondChild().setTypeI(funcType); // FUNCTION node exprResult .getFirstChild() .getSecondChild() .getChildAtIndex(2) .getFirstFirstChild() // THIS node .setTypeI(generatorType); // Add type to the final return node of genBlock exprResult.getNext().getFirstChild().setTypeI(generatorType); } /** Returns the generator function that was preloaded, or null if not found. */ @Nullable private static Node getPreloadedGeneratorFunc(Node root) { if (root.getFirstChild() == null) { return null; } for (Node c = root.getFirstFirstChild(); c != null; c = c.getNext()) { if (c.isFunction() && GENERATOR_PRELOAD_FUNCTION_NAME.equals(c.getFirstChild().getString())) { return c; } } return null; } /** * Delete the preloaded generator function, and report code change if reportChange is true. * * We only want to reportChange if the generator function was preloaded in the * {@link EarlyEs6ToEs3Converter} class, since a change was reported there. * If we preload the generator function in this class, it will be an addition and deletion of the * same node, which means we do not have to report code change in either case since the code was * ultimately not changed. */ private void cleanUpGeneratorSkeleton(boolean reportChange) { Node genFunc = getPreloadedGeneratorFunc(compiler.getJsRoot()); if (genFunc != null) { if (reportChange) { NodeUtil.deleteNode(genFunc, compiler); } else { genFunc.detach(); } } } private TypeI createGenericType(JSTypeNative typeName, TypeI typeArg) { return Es6ToEs3Util.createGenericType(addTypes, registry, typeName, typeArg); } private Node withStringType(Node n) { return withType(n, stringType); } private Node withBooleanType(Node n) { return withType(n, booleanType); } private Node withFalseType(Node n) { return withType(n, falseType); } private Node withTrueType(Node n) { return withType(n, trueType); } private Node withUnknownType(Node n) { return withType(n, unknownType); } private Node withNumberType(Node n) { return withType(n, numberType); } private Node withUndefinedType(Node n) { return withType(n, undefinedType); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy