"use strict";

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

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

var _generator = require("../utils/generator.js");

var _builder = require("../utils/builder.js");

var _builder2 = _interopRequireDefault(_builder);

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

var t = _interopRequireWildcard(_babelTypes);

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; } }

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

// In addition to the explicitly listed kinds,
// all strings that start with `AbstractValueKindPrefix` are also legal kinds.


// Use AbstractValue.makeKind to make a kind from one of these prefices.
class AbstractValue extends _index.Value {
  constructor(realm, types, values, hashValue, args, buildNode, optionalArgs) {
    (0, _invariant2.default)(realm.useAbstractInterpretation);
    super(realm, optionalArgs ? optionalArgs.intrinsicName : undefined);
    (0, _invariant2.default)(buildNode instanceof Function || args.length === 0);
    (0, _invariant2.default)(!_index.Value.isTypeCompatibleWith(types.getType(), _index.ObjectValue) || this instanceof _index.AbstractObjectValue);
    (0, _invariant2.default)(types.getType() !== _index.NullValue && types.getType() !== _index.UndefinedValue);
    this.types = types;
    this.values = values;
    this.mightBeEmpty = false;
    this._buildNode = buildNode;
    this.args = args;
    this.hashValue = hashValue;
    this.kind = optionalArgs ? optionalArgs.kind : undefined;
  }

  addSourceLocationsTo(locations, seenValues = new Set()) {
    if (seenValues.has(this)) return;
    seenValues.add(this);
    if (this._buildNode && !(this._buildNode instanceof Function)) {
      if (this._buildNode.loc) locations.push(this._buildNode.loc);
    }
    for (let val of this.args) {
      if (val instanceof AbstractValue) val.addSourceLocationsTo(locations, seenValues);
    }
  }

  addSourceNamesTo(names) {
    let gen = this.$Realm.preludeGenerator;
    function add_intrinsic(name) {
      if (name.startsWith("_$")) {
        if (gen === undefined) return;
        add_args(gen.derivedIds.get(name));
      } else if (names.indexOf(name) < 0) {
        names.push(name);
      }
    }
    function add_args(args) {
      if (args === undefined) return;
      for (let val of args) {
        if (val.intrinsicName) {
          add_intrinsic(val.intrinsicName);
        } else if (val instanceof AbstractValue) {
          val.addSourceNamesTo(names);
        } else if (val instanceof _index.StringValue) {
          if (val.value.startsWith("__")) {
            names.push(val.value.slice(2));
          }
        }
      }
    }
    if (this.intrinsicName) {
      add_intrinsic(this.intrinsicName);
    }
    add_args(this.args);
  }

  buildNode(args) {
    let buildNode = this.getBuildNode();
    return buildNode instanceof Function ? buildNode(args) : buildNode;
  }

  equals(x) {
    if (x instanceof _index.ConcreteValue) return false;
    let thisArgs = this.args;
    let n = thisArgs.length;

    let argsAreEqual = () => {
      (0, _invariant2.default)(x instanceof AbstractValue);
      let xArgs = x.args;
      let m = xArgs.length;
      (0, _invariant2.default)(n === m); // Will be true if kinds are the same. Caller should see to it.
      for (let i = 0; i < n; i++) {
        let a = thisArgs[i];
        let b = xArgs[i];
        if (!a.equals(b)) return false;
      }
      return true;
    };

    return this === x || x instanceof AbstractValue && this.kind === x.kind && this.hashValue === x.hashValue && (this.intrinsicName && this.intrinsicName.length > 0 && this.intrinsicName === x.intrinsicName || n > 0 && argsAreEqual());
  }

  getBuildNode() {
    (0, _invariant2.default)(this._buildNode);
    return this._buildNode;
  }

  getHash() {
    return this.hashValue;
  }

  getType() {
    return this.types.getType();
  }

  getIdentifier() {
    (0, _invariant2.default)(this.hasIdentifier());
    return this._buildNode;
  }

  hasIdentifier() {
    return this._buildNode && this._buildNode.type === "Identifier";
  }

