"use strict";

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

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

var _utils = require("./utils.js");

var _ResidualHeapVisitor = require("../serializer/ResidualHeapVisitor.js");

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

// a nested object of a React Element should be hoisted where all its properties are known
// at evaluation time to be safe to hoist (because of the heuristics of a React render)
/**
 * 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 canHoistObject(realm, object, residualHeapVisitor, visitedValues) {
  if ((0, _utils.isReactElement)(object)) {
    return canHoistReactElement(realm, object, residualHeapVisitor, visitedValues);
  }
  for (let [propName] of object.properties) {
    let prop = (0, _index2.Get)(realm, object, propName);
    if (!canHoistValue(realm, prop, residualHeapVisitor, visitedValues)) {
      return false;
    }
  }
  for (let [symbol] of object.symbols) {
    let prop = (0, _index2.Get)(realm, object, symbol);
    if (!canHoistValue(realm, prop, residualHeapVisitor, visitedValues)) {
      return false;
    }
  }
  return true;
}

function canHoistArray(realm, array, residualHeapVisitor, visitedValues) {
  let lengthValue = (0, _index2.Get)(realm, array, "length");
  (0, _invariant2.default)(lengthValue instanceof _index.NumberValue);
  let length = lengthValue.value;
  for (let i = 0; i < length; i++) {
    let element = (0, _index2.Get)(realm, array, "" + i);

    if (!canHoistValue(realm, element, residualHeapVisitor, visitedValues)) {
      return false;
    }
  }
  return true;
}

function canHoistFunction(realm, func, residualHeapVisitor, visitedValues) {
  if (realm.react.hoistableFunctions.has(func)) {
    // cast because Flow thinks that we may have set a value to be something other than a boolean?
    return realm.react.hoistableFunctions.get(func);
  }
  if (residualHeapVisitor === undefined) {
    return false;
  }
  // get the function instance
  let functionInstance = residualHeapVisitor.functionInstances.get(func);
  // we can safely hoist the function if the residual bindings hoistable too
  if (functionInstance !== undefined) {
    (0, _invariant2.default)(functionInstance.residualFunctionBindings instanceof Map);
    let residualBindings = functionInstance.residualFunctionBindings;
    for (let [, { declarativeEnvironmentRecord, value }] of residualBindings) {
      // if declarativeEnvironmentRecord is null, it's likely a global binding
      // so we can assume that we can still hoist this function
      if (declarativeEnvironmentRecord !== null) {
        (0, _invariant2.default)(value instanceof _index.Value);
        if (!canHoistValue(realm, value, residualHeapVisitor, visitedValues)) {
          return false;
        }
      }
    }
    if (func instanceof _index.ECMAScriptSourceFunctionValue) {
      let code = func.$ECMAScriptCode;
      let functionInfos = residualHeapVisitor.functionInfos.get(code);
      if (functionInfos && functionInfos.unbound.size > 0) {
        return false;
      }
    }
    realm.react.hoistableFunctions.set(func, true);
    return true;
  }
  realm.react.hoistableFunctions.set(func, false);
  return false;
}

function canHoistAbstract(realm, abstract, residualHeapVisitor) {
  // TODO #1687: add abstract value hoisting
  return false;
}

function isPrimitive(realm, value) {
  return value instanceof _index.StringValue || value instanceof _index.NumberValue || value instanceof _index.SymbolValue || value instanceof _index.BooleanValue || value === realm.intrinsics.null || value === realm.intrinsics.undefined;
}

function canHoistValue(realm, value, residualHeapVisitor, visitedValues) {
  if (visitedValues.has(value)) {
    // If there is a cycle, bail out.
    // TODO: is there some way to *not* bail out in this case?
    // Currently if we don't, the output is broken.
    return false;
  }
  visitedValues.add(value);
  const canHoist = value instanceof _index.ArrayValue && canHoistArray(realm, value, residualHeapVisitor, visitedValues) || value instanceof _index.FunctionValue && canHoistFunction(realm, value, residualHeapVisitor, visitedValues) || value instanceof _index.ObjectValue && canHoistObject(realm, value, residualHeapVisitor, visitedValues) || value instanceof _index.AbstractValue && canHoistAbstract(realm, value, residualHeapVisitor) || isPrimitive(realm, value);
  visitedValues.delete(value);
  return canHoist;
}

function canHoistReactElement(realm, reactElement, residualHeapVisitor, visitedValues) {
  if (realm.react.hoistableReactElements.has(reactElement)) {
    // cast because Flow thinks that we may have set a value to be something other than a boolean?
    return realm.react.hoistableReactElements.get(reactElement);
  }
  if (residualHeapVisitor === undefined) {
    return false;
  }
  let type = (0, _index2.Get)(realm, reactElement, "type");
  let ref = (0, _index2.Get)(realm, reactElement, "ref");
  let key = (0, _index2.Get)(realm, reactElement, "key");
  let props = (0, _index2.Get)(realm, reactElement, "props");

  if (visitedValues === undefined) {
    visitedValues = new Set();
    visitedValues.add(reactElement);
  }
  if (canHoistValue(realm, type, residualHeapVisitor, visitedValues) &&
  // we can't hoist string "refs" or if they're abstract, as they might be abstract strings
  !(ref instanceof String || ref instanceof _index.AbstractValue) && canHoistValue(realm, ref, residualHeapVisitor, visitedValues) && canHoistValue(realm, key, residualHeapVisitor, visitedValues) && !props.isPartialObject() && canHoistValue(realm, props, residualHeapVisitor, visitedValues)) {
    realm.react.hoistableReactElements.set(reactElement, true);
    return true;
  }
  realm.react.hoistableReactElements.set(reactElement, false);
  return false;
}
//# sourceMappingURL=hoisting.js.map