"use strict";

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

var _realm = require("../realm.js");

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

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

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

function simplifyAndRefineAbstractValue(realm, isCondition, // The value is only used after converting it to a Boolean
value) {
  let savedHandler = realm.errorHandler;
  let savedIsReadOnly = realm.isReadOnly;
  realm.isReadOnly = true;
  let isRootSimplification = false;

  if (!realm.inSimplificationPath) {
    realm.inSimplificationPath = isRootSimplification = true;
  }
  try {
    realm.errorHandler = diagnostic => {
      if (diagnostic.errorCode === "PP0029") {
        throw new _errors.FatalError(`${diagnostic.errorCode}: ${diagnostic.message}`);
      }
      throw new _errors.FatalError();
    };
    return simplify(realm, value, isCondition);
  } catch (e) {
    if (e.name === "Invariant Violation") throw e;
    if (e instanceof _errors.FatalError && typeof e.message === "string" && e.message.includes("PP0029")) {
      if (isRootSimplification) {
        return value;
      }
      throw e;
    }
    return value;
  } finally {
    if (isRootSimplification) {
      realm.abstractValueImpliesCounter = 0;
      realm.inSimplificationPath = false;
    }
    realm.errorHandler = savedHandler;
    realm.isReadOnly = savedIsReadOnly;
  }
} /**
   * 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 simplify(realm, value, isCondition = false) {
  if (value instanceof _index2.ConcreteValue) return value;
  (0, _invariant2.default)(value instanceof _index2.AbstractValue);
  if (isCondition || value.getType() === _index2.BooleanValue) {
    if (_singletons.Path.implies(value)) return realm.intrinsics.true;
    if (_singletons.Path.impliesNot(value)) return realm.intrinsics.false;
  }
  let loc = value.expressionLocation;
  let op = value.kind;
  switch (op) {
    case "!":
      {
        let [x0] = value.args;
        (0, _invariant2.default)(x0 instanceof _index2.AbstractValue);
        if (x0.kind === "!") {
          (0, _invariant2.default)(x0 instanceof _index2.AbstractValue);
          let [x00] = x0.args;
          let xx = simplify(realm, x00, true);
          if (isCondition || xx.getType() === _index2.BooleanValue) return xx;
        }
        return negate(realm, x0, loc, value, isCondition);
      }
    case "||":
    case "&&":
      {
        let [x0, y0] = value.args;
        let x = simplify(realm, x0, isCondition);
        let y = simplify(realm, y0, isCondition);
        if (x instanceof _index2.AbstractValue && x.equals(y)) return x;
        // true && y <=> y
        // true || y <=> true
        if (!x.mightNotBeTrue()) return op === "&&" ? y : x;
        // (x == false) && y <=> x
        // false || y <=> y
        if (!x.mightNotBeFalse()) return op === "||" ? y : x;
        if (isCondition || x.getType() === _index2.BooleanValue && y.getType() === _index2.BooleanValue) {
          // (x: boolean) && true <=> x
          // x || true <=> true
          if (!y.mightNotBeTrue()) return op === "&&" ? x : realm.intrinsics.true;
          // (x: boolean) && false <=> false
          // (x: boolean) || false <=> x
          if (!y.mightNotBeFalse()) return op === "||" ? x : realm.intrinsics.false;
        }
        if (op === "||" && y instanceof _index2.AbstractValue && y.kind === "||" && x.equals(y.args[0]) && !y.args[1].mightNotBeTrue()) return y;
        if (x.equals(x0) && y.equals(y0)) return value;
        return _index2.AbstractValue.createFromLogicalOp(realm, value.kind, x, y, loc, isCondition, true);
      }
    case "==":
    case "!=":
    case "===":
    case "!==":
      return simplifyEquality(realm, value);
    case "conditional":
      {
        let [c0, x0, y0] = value.args;
        let c = simplify(realm, c0, true);
        let x, y;
        if (c0 instanceof _index2.AbstractValue && c.mightBeFalse() && c.mightBeTrue()) {
          try {
            x = _singletons.Path.withCondition(c0, () => simplify(realm, x0, isCondition));
          } catch (e) {
            if (e instanceof _errors.InfeasiblePathError) {
              // We now know that c0 cannot be be true on this path
              return simplify(realm, y0, isCondition);
            }
            throw e;
          }
          try {
            y = _singletons.Path.withInverseCondition(c0, () => simplify(realm, y0, isCondition));
          } catch (e) {
            if (e instanceof _errors.InfeasiblePathError) {
              // We now know that c0 cannot be be false on this path
              return x;
            }
            throw e;
          }
        }
        if (x === undefined) x = simplify(realm, x0, isCondition);
        if (y === undefined) y = simplify(realm, y0, isCondition);
        if (!c.mightNotBeTrue()) return x;
        if (!c.mightNotBeFalse()) return y;
        (0, _invariant2.default)(c instanceof _index2.AbstractValue);
        if (_singletons.Path.implies(c)) return x;
        let notc = _index2.AbstractValue.createFromUnaryOp(realm, "!", c, true, loc, isCondition, true);
        if (!notc.mightNotBeTrue()) return y;
        if (!notc.mightNotBeFalse()) return x;
        (0, _invariant2.default)(notc instanceof _index2.AbstractValue);
        if (_singletons.Path.implies(notc)) return y;
        if (_singletons.Path.implies(_index2.AbstractValue.createFromBinaryOp(realm, "===", value, x))) return x;
        if (_singletons.Path.implies(_index2.AbstractValue.createFromBinaryOp(realm, "!==", value, x))) return y;
        if (_singletons.Path.implies(_index2.AbstractValue.createFromBinaryOp(realm, "!==", value, y))) return x;
        if (_singletons.Path.implies(_index2.AbstractValue.createFromBinaryOp(realm, "===", value, y))) return y;
        // c ? x : x <=> x
        if (x.equals(y)) return x;
        // x ? x : y <=> x || y
        let cs = isCondition ? c : simplify(realm, c0);
        if (cs.equals(x)) return _index2.AbstractValue.createFromLogicalOp(realm, "||", x, y, loc, isCondition, true);
        // y ? x : y <=> y && x
        if (cs.equals(y)) return _index2.AbstractValue.createFromLogicalOp(realm, "&&", y, x, loc, isCondition, true);
        // c ? (c ? xx : xy) : y <=> c ? xx : y
        if (x instanceof _index2.AbstractValue && x.kind === "conditional") {
          let [xc, xx] = x.args;
          if (c.equals(xc)) return _index2.AbstractValue.createFromConditionalOp(realm, c, xx, y, value.expressionLocation, isCondition, true);
        }
        // c ? x : (c ? y : z) : z <=> c ? x : z
        if (y instanceof _index2.AbstractValue && y.kind === "conditional") {
          let [yc,, z] = y.args;
          if (c.equals(yc)) return _index2.AbstractValue.createFromConditionalOp(realm, c, x, z, value.expressionLocation, isCondition, true);
        }
        if (isCondition || x.getType() === _index2.BooleanValue && y.getType() === _index2.BooleanValue) {
          // c ? true : false <=> c
          if (!x.mightNotBeTrue() && !y.mightNotBeFalse()) return c;
          // c ? false : true <=> !c
          if (!x.mightNotBeFalse() && !y.mightNotBeTrue()) return _index2.AbstractValue.createFromUnaryOp(realm, "!", c, true, loc, true);
        }
        if (c.equals(c0) && x.equals(x0) && y.equals(y0)) return value;
        return _index2.AbstractValue.createFromConditionalOp(realm, c, x, y, value.expressionLocation, isCondition, true);
      }
    case "abstractConcreteUnion":
      {
        // The union of an abstract value with one or more concrete values.
        if (realm.pathConditions.length === 0) return value;
        let [abstractValue, ...concreteValues] = value.args;
        (0, _invariant2.default)(abstractValue instanceof _index2.AbstractValue);
        let remainingConcreteValues = [];
        for (let concreteValue of concreteValues) {
          if (_singletons.Path.implies(_index2.AbstractValue.createFromBinaryOp(realm, "!==", value, concreteValue))) continue;
          if (_singletons.Path.implies(_index2.AbstractValue.createFromBinaryOp(realm, "===", value, concreteValue))) return concreteValue;
          remainingConcreteValues.push(concreteValue);
        }
        if (remainingConcreteValues.length === 0) return abstractValue;
        if (remainingConcreteValues.length === concreteValues.length) return value;
        return _index2.AbstractValue.createAbstractConcreteUnion(realm, abstractValue, ...remainingConcreteValues);
      }
    default:
      return value;
  }
}

function simplifyEquality(realm, equality) {
  let loc = equality.expressionLocation;
  let op = equality.kind;
  let [x, y] = equality.args;
  if (x instanceof _index2.ConcreteValue) [x, y] = [y, x];
  if (x instanceof _index2.AbstractValue && x.kind === "conditional" && (!y.mightNotBeUndefined() || !y.mightNotBeNull())) {
    // try to simplify "(cond ? xx : xy) op undefined/null" to just "cond" or "!cond"
    let [cond, xx, xy] = x.args;
    (0, _invariant2.default)(cond instanceof _index2.AbstractValue); // otherwise the the conditional should not have been created
    if (op === "===" || op === "!==") {
      // if xx === undefined && xy !== undefined then cond <=> x === undefined
      if (!y.mightNotBeUndefined() && !xx.mightNotBeUndefined() && !xy.mightBeUndefined()) return op === "===" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
      // if xx !== undefined && xy === undefined then !cond <=> x === undefined
      if (!y.mightNotBeUndefined() && !xx.mightBeUndefined() && !xy.mightNotBeUndefined()) return op === "===" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
      // if xx === null && xy !== null then cond <=> x === null
      if (!y.mightNotBeNull() && !xx.mightNotBeNull() && !xy.mightBeNull()) return op === "===" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
      // if xx !== null && xy === null then !cond <=> x === null
      if (!y.mightNotBeNull() && !xx.mightBeNull() && !xy.mightNotBeNull()) return op === "===" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
    } else {
      (0, _invariant2.default)(op === "==" || op === "!=");
      // if xx cannot be undefined/null and xy is undefined/null then !cond <=> x == undefined/null
      if (!xx.mightBeUndefined() && !xx.mightBeNull() && (!xy.mightNotBeUndefined() || !xy.mightNotBeNull())) return op === "==" ? negate(realm, cond, loc) : makeBoolean(realm, cond, loc);
      // if xx is undefined/null and xy cannot be undefined/null then cond <=> x == undefined/null
      if ((!xx.mightNotBeUndefined() || !xx.mightNotBeNull()) && !xy.mightBeUndefined() && !xy.mightBeNull()) return op === "==" ? makeBoolean(realm, cond, loc) : negate(realm, cond, loc);
    }
  }
  return equality;
}

function makeBoolean(realm, value, loc = undefined) {
  if (value.getType() === _index2.BooleanValue) return value;
  if (value instanceof _index2.ConcreteValue) return new _index2.BooleanValue(realm, _singletons.To.ToBoolean(realm, value));
  (0, _invariant2.default)(value instanceof _index2.AbstractValue);
  let v = _index2.AbstractValue.createFromUnaryOp(realm, "!", value, true, value.expressionLocation);
  if (v instanceof _index2.ConcreteValue) return new _index2.BooleanValue(realm, !_singletons.To.ToBoolean(realm, v));
  (0, _invariant2.default)(v instanceof _index2.AbstractValue);
  return _index2.AbstractValue.createFromUnaryOp(realm, "!", v, true, loc || value.expressionLocation);
}

function negate(realm, value, loc = undefined, unsimplifiedNegation = undefined, isCondition) {
  if (value instanceof _index2.ConcreteValue) return _index.ValuesDomain.computeUnary(realm, "!", value);
  (0, _invariant2.default)(value instanceof _index2.AbstractValue);
  value = simplify(realm, value, true);
  if (!value.mightNotBeTrue()) return realm.intrinsics.false;
  if (!value.mightNotBeFalse()) return realm.intrinsics.true;
  (0, _invariant2.default)(value instanceof _index2.AbstractValue);
  if (value.kind === "!") {
    let [x] = value.args;
    if (isCondition || x.getType() === _index2.BooleanValue) return simplify(realm, x, true);
    if (unsimplifiedNegation !== undefined) return unsimplifiedNegation;
    return makeBoolean(realm, x, loc);
  }
  // If NaN is not an issue, invert binary ops
  if (value.args.length === 2 && !value.args[0].mightBeNumber() && !value.args[1].mightBeNumber()) {
    let invertedComparison;
    switch (value.kind) {
      case "===":
        invertedComparison = "!==";
        break;
      case "==":
        invertedComparison = "!=";
        break;
      case "!==":
        invertedComparison = "===";
        break;
      case "!=":
        invertedComparison = "==";
        break;
      case "<":
        invertedComparison = ">=";
        break;
      case "<=":
        invertedComparison = ">";
        break;
      case ">":
        invertedComparison = "<=";
        break;
      case ">=":
        invertedComparison = "<";
        break;
      default:
        break;
    }
    if (invertedComparison !== undefined) {
      let left = simplify(realm, value.args[0]);
      let right = simplify(realm, value.args[1]);
      return _index2.AbstractValue.createFromBinaryOp(realm, invertedComparison, left, right, loc || value.expressionLocation);
    }
    let invertedLogicalOp;
    switch (value.kind) {
      case "&&":
        invertedLogicalOp = "||";
        break;
      case "||":
        invertedLogicalOp = "&&";
        break;
      default:
        break;
    }
    if (invertedLogicalOp !== undefined) {
      let left = negate(realm, value.args[0]);
      let right = negate(realm, value.args[1]);
      return _index2.AbstractValue.createFromLogicalOp(realm, invertedLogicalOp, left, right, loc || value.expressionLocation, true);
    }
  }
  if (unsimplifiedNegation !== undefined) return unsimplifiedNegation;
  return _index2.AbstractValue.createFromUnaryOp(realm, "!", value, true, loc || value.expressionLocation, true);
}
//# sourceMappingURL=simplifier.js.map