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

function assertFulfilledWith(expected, thenable) {
  assertPromiseResult(thenable, v => assertEquals(expected, v));
}

(function() {
  function foo() { return Promise.resolve(); }
  %PrepareFunctionForOptimization(foo);
  assertFulfilledWith(undefined, foo());
  assertFulfilledWith(undefined, foo());
  %OptimizeFunctionOnNextCall(foo);
  assertFulfilledWith(undefined, foo());
})();

(function() {
  function foo(x) { return Promise.resolve(x); }
  %PrepareFunctionForOptimization(foo);
  assertFulfilledWith(3, foo(3));
  assertFulfilledWith(3, foo(3));
  %OptimizeFunctionOnNextCall(foo);
  assertFulfilledWith(3, foo(3));
})();

(function() {
  function foo(x, y) { return Promise.resolve(x, y); }
  %PrepareFunctionForOptimization(foo);
  assertFulfilledWith(1, foo(1, 0));
  assertFulfilledWith(2, foo(2, 1));
  %OptimizeFunctionOnNextCall(foo);
  assertFulfilledWith(3, foo(3, 2));
})();

(function() {
  function foo(x) { return Promise.resolve({x}); }
  %PrepareFunctionForOptimization(foo);
  assertFulfilledWith({x:1}, foo(1));
  assertFulfilledWith({x:2}, foo(2));
  %OptimizeFunctionOnNextCall(foo);
  assertFulfilledWith({x:3}, foo(3));
})();

(function() {
  function foo(x) { return Promise.resolve(Promise.resolve(x)); }
  %PrepareFunctionForOptimization(foo);
  assertFulfilledWith(null, foo(null));
  assertFulfilledWith('a', foo('a'));
  %OptimizeFunctionOnNextCall(foo);
  assertFulfilledWith(42, foo(42));
})();

(function() {
  const thenable = new class Thenable {
    then(fulfill, reject) {
      fulfill(1);
    }
  };
  function foo() { return Promise.resolve(thenable); }
  %PrepareFunctionForOptimization(foo);
  assertFulfilledWith(1, foo());
  assertFulfilledWith(1, foo());
  %OptimizeFunctionOnNextCall(foo);
  assertFulfilledWith(1, foo());
})();

(function() {
  const MyPromise = class MyPromise extends Promise {};

  (function() {
    function foo() { return MyPromise.resolve(); }
    %PrepareFunctionForOptimization(foo);
    assertFulfilledWith(undefined, foo());
    assertFulfilledWith(undefined, foo());
    %OptimizeFunctionOnNextCall(foo);
    assertFulfilledWith(undefined, foo());
  })();

  (function() {
    function foo(x) { return MyPromise.resolve(x); }
    %PrepareFunctionForOptimization(foo);
    assertFulfilledWith(3, foo(3));
    assertFulfilledWith(3, foo(3));
    %OptimizeFunctionOnNextCall(foo);
    assertFulfilledWith(3, foo(3));
  })();

  (function() {
    function foo(x, y) { return MyPromise.resolve(x, y); }
    %PrepareFunctionForOptimization(foo);
    assertFulfilledWith(1, foo(1, 0));
    assertFulfilledWith(2, foo(2, 1));
    %OptimizeFunctionOnNextCall(foo);
    assertFulfilledWith(3, foo(3, 2));
  })();

  (function() {
    function foo(x) { return MyPromise.resolve({x}); }
    %PrepareFunctionForOptimization(foo);
    assertFulfilledWith({x:1}, foo(1));
    assertFulfilledWith({x:2}, foo(2));
    %OptimizeFunctionOnNextCall(foo);
    assertFulfilledWith({x:3}, foo(3));
  })();

  (function() {
    function foo(x) { return MyPromise.resolve(Promise.resolve(x)); }
    %PrepareFunctionForOptimization(foo);
    assertFulfilledWith(null, foo(null));
    assertFulfilledWith('a', foo('a'));
    %OptimizeFunctionOnNextCall(foo);
    assertFulfilledWith(42, foo(42));
  })();

  (function() {
    const thenable = new class Thenable {
      then(fulfill, reject) {
        fulfill(1);
      }
    };
    function foo() { return MyPromise.resolve(thenable); }
    %PrepareFunctionForOptimization(foo);
    assertFulfilledWith(1, foo());
    assertFulfilledWith(1, foo());
    %OptimizeFunctionOnNextCall(foo);
    assertFulfilledWith(1, foo());
  })();
})();