  _checkAbstractValueImpliesCounter() {
    let realm = this.$Realm;
    let abstractValueImpliesMax = realm.abstractValueImpliesMax;
    // if abstractValueImpliesMax is 0, then the counter is disabled
    if (abstractValueImpliesMax !== 0 && realm.abstractValueImpliesCounter++ > abstractValueImpliesMax) {
      realm.abstractValueImpliesCounter = 0;
      let diagnostic = new _errors.CompilerDiagnostic(`the implies counter has exceeded the maximum value when trying to simplify abstract values`, realm.currentLocation, "PP0029", "FatalError");
      realm.handleError(diagnostic);
    }
  }

  // this => val. A false value does not imply that !(this => val).
  implies(val) {
    this._checkAbstractValueImpliesCounter();
    if (this.equals(val)) return true; // x => x regardless of its value
    if (!this.mightNotBeFalse()) return true; // false => val
    if (!val.mightNotBeTrue()) return true; // x => true regardless of the value of x
    if (val instanceof AbstractValue) {
      // Neither this (x) nor val (y) is a known value, so we need to do some reasoning based on the structure
      // x => x || y
      if (val.kind === "||") {
        let [x, y] = val.args;
        return this.implies(x) || this.implies(y);
      }

      // x => !y if y => !x
      if (val.kind === "!") {
        let [y] = val.args;
        (0, _invariant2.default)(y instanceof AbstractValue);
        return y.impliesNot(this);
      }
      // x => x !== null && x !== undefined
      if (val.kind === "!==") {
        let [x, y] = val.args;
        if (this.implies(x)) return y instanceof _index.NullValue || y instanceof _index.UndefinedValue;
        if (this.implies(y)) return x instanceof _index.NullValue || x instanceof _index.UndefinedValue;
      }
      // !!x => y if x => y
      if (this.kind === "!") {
        let [nx] = this.args;
        (0, _invariant2.default)(nx instanceof AbstractValue);
        if (nx.kind === "!") {
          let [x] = nx.args;
          (0, _invariant2.default)(x instanceof AbstractValue);
          return x.implies(val);
        }
      }
      if (this.kind === "conditional") {
        let [c, x, y] = this.args;
        // (c ? x : y) => val if x is true and y is false and c = val
        if (!x.mightNotBeTrue() && !y.mightNotBeFalse()) {
          return c.equals(val);
        }

        // (c ? false : y) => y !== undefined && y !== null && y !== f
        if (val.kind === "!==") {
          let [vx, vy] = val.args;
          if (!x.mightNotBeFalse()) {
            if (y.implies(vx)) return vy instanceof _index.NullValue || vy instanceof _index.UndefinedValue;
            if (y.implies(vy)) return vx instanceof _index.NullValue || vx instanceof _index.UndefinedValue;
          } else if (!y.mightNotBeFalse()) {
            if (x.implies(vx)) return vy instanceof _index.NullValue || vy instanceof _index.UndefinedValue;
            if (x.implies(vy)) return vx instanceof _index.NullValue || vx instanceof _index.UndefinedValue;
          }
        }

        // (c ? x : false) => c && x (if c or x were falsy, (c ? x : false) could not be true)
        if (!y.mightNotBeFalse()) {
          if (c.implies(val)) return true;
          if (x.implies(val)) return true;
        }
      }
      // (0 !== x) => x since undefined, null, false, 0, NaN and "" are excluded by the !== and all other values are thruthy
      if (this.kind === "!==") {
        let [x, y] = this.args;
        if (x instanceof _index.NumberValue && x.value === 0) return y.equals(val);
        if (y instanceof _index.NumberValue && y.value === 0) return x.equals(val);
      }
      if (this.kind === "===" && val.kind === "==") {
        // x === undefined/null => y == undefined/null
        let [x, y] = val.args;
        if (x instanceof _index.NullValue || x instanceof _index.UndefinedValue || y instanceof _index.NullValue || y instanceof _index.UndefinedValue) {
          let [vx, vy] = val.args;
          if (vx instanceof _index.NullValue || vx instanceof _index.UndefinedValue || vy instanceof _index.NullValue || vy instanceof _index.UndefinedValue) {
            return true;
          }
        }
      }
    }
    return false;
  }

