"use strict";

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

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

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

var _environment = require("../environment.js");

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

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

function joinGenerators(realm, joinCondition, generator1, generator2) {
  let result = new _generator.Generator(realm, "joined");
  if (!generator1.empty() || !generator2.empty()) {
    result.joinGenerators(joinCondition, generator1, generator2);
  }
  return result;
} /**
   * 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 joinArrays(realm, v1, v2, getAbstractValue) {
  let e = v1 && v1[0] || v2 && v2[0];
  if (e instanceof _index2.Value) return joinArraysOfValues(realm, v1, v2, getAbstractValue);else return joinArrayOfsMapEntries(realm, v1, v2, getAbstractValue);
}

function joinArrayOfsMapEntries(realm, a1, a2, getAbstractValue) {
  let empty = realm.intrinsics.empty;
  let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
  let result = [];
  for (let i = 0; i < n; i++) {
    let { $Key: key1, $Value: val1 } = a1 && a1[i] || { $Key: empty, $Value: empty };
    let { $Key: key2, $Value: val2 } = a2 && a2[i] || { $Key: empty, $Value: empty };
    if (key1 === undefined && key2 === undefined) {
      result[i] = { $Key: undefined, $Value: undefined };
    } else {
      let key3 = getAbstractValue(key1, key2);
      let val3 = getAbstractValue(val1, val2);
      result[i] = { $Key: key3, $Value: val3 };
    }
  }
  return result;
}

function joinArraysOfValues(realm, a1, a2, getAbstractValue) {
  let n = Math.max(a1 && a1.length || 0, a2 && a2.length || 0);
  let result = [];
  for (let i = 0; i < n; i++) {
    result[i] = getAbstractValue(a1 && a1[i] || undefined, a2 && a2[i] || undefined);
  }
  return result;
}

class JoinImplementation {
  stopEffectCaptureJoinApplyAndReturnCompletion(c1, c2, realm) {
    let e = realm.getCapturedEffects(c1);
    (0, _invariant2.default)(e !== undefined);
    realm.stopEffectCaptureAndUndoEffects(c1);
    let joined_effects = this.joinPossiblyNormalCompletionWithAbruptCompletion(realm, c1, c2, e);
    realm.applyEffects(joined_effects);
    let result = joined_effects[0];
    (0, _invariant2.default)(result instanceof _completions.AbruptCompletion);
    return result;
  }

  unbundleNormalCompletion(completionOrValue) {
    let completion, value;
    if (completionOrValue instanceof _completions.PossiblyNormalCompletion) {
      completion = completionOrValue;
      value = completionOrValue.value;
    } else {
      (0, _invariant2.default)(completionOrValue instanceof _index2.Value || completionOrValue instanceof _environment.Reference);
      value = completionOrValue;
    }
    return [completion, value];
  }

  composeNormalCompletions(leftCompletion, rightCompletion, resultValue, realm) {
    if (leftCompletion instanceof _completions.PossiblyNormalCompletion) {
      if (rightCompletion instanceof _completions.PossiblyNormalCompletion) {
        this.updatePossiblyNormalCompletionWithValue(realm, rightCompletion, resultValue);
        return this.composePossiblyNormalCompletions(realm, leftCompletion, rightCompletion);
      }
      this.updatePossiblyNormalCompletionWithValue(realm, leftCompletion, resultValue);
      return leftCompletion;
    } else if (rightCompletion instanceof _completions.PossiblyNormalCompletion) {
      this.updatePossiblyNormalCompletionWithValue(realm, rightCompletion, resultValue);
      return rightCompletion;
    } else {
      (0, _invariant2.default)(leftCompletion === undefined && rightCompletion === undefined);
      return resultValue;
    }
  }

  composePossiblyNormalCompletions(realm, pnc, c) {
    (0, _invariant2.default)(c.savedEffects === undefined); // the caller should ensure this
    let savedPathConditions = pnc.savedPathConditions;
    if (pnc.consequent instanceof _completions.AbruptCompletion) {
      if (pnc.alternate instanceof _index2.Value) {
        let [, g, b, p, o] = pnc.alternateEffects;
        let newAlternateEffects = [c, g, b, p, o];
        return new _completions.PossiblyNormalCompletion(c.value, pnc.joinCondition, pnc.consequent, pnc.consequentEffects, c, newAlternateEffects, savedPathConditions, pnc.savedEffects);
      }
      (0, _invariant2.default)(pnc.alternate instanceof _completions.PossiblyNormalCompletion);
      let new_alternate = this.composePossiblyNormalCompletions(realm, pnc.alternate, c);
      let [, g, b, p, o] = pnc.alternateEffects;
      let newAlternateEffects = [new_alternate, g, b, p, o];
      return new _completions.PossiblyNormalCompletion(new_alternate.value, pnc.joinCondition, pnc.consequent, pnc.consequentEffects, new_alternate, newAlternateEffects, savedPathConditions, pnc.savedEffects);
    } else {
      (0, _invariant2.default)(pnc.alternate instanceof _completions.AbruptCompletion);
      if (pnc.consequent instanceof _index2.Value) {
        let [, g, b, p, o] = pnc.consequentEffects;
        let newConsequentEffects = [c, g, b, p, o];
        return new _completions.PossiblyNormalCompletion(c.value, pnc.joinCondition, c, newConsequentEffects, pnc.alternate, pnc.alternateEffects, savedPathConditions, pnc.savedEffects);
      }
      (0, _invariant2.default)(pnc.consequent instanceof _completions.PossiblyNormalCompletion);
      let new_consequent = this.composePossiblyNormalCompletions(realm, pnc.consequent, c);
      let [, g, b, p, o] = pnc.consequentEffects;
      let newConsequentEffects = [new_consequent, g, b, p, o];
      return new _completions.PossiblyNormalCompletion(new_consequent.value, pnc.joinCondition, new_consequent, newConsequentEffects, pnc.alternate, pnc.alternateEffects, savedPathConditions, pnc.savedEffects);
    }
  }

  updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc, subsequentEffects) {
    let v = subsequentEffects[0];
    (0, _invariant2.default)(v instanceof _index2.Value);
    pnc.value = v;
    if (pnc.consequent instanceof _completions.AbruptCompletion) {
      if (pnc.alternate instanceof _index2.Value) {
        pnc.alternate = v;
        pnc.alternateEffects[0] = v;
        pnc.alternateEffects = realm.composeEffects(pnc.alternateEffects, subsequentEffects);
      } else {
        (0, _invariant2.default)(pnc.alternate instanceof _completions.PossiblyNormalCompletion);
        this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.alternate, subsequentEffects);
      }
    } else {
      if (pnc.consequent instanceof _index2.Value) {
        pnc.consequent = v;
        pnc.consequentEffects[0] = v;
        pnc.consequentEffects = realm.composeEffects(pnc.consequentEffects, subsequentEffects);
      } else {
        (0, _invariant2.default)(pnc.consequent instanceof _completions.PossiblyNormalCompletion);
        this.updatePossiblyNormalCompletionWithSubsequentEffects(realm, pnc.consequent, subsequentEffects);
      }
    }
  }

  updatePossiblyNormalCompletionWithValue(realm, pnc, v) {
    pnc.value = v;
    if (pnc.consequent instanceof _completions.AbruptCompletion) {
      if (pnc.alternate instanceof _index2.Value) {
        pnc.alternate = v;
        pnc.alternateEffects[0] = v;
      } else {
        (0, _invariant2.default)(pnc.alternate instanceof _completions.PossiblyNormalCompletion);
        this.updatePossiblyNormalCompletionWithValue(realm, pnc.alternate, v);
      }
    } else {
      if (pnc.consequent instanceof _index2.Value) {
        pnc.consequent = v;
        pnc.consequentEffects[0] = v;
      } else {
        (0, _invariant2.default)(pnc.consequent instanceof _completions.PossiblyNormalCompletion);
        this.updatePossiblyNormalCompletionWithValue(realm, pnc.consequent, v);
      }
    }
  }

  // Returns the joined effects of all of the paths in pnc.
  // The normal path in pnc is modified to become terminated by ac,
  // so the overall completion will always be an instance of JoinedAbruptCompletions
  joinPossiblyNormalCompletionWithAbruptCompletion(realm,
  // a forked path with a non abrupt (normal) component
  pnc,
  // an abrupt completion that completes the normal path
  ac,
  // effects collected after pnc was constructed
  e) {
    // set up e with ac as the completion. It's OK to do this repeatedly since ac is not changed by recursive calls.
    e[0] = ac;
    if (pnc.consequent instanceof _completions.AbruptCompletion) {
      if (pnc.alternate instanceof _index2.Value) {
        return this.joinEffects(realm, pnc.joinCondition, pnc.consequentEffects, realm.composeEffects(pnc.alternateEffects, e));
      }
      (0, _invariant2.default)(pnc.alternate instanceof _completions.PossiblyNormalCompletion);
      let alternate_effects = this.joinPossiblyNormalCompletionWithAbruptCompletion(realm, pnc.alternate, ac, e);
      let composedEffects = realm.composeEffects(pnc.alternateEffects, alternate_effects);
      (0, _invariant2.default)(pnc.consequent instanceof _completions.AbruptCompletion);
      return this.joinEffects(realm, pnc.joinCondition, pnc.consequentEffects, composedEffects);
    } else {
      (0, _invariant2.default)(pnc.alternate instanceof _completions.AbruptCompletion);
      if (pnc.consequent instanceof _index2.Value) {
        return this.joinEffects(realm, pnc.joinCondition, realm.composeEffects(pnc.consequentEffects, e), pnc.alternateEffects);
      }
      (0, _invariant2.default)(pnc.consequent instanceof _completions.PossiblyNormalCompletion);
      let consequent_effects = this.joinPossiblyNormalCompletionWithAbruptCompletion(realm, pnc.consequent, ac, e);
      let composedEffects = realm.composeEffects(pnc.consequentEffects, consequent_effects);
      (0, _invariant2.default)(pnc.alternate instanceof _completions.AbruptCompletion);
      return this.joinEffects(realm, pnc.joinCondition, composedEffects, pnc.alternateEffects);
    }
  }

  joinPossiblyNormalCompletionWithValue(realm, joinCondition, pnc, v) {
    if (pnc.consequent instanceof _completions.AbruptCompletion) {
      if (pnc.alternate instanceof _index2.Value) {
        pnc.alternate = this.joinValuesAsConditional(realm, joinCondition, pnc.alternate, v);
        pnc.alternateEffects[0] = pnc.alternate;
      } else {
        (0, _invariant2.default)(pnc.alternate instanceof _completions.PossiblyNormalCompletion);
        this.joinPossiblyNormalCompletionWithValue(realm, joinCondition, pnc.alternate, v);
      }
    } else {
      if (pnc.consequent instanceof _index2.Value) {
        pnc.consequent = this.joinValuesAsConditional(realm, joinCondition, pnc.consequent, v);
        pnc.consequentEffects[0] = pnc.consequent;
      } else {
        (0, _invariant2.default)(pnc.consequent instanceof _completions.PossiblyNormalCompletion);
        this.joinPossiblyNormalCompletionWithValue(realm, joinCondition, pnc.consequent, v);
      }
    }
  }

  joinValueWithPossiblyNormalCompletion(realm, joinCondition, pnc, v) {
    if (pnc.consequent instanceof _completions.AbruptCompletion) {
      if (pnc.alternate instanceof _index2.Value) {
        pnc.alternate = this.joinValuesAsConditional(realm, joinCondition, v, pnc.alternate);
        pnc.alternateEffects[0] = pnc.alternate;
      } else {
        (0, _invariant2.default)(pnc.alternate instanceof _completions.PossiblyNormalCompletion);
        this.joinValueWithPossiblyNormalCompletion(realm, joinCondition, pnc.alternate, v);
      }
    } else {
      if (pnc.consequent instanceof _index2.Value) {
        pnc.consequent = this.joinValuesAsConditional(realm, joinCondition, v, pnc.consequent);
        pnc.consequentEffects[0] = pnc.consequent;
      } else {
        (0, _invariant2.default)(pnc.consequent instanceof _completions.PossiblyNormalCompletion);
        this.joinValueWithPossiblyNormalCompletion(realm, joinCondition, pnc.consequent, v);
      }
    }
  }

  joinPossiblyNormalCompletions(realm, joinCondition, c, a) {
    let rJoinCondition;
    let cp;
    let ap;
    if (c.consequent instanceof _completions.AbruptCompletion) {
      if (a.consequent instanceof _completions.AbruptCompletion) {
        rJoinCondition = _index2.AbstractValue.createFromLogicalOp(realm, "&&", c.joinCondition, a.joinCondition);
        cp = [c.consequentEffects, a.consequentEffects];
        ap = [c.alternateEffects, a.alternateEffects];
      } else {
        let notA = _index2.AbstractValue.createFromUnaryOp(realm, "!", a.joinCondition);
        rJoinCondition = _index2.AbstractValue.createFromLogicalOp(realm, "&&", c.joinCondition, notA);
        cp = [c.consequentEffects, a.alternateEffects];
        ap = [c.alternateEffects, a.consequentEffects];
      }
    } else {
      let notC = _index2.AbstractValue.createFromUnaryOp(realm, "!", c.joinCondition);
      if (a.consequent instanceof _completions.AbruptCompletion) {
        rJoinCondition = _index2.AbstractValue.createFromLogicalOp(realm, "&&", notC, a.joinCondition);
        cp = [c.alternateEffects, a.consequentEffects];
        ap = [c.consequentEffects, a.alternateEffects];
      } else {
        let notA = _index2.AbstractValue.createFromUnaryOp(realm, "!", a.joinCondition);
        rJoinCondition = _index2.AbstractValue.createFromLogicalOp(realm, "&&", notC, notA);
        cp = [c.alternateEffects, a.alternateEffects];
        ap = [c.consequentEffects, a.consequentEffects];
      }
    }
    (0, _invariant2.default)(rJoinCondition instanceof _index2.AbstractValue); // the transformations will not result in tautologies
    let [ce1, ce2] = cp;
    let [ae1, ae2] = ap;
    let rce = this.joinEffects(realm, joinCondition, ce1, ce2);
    let rae = this.joinEffects(realm, joinCondition, ae1, ae2);
    let rc = rce[0];
    (0, _invariant2.default)(rc instanceof _index2.Value || rc instanceof _completions.Completion);
    let ra = rae[0];
    (0, _invariant2.default)(ra instanceof _index2.Value || ra instanceof _completions.Completion);
    let rv = ra instanceof _completions.PossiblyNormalCompletion ? ra.value : ra;
    (0, _invariant2.default)(rv instanceof _index2.Value);
    return new _completions.PossiblyNormalCompletion(rv, rJoinCondition, rc, rce, ra, rae, []);
  }

  joinEffectsAndPromoteNestedReturnCompletions(realm, c, e, nested_effects) {
    if (c instanceof _index2.Value) {
      // If not undefined, the nested effects were captured when evaluating a conditional code block that ended normally.
      // e represent effects that were captured since reaching the join point where the normal and abrupt
      // completions came together into the completion supplied to the outermost call to this recursive function.
      if (nested_effects !== undefined) e = realm.composeEffects(nested_effects, e);
      return e;
    }
    if (c instanceof _completions.AbruptCompletion && !(c instanceof _completions.JoinedAbruptCompletions)) {
      // The nested effects were captured when evaluating a conditional code block that ended abruptly.
      // An abrupt completion does not care about the effects that were collected since the join point.
      (0, _invariant2.default)(nested_effects !== undefined);
      return nested_effects;
    }
    if (c instanceof _completions.PossiblyNormalCompletion) {
      let e1 = this.joinEffectsAndPromoteNestedReturnCompletions(realm, c.consequent, e, c.consequentEffects);
      let e2 = this.joinEffectsAndPromoteNestedReturnCompletions(realm, c.alternate, e, c.alternateEffects);
      if (e1[0] instanceof _completions.AbruptCompletion) {
        if (e2[0] instanceof _index2.Value) e2[0] = new _completions.ReturnCompletion(realm.intrinsics.undefined, realm.currentLocation);
        return this.joinEffects(realm, c.joinCondition, e1, e2);
      } else if (e2[0] instanceof _completions.AbruptCompletion) {
        if (e1[0] instanceof _index2.Value) e1[0] = new _completions.ReturnCompletion(realm.intrinsics.undefined, realm.currentLocation);
        return this.joinEffects(realm, c.joinCondition, e1, e2);
      }
    }
    (0, _invariant2.default)(c instanceof _completions.JoinedAbruptCompletions);
    // e will be ignored in the calls below since the branches are all abrupt.
    let e1 = this.joinEffectsAndPromoteNestedReturnCompletions(realm, c.consequent, e, c.consequentEffects);
    let e2 = this.joinEffectsAndPromoteNestedReturnCompletions(realm, c.alternate, e, c.alternateEffects);
    let [r1, r2] = [e1[0], e2[0]];
    if (r1 instanceof _completions.ReturnCompletion) {
      // this can happen because joinEffectsAndPromoteNestedReturnCompletions above both had nested ReturnCompletions
      if (r2 instanceof _completions.ReturnCompletion) {
        return this.joinEffects(realm, c.joinCondition, e1, e2);
      }
      if (r2 instanceof _completions.JoinedAbruptCompletions) {
        if (r2.consequent instanceof _completions.ReturnCompletion) {
          let r1jr2c = this.joinEffects(realm, c.joinCondition, e1, r2.consequentEffects);
          (0, _invariant2.default)(r1jr2c[0] instanceof _completions.ReturnCompletion);
          let or = _index2.AbstractValue.createFromLogicalOp(realm, "||", c.joinCondition, r2.joinCondition);
          (0, _invariant2.default)(or instanceof _index2.AbstractValue);
          return this.joinEffects(realm, or, r1jr2c, r2.alternateEffects);
        }
        if (r2.alternate instanceof _completions.ReturnCompletion) {
          let r1jr2a = this.joinEffects(realm, c.joinCondition, e1, r2.alternateEffects);
          (0, _invariant2.default)(r1jr2a[0] instanceof _completions.ReturnCompletion);
          let notR2jc = _index2.AbstractValue.createFromUnaryOp(realm, "!", r2.joinCondition);
          let or = _index2.AbstractValue.createFromLogicalOp(realm, "||", c.joinCondition, notR2jc);
          (0, _invariant2.default)(or instanceof _index2.AbstractValue);
          return this.joinEffects(realm, or, r1jr2a, r2.consequentEffects);
        }
      }
    } else if (r2 instanceof _completions.ReturnCompletion) {
      (0, _invariant2.default)(!(r1 instanceof _completions.ReturnCompletion)); // Otherwise their values should have been joined
      if (r1 instanceof _completions.JoinedAbruptCompletions) {
        if (r1.consequent instanceof _completions.ReturnCompletion) {
          let r2jr1c = this.joinEffects(realm, c.joinCondition, r1.consequentEffects, e2);
          (0, _invariant2.default)(r2jr1c[0] instanceof _completions.ReturnCompletion);
          let or = _index2.AbstractValue.createFromLogicalOp(realm, "||", c.joinCondition, r1.joinCondition);
          (0, _invariant2.default)(or instanceof _index2.AbstractValue);
          return this.joinEffects(realm, or, r2jr1c, r1.alternateEffects);
        }
        if (r1.alternate instanceof _completions.ReturnCompletion) {
          let r2jr1a = this.joinEffects(realm, c.joinCondition, r1.alternateEffects, e2);
          let notR1jc = _index2.AbstractValue.createFromUnaryOp(realm, "!", r1.joinCondition);
          (0, _invariant2.default)(r2jr1a[0] instanceof _completions.ReturnCompletion);
          let or = _index2.AbstractValue.createFromLogicalOp(realm, "||", c.joinCondition, notR1jc);
          (0, _invariant2.default)(or instanceof _index2.AbstractValue);
          return this.joinEffects(realm, or, r2jr1a, r1.consequentEffects);
        }
      }
    }
    let e3 = this.joinEffects(realm, c.joinCondition, e1, e2);
    let [r3] = e3;
    if (r3 instanceof _completions.JoinedAbruptCompletions) {
      let [joinedEffects, possiblyNormalCompletion] = this.unbundleReturnCompletion(realm, r3);
      realm.composeWithSavedCompletion(possiblyNormalCompletion);
      return joinedEffects;
    }
    return e3;
  }

  unbundleReturnCompletion(realm, c) {
    let empty_effects = (0, _realm.construct_empty_effects)(realm);
    let v = realm.intrinsics.empty;
    if (c.consequent instanceof _completions.ReturnCompletion) {
      let pnc = new _completions.PossiblyNormalCompletion(v, c.joinCondition, v, empty_effects, c.alternate, c.alternateEffects, []);
      return [c.consequentEffects, pnc];
    } else if (c.alternate instanceof _completions.ReturnCompletion) {
      let pnc = new _completions.PossiblyNormalCompletion(v, c.joinCondition, c.consequent, c.consequentEffects, v, empty_effects, []);
      return [c.alternateEffects, pnc];
    } else {
      (0, _invariant2.default)(false, "unbundleReturnCompletion needs an argument that contains a non nested return completion");
    }
  }

  removeNormalEffects(realm, c) {
    if (c.consequent instanceof _completions.AbruptCompletion) {
      if (c.alternate instanceof _index2.Value) {
        let result = c.alternateEffects;
        c.alternateEffects = (0, _realm.construct_empty_effects)(realm);
        return result;
      } else {
        (0, _invariant2.default)(c.alternate instanceof _completions.PossiblyNormalCompletion);
        let result = realm.composeEffects(c.alternateEffects, this.removeNormalEffects(realm, c.alternate));
        c.alternateEffects = (0, _realm.construct_empty_effects)(realm);
        return result;
      }
    } else {
      if (c.consequent instanceof _index2.Value) {
        let result = c.consequentEffects;
        c.consequentEffects = (0, _realm.construct_empty_effects)(realm);
        return result;
      } else {
        (0, _invariant2.default)(c.consequent instanceof _completions.PossiblyNormalCompletion);
        let result = realm.composeEffects(c.consequentEffects, this.removeNormalEffects(realm, c.consequent));
        c.consequentEffects = (0, _realm.construct_empty_effects)(realm);
        return result;
      }
    }
  }

  joinEffects(realm, joinCondition, e1, e2) {
    let [result1, gen1, bindings1, properties1, createdObj1] = e1;
    let [result2, gen2, bindings2, properties2, createdObj2] = e2;

    let result = this.joinResults(realm, joinCondition, result1, result2, e1, e2);
    if (result1 instanceof _completions.AbruptCompletion) {
      if (!(result2 instanceof _completions.AbruptCompletion)) {
        (0, _invariant2.default)(result instanceof _completions.PossiblyNormalCompletion);
        return [result, gen2, bindings2, properties2, createdObj2];
      }
    } else if (result2 instanceof _completions.AbruptCompletion) {
      (0, _invariant2.default)(result instanceof _completions.PossiblyNormalCompletion);
      return [result, gen1, bindings1, properties1, createdObj1];
    }

    let bindings = this.joinBindings(realm, joinCondition, bindings1, bindings2);
    let properties = this.joinPropertyBindings(realm, joinCondition, properties1, properties2, createdObj1, createdObj2);
    let createdObjects = new Set();
    createdObj1.forEach(o => {
      createdObjects.add(o);
    });
    createdObj2.forEach(o => {
      createdObjects.add(o);
    });

    let generator = joinGenerators(realm, joinCondition, gen1, gen2);

    return [result, generator, bindings, properties, createdObjects];
  }

  joinResults(realm, joinCondition, result1, result2, e1, e2) {
    let getAbstractValue = (v1, v2) => {
      return this.joinValuesAsConditional(realm, joinCondition, v1, v2);
    };
    if (result1 instanceof _environment.Reference || result2 instanceof _environment.Reference) {
      _index2.AbstractValue.reportIntrospectionError(joinCondition);
      throw new _errors.FatalError();
    }
    if (result1 instanceof _completions.BreakCompletion && result2 instanceof _completions.BreakCompletion && result1.target === result2.target) {
      return new _completions.BreakCompletion(realm.intrinsics.empty, joinCondition.expressionLocation, result1.target);
    }
    if (result1 instanceof _completions.ContinueCompletion && result2 instanceof _completions.ContinueCompletion && result1.target === result2.target) {
      return new _completions.ContinueCompletion(realm.intrinsics.empty, joinCondition.expressionLocation, result1.target);
    }
    if (result1 instanceof _completions.ReturnCompletion && result2 instanceof _completions.ReturnCompletion) {
      let val = this.joinValues(realm, result1.value, result2.value, getAbstractValue);
      (0, _invariant2.default)(val instanceof _index2.Value);
      return new _completions.ReturnCompletion(val, joinCondition.expressionLocation);
    }
    if (result1 instanceof _completions.ThrowCompletion && result2 instanceof _completions.ThrowCompletion) {
      let val = this.joinValues(realm, result1.value, result2.value, getAbstractValue);
      (0, _invariant2.default)(val instanceof _index2.Value);
      return new _completions.ThrowCompletion(val, result1.location);
    }
    if (result1 instanceof _completions.AbruptCompletion && result2 instanceof _completions.AbruptCompletion) {
      return new _completions.JoinedAbruptCompletions(realm, joinCondition, result1, e1, result2, e2);
    }
    if (result1 instanceof _index2.Value && result2 instanceof _index2.Value) {
      let val = this.joinValues(realm, result1, result2, getAbstractValue);
      (0, _invariant2.default)(val instanceof _index2.Value);
      return val;
    }
    if (result1 instanceof _completions.PossiblyNormalCompletion && result2 instanceof _completions.PossiblyNormalCompletion) {
      return this.joinPossiblyNormalCompletions(realm, joinCondition, result1, result2);
    }
    if (result1 instanceof _completions.AbruptCompletion) {
      let value = result2;
      let savedEffects;
      let savedPathConditions = [];
      if (result2 instanceof _completions.PossiblyNormalCompletion) {
        value = result2.value;
        savedEffects = result2.savedEffects;
        savedPathConditions = result2.savedPathConditions;
      }
      (0, _invariant2.default)(value instanceof _index2.Value);
      return new _completions.PossiblyNormalCompletion(value, joinCondition, result1, e1, result2, e2, savedPathConditions, savedEffects);
    }
    if (result2 instanceof _completions.AbruptCompletion) {
      let value = result1;
      let savedEffects;
      let savedPathConditions = [];
      if (result1 instanceof _completions.PossiblyNormalCompletion) {
        value = result1.value;
        savedEffects = result1.savedEffects;
        savedPathConditions = result1.savedPathConditions;
      }
      (0, _invariant2.default)(value instanceof _index2.Value);
      return new _completions.PossiblyNormalCompletion(value, joinCondition, result1, e1, result2, e2, savedPathConditions, savedEffects);
    }
    if (result1 instanceof _completions.PossiblyNormalCompletion) {
      (0, _invariant2.default)(result2 instanceof _index2.Value);
      this.joinPossiblyNormalCompletionWithValue(realm, joinCondition, result1, result2);
      return result1;
    }
    if (result2 instanceof _completions.PossiblyNormalCompletion) {
      (0, _invariant2.default)(result1 instanceof _index2.Value);
      this.joinValueWithPossiblyNormalCompletion(realm, joinCondition, result2, result1);
      return result2;
    }
    (0, _invariant2.default)(false);
  }

  composeGenerators(realm, generator1, generator2) {
    let result = new _generator.Generator(realm, "composed");
    if (!generator1.empty() || !generator2.empty()) {
      result.composeGenerators(generator1, generator2);
    }
    return result;
  }

  // Creates a single map that joins together maps m1 and m2 using the given join
  // operator. If an entry is present in one map but not the other, the missing
  // entry is treated as if it were there and its value were undefined.
  joinMaps(m1, m2, join) {
    let m3 = new Map();
    m1.forEach((val1, key, map1) => {
      let val2 = m2.get(key);
      let val3 = join(key, val1, val2);
      m3.set(key, val3);
    });
    m2.forEach((val2, key, map2) => {
      if (!m1.has(key)) {
        m3.set(key, join(key, undefined, val2));
      }
    });
    return m3;
  }

  // Creates a single map that has an key, value pair for the union of the key
  // sets of m1 and m2. The value of a pair is the join of m1[key] and m2[key]
  // where the join is defined to be just m1[key] if m1[key] === m2[key] and
  // and abstract value with expression "joinCondition ? m1[key] : m2[key]" if not.
  joinBindings(realm, joinCondition, m1, m2) {
    let getAbstractValue = (v1, v2) => {
      return this.joinValuesAsConditional(realm, joinCondition, v1, v2);
    };
    let join = (b, b1, b2) => {
      let l1 = b1 === undefined ? b.hasLeaked : b1.hasLeaked;
      let l2 = b2 === undefined ? b.hasLeaked : b2.hasLeaked;
      let v1 = b1 === undefined ? b.value : b1.value;
      let v2 = b2 === undefined ? b.value : b2.value;
      let hasLeaked = l1 || l2; // If either has leaked, then this binding has leaked.
      let value = this.joinValues(realm, v1, v2, getAbstractValue);
      (0, _invariant2.default)(value instanceof _index2.Value);
      return { hasLeaked, value };
    };
    return this.joinMaps(m1, m2, join);
  }

  // If v1 is known and defined and v1 === v2 return v1,
  // otherwise return getAbstractValue(v1, v2)
  joinValues(realm, v1, v2, getAbstractValue) {
    if (Array.isArray(v1) || Array.isArray(v2)) {
      (0, _invariant2.default)(v1 === undefined || Array.isArray(v1));
      (0, _invariant2.default)(v2 === undefined || Array.isArray(v2));
      return joinArrays(realm, v1, v2, getAbstractValue);
    }
    (0, _invariant2.default)(v1 === undefined || v1 instanceof _index2.Value);
    (0, _invariant2.default)(v2 === undefined || v2 instanceof _index2.Value);
    if (v1 !== undefined && v2 !== undefined && !(v1 instanceof _index2.AbstractValue) && !(v2 instanceof _index2.AbstractValue) && (0, _index.StrictEqualityComparison)(realm, v1.throwIfNotConcrete(), v2.throwIfNotConcrete())) {
      return v1;
    } else {
      return getAbstractValue(v1, v2);
    }
  }

  joinValuesAsConditional(realm, condition, v1, v2) {
    return _index2.AbstractValue.createFromConditionalOp(realm, condition, v1, v2);
  }

  joinPropertyBindings(realm, joinCondition, m1, m2, c1, c2) {
    let join = (b, d1, d2) => {
      // If the PropertyBinding object has been freshly allocated do not join
      if (d1 === undefined) {
        if (b.object instanceof _index2.ObjectValue && c2.has(b.object)) return d2; // no join
        if (b.descriptor !== undefined && m1.has(b)) {
          // property was deleted
          d1 = (0, _index.cloneDescriptor)(b.descriptor);
          (0, _invariant2.default)(d1 !== undefined);
          d1.value = realm.intrinsics.empty;
        } else {
          // no write to property
          d1 = b.descriptor; //Get value of property before the split
        }
      }
      if (d2 === undefined) {
        if (b.object instanceof _index2.ObjectValue && c1.has(b.object)) return d1; // no join
        if (b.descriptor !== undefined && m2.has(b)) {
          // property was deleted
          d2 = (0, _index.cloneDescriptor)(b.descriptor);
          (0, _invariant2.default)(d2 !== undefined);
          d2.value = realm.intrinsics.empty;
        } else {
          // no write to property
          d2 = b.descriptor; //Get value of property before the split
        }
      }
      return this.joinDescriptors(realm, joinCondition, d1, d2);
    };
    return this.joinMaps(m1, m2, join);
  }

  joinDescriptors(realm, joinCondition, d1, d2) {
    let getAbstractValue = (v1, v2) => {
      return this.joinValuesAsConditional(realm, joinCondition, v1, v2);
    };
    let clone_with_abstract_value = d => {
      if (!(0, _index.IsDataDescriptor)(realm, d)) {
        let d3 = {};
        d3.joinCondition = joinCondition;
        return d3;
      }
      let dc = (0, _index.cloneDescriptor)(d);
      (0, _invariant2.default)(dc !== undefined);
      let dcValue = dc.value;
      if (Array.isArray(dcValue)) {
        (0, _invariant2.default)(dcValue.length > 0);
        let elem0 = dcValue[0];
        if (elem0 instanceof _index2.Value) {
          dc.value = dcValue.map(e => getAbstractValue(e, realm.intrinsics.empty));
        } else {
          dc.value = dcValue.map(e => {
            let { $Key: key1, $Value: val1 } = e;
            let key3 = getAbstractValue(key1, realm.intrinsics.empty);
            let val3 = getAbstractValue(val1, realm.intrinsics.empty);
            return { $Key: key3, $Value: val3 };
          });
        }
      } else {
        (0, _invariant2.default)(dcValue === undefined || dcValue instanceof _index2.Value);
        dc.value = getAbstractValue(dcValue, realm.intrinsics.empty);
      }
      return dc;
    };
    if (d1 === undefined) {
      if (d2 === undefined) return undefined;
      // d2 is a new property created in only one branch, join with empty
      let d3 = clone_with_abstract_value(d2);
      if (!(0, _index.IsDataDescriptor)(realm, d2)) d3.descriptor2 = d2;
      return d3;
    } else if (d2 === undefined) {
      (0, _invariant2.default)(d1 !== undefined);
      // d1 is a new property created in only one branch, join with empty
      let d3 = clone_with_abstract_value(d1);
      if (!(0, _index.IsDataDescriptor)(realm, d1)) d3.descriptor1 = d1;
      return d3;
    } else {
      if ((0, _index.equalDescriptors)(d1, d2) && (0, _index.IsDataDescriptor)(realm, d1)) {
        let dc = (0, _index.cloneDescriptor)(d1);
        (0, _invariant2.default)(dc !== undefined);
        dc.value = this.joinValues(realm, d1.value, d2.value, getAbstractValue);
        return dc;
      }
      let d3 = {};
      d3.joinCondition = joinCondition;
      d3.descriptor1 = d1;
      d3.descriptor2 = d2;
      return d3;
    }
  }
}
exports.JoinImplementation = JoinImplementation;
//# sourceMappingURL=join.js.map