"use strict";

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

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

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

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

var _invariant2 = _interopRequireDefault(_invariant);

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

var _errors2 = require("../utils/errors.js");

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

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

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

var _modules = require("../utils/modules.js");

var _types = require("./types");

var _reconcilation = require("../react/reconcilation.js");

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

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

var t = _interopRequireWildcard(_babelTypes);

var _utils2 = require("../intrinsics/prepack/utils.js");

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

class Functions {
  constructor(realm, moduleTracer) {
    this.realm = realm;
    this.moduleTracer = moduleTracer;
    this.writeEffects = new Map();
    this.functionExpressions = new Map();
  }
  // maps back from FunctionValue to the expression string


  __generateAdditionalFunctionsMap(globalKey) {
    let recordedAdditionalFunctions = new Map();
    let realm = this.realm;
    let globalRecordedAdditionalFunctionsMap = this.moduleTracer.modules.logger.tryQuery(() => (0, _index2.Get)(realm, realm.$GlobalObject, globalKey), realm.intrinsics.undefined);
    (0, _invariant2.default)(globalRecordedAdditionalFunctionsMap instanceof _index.ObjectValue);
    for (let funcId of globalRecordedAdditionalFunctionsMap.getOwnPropertyKeysArray()) {
      let property = globalRecordedAdditionalFunctionsMap.properties.get(funcId);
      if (property) {
        let value = property.descriptor && property.descriptor.value;

        if (value instanceof _index.ECMAScriptSourceFunctionValue) {
          // additional function logic
          recordedAdditionalFunctions.set(value, { funcId });
          continue;
        } else if (value instanceof _index.ObjectValue) {
          // React component tree logic
          let config = (0, _index2.Get)(realm, value, "config");
          let rootComponent = (0, _index2.Get)(realm, value, "rootComponent");
          let validConfig = config instanceof _index.ObjectValue || config === realm.intrinsics.undefined;
          let validRootComponent = rootComponent instanceof _index.ECMAScriptSourceFunctionValue || rootComponent instanceof _index.AbstractValue && (0, _utils.valueIsKnownReactAbstraction)(this.realm, rootComponent);

          if (validConfig && validRootComponent) {
            recordedAdditionalFunctions.set(rootComponent, {
              funcId,
              config: (0, _utils.convertConfigObjectToReactComponentTreeConfig)(realm, config)
            });
          }
          continue;
        }
        realm.handleError(new _errors.CompilerDiagnostic(`Additional Function Value ${funcId} is an invalid value`, undefined, "PP0001", "FatalError"));
        throw new _errors.FatalError("invalid Additional Function value");
      }
    }
    return recordedAdditionalFunctions;
  }

  // This will also handle postprocessing for PossiblyNormalCompletion
  _createAdditionalEffects(effects, fatalOnAbrupt, name, environmentRecordIdAfterGlobalCode) {
    let retValue = {
      effects,
      transforms: [],
      generator: _generator.Generator.fromEffects(effects, this.realm, name, environmentRecordIdAfterGlobalCode)
    };
    return retValue;
  }