  // this => !val. A false value does not imply that !(this => !val).
  impliesNot(val) {
    if (this.equals(val)) return false; // x => x regardless of its value, hence x => !val is false
    if (!this.mightNotBeFalse()) return true; // false => !val
    if (!val.mightNotBeFalse()) return true; // x => !false regardless of the value of x
    if (val instanceof AbstractValue) {
      // !x => !y if y => x
      if (this.kind === "!") {
        let [x] = this.args;
        (0, _invariant2.default)(x instanceof AbstractValue);
        if (x.kind === "!") {
          // !!x => !y if y => !x
          (0, _invariant2.default)(x instanceof AbstractValue);
          let [xx] = x.args;
          (0, _invariant2.default)(xx instanceof AbstractValue);
          return xx.impliesNot(val);
        }
        if (x.kind === "abstractConcreteUnion") return false; // can't use two valued logic for this.
        return val.implies(x);
      }
      if (this.kind === "conditional") {
        let [c, x, y] = this.args;
        // (c ? x : y) => !val if x is false and y is true and c = val
        if (!x.mightNotBeFalse() && !y.mightNotBeTrue()) {
          return c.equals(val);
        }
      }
    }
    return false;
  }

  // todo: abstract values should never be of type UndefinedValue or NullValue, assert this
  mightBeFalse() {
    let valueType = this.getType();
    if (valueType === _index.UndefinedValue) return true;
    if (valueType === _index.NullValue) return true;
    if (valueType === _index.SymbolValue) return false;
    if (_index.Value.isTypeCompatibleWith(valueType, _index.ObjectValue)) return false;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightBeFalse()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.mightBeFalse();
  }

  mightNotBeFalse() {
    let valueType = this.getType();
    if (valueType === _index.UndefinedValue) return false;
    if (valueType === _index.NullValue) return false;
    if (valueType === _index.SymbolValue) return true;
    if (_index.Value.isTypeCompatibleWith(valueType, _index.ObjectValue)) return true;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightNotBeFalse()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.mightNotBeFalse();
  }

  mightBeNull() {
    let valueType = this.getType();
    if (valueType === _index.NullValue) return true;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return false;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightBeNull()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueOfType(_index.NullValue);
  }

  mightNotBeNull() {
    let valueType = this.getType();
    if (valueType === _index.NullValue) return false;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return true;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightNotBeNull()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueNotOfType(_index.NullValue);
  }

  mightBeNumber() {
    let valueType = this.getType();
    if (_index.Value.isTypeCompatibleWith(valueType, _index.NumberValue)) return true;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return false;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightBeNumber()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueOfType(_index.NumberValue);
  }

  mightNotBeNumber() {
    let valueType = this.getType();
    if (_index.Value.isTypeCompatibleWith(valueType, _index.NumberValue)) return false;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return true;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightNotBeNumber()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueNotOfType(_index.NumberValue);
  }

  mightNotBeObject() {
    let valueType = this.getType();
    if (_index.Value.isTypeCompatibleWith(valueType, _index.PrimitiveValue)) return true;
    if (_index.Value.isTypeCompatibleWith(valueType, _index.ObjectValue)) return false;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightNotBeObject()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueNotOfType(_index.ObjectValue);
  }

  mightBeObject() {
    let valueType = this.getType();
    if (_index.Value.isTypeCompatibleWith(valueType, _index.PrimitiveValue)) return false;
    if (_index.Value.isTypeCompatibleWith(valueType, _index.ObjectValue)) return true;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightBeObject()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueOfType(_index.ObjectValue);
  }

  mightBeString() {
    let valueType = this.getType();
    if (valueType === _index.StringValue) return true;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return false;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightBeString()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueOfType(_index.StringValue);
  }

