// Copyright 2018 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.

// Flags: --allow-natives-syntax --experimental-inline-promise-constructor

// We have to patch mjsunit because normal assertion failures just throw
// exceptions which are swallowed in a then clause.
failWithMessage = (msg) => %AbortJS(msg);

// Don't crash.
(function() {
  function foo() {
    let resolve, reject, promise;
    promise = new Promise((a, b) => { resolve = a; reject = b; });

    return {resolve, reject, promise};
  }

  %PrepareFunctionForOptimization(foo);
  foo();
  foo();
  %OptimizeFunctionOnNextCall(foo);
  foo();
})();

// Check that when executor is non-callable, the constructor throws.
(function() {
  function foo() {
    return new Promise(1);
  }

  %PrepareFunctionForOptimization(foo);
  assertThrows(foo, TypeError);
  assertThrows(foo, TypeError);
  %OptimizeFunctionOnNextCall(foo);
  assertThrows(foo, TypeError);
})();

// Check that when the promise constructor throws because the executor is
// non-callable, the stack contains 'new Promise'.
(function() {
  function foo() {
    return new Promise(1);
  }

  %PrepareFunctionForOptimization(foo);
  let threw;
  try {
    threw = false;
    foo();
  } catch (e) {
    threw = true;
    assertContains('new Promise', e.stack);
  } finally {
    assertTrue(threw);
  }
  try {
    threw = false;
    foo();
  } catch (e) {
    threw = true;
    assertContains('new Promise', e.stack);
  } finally {
    assertTrue(threw);
  }

  %OptimizeFunctionOnNextCall(foo);
  try {
    threw = false;
    foo();
  } catch (e) {
    threw = true;
    assertContains('new Promise', e.stack);
  } finally {
    assertTrue(threw);
  }
})();

// Check that when executor throws, the promise is rejected.
(function() {
  function foo() {
    return new Promise((a, b) => { throw new Error(); });
  }

  function bar(i) {
    let error = null;
    foo().then(_ => error = 1, e => error = e);
    setTimeout(_ => assertInstanceof(error, Error));
    if (i == 1) %OptimizeFunctionOnNextCall(foo);
    if (i > 0) setTimeout(bar.bind(null, i - 1));
    }
  bar(3);
})();

// Check that when executor causes lazy deoptimization of the inlined
// constructor, we return the promise value and not the return value of the
// executor function itself.
(function() {
  function foo() {
    let p;
    try {
      p = new Promise((a, b) => { %DeoptimizeFunction(foo); });
    } catch (e) {
      // Nothing should throw
      assertUnreachable();
    }
    assertInstanceof(p, Promise);
  }

  %PrepareFunctionForOptimization(foo);
  foo();
  foo();
  %OptimizeFunctionOnNextCall(foo);
  foo();
})();

// The same as above, except that the executor function also creates a promise
// and both executor functions cause a lazy deopt of the calling function.
(function() {
  function executor(a, b) {
    %DeoptimizeFunction(foo);
    let p = new Promise((a, b) => { %DeoptimizeFunction(executor); });
  }
  function foo() {
    let p;
    try {
      p = new Promise(executor);
    } catch (e) {
      // Nothing should throw
      assertUnreachable();
    }
    assertInstanceof(p, Promise);
  }

  %PrepareFunctionForOptimization(foo);
  foo();
  foo();
  %OptimizeFunctionOnNextCall(foo);
  foo();
})();

// Check that when the executor causes lazy deoptimization of the inlined
// constructor, and then throws, the deopt continuation catches and then calls
// the reject function instead of propagating the exception.
(function() {
  function foo() {
    let p;
    try {
      p = new Promise((a, b) => {
        %DeoptimizeFunction(foo);
        throw new Error();
      });
    } catch (e) {
      // The promise constructor should catch the exception and reject the
      // promise instead.
      assertUnreachable();
    }
    assertInstanceof(p, Promise);
  }

  %PrepareFunctionForOptimization(foo);
  foo();
  foo();
  %OptimizeFunctionOnNextCall(foo);
  foo();
})();


// Check that when the promise constructor is marked for lazy deoptimization
// from below, but not immediatelly deoptimized, and then throws, the deopt continuation
// catches and calls the reject function instead of propagating the exception.
(function() {
  function foo() {
    let p;
    try {
      p = new Promise((resolve, reject) => { bar(); resolve()});
    } catch (e) {
       // The promise constructor should catch the exception and reject the
      // promise instead.
      assertUnreachable();
    }
    assertInstanceof(p, Promise);
  }

  function bar() {
    %DeoptimizeFunction(foo);
    throw new Error();
  }
  %NeverOptimizeFunction(bar);

  %PrepareFunctionForOptimization(foo);
  foo();
  foo();
  %OptimizeFunctionOnNextCall(foo);
  foo();
})();

// Test when the executor is not inlined.
(function() {
  let resolve, reject, promise;
  function bar(a, b) {
    resolve = a; reject = b;
    throw new Error();
  }
  function foo() {
    promise = new Promise(bar);
  }
  %PrepareFunctionForOptimization(foo);
  foo();
  foo();
  %NeverOptimizeFunction(bar);
  %OptimizeFunctionOnNextCall(foo);
  foo();
})();

// Test that the stack trace contains 'new Promise'
(function() {
  let resolve, reject, promise;
  function bar(a, b) {
    resolve = a; reject = b;
    let stack = new Error().stack;
    assertContains("new Promise", stack);
    throw new Error();
  }
  function foo() {
    promise = new Promise(bar);
  }
  %PrepareFunctionForOptimization(foo);
  foo();
  foo();
  %OptimizeFunctionOnNextCall(foo);
  foo();
})();