  _generateWriteEffectsForReactComponentTree(componentType, effects, componentTreeState, evaluatedNode, environmentRecordIdAfterGlobalCode) {
    let additionalFunctionEffects = this._createAdditionalEffects(effects, false, "ReactAdditionalFunctionEffects", environmentRecordIdAfterGlobalCode);
    if (additionalFunctionEffects === null) {
      // TODO we don't support this yet, but will do very soon
      // to unblock work, we'll just return at this point right now
      evaluatedNode.status = "UNSUPPORTED_COMPLETION";
      return;
    }
    let value = effects[0];

    if (value === this.realm.intrinsics.undefined) {
      // if we get undefined, then this component tree failed and a message was already logged
      // in the reconciler
      return;
    }
    if ((0, _utils.valueIsClassComponent)(this.realm, componentType)) {
      if (componentTreeState.status === "SIMPLE") {
        // if the root component was a class and is now simple, we can convert it from a class
        // component to a functional component
        (0, _utils.convertSimpleClassComponentToFunctionalComponent)(this.realm, componentType, additionalFunctionEffects);
        (0, _utils.normalizeFunctionalComponentParamaters)(componentType);
        this.writeEffects.set(componentType, additionalFunctionEffects);
      } else {
        let prototype = (0, _index2.Get)(this.realm, componentType, "prototype");
        (0, _invariant2.default)(prototype instanceof _index.ObjectValue);
        let renderMethod = (0, _index2.Get)(this.realm, prototype, "render");
        (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue);
        this.writeEffects.set(renderMethod, additionalFunctionEffects);
      }
    } else {
      if (componentTreeState.status === "COMPLEX") {
        (0, _utils.convertFunctionalComponentToComplexClassComponent)(this.realm, componentType, componentTreeState.componentType, additionalFunctionEffects);
        let prototype = (0, _index2.Get)(this.realm, componentType, "prototype");
        (0, _invariant2.default)(prototype instanceof _index.ObjectValue);
        let renderMethod = (0, _index2.Get)(this.realm, prototype, "render");
        (0, _invariant2.default)(renderMethod instanceof _index.ECMAScriptSourceFunctionValue);
        this.writeEffects.set(renderMethod, additionalFunctionEffects);
      } else {
        (0, _utils.normalizeFunctionalComponentParamaters)(componentType);
        this.writeEffects.set(componentType, additionalFunctionEffects);
      }
    }
  }

  optimizeReactComponentTreeRoots(statistics, react, environmentRecordIdAfterGlobalCode) {
    let logger = this.moduleTracer.modules.logger;
    let recordedReactRootValues = this.__generateAdditionalFunctionsMap("__reactComponentTrees");
    // Get write effects of the components
    if (this.realm.react.verbose) {
      logger.logInformation(`Evaluating ${recordedReactRootValues.size} React component tree roots...`);
    }
    for (let [componentRoot, { config }] of recordedReactRootValues) {
      (0, _invariant2.default)(config);
      let reconciler = new _reconcilation.Reconciler(this.realm, this.moduleTracer.modules.logger, statistics, react, config);
      let componentType = (0, _utils.getComponentTypeFromRootValue)(this.realm, componentRoot);
      if (componentType === null) {
        continue;
      }
      let evaluatedRootNode = (0, _utils.createReactEvaluatedNode)("ROOT", (0, _utils.getComponentName)(this.realm, componentType));
      statistics.evaluatedRootNodes.push(evaluatedRootNode);
      if (reconciler.hasEvaluatedRootNode(componentType, evaluatedRootNode)) {
        continue;
      }
      let componentTreeEffects = reconciler.renderReactComponentTree(componentType, null, null, evaluatedRootNode);
      if (componentTreeEffects === null) {
        if (this.realm.react.verbose) {
          logger.logInformation(`  ✖ ${evaluatedRootNode.name} (root)`);
        }
        continue;
      } else if (this.realm.react.verbose) {
        logger.logInformation(`  ✔ ${evaluatedRootNode.name} (root)`);
      }
      this._generateWriteEffectsForReactComponentTree(componentType, componentTreeEffects, reconciler.componentTreeState, evaluatedRootNode, environmentRecordIdAfterGlobalCode);

      let startingComponentTreeBranches = 0;
      do {
        startingComponentTreeBranches = reconciler.branchedComponentTrees.length;
        this._optimizeReactComponentTreeBranches(reconciler, environmentRecordIdAfterGlobalCode);
        this._optimizeReactNestedClosures(reconciler, environmentRecordIdAfterGlobalCode);
      } while (startingComponentTreeBranches !== reconciler.branchedComponentTrees.length);
    }
  }