  mightNotBeString() {
    let valueType = this.getType();
    if (valueType === _index.StringValue) return false;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return true;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightNotBeString()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueNotOfType(_index.StringValue);
  }

  mightBeUndefined() {
    let valueType = this.getType();
    if (valueType === _index.UndefinedValue) return true;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return false;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightBeUndefined()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueOfType(_index.UndefinedValue);
  }

  mightNotBeUndefined() {
    let valueType = this.getType();
    if (valueType === _index.UndefinedValue) return false;
    if (valueType !== _index.PrimitiveValue && valueType !== _index.Value) return true;
    if (this.kind === "abstractConcreteUnion") {
      for (let arg of this.args) if (arg.mightNotBeUndefined()) return true;
      return false;
    }
    if (this.values.isTop()) return true;
    return this.values.includesValueNotOfType(_index.UndefinedValue);
  }

  mightHaveBeenDeleted() {
    return this.mightBeEmpty;
  }

  promoteEmptyToUndefined() {
    if (this.values.isTop()) return this;
    if (!this.mightBeEmpty) return this;
    let cond = AbstractValue.createFromBinaryOp(this.$Realm, "===", this, this.$Realm.intrinsics.empty);
    let result = AbstractValue.createFromConditionalOp(this.$Realm, cond, this.$Realm.intrinsics.undefined, this);
    if (result instanceof AbstractValue) result.values = this.values.promoteEmptyToUndefined();
    return result;
  }

