"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});

exports.default = function (ast, strictCode, env, realm) {
  // evaluate left
  let lref = env.evaluate(ast.left, strictCode);
  let lval = _singletons.Environment.GetValue(realm, lref);

  // evaluate right
  let rref = env.evaluate(ast.right, strictCode);
  let rval = _singletons.Environment.GetValue(realm, rref);

  return computeBinary(realm, ast.operator, lval, rval, ast.left.loc, ast.right.loc, ast.loc);
};

exports.getPureBinaryOperationResultType = getPureBinaryOperationResultType;
exports.computeBinary = computeBinary;

var _index = require("../domains/index.js");

var _errors = require("../errors.js");

var _index2 = require("../values/index.js");

var _completions = require("../completions.js");

var _singletons = require("../singletons.js");

var _babelTypes = require("babel-types");

var t = _interopRequireWildcard(_babelTypes);

var _invariant = require("../invariant.js");

var _invariant2 = _interopRequireDefault(_invariant);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

let unknownValueOfOrToString = "might be an object with an unknown valueOf or toString or Symbol.toPrimitive method";

// Returns result type if binary operation is pure (terminates, does not throw exception, does not read or write heap), otherwise undefined.
/**
 * Copyright (c) 2017-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

function getPureBinaryOperationResultType(realm, op, lval, rval, lloc, rloc) {
  function reportErrorIfNotPure(purityTest, typeIfPure) {
    let leftPure = purityTest(realm, lval);
    let rightPure = purityTest(realm, rval);
    if (leftPure && rightPure) return typeIfPure;
    let loc = !leftPure ? lloc : rloc;
    let error = new _errors.CompilerDiagnostic(unknownValueOfOrToString, loc, "PP0002", "RecoverableError");
    if (realm.handleError(error) === "Recover") {
      // Assume that an unknown value is actually a primitive or otherwise a well behaved object.
      return typeIfPure;
    }
    throw new _errors.FatalError();
  }
  if (op === "+") {
    let ltype = _singletons.To.GetToPrimitivePureResultType(realm, lval);
    let rtype = _singletons.To.GetToPrimitivePureResultType(realm, rval);
    if (ltype === undefined || rtype === undefined) {
      let loc = ltype === undefined ? lloc : rloc;
      let error = new _errors.CompilerDiagnostic(unknownValueOfOrToString, loc, "PP0002", "RecoverableError");
      if (realm.handleError(error) === "Recover") {
        // Assume that the unknown value is actually a primitive or otherwise a well behaved object.
        ltype = lval.getType();
        rtype = rval.getType();
        if (ltype === _index2.StringValue || rtype === _index2.StringValue) return _index2.StringValue;
        if (ltype === _index2.IntegralValue && rtype === _index2.IntegralValue) return _index2.IntegralValue;
        if ((ltype === _index2.NumberValue || ltype === _index2.IntegralValue) && (rtype === _index2.NumberValue || rtype === _index2.IntegralValue)) return _index2.NumberValue;

        return _index2.Value;
      }
      throw new _errors.FatalError();
    }
    if (ltype === _index2.StringValue || rtype === _index2.StringValue) return _index2.StringValue;
    return _index2.NumberValue;
  } else if (op === "<" || op === ">" || op === ">=" || op === "<=") {
    return reportErrorIfNotPure(_singletons.To.IsToPrimitivePure.bind(_singletons.To), _index2.BooleanValue);
  } else if (op === "!=" || op === "==") {
    let ltype = lval.getType();
    let rtype = rval.getType();
    if (ltype === _index2.NullValue || ltype === _index2.UndefinedValue || rtype === _index2.NullValue || rtype === _index2.UndefinedValue) return _index2.BooleanValue;
    return reportErrorIfNotPure(_singletons.To.IsToPrimitivePure.bind(_singletons.To), _index2.BooleanValue);
  } else if (op === "===" || op === "!==") {
    return _index2.BooleanValue;
  } else if (op === ">>>" || op === "<<" || op === ">>" || op === "&" || op === "|" || op === "^" || op === "**" || op === "%" || op === "/" || op === "*" || op === "-") {
    return reportErrorIfNotPure(_singletons.To.IsToNumberPure.bind(_singletons.To), _index2.NumberValue);
  } else if (op === "in" || op === "instanceof") {
    if (rval.mightNotBeObject()) {
      let error = new _errors.CompilerDiagnostic(`might not be an object, hence the ${op} operator might throw a TypeError`, rloc, "PP0003", "RecoverableError");
      if (realm.handleError(error) === "Recover") {
        // Assume that the object is actually a well behaved object.
        return _index2.BooleanValue;
      }
      throw new _errors.FatalError();
    }
    if (!rval.mightNotBeObject()) {
      // Simple object won't throw here, aren't proxy objects or typed arrays and do not have @@hasInstance properties.
      if (rval.isSimpleObject()) return _index2.BooleanValue;
    }
    let error = new _errors.CompilerDiagnostic(`might be an object that behaves badly for the ${op} operator`, rloc, "PP0004", "RecoverableError");
    if (realm.handleError(error) === "Recover") {
      // Assume that the object is actually a well behaved object.
      return _index2.BooleanValue;
    }
    throw new _errors.FatalError();
  }
  (0, _invariant2.default)(false, "unimplemented " + op);
}

function computeBinary(realm, op, lval, rval, lloc, rloc, loc) {
  // partial evaluation shortcut for a particular pattern
  if (realm.useAbstractInterpretation && (op === "==" || op === "===" || op === "!=" || op === "!==")) {
    if (!lval.mightNotBeObject() && (rval instanceof _index2.NullValue || rval instanceof _index2.UndefinedValue) || (lval instanceof _index2.NullValue || lval instanceof _index2.UndefinedValue) && !rval.mightNotBeObject()) {
      return new _index2.BooleanValue(realm, op[0] !== "=");
    }
  }

  let resultType;
  const compute = () => {
    if (lval instanceof _index2.AbstractValue || rval instanceof _index2.AbstractValue) {
      // generate error if binary operation might throw or have side effects
      resultType = getPureBinaryOperationResultType(realm, op, lval, rval, lloc, rloc);
      return _index2.AbstractValue.createFromBinaryOp(realm, op, lval, rval, loc);
    } else {
      // ECMA262 12.10.3

      // 5. If Type(rval) is not Object, throw a TypeError exception.
      if (op === "in" && !(rval instanceof _index2.ObjectValue)) {
        throw realm.createErrorThrowCompletion(realm.intrinsics.TypeError);
      }
      (0, _invariant2.default)(lval instanceof _index2.ConcreteValue);
      (0, _invariant2.default)(rval instanceof _index2.ConcreteValue);
      const result = _index.ValuesDomain.computeBinary(realm, op, lval, rval);
      resultType = result.getType();
      return result;
    }
  };

  if (realm.isInPureScope()) {
    // If we're in pure mode we can recover even if this operation might not be pure.
    // To do that, we'll temporarily override the error handler.
    const previousErrorHandler = realm.errorHandler;
    let isPure = true;
    realm.errorHandler = diagnostic => {
      isPure = false;
      return "Recover";
    };
    let effects;
    try {
      effects = realm.evaluateForEffects(compute, undefined, "computeBinary");
    } catch (x) {
      if (x instanceof _errors.FatalError) {
        isPure = false;
      } else {
        throw x;
      }
    } finally {
      realm.errorHandler = previousErrorHandler;
    }

    if (isPure && effects) {
      let completion = effects[0];
      if (completion instanceof _completions.PossiblyNormalCompletion) {
        // in this case one of the branches may complete abruptly, which means that
        // not all control flow branches join into one flow at this point.
        // Consequently we have to continue tracking changes until the point where
        // all the branches come together into one.
        completion = realm.composeWithSavedCompletion(completion);
      }
      // Note that the effects of (non joining) abrupt branches are not included
      // in effects, but are tracked separately inside completion.
      realm.applyEffects(effects);
      // return or throw completion
      if (completion instanceof _completions.AbruptCompletion) throw completion;
      (0, _invariant2.default)(completion instanceof _index2.Value);
      return completion;
    }

    // If this ended up reporting an error, it might not be pure, so we'll leave it in
    // as a temporal operation with a known return type.
    // Some of these values may trigger side-effectful user code such as valueOf.
    // To be safe, we have to Havoc them.
    _singletons.Havoc.value(realm, lval, loc);
    if (op !== "in") {
      // The "in" operator have side-effects on its right val other than throw.
      _singletons.Havoc.value(realm, rval, loc);
    }
    return realm.evaluateWithPossibleThrowCompletion(() => _index2.AbstractValue.createTemporalFromBuildFunction(realm, resultType, [lval, rval], ([lnode, rnode]) => t.binaryExpression(op, lnode, rnode)), _index.TypesDomain.topVal, _index.ValuesDomain.topVal);
  }
  return compute();
}
//# sourceMappingURL=BinaryExpression.js.map