"use strict";

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

exports.default = function (ast, strictCode, env, realm) {
  if (ast.callee.type === "Super") {
    return (0, _SuperCall2.default)(ast.arguments, strictCode, env, realm);
  }

  // ECMA262 12.3.4.1
  realm.setNextExecutionContextLocation(ast.loc);

  // 1. Let ref be the result of evaluating MemberExpression.
  let ref = env.evaluate(ast.callee, strictCode);

  // 2. Let func be ? GetValue(ref).
  let func = _singletons.Environment.GetValue(realm, ref);

  return EvaluateCall(ref, func, ast, strictCode, env, realm);
};

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

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

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

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

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

var t = _interopRequireWildcard(_babelTypes);

var _SuperCall = require("./SuperCall");

var _SuperCall2 = _interopRequireDefault(_SuperCall);

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

function callBothFunctionsAndJoinTheirEffects(args, ast, strictCode, env, realm) {
  let [cond, func1, func2] = args;
  (0, _invariant2.default)(cond instanceof _index2.AbstractValue && cond.getType() === _index2.BooleanValue);
  (0, _invariant2.default)(_index2.Value.isTypeCompatibleWith(func1.getType(), _index2.FunctionValue));
  (0, _invariant2.default)(_index2.Value.isTypeCompatibleWith(func2.getType(), _index2.FunctionValue));

  let [compl1, gen1, bindings1, properties1, createdObj1] = realm.evaluateForEffects(() => EvaluateCall(func1, func1, ast, strictCode, env, realm), undefined, "callBothFunctionsAndJoinTheirEffects/1");

  let [compl2, gen2, bindings2, properties2, createdObj2] = realm.evaluateForEffects(() => EvaluateCall(func2, func2, ast, strictCode, env, realm), undefined, "callBothFunctionsAndJoinTheirEffects/2");

  let joinedEffects = _singletons.Join.joinEffects(realm, cond, [compl1, gen1, bindings1, properties1, createdObj1], [compl2, gen2, bindings2, properties2, createdObj2]);
  let completion = joinedEffects[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 joinedEffects, but are tracked separately inside completion.
  realm.applyEffects(joinedEffects);

  // return or throw completion
  if (completion instanceof _completions.AbruptCompletion) throw completion;
  (0, _invariant2.default)(completion instanceof _index2.Value);
  return completion;
} /**
   * 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 generateRuntimeCall(ref, func, ast, strictCode, env, realm) {
  let args = [func];
  let [thisArg, propName] = ref instanceof _environment.Reference ? [ref.base, ref.referencedName] : [];
  if (thisArg instanceof _index2.Value) args = [thisArg];
  if (propName !== undefined && typeof propName !== "string") args.push(propName);
  args = args.concat((0, _index3.ArgumentListEvaluation)(realm, strictCode, env, ast.arguments));
  for (let arg of args) {
    if (arg !== func) {
      // Since we don't know which function we are calling, we assume that any unfrozen object
      // passed as an argument has leaked to the environment and is henceforth in an unknown (havoced) state,
      // as is any other object that is known to be reachable from this object.
      // NB: Note that this is still optimistic, particularly if the environment exposes the same object
      // to Prepack via alternative means, thus creating aliasing that is not tracked by Prepack.
      _singletons.Havoc.value(realm, arg, ast.loc);
    }
  }
  let resultType = (func instanceof _index2.AbstractObjectValue ? func.functionResultType : undefined) || _index2.Value;
  return _index2.AbstractValue.createTemporalFromBuildFunction(realm, resultType, args, nodes => {
    let callFunc;
    let argStart = 1;
    if (thisArg instanceof _index2.Value) {
      if (typeof propName === "string") {
        callFunc = t.isValidIdentifier(propName) ? t.memberExpression(nodes[0], t.identifier(propName), false) : t.memberExpression(nodes[0], t.stringLiteral(propName), true);
      } else {
        callFunc = t.memberExpression(nodes[0], nodes[1], true);
        argStart = 2;
      }
    } else {
      callFunc = nodes[0];
    }
    let fun_args = nodes.slice(argStart);
    return t.callExpression(callFunc, fun_args);
  });
}

function tryToEvaluateCallOrLeaveAsAbstract(ref, func, ast, strictCode, env, realm, thisValue, tailCall) {
  let effects;
  try {
    effects = realm.evaluateForEffects(() => (0, _index3.EvaluateDirectCall)(realm, strictCode, env, ref, func, thisValue, ast.arguments, tailCall), undefined, "tryToEvaluateCallOrLeaveAsAbstract");
  } catch (error) {
    if (error instanceof _errors.FatalError) {
      return realm.evaluateWithPossibleThrowCompletion(() => generateRuntimeCall(ref, func, ast, strictCode, env, realm), _index.TypesDomain.topVal, _index.ValuesDomain.topVal);
    } else {
      throw error;
    }
  }
  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;
}

function EvaluateCall(ref, func, ast, strictCode, env, realm) {
  if (func instanceof _index2.AbstractValue) {
    let loc = ast.callee.type === "MemberExpression" ? ast.callee.property.loc : ast.callee.loc;
    if (!_index2.Value.isTypeCompatibleWith(func.getType(), _index2.FunctionValue)) {
      if (!realm.isInPureScope()) {
        // If this is not a function, this call might throw which can change the state of the program.
        // If this is called from a pure function we handle it using evaluateWithPossiblyAbruptCompletion.
        let error = new _errors.CompilerDiagnostic("might not be a function", loc, "PP0005", "RecoverableError");
        if (realm.handleError(error) === "Fail") throw new _errors.FatalError();
      }
    } else if (func.kind === "conditional") {
      return callBothFunctionsAndJoinTheirEffects(func.args, ast, strictCode, env, realm);
    } else {
      // Assume that it is a safe function. TODO #705: really?
    }
    if (realm.isInPureScope()) {
      // In pure functions we allow abstract functions to throw, which this might.
      return realm.evaluateWithPossibleThrowCompletion(() => generateRuntimeCall(ref, func, ast, strictCode, env, realm), _index.TypesDomain.topVal, _index.ValuesDomain.topVal);
    }
    return generateRuntimeCall(ref, func, ast, strictCode, env, realm);
  }
  (0, _invariant2.default)(func instanceof _index2.ConcreteValue);

  // 3. If Type(ref) is Reference and IsPropertyReference(ref) is false and GetReferencedName(ref) is "eval", then
  if (ref instanceof _environment.Reference && !_singletons.Environment.IsPropertyReference(realm, ref) && _singletons.Environment.GetReferencedName(realm, ref) === "eval") {
    // a. If SameValue(func, %eval%) is true, then
    if ((0, _index3.SameValue)(realm, func, realm.intrinsics.eval)) {
      // i. Let argList be ? ArgumentListEvaluation(Arguments).
      let argList = (0, _index3.ArgumentListEvaluation)(realm, strictCode, env, ast.arguments);
      // ii. If argList has no elements, return undefined.
      if (argList.length === 0) return realm.intrinsics.undefined;
      // iii. Let evalText be the first element of argList.
      let evalText = argList[0];
      // iv. If the source code matching this CallExpression is strict code, let strictCaller be true. Otherwise let strictCaller be false.
      let strictCaller = strictCode;
      // v. Let evalRealm be the current Realm Record.
      let evalRealm = realm;
      // vi. Return ? PerformEval(evalText, evalRealm, strictCaller, true).
      if (evalText instanceof _index2.AbstractValue) {
        let loc = ast.arguments[0].loc;
        let error = new _errors.CompilerDiagnostic("eval argument must be a known value", loc, "PP0006", "RecoverableError");
        if (realm.handleError(error) === "Fail") throw new _errors.FatalError();
        // Assume that it is a safe eval with no visible heap changes or abrupt control flow.
        return generateRuntimeCall(ref, func, ast, strictCode, env, realm);
      }
      return _singletons.Functions.PerformEval(realm, evalText, evalRealm, strictCaller, true);
    }
  }

  let thisValue;

  // 4. If Type(ref) is Reference, then
  if (ref instanceof _environment.Reference) {
    // a. If IsPropertyReference(ref) is true, then
    if (_singletons.Environment.IsPropertyReference(realm, ref)) {
      // i. Let thisValue be GetThisValue(ref).
      thisValue = (0, _index3.GetThisValue)(realm, ref);
    } else {
      // b. Else, the base of ref is an Environment Record
      // i. Let refEnv be GetBase(ref).
      let refEnv = _singletons.Environment.GetBase(realm, ref);
      (0, _invariant2.default)(refEnv instanceof _environment.EnvironmentRecord);

      // ii. Let thisValue be refEnv.WithBaseObject().
      thisValue = refEnv.WithBaseObject();
    }
  } else {
    // 5. Else Type(ref) is not Reference,
    // a. Let thisValue be undefined.
    thisValue = realm.intrinsics.undefined;
  }

  // 6. Let thisCall be this CallExpression.
  let thisCall = ast;

  // 7. Let tailCall be IsInTailPosition(thisCall). (See 14.6.1)
  let tailCall = (0, _index3.IsInTailPosition)(realm, thisCall);

  // 8. Return ? EvaluateDirectCall(func, thisValue, Arguments, tailCall).
  if (realm.isInPureScope()) {
    return tryToEvaluateCallOrLeaveAsAbstract(ref, func, ast, strictCode, env, realm, thisValue, tailCall);
  } else {
    return (0, _index3.EvaluateDirectCall)(realm, strictCode, env, ref, func, thisValue, ast.arguments, tailCall);
  }
}
//# sourceMappingURL=CallExpression.js.map