  _optimizeReactNestedClosures(reconciler, environmentRecordIdAfterGlobalCode) {
    let logger = this.moduleTracer.modules.logger;

    if (this.realm.react.verbose && reconciler.nestedOptimizedClosures.length > 0) {
      logger.logInformation(`    Evaluating nested closures...`);
    }
    for (let _ref of reconciler.nestedOptimizedClosures) {
      let {
        func,
        evaluatedNode,
        nestedEffects,
        componentType,
        context,
        branchState
      } = _ref;

      if (reconciler.hasEvaluatedNestedClosure(func)) {
        continue;
      }
      if (func instanceof _index.ECMAScriptSourceFunctionValue && reconciler.hasEvaluatedRootNode(func, evaluatedNode)) {
        continue;
      }
      let closureEffects = reconciler.renderNestedOptimizedClosure(func, nestedEffects, componentType, context, branchState, evaluatedNode);
      if (closureEffects === null) {
        if (this.realm.react.verbose) {
          logger.logInformation(`      ✖ function "${(0, _utils.getComponentName)(this.realm, func)}"`);
        }
        continue;
      } else if (this.realm.react.verbose) {
        logger.logInformation(`      ✔ function "${(0, _utils.getComponentName)(this.realm, func)}"`);
      }
      let additionalFunctionEffects = this._createAdditionalEffects(closureEffects, true, "ReactNestedAdditionalFunctionEffects", environmentRecordIdAfterGlobalCode);
      (0, _invariant2.default)(additionalFunctionEffects);
      if (func instanceof _index.BoundFunctionValue) {
        (0, _invariant2.default)(func.$BoundTargetFunction instanceof _index.ECMAScriptSourceFunctionValue);
        this.writeEffects.set(func.$BoundTargetFunction, additionalFunctionEffects);
      } else {
        this.writeEffects.set(func, additionalFunctionEffects);
      }
    }
  }

  _optimizeReactComponentTreeBranches(reconciler, environmentRecordIdAfterGlobalCode) {
    let logger = this.moduleTracer.modules.logger;
    // for now we just use abstract props/context, in the future we'll create a new branch with a new component
    // that used the props/context. It will extend the original component and only have a render method
    for (let _ref2 of reconciler.branchedComponentTrees) {
      let { rootValue: branchRootValue, evaluatedNode } = _ref2;

      let branchComponentType = (0, _utils.getComponentTypeFromRootValue)(this.realm, branchRootValue);
      if (branchComponentType === null) {
        evaluatedNode.status = "UNKNOWN_TYPE";
        continue;
      }
      // so we don't process the same component multiple times (we might change this logic later)
      if (reconciler.hasEvaluatedRootNode(branchComponentType, evaluatedNode)) {
        continue;
      }
      reconciler.clearComponentTreeState();
      let branchEffects = reconciler.renderReactComponentTree(branchComponentType, null, null, evaluatedNode);
      if (branchEffects === null) {
        if (this.realm.react.verbose) {
          logger.logInformation(`    ✖ ${evaluatedNode.name} (branch)`);
        }
        continue;
      } else if (this.realm.react.verbose) {
        logger.logInformation(`    ✔ ${evaluatedNode.name} (branch)`);
      }
      let branchComponentTreeState = reconciler.componentTreeState;
      this._generateWriteEffectsForReactComponentTree(branchComponentType, branchEffects, branchComponentTreeState, evaluatedNode, environmentRecordIdAfterGlobalCode);
    }
  }

  _generateAdditionalFunctionCallsFromDirective() {
    let recordedAdditionalFunctions = this.__generateAdditionalFunctionsMap("__optimizedFunctions");

    // The additional functions we registered at runtime are recorded at:
    // global.__optimizedFunctions.id
    let calls = [];
    for (let [funcValue, { funcId }] of recordedAdditionalFunctions) {
      // TODO #987: Make Additional Functions work with arguments
      (0, _invariant2.default)(funcValue instanceof _index.FunctionValue);
      calls.push([funcValue, t.callExpression(t.memberExpression(t.memberExpression(t.identifier("global"), t.identifier("__optimizedFunctions")), t.identifier(funcId)), [])]);
    }
    return calls;
  }

  _callOfFunction(funcValue) {
    let call = funcValue.$Call;
    (0, _invariant2.default)(call);
    let numArgs = funcValue.getLength();
    let args = [];
    (0, _invariant2.default)(funcValue instanceof _index.ECMAScriptSourceFunctionValue);
    let params = funcValue.$FormalParameters;
    if (numArgs && numArgs > 0 && params) {
      for (let parameterId of params) {
        if (t.isIdentifier(parameterId)) {
          // Create an AbstractValue similar to __abstract being called
          args.push((0, _utils2.createAbstractArgument)(this.realm, parameterId.name, funcValue.expressionLocation));
        } else {
          this.realm.handleError(new _errors.CompilerDiagnostic("Non-identifier args to additional functions unsupported", funcValue.expressionLocation, "PP1005", "FatalError"));
          throw new _errors.FatalError("Non-identifier args to additional functions unsupported");
        }
      }
    }

    let thisArg = (0, _utils2.createAbstractArgument)(this.realm, "this", funcValue.expressionLocation, _index.ObjectValue);
    return call.bind(this, thisArg, args);
  }

