Ep.explodeExpression = function()

in packages/transform/src/emit.js [951:1271]


Ep.explodeExpression = function(path, ignoreResult) {
  const t = util.getTypes();
  let expr = path.node;
  if (expr) {
    t.assertExpression(expr);
  } else {
    return expr;
  }

  let self = this;
  let result; // Used optionally by several cases below.
  let after;

  function finish(expr) {
    t.assertExpression(expr);
    if (ignoreResult) {
      self.emit(expr);
    }
    return expr;
  }

  // If the expression does not contain a leap, then we either emit the
  // expression as a standalone statement or return it whole.
  if (!meta.containsLeap(expr)) {
    return finish(expr);
  }

  // If any child contains a leap (such as a yield or labeled continue or
  // break statement), then any sibling subexpressions will almost
  // certainly have to be exploded in order to maintain the order of their
  // side effects relative to the leaping child(ren).
  let hasLeapingChildren = meta.containsLeap.onlyChildren(expr);

  // If ignoreResult is true, then we must take full responsibility for
  // emitting the expression with all its side effects, and we should not
  // return a result.

  switch (expr.type) {
  case "MemberExpression":
    return finish(t.memberExpression(
      self.explodeExpression(path.get("object")),
      expr.computed
        ? self.explodeViaTempVar(null, path.get("property"), hasLeapingChildren)
        : expr.property,
      expr.computed
    ));

  case "CallExpression":
    let calleePath = path.get("callee");
    let argsPath = path.get("arguments");

    let newCallee;
    let newArgs;

    let hasLeapingArgs = argsPath.some(
      argPath => meta.containsLeap(argPath.node)
    );

    let injectFirstArg = null;

    if (t.isMemberExpression(calleePath.node)) {
      if (hasLeapingArgs) {
        // If the arguments of the CallExpression contained any yield
        // expressions, then we need to be sure to evaluate the callee
        // before evaluating the arguments, but if the callee was a member
        // expression, then we must be careful that the object of the
        // member expression still gets bound to `this` for the call.

        let newObject = self.explodeViaTempVar(
          // Assign the exploded callee.object expression to a temporary
          // variable so that we can use it twice without reevaluating it.
          self.makeTempVar(),
          calleePath.get("object"),
          hasLeapingChildren
        );

        let newProperty = calleePath.node.computed
          ? self.explodeViaTempVar(null, calleePath.get("property"), hasLeapingChildren)
          : calleePath.node.property;

        injectFirstArg = newObject;

        newCallee = t.memberExpression(
          t.memberExpression(
            t.cloneDeep(newObject),
            newProperty,
            calleePath.node.computed
          ),
          t.identifier("call"),
          false
        );

      } else {
        newCallee = self.explodeExpression(calleePath);
      }

    } else {
      newCallee = self.explodeViaTempVar(null, calleePath, hasLeapingChildren);

      if (t.isMemberExpression(newCallee)) {
        // If the callee was not previously a MemberExpression, then the
        // CallExpression was "unqualified," meaning its `this` object
        // should be the global object. If the exploded expression has
        // become a MemberExpression (e.g. a context property, probably a
        // temporary variable), then we need to force it to be unqualified
        // by using the (0, object.property)(...) trick; otherwise, it
        // will receive the object of the MemberExpression as its `this`
        // object.
        newCallee = t.sequenceExpression([
          t.numericLiteral(0),
          t.cloneDeep(newCallee)
        ]);
      }
    }

    if (hasLeapingArgs) {
      newArgs = argsPath.map(argPath => self.explodeViaTempVar(null, argPath, hasLeapingChildren));
      if (injectFirstArg) newArgs.unshift(injectFirstArg);

      newArgs = newArgs.map(arg => t.cloneDeep(arg));
    } else {
      newArgs = path.node.arguments;
    }

    return finish(t.callExpression(newCallee, newArgs));

  case "NewExpression":
    return finish(t.newExpression(
     self.explodeViaTempVar(null, path.get("callee"), hasLeapingChildren),
       path.get("arguments").map(function(argPath) {
        return self.explodeViaTempVar(null, argPath, hasLeapingChildren);
      })
    ));

  case "ObjectExpression":
    return finish(t.objectExpression(
      path.get("properties").map(function(propPath) {
        if (propPath.isObjectProperty()) {
          return t.objectProperty(
            propPath.node.key,
            self.explodeViaTempVar(null, propPath.get("value"), hasLeapingChildren),
            propPath.node.computed
          );
        } else {
          return propPath.node;
        }
      })
    ));

  case "ArrayExpression":
    return finish(t.arrayExpression(
      path.get("elements").map(function(elemPath) {
        if (elemPath.isSpreadElement()) {
          return t.spreadElement(
            self.explodeViaTempVar(null, elemPath.get("argument"), hasLeapingChildren)
          );
        } else {
          return self.explodeViaTempVar(null, elemPath, hasLeapingChildren);
        }
      })
    ));

  case "SequenceExpression":
    let lastIndex = expr.expressions.length - 1;

    path.get("expressions").forEach(function(exprPath) {
      if (exprPath.key === lastIndex) {
        result = self.explodeExpression(exprPath, ignoreResult);
      } else {
        self.explodeExpression(exprPath, true);
      }
    });

    return result;

  case "LogicalExpression":
    after = this.loc();

    if (!ignoreResult) {
      result = self.makeTempVar();
    }

    let left = self.explodeViaTempVar(result, path.get("left"), hasLeapingChildren);

    if (expr.operator === "&&") {
      self.jumpIfNot(left, after);
    } else {
      assert.strictEqual(expr.operator, "||");
      self.jumpIf(left, after);
    }

    self.explodeViaTempVar(result, path.get("right"), hasLeapingChildren, ignoreResult);

    self.mark(after);

    return result;

  case "ConditionalExpression":
    let elseLoc = this.loc();
    after = this.loc();
    let test = self.explodeExpression(path.get("test"));

    self.jumpIfNot(test, elseLoc);

    if (!ignoreResult) {
      result = self.makeTempVar();
    }

    self.explodeViaTempVar(result, path.get("consequent"), hasLeapingChildren, ignoreResult);
    self.jump(after);

    self.mark(elseLoc);
    self.explodeViaTempVar(result, path.get("alternate"), hasLeapingChildren, ignoreResult);

    self.mark(after);

    return result;

  case "UnaryExpression":
    return finish(t.unaryExpression(
      expr.operator,
      // Can't (and don't need to) break up the syntax of the argument.
      // Think about delete a[b].
      self.explodeExpression(path.get("argument")),
      !!expr.prefix
    ));

  case "BinaryExpression":
    return finish(t.binaryExpression(
      expr.operator,
      self.explodeViaTempVar(null, path.get("left"), hasLeapingChildren),
      self.explodeViaTempVar(null, path.get("right"), hasLeapingChildren)
    ));

  case "AssignmentExpression":
    if (expr.operator === "=") {
      // If this is a simple assignment, the left hand side does not need
      // to be read before the right hand side is evaluated, so we can
      // avoid the more complicated logic below.
      return finish(t.assignmentExpression(
        expr.operator,
        self.explodeExpression(path.get("left")),
        self.explodeExpression(path.get("right"))
      ));
    }

    const lhs = self.explodeExpression(path.get("left"));
    const temp = self.emitAssign(self.makeTempVar(), lhs);

    // For example,
    //
    //   x += yield y
    //
    // becomes
    //
    //   context.t0 = x
    //   x = context.t0 += yield y
    //
    // so that the left-hand side expression is read before the yield.
    // Fixes https://github.com/facebook/regenerator/issues/345.

    return finish(t.assignmentExpression(
      "=",
      t.cloneDeep(lhs),
      t.assignmentExpression(
        expr.operator,
        t.cloneDeep(temp),
        self.explodeExpression(path.get("right"))
      )
    ));

  case "UpdateExpression":
    return finish(t.updateExpression(
      expr.operator,
      self.explodeExpression(path.get("argument")),
      expr.prefix
    ));

  case "YieldExpression":
    after = this.loc();
    let arg = expr.argument && self.explodeExpression(path.get("argument"));

    if (arg && expr.delegate) {
      let result = self.makeTempVar();

      let ret = t.returnStatement(t.callExpression(
        self.contextProperty("delegateYield"),
        [
          arg,
          t.stringLiteral(result.property.name),
          after
        ]
      ));
      ret.loc = expr.loc;

      self.emit(ret);
      self.mark(after);

      return result;
    }

    self.emitAssign(self.contextProperty("next"), after);

    let ret = t.returnStatement(t.cloneDeep(arg) || null);
    // Preserve the `yield` location so that source mappings for the statements
    // link back to the yield properly.
    ret.loc = expr.loc;
    self.emit(ret);
    self.mark(after);

    return self.contextProperty("sent");

  case "ClassExpression":
    return finish(self.explodeClass(path));

  default:
    throw new Error(
      "unknown Expression of type " +
        JSON.stringify(expr.type));
  }
};