  throwIfNotConcrete() {
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  throwIfNotConcreteNumber() {
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  throwIfNotConcreteString() {
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  throwIfNotConcreteBoolean() {
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  throwIfNotConcreteSymbol() {
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  throwIfNotConcreteObject() {
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  throwIfNotConcretePrimitive() {
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  throwIfNotObject() {
    (0, _invariant2.default)(!(this instanceof _index.AbstractObjectValue));
    AbstractValue.reportIntrospectionError(this);
    throw new _errors.FatalError();
  }

  static createFromBinaryOp(realm, op, left, right, loc, kind) {
    let leftTypes, leftValues;
    if (left instanceof AbstractValue) {
      leftTypes = left.types;
      leftValues = left.values;
    } else {
      leftTypes = new _index3.TypesDomain(left.getType());
      (0, _invariant2.default)(left instanceof _index.ConcreteValue);
      leftValues = new _index3.ValuesDomain(left);
    }

    let rightTypes, rightValues;
    if (right instanceof AbstractValue) {
      rightTypes = right.types;
      rightValues = right.values;
    } else {
      rightTypes = new _index3.TypesDomain(right.getType());
      (0, _invariant2.default)(right instanceof _index.ConcreteValue);
      rightValues = new _index3.ValuesDomain(right);
    }

    let resultTypes = _index3.TypesDomain.binaryOp(op, leftTypes, rightTypes);
    let resultValues = kind === "template for property name condition" ? _index3.ValuesDomain.topVal : _index3.ValuesDomain.binaryOp(realm, op, leftValues, rightValues);
    let [hash, args] = kind === undefined ? (0, _index2.hashBinary)(op, left, right) : (0, _index2.hashCall)(kind, left, right);
    let result = new AbstractValue(realm, resultTypes, resultValues, hash, args, ([x, y]) => t.binaryExpression(op, x, y));
    result.kind = kind || op;
    result.expressionLocation = loc;
    return result;
  }

  static createFromLogicalOp(realm, op, left, right, loc, isCondition, doNotSimplify) {
    let leftTypes, leftValues;
    if (left instanceof AbstractValue) {
      leftTypes = left.types;
      leftValues = left.values;
    } else {
      leftTypes = new _index3.TypesDomain(left.getType());
      (0, _invariant2.default)(left instanceof _index.ConcreteValue);
      leftValues = new _index3.ValuesDomain(left);
    }

    let rightTypes, rightValues;
    if (right instanceof AbstractValue) {
      rightTypes = right.types;
      rightValues = right.values;
    } else {
      rightTypes = new _index3.TypesDomain(right.getType());
      (0, _invariant2.default)(right instanceof _index.ConcreteValue);
      rightValues = new _index3.ValuesDomain(right);
    }

    let resultTypes = _index3.TypesDomain.logicalOp(op, leftTypes, rightTypes);
    let resultValues = _index3.ValuesDomain.logicalOp(realm, op, leftValues, rightValues);
    let [hash, args] = (0, _index2.hashCall)(op, left, right);
    let Constructor = _index.Value.isTypeCompatibleWith(resultTypes.getType(), _index.ObjectValue) ? _index.AbstractObjectValue : AbstractValue;
    let result = new Constructor(realm, resultTypes, resultValues, hash, args, ([x, y]) => t.logicalExpression(op, x, y));
    result.kind = op;
    result.expressionLocation = loc;
    if (doNotSimplify) return result;
    return isCondition ? realm.simplifyAndRefineAbstractCondition(result) : realm.simplifyAndRefineAbstractValue(result);
  }

  static createFromConditionalOp(realm, condition, left, right, loc, isCondition, doNotSimplify) {
    let types = _index3.TypesDomain.joinValues(left, right);
    if (types.getType() === _index.NullValue) return realm.intrinsics.null;
    if (types.getType() === _index.UndefinedValue) return realm.intrinsics.undefined;
    let values = _index3.ValuesDomain.joinValues(realm, left, right);
    let [hash, args] = (0, _index2.hashTernary)(condition, left || realm.intrinsics.undefined, right || realm.intrinsics.undefined);
    let Constructor = _index.Value.isTypeCompatibleWith(types.getType(), _index.ObjectValue) ? _index.AbstractObjectValue : AbstractValue;
    let result = new Constructor(realm, types, values, hash, args, ([c, x, y]) => t.conditionalExpression(c, x, y), {
      kind: "conditional"
    });
    result.expressionLocation = loc;
    if (left) result.mightBeEmpty = left.mightHaveBeenDeleted();
    if (right && !result.mightBeEmpty) result.mightBeEmpty = right.mightHaveBeenDeleted();
    if (doNotSimplify || result.mightBeEmpty) return result;
    return isCondition ? realm.simplifyAndRefineAbstractCondition(result) : realm.simplifyAndRefineAbstractValue(result);
  }

  static createFromUnaryOp(realm, op, operand, prefix, loc, isCondition, doNotSimplify) {
    (0, _invariant2.default)(op !== "delete" && op !== "++" && op !== "--"); // The operation must be pure
    let resultTypes = _index3.TypesDomain.unaryOp(op, new _index3.TypesDomain(operand.getType()));
    let resultValues = _index3.ValuesDomain.unaryOp(realm, op, operand.values);
    let result = new AbstractValue(realm, resultTypes, resultValues, (0, _index2.hashUnary)(op, operand), [operand], ([x]) => t.unaryExpression(op, x, prefix));
    result.kind = op;
    result.expressionLocation = loc;
    if (doNotSimplify) return result;
    return isCondition ? realm.simplifyAndRefineAbstractCondition(result) : realm.simplifyAndRefineAbstractValue(result);
  }

  /* Note that the template is parameterized by the names A, B, C and so on.
     When the abstract value is serialized, the serialized operations are substituted
     for the corresponding parameters and the resulting template is parsed into an AST subtree
     that is incorporated into the AST produced by the serializer. */
  static createFromTemplate(realm, template, resultType, operands, kindSuffix, loc) {
    let kind = AbstractValue.makeKind("template", kindSuffix);
    let resultTypes = new _index3.TypesDomain(resultType);
    let resultValues = _index3.ValuesDomain.topVal;
    let hash;
    [hash, operands] = (0, _index2.hashCall)(kind, ...operands);
    let Constructor = _index.Value.isTypeCompatibleWith(resultType, _index.ObjectValue) ? _index.AbstractObjectValue : AbstractValue;
    let labels = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    (0, _invariant2.default)(labels.length >= operands.length);
    let result = new Constructor(realm, resultTypes, resultValues, hash, operands, args => {
      (0, _invariant2.default)(realm.preludeGenerator !== undefined);
      let generatorArgs = {};
      let i = 0;
      for (let arg of args) generatorArgs[labels.charAt(i++)] = arg;
      return template(realm.preludeGenerator)(generatorArgs);
    });
    result.kind = kind;
    result.expressionLocation = loc || realm.currentLocation;
    return result;
  }

  static createFromType(realm, resultType, kind) {
    let types = new _index3.TypesDomain(resultType);
    let Constructor = _index.Value.isTypeCompatibleWith(resultType, _index.ObjectValue) ? _index.AbstractObjectValue : AbstractValue;
    let hash = (0, _index2.hashString)(resultType.name + (kind || ""));
    let result = new Constructor(realm, types, _index3.ValuesDomain.topVal, hash, []);
    if (kind) result.kind = kind;
    result.expressionLocation = realm.currentLocation;
    return result;
  }

  /* Emits a declaration for an identifier into the generator at the current point in time
     and initializes it with an expression constructed from the given template.
     Returns an abstract value that refers to the newly declared identifier.
     Note that the template must generate an expression which has no side-effects
     on the prepack state. It is assumed, however, that there could be side-effects
     on the native state unless the isPure option is specified.  */
  static createTemporalFromTemplate(realm, template, resultType, operands, optionalArgs) {
    (0, _invariant2.default)(resultType !== _index.UndefinedValue);
    let temp = AbstractValue.createFromTemplate(realm, template, resultType, operands, "");
    let types = temp.types;
    let values = temp.values;
    let args = temp.args;
    let buildNode_ = temp.getBuildNode();
    (0, _invariant2.default)(realm.generator !== undefined);
    return realm.generator.derive(types, values, args, buildNode_, optionalArgs);
  }

  static createFromBuildFunction(realm, resultType, args, buildFunction, optionalArgs) {
    let types = new _index3.TypesDomain(resultType);
    let values = _index3.ValuesDomain.topVal;
    let Constructor = _index.Value.isTypeCompatibleWith(resultType, _index.ObjectValue) ? _index.AbstractObjectValue : AbstractValue;
    let kind = optionalArgs && optionalArgs.kind || "build function";
    let hash;
    [hash, args] = (0, _index2.hashCall)(kind, ...args);
    let result = new Constructor(realm, types, values, hash, args, buildFunction);
    result.kind = kind;
    return result;
  }

  static createTemporalFromBuildFunction(realm, resultType, args, buildFunction, optionalArgs) {
    let types = new _index3.TypesDomain(resultType);
    let values = _index3.ValuesDomain.topVal;
    (0, _invariant2.default)(realm.generator !== undefined);
    if (resultType === _index.UndefinedValue) {
      return realm.generator.emitVoidExpression(types, values, args, buildFunction);
    } else {
      return realm.generator.derive(types, values, args, buildFunction, optionalArgs);
    }
  }

  // Creates a union of an abstract value with one or more concrete values.
  // The build node for the abstract values becomes the build node for the union.
  // Use this only to allow instrinsic abstract objects to be null and/or undefined.
  static createAbstractConcreteUnion(realm, ...elements) {
    let concreteValues = elements.filter(e => e instanceof _index.ConcreteValue);
    (0, _invariant2.default)(concreteValues.length > 0 && concreteValues.length === elements.length - 1);
    let concreteSet = new Set(concreteValues);
    let abstractValue = elements.find(e => e instanceof AbstractValue);
    (0, _invariant2.default)(abstractValue instanceof AbstractValue);
    let values;
    if (!abstractValue.values.isTop()) {
      abstractValue.values.getElements().forEach(v => concreteSet.add(v));
      values = new _index3.ValuesDomain(concreteSet);
    } else {
      values = _index3.ValuesDomain.topVal;
    }
    let types = _index3.TypesDomain.topVal;
    let [hash, operands] = (0, _index2.hashCall)("abstractConcreteUnion", abstractValue, ...concreteValues);
    let result = new AbstractValue(realm, types, values, hash, operands, nodes => nodes[0], {
      kind: "abstractConcreteUnion"
    });
    result.expressionLocation = realm.currentLocation;
    return result;
  }

  static createFromWidenedProperty(realm, resultTemplate, args, buildFunction) {
    let types = resultTemplate.types;
    let values = resultTemplate.values;
    let [hash] = (0, _index2.hashCall)("widened property", ...args);
    let Constructor = _index.Value.isTypeCompatibleWith(types.getType(), _index.ObjectValue) ? _index.AbstractObjectValue : AbstractValue;
    let result = new Constructor(realm, types, values, hash, args, buildFunction);
    result.kind = "widened property";
    result.mightBeEmpty = resultTemplate.mightBeEmpty;
    result.expressionLocation = resultTemplate.expressionLocation;
    return result;
  }

  static createFromWidening(realm, value1, value2) {
    // todo: #1174 look at kind and figure out much narrower widenings
    let types = _index3.TypesDomain.joinValues(value1, value2);
    let values = _index3.ValuesDomain.topVal;
    let [hash] = (0, _index2.hashCall)("widened");
    let Constructor = _index.Value.isTypeCompatibleWith(types.getType(), _index.ObjectValue) ? _index.AbstractObjectValue : AbstractValue;
    let result = new Constructor(realm, types, values, hash, []);
    result.kind = "widened";
    result.mightBeEmpty = value1.mightHaveBeenDeleted() || value2.mightHaveBeenDeleted();
    result.expressionLocation = value1.expressionLocation;
    return result;
  }

  static generateErrorInformationForAbstractVal(val) {
    let names = [];
    val.addSourceNamesTo(names);
    if (names.length === 0) {
      val.addSourceNamesTo(names);
    }
    return `abstract value${names.length > 1 ? "s" : ""} ${names.join(" and ")}`;
  }

  static describe(val, propertyName) {
    let realm = val.$Realm;

    let identity;
    if (val === realm.$GlobalObject) identity = "global";else if (val instanceof AbstractValue) {
      identity = this.generateErrorInformationForAbstractVal(val);
    } else identity = val.intrinsicName || "(some value)";

    let source_locations = [];
    if (val instanceof AbstractValue) val.addSourceLocationsTo(source_locations);

    let location;
    if (propertyName instanceof _index.SymbolValue) {
      let desc = propertyName.$Description;
      if (desc) {
        location = `at symbol [${desc.throwIfNotConcreteString().value}]`;
      } else {
        location = `at symbol [${"(no description)"}]`;
      }
    } else if (propertyName instanceof _index.StringValue) location = `at ${propertyName.value}`;else if (typeof propertyName === "string") location = `at ${propertyName}`;else location = source_locations.length === 0 ? "" : `at ${source_locations.join("\n")}`;

    return `${identity} ${location}`;
  }

  static reportIntrospectionError(val, propertyName) {
    let message = `This operation is not yet supported on ${AbstractValue.describe(val, propertyName)}`;

    let realm = val.$Realm;
    return realm.reportIntrospectionError(message);
  }

  static createAbstractObject(realm, name, template) {
    let value;
    if (template === undefined) {
      template = new _index.ObjectValue(realm, realm.intrinsics.ObjectPrototype);
    }
    template.makePartial();
    template.makeSimple();
    value = AbstractValue.createFromTemplate(realm, (0, _builder2.default)(name), _index.ObjectValue, [], name);
    value.intrinsicName = name;
    value.values = new _index3.ValuesDomain(new Set([template]));
    realm.rebuildNestedProperties(value, name);
    (0, _invariant2.default)(value instanceof _index.AbstractObjectValue);
    return value;
  }

  static makeKind(prefix, suffix) {
    return `${prefix}:${suffix}`;
  }
}
exports.default = AbstractValue; /**
                                  * 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.
                                  */
//# sourceMappingURL=AbstractValue.js.map