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

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

/*
 * 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 - 2025 Weber Informatics LLC | Privacy Policy