  checkThatFunctionsAreIndependent(environmentRecordIdAfterGlobalCode) {
    let additionalFunctions = this.__generateAdditionalFunctionsMap("__optimizedFunctions");

    for (let [funcValue] of additionalFunctions) {
      (0, _invariant2.default)(funcValue instanceof _index.ECMAScriptSourceFunctionValue);
      let call = this._callOfFunction(funcValue);
      let effects = this.realm.evaluatePure(() => this.realm.evaluateForEffectsInGlobalEnv(call, undefined, "additional function"));
      (0, _invariant2.default)(effects);
      let additionalFunctionEffects = this._createAdditionalEffects(effects, true, "AdditionalFunctionEffects", environmentRecordIdAfterGlobalCode);
      (0, _invariant2.default)(additionalFunctionEffects);
      this.writeEffects.set(funcValue, additionalFunctionEffects);
    }

    // check that functions are independent
    let conflicts = new Map();
    for (let [fun1] of additionalFunctions) {
      (0, _invariant2.default)(fun1 instanceof _index.FunctionValue);
      let fun1Name = this.functionExpressions.get(fun1) || fun1.intrinsicName || "(unknown function)";
      // Also do argument validation here
      let additionalFunctionEffects = this.writeEffects.get(fun1);
      (0, _invariant2.default)(additionalFunctionEffects !== undefined);
      let e1 = additionalFunctionEffects.effects;
      (0, _invariant2.default)(e1 !== undefined);
      if (e1[0] instanceof _completions.Completion && !e1[0] instanceof _completions.PossiblyNormalCompletion) {
        let error = new _errors.CompilerDiagnostic(`Additional function ${fun1Name} may terminate abruptly`, e1[0].location, "PP1002", "FatalError");
        this.realm.handleError(error);
        throw new _errors.FatalError();
      }
      for (let [fun2] of additionalFunctions) {
        if (fun1 === fun2) continue;
        (0, _invariant2.default)(fun2 instanceof _index.FunctionValue);
        this.reportWriteConflicts(fun1Name, conflicts, e1[3], this._callOfFunction(fun2));
      }
    }
    if (conflicts.size > 0) {
      for (let diagnostic of conflicts.values()) this.realm.handleError(diagnostic);
      throw new _errors.FatalError();
    }
  }

  getAdditionalFunctionValuesToEffects() {
    return this.writeEffects;
  }

  reportWriteConflicts(fname, conflicts, pbs, call2) {
    let reportConflict = location => {
      let error = new _errors.CompilerDiagnostic(`Property access conflicts with write in additional function ${fname}`, location, "PP1003", "FatalError");
      conflicts.set(location, error);
    };
    let writtenObjects = new Set();
    pbs.forEach((val, key, m) => {
      writtenObjects.add(key.object);
    });
    let oldReportObjectGetOwnProperties = this.realm.reportObjectGetOwnProperties;
    this.realm.reportObjectGetOwnProperties = ob => {
      let location = this.realm.currentLocation;
      (0, _invariant2.default)(location);
      if (writtenObjects.has(ob) && !conflicts.has(location)) reportConflict(location);
    };
    let oldReportPropertyAccess = this.realm.reportPropertyAccess;
    this.realm.reportPropertyAccess = pb => {
      let location = this.realm.currentLocation;
      if (!location) return; // happens only when accessing an additional function property
      if (pbs.has(pb) && !conflicts.has(location)) reportConflict(location);
    };
    try {
      (0, _errors2.ignoreErrorsIn)(this.realm, () => this.realm.evaluateForEffectsInGlobalEnv(call2));
    } finally {
      this.realm.reportPropertyAccess = oldReportPropertyAccess;
      this.realm.reportObjectGetOwnProperties = oldReportObjectGetOwnProperties;
    }
  }
}
exports.Functions = Functions; /**
                                * 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=functions.js.map