// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include 'src/builtins/builtins-promise-gen.h'

namespace promise {

macro
IsPromiseSpeciesLookupChainIntact(
    nativeContext: NativeContext, promiseMap: Map): bool {
  const promisePrototype =
      *NativeContextSlot(nativeContext, ContextSlot::PROMISE_PROTOTYPE_INDEX);
  if (IsForceSlowPath()) return false;
  if (promiseMap.prototype != promisePrototype) return false;
  return !IsPromiseSpeciesProtectorCellInvalid();
}

// https://tc39.es/ecma262/#sec-promise.prototype.then
transitioning javascript builtin
PromisePrototypeThen(js-implicit context: NativeContext, receiver: JSAny)(
    onFulfilled: JSAny, onRejected: JSAny): JSAny {
  // 1. Let promise be the this value.
  // 2. If IsPromise(promise) is false, throw a TypeError exception.
  const promise = Cast<JSPromise>(receiver) otherwise ThrowTypeError(
      MessageTemplate::kIncompatibleMethodReceiver, 'Promise.prototype.then',
      receiver);

  // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
  const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX);

  // 4. Let resultCapability be ? NewPromiseCapability(C).
  let resultPromiseOrCapability: JSPromise|PromiseCapability;
  let resultPromise: JSAny;
  try {
    if (IsPromiseSpeciesLookupChainIntact(context, promise.map)) {
      goto AllocateAndInit;
    }

    const constructor = SpeciesConstructor(promise, promiseFun);
    if (TaggedEqual(constructor, promiseFun)) {
      goto AllocateAndInit;
    } else {
      const promiseCapability = NewPromiseCapability(constructor, True);
      resultPromiseOrCapability = promiseCapability;
      resultPromise = promiseCapability.promise;
    }
  } label AllocateAndInit {
    const resultJSPromise = NewJSPromise(promise);
    resultPromiseOrCapability = resultJSPromise;
    resultPromise = resultJSPromise;
  }

  // We do some work of the PerformPromiseThen operation here, in that
  // we check the handlers and turn non-callable handlers into undefined.
  // This is because this is the one and only callsite of PerformPromiseThen
  // that has to do this.

  // 3. If IsCallable(onFulfilled) is false, then
  //    a. Set onFulfilled to undefined.
  const onFulfilled = CastOrDefault<Callable>(onFulfilled, Undefined);

  // 4. If IsCallable(onRejected) is false, then
  //    a. Set onRejected to undefined.
  const onRejected = CastOrDefault<Callable>(onRejected, Undefined);

  // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
  //    resultCapability).
  PerformPromiseThenImpl(
      promise, onFulfilled, onRejected, resultPromiseOrCapability);
  return resultPromise;
}
}