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

const object1 = {[Symbol.toPrimitive]() { return 1; }};
const thrower = {[Symbol.toPrimitive]() { throw new Error(); }};

// Test that JSAdd is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y + x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(0));
  assertEquals(2, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(0));
  assertEquals(2, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSSubtract is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y - x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(0));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(0));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSMultiply is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y * x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSDivide is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y / x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSModulus is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y % x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSExponentiate is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y ** x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSBitwiseOr is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y | x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSBitwiseAnd is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y & x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(1));
  assertEquals(1, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSBitwiseXor is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y ^ x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSShiftLeft is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y << x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(2, foo(1));
  assertEquals(2, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(2, foo(1));
  assertEquals(2, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSShiftRight is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y >> x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSShiftRightLogical is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y >>> x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(0, foo(1));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSEqual is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y == x);
  }

  %PrepareFunctionForOptimization(foo);
  assertFalse(foo(0));
  assertTrue(foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertFalse(foo(0));
  assertTrue(foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSLessThan is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y < x);
  }

  %PrepareFunctionForOptimization(foo);
  assertFalse(foo(0));
  assertFalse(foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertFalse(foo(0));
  assertFalse(foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSGreaterThan is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => x > y);
  }

  %PrepareFunctionForOptimization(foo);
  assertFalse(foo(0));
  assertFalse(foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertFalse(foo(0));
  assertFalse(foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSLessThanOrEqual is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => y <= x);
  }

  %PrepareFunctionForOptimization(foo);
  assertFalse(foo(0));
  assertTrue(foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertFalse(foo(0));
  assertTrue(foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSGreaterThanOrEqual is not context-sensitive.
(function() {
  function bar(fn) {
    return fn(1);
  }

  function foo(x) {
    return bar(y => x >= y);
  }

  %PrepareFunctionForOptimization(foo);
  assertFalse(foo(0));
  assertTrue(foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertFalse(foo(0));
  assertTrue(foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSInstanceOf is not context-sensitive.
(function() {
  function bar(fn) {
    return fn({});
  }

  function foo(c) {
    return bar(o => o instanceof c);
  }

  %PrepareFunctionForOptimization(foo);
  assertTrue(foo(Object));
  assertFalse(foo(Array));
  assertThrows(() => foo({[Symbol.hasInstance]() { throw new Error(); }}));
  %OptimizeFunctionOnNextCall(foo);
  assertTrue(foo(Object));
  assertFalse(foo(Array));
  assertThrows(() => foo({[Symbol.hasInstance]() { throw new Error(); }}));
})();

// Test that JSBitwiseNot is not context-sensitive.
(function() {
  function bar(fn) {
    return fn();
  }

  function foo(x) {
    return bar(() => ~x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(0, foo(-1));
  assertEquals(~1, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(0, foo(-1));
  assertEquals(~1, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSNegate is not context-sensitive.
(function() {
  function bar(fn) {
    return fn();
  }

  function foo(x) {
    return bar(() => -x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(-1));
  assertEquals(-1, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(-1));
  assertEquals(-1, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSIncrement is not context-sensitive.
(function() {
  function bar(fn) {
    return fn();
  }

  function foo(x) {
    return bar(() => ++x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(0));
  assertEquals(2, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(0));
  assertEquals(2, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSDecrement is not context-sensitive.
(function() {
  function bar(fn) {
    return fn();
  }

  function foo(x) {
    return bar(() => --x);
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(1, foo(2));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(1, foo(2));
  assertEquals(0, foo(object1));
  assertThrows(() => foo(thrower));
})();

// Test that JSCreateArguments[UnmappedArguments] is not context-sensitive.
(function() {
  function bar(fn) {
    return fn();
  }

  function foo() {
    "use strict";
    return bar(() => arguments)[0];
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(0, foo(0, 1));
  assertEquals(1, foo(1, 2));
  assertEquals(undefined, foo());
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(0, foo(0, 1));
  assertEquals(1, foo(1, 2));
  assertEquals(undefined, foo());
})();

// Test that JSCreateArguments[RestParameters] is not context-sensitive.
(function() {
  function bar(fn) {
    return fn();
  }

  function foo(...args) {
    return bar(() => args)[0];
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(0, foo(0, 1));
  assertEquals(1, foo(1, 2));
  assertEquals(undefined, foo());
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(0, foo(0, 1));
  assertEquals(1, foo(1, 2));
  assertEquals(undefined, foo());
})();

// Test that JSLoadGlobal/JSStoreGlobal are not context-sensitive.
(function(global) {
  var actualValue = 'Some value';

  Object.defineProperty(global, 'globalValue', {
    configurable: true,
    enumerable: true,
    get: function() {
      return actualValue;
    },
    set: function(v) {
      actualValue = v;
    }
  });

  function bar(fn) {
    return fn();
  }

  function foo(v) {
    return bar(() => {
      const o = globalValue;
      globalValue = v;
      return o;
    });
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals('Some value', foo('Another value'));
  assertEquals('Another value', actualValue);
  assertEquals('Another value', foo('Some value'));
  assertEquals('Some value', actualValue);
  %OptimizeFunctionOnNextCall(foo);
  assertEquals('Some value', foo('Another value'));
  assertEquals('Another value', actualValue);
  assertEquals('Another value', foo('Some value'));
  assertEquals('Some value', actualValue);
})(this);

// Test that for..in is not context-sensitive.
(function() {
  function bar(fn) {
    return fn();
  }

  function foo(o) {
    return bar(() => {
      var s = "";
      for (var k in o) { s += k; }
      return s;
    });
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals('abc', foo({a: 1, b: 2, c: 3}));
  assertEquals('ab', foo(Object.create({a: 1, b: 2})));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals('abc', foo({a: 1, b: 2, c: 3}));
  assertEquals("ab", foo(Object.create({a:1, b:2})));
})();

// Test that most generator operations are not context-sensitive.
(function() {
  function bar(fn) {
    let s = undefined;
    for (const x of fn()) {
      if (s === undefined) s = x;
      else s += x;
    }
    return s;
  }

  function foo(x, y, z) {
    return bar(function*() {
      yield x;
      yield y;
      yield z;
    });
  }

  %PrepareFunctionForOptimization(foo);
  assertEquals(6, foo(1, 2, 3));
  assertEquals("abc", foo("a", "b", "c"));
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(6, foo(1, 2, 3));
  assertEquals("abc", foo("a", "b", "c"));
})();