// 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 {

  extern macro PromiseForwardingHandlerSymbolConstant(): Symbol;
  const kPromiseForwardingHandlerSymbol: Symbol =
      PromiseForwardingHandlerSymbolConstant();
  extern macro PromiseHandledBySymbolConstant(): Symbol;
  const kPromiseHandledBySymbol: Symbol = PromiseHandledBySymbolConstant();
  extern macro ResolveStringConstant(): String;
  const kResolveString: String = ResolveStringConstant();
  extern macro SetPropertyStrict(Context, Object, Object, Object): Object;
  extern macro IsPromiseResolveProtectorCellInvalid(): bool;

  macro IsPromiseResolveLookupChainIntact(implicit context: Context)(
      nativeContext: NativeContext, constructor: JSReceiver): bool {
    if (IsForceSlowPath()) return false;
    const promiseFun = UnsafeCast<JSFunction>(
        nativeContext[NativeContextSlot::PROMISE_FUNCTION_INDEX]);
    return promiseFun == constructor && !IsPromiseResolveProtectorCellInvalid();
  }

  // https://tc39.es/ecma262/#sec-promise.race
  transitioning javascript builtin
  PromiseRace(js-implicit context: Context, receiver: JSAny)(iterable: JSAny):
      JSAny {
    const receiver = Cast<JSReceiver>(receiver)
        otherwise ThrowTypeError(
        MessageTemplate::kCalledOnNonObject, 'Promise.race');

    // Let promiseCapability be ? NewPromiseCapability(C).
    // Don't fire debugEvent so that forwarding the rejection through all does
    // not trigger redundant ExceptionEvents
    const capability = NewPromiseCapability(receiver, False);
    const resolve = capability.resolve;
    const reject = capability.reject;
    const promise = capability.promise;

    // For catch prediction, don't treat the .then calls as handling it;
    // instead, recurse outwards.
    if (IsDebugActive()) deferred {
        SetPropertyStrict(
            context, reject, kPromiseForwardingHandlerSymbol, True);
      }

    try {
      // Let iterator be GetIterator(iterable).
      // IfAbruptRejectPromise(iterator, promiseCapability).
      let i: iterator::IteratorRecord;
      try {
        i = iterator::GetIterator(iterable);
      } catch (e) deferred {
        goto Reject(e);
      }

      // Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability).
      try {
        // We can skip the "resolve" lookup on {constructor} if it's the
        // Promise constructor and the Promise.resolve protector is intact,
        // as that guards the lookup path for the "resolve" property on the
        // Promise constructor.
        const nativeContext = LoadNativeContext(context);
        let promiseResolveFunction: JSAny = Undefined;
        if (!IsPromiseResolveLookupChainIntact(nativeContext, receiver))
          deferred {
            // 3. Let _promiseResolve_ be ? Get(_constructor_, `"resolve"`).
            const resolve = GetProperty(receiver, kResolveString);

            // 4. If IsCallable(_promiseResolve_) is *false*, throw a
            // *TypeError* exception.
            promiseResolveFunction = Cast<Callable>(resolve)
                otherwise ThrowTypeError(
                MessageTemplate::kCalledNonCallable, 'resolve');
          }

        const fastIteratorResultMap = UnsafeCast<Map>(
            nativeContext[NativeContextSlot::ITERATOR_RESULT_MAP_INDEX]);
        while (true) {
          let nextValue: JSAny;
          try {
            // Let next be IteratorStep(iteratorRecord.[[Iterator]]).
            // If next is an abrupt completion, set iteratorRecord.[[Done]] to
            // true. ReturnIfAbrupt(next).
            const next: JSReceiver = iterator::IteratorStep(
                i, fastIteratorResultMap) otherwise return promise;

            // Let nextValue be IteratorValue(next).
            // If nextValue is an abrupt completion, set iteratorRecord.[[Done]]
            // to true.
            // ReturnIfAbrupt(nextValue).
            nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
          } catch (e) {
            goto Reject(e);
          }
          // Let nextPromise be ? Call(constructor, _promiseResolve_, «
          // nextValue »).
          const nextPromise = CallResolve(
              UnsafeCast<Constructor>(receiver), promiseResolveFunction,
              nextValue);

          // Perform ? Invoke(nextPromise, "then", « resolveElement,
          //                  resultCapability.[[Reject]] »).
          const then = GetProperty(nextPromise, kThenString);
          const thenResult = Call(
              context, then, nextPromise, UnsafeCast<JSAny>(resolve),
              UnsafeCast<JSAny>(reject));

          // For catch prediction, mark that rejections here are semantically
          // handled by the combined Promise.
          if (IsDebugActive() && !Is<JSPromise>(promise)) deferred {
              SetPropertyStrict(
                  context, thenResult, kPromiseHandledBySymbol, promise);
            }
        }
      } catch (e) deferred {
        iterator::IteratorCloseOnException(i, e) otherwise Reject;
      }
    }
    label Reject(exception: Object) deferred {
      Call(
          context, UnsafeCast<JSAny>(reject), Undefined,
          UnsafeCast<JSAny>(exception));
      return promise;
    }
    unreachable;
  }
}