"use strict";

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

class PathImplementation {
  implies(condition) {
    if (!condition.mightNotBeTrue()) return true; // any path implies true
    let path = condition.$Realm.pathConditions;
    for (let i = path.length - 1; i >= 0; i--) {
      let pathCondition = path[i];
      if (pathCondition.implies(condition)) return true;
    }
    return false;
  }

  impliesNot(condition) {
    if (!condition.mightNotBeFalse()) return true; // any path implies !false
    let path = condition.$Realm.pathConditions;
    for (let i = path.length - 1; i >= 0; i--) {
      let pathCondition = path[i];
      if (pathCondition.impliesNot(condition)) return true;
    }
    return false;
  }

  withCondition(condition, evaluate) {
    let realm = condition.$Realm;
    let savedPath = realm.pathConditions;
    realm.pathConditions = [];
    try {
      pushPathCondition(condition);
      pushRefinedConditions(condition, savedPath);
      return evaluate();
    } catch (e) {
      if (e instanceof _errors.InfeasiblePathError) {
        // if condition is true, one of the saved path conditions must be false
        // since we have to assume that those conditions are true we now know that on this path, condition is false
        realm.pathConditions = savedPath;
        pushInversePathCondition(condition);
      }
      throw e;
    } finally {
      realm.pathConditions = savedPath;
    }
  }

  withInverseCondition(condition, evaluate) {
    let realm = condition.$Realm;
    let savedPath = realm.pathConditions;
    realm.pathConditions = [];
    try {
      pushInversePathCondition(condition);
      pushRefinedConditions(condition, savedPath);
      return evaluate();
    } catch (e) {
      if (e instanceof _errors.InfeasiblePathError) {
        // if condition is false, one of the saved path conditions must be false
        // since we have to assume that those conditions are true we now know that on this path, condition is true
        realm.pathConditions = savedPath;
        pushPathCondition(condition);
      }
      throw e;
    } finally {
      realm.pathConditions = savedPath;
    }
  }

  pushAndRefine(condition) {
    let realm = condition.$Realm;
    let savedPath = realm.pathConditions;
    realm.pathConditions = [];

    pushPathCondition(condition);
    pushRefinedConditions(condition, savedPath);
  }

  pushInverseAndRefine(condition) {
    let realm = condition.$Realm;
    let savedPath = realm.pathConditions;
    realm.pathConditions = [];

    pushInversePathCondition(condition);
    pushRefinedConditions(condition, savedPath);
  }
}

exports.PathImplementation = PathImplementation; // A path condition is an abstract value that must be true in this particular code path, so we want to assume as much
/**
 * 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 pushPathCondition(condition) {
  (0, _invariant2.default)(condition.mightNotBeFalse(), "pushing false"); // it is mistake to assume that false is true
  if (condition instanceof _index.ConcreteValue) return;
  if (!condition.mightNotBeTrue()) return;
  (0, _invariant2.default)(condition instanceof _index.AbstractValue);
  let realm = condition.$Realm;
  if (condition.kind === "&&") {
    let left = condition.args[0];
    let right = condition.args[1];
    (0, _invariant2.default)(left instanceof _index.AbstractValue); // it is a mistake to create an abstract value when concrete value will do
    pushPathCondition(left);
    pushPathCondition(right);
  } else {
    if (condition.kind === "!=" || condition.kind === "==") {
      let left = condition.args[0];
      let right = condition.args[1];
      if (left instanceof _index.ConcreteValue && right instanceof _index.AbstractValue) [left, right] = [right, left];
      if (left instanceof _index.AbstractValue && (right instanceof _index.UndefinedValue || right instanceof _index.NullValue)) {
        let op = condition.kind === "!=" ? "!==" : "===";
        if (op === "!==") pushPathCondition(left);else pushInversePathCondition(left);
        let leftNeNull = _index.AbstractValue.createFromBinaryOp(realm, op, left, realm.intrinsics.null);
        if (leftNeNull.mightNotBeFalse()) pushPathCondition(leftNeNull);
        let leftNeUndefined = _index.AbstractValue.createFromBinaryOp(realm, op, left, realm.intrinsics.undefined);
        if (leftNeUndefined.mightNotBeFalse()) pushPathCondition(leftNeUndefined);
        return;
      }
    }
    realm.pathConditions.push(condition);
  }
}

// An inverse path condition is an abstract value that must be false in this particular code path, so we want to assume as much
function pushInversePathCondition(condition) {
  // it is mistake to assume that true is false.
  (0, _invariant2.default)(condition.mightNotBeTrue());
  if (condition instanceof _index.ConcreteValue) return;
  (0, _invariant2.default)(condition instanceof _index.AbstractValue);
  if (condition.kind === "||") {
    let left = condition.args[0];
    let right = condition.args[1];
    (0, _invariant2.default)(left instanceof _index.AbstractValue); // it is a mistake to create an abstract value when concrete value will do
    pushInversePathCondition(left);
    if (right.mightNotBeTrue()) pushInversePathCondition(right);
  } else {
    let realm = condition.$Realm;
    if (condition.kind === "!=" || condition.kind === "==") {
      let left = condition.args[0];
      let right = condition.args[1];
      if (left instanceof _index.ConcreteValue && right instanceof _index.AbstractValue) [left, right] = [right, left];
      if (left instanceof _index.AbstractValue && (right instanceof _index.UndefinedValue || right instanceof _index.NullValue)) {
        let op = condition.kind === "!=" ? "===" : "!==";
        if (op === "!==") pushInversePathCondition(left);else pushPathCondition(left);
        let leftEqNull = _index.AbstractValue.createFromBinaryOp(realm, op, left, realm.intrinsics.null);
        if (leftEqNull.mightNotBeFalse()) pushPathCondition(leftEqNull);
        let leftEqUndefined = _index.AbstractValue.createFromBinaryOp(realm, op, left, realm.intrinsics.undefined);
        if (leftEqUndefined.mightNotBeFalse()) pushPathCondition(leftEqUndefined);
        return;
      }
    }
    let inverseCondition = _index.AbstractValue.createFromUnaryOp(realm, "!", condition);
    pushPathCondition(inverseCondition);
    if (inverseCondition instanceof _index.AbstractValue) {
      let simplifiedInverseCondition = realm.simplifyAndRefineAbstractCondition(inverseCondition);
      if (!simplifiedInverseCondition.equals(inverseCondition)) pushPathCondition(simplifiedInverseCondition);
    }
  }
}

function pushRefinedConditions(condition, unrefinedConditions) {
  let realm = condition.$Realm;
  let refinedConditions = unrefinedConditions.map(c => realm.simplifyAndRefineAbstractCondition(c));
  if (refinedConditions.some(c => !c.mightNotBeFalse())) throw new _errors.InfeasiblePathError();
  let pc = realm.pathConditions;
  realm.pathConditions = [];
  for (let c of refinedConditions) pushPathCondition(c);
  for (let c of pc) realm.pathConditions.push(c);
}
//# sourceMappingURL=paths.js.map