// Copyright 2014 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 TestDefaultConstructorNoCrash() {
  // Regression test for https://code.google.com/p/v8/issues/detail?id=3661
  class C {}
  assertThrows(function () {C();}, TypeError);
  assertThrows(function () {C(1);}, TypeError);
  assertTrue(new C() instanceof C);
  assertTrue(new C(1) instanceof C);
})();


(function TestConstructorCall(){
  var realmIndex = Realm.create();
  var otherTypeError = Realm.eval(realmIndex, "TypeError");
  var A = Realm.eval(realmIndex, '"use strict"; class A {}; A');
  var instance = new A();
  var constructor = instance.constructor;
  var otherTypeError = Realm.eval(realmIndex, 'TypeError');
  if (otherTypeError === TypeError) {
    throw Error('Should not happen!');
  }

  // ES6 9.2.1[[Call]] throws a TypeError in the caller context/Realm when the
  // called function is a classConstructor
  assertThrows(function() { Realm.eval(realmIndex, "A()") }, otherTypeError);
  assertThrows(function() { instance.constructor() }, TypeError);
  assertThrows(function() { A() }, TypeError);

  // ES6 9.3.1 call() first activates the callee context before invoking the
  // method. The TypeError from the constructor is thus thrown in the other
  // Realm.
  assertThrows(function() { Realm.eval(realmIndex, "A.call()") },
      otherTypeError);
  assertThrows(function() { constructor.call() }, otherTypeError);
  assertThrows(function() { A.call() }, otherTypeError);
})();


(function TestConstructorCallOptimized() {
  class A { };

  function invoke_constructor() { A() }
  function call_constructor() { A.call() }
  function apply_constructor() { A.apply() }
  %PrepareFunctionForOptimization(invoke_constructor);
  %PrepareFunctionForOptimization(call_constructor);
  %PrepareFunctionForOptimization(apply_constructor);

  for (var i=0; i<3; i++) {
    assertThrows(invoke_constructor);
    assertThrows(call_constructor);
    assertThrows(apply_constructor);
  }
  // Make sure we still check for class constructors when calling optimized
  // code.
  %OptimizeFunctionOnNextCall(invoke_constructor);
  assertThrows(invoke_constructor);
  %OptimizeFunctionOnNextCall(call_constructor);
  assertThrows(call_constructor);
  %OptimizeFunctionOnNextCall(apply_constructor);
  assertThrows(apply_constructor);
})();


(function TestDefaultConstructor() {
  var calls = 0;
  class Base {
    constructor() {
      calls++;
    }
  }
  class Derived extends Base {}
  var object = new Derived;
  assertEquals(1, calls);

  calls = 0;
  assertThrows(function() { Derived(); }, TypeError);
  assertEquals(0, calls);
})();


(function TestDefaultConstructorArguments() {
  var args, self;
  class Base {
    constructor() {
      self = this;
      args = arguments;
    }
  }
  class Derived extends Base {}

  new Derived;
  assertEquals(0, args.length);

  new Derived(0, 1, 2);
  assertEquals(3, args.length);
  assertTrue(self instanceof Derived);

  var arr = new Array(100);
  var obj = {};
  assertThrows(function() {Derived.apply(obj, arr);}, TypeError);
})();


(function TestDefaultConstructorArguments2() {
  var args;
  class Base {
    constructor(x, y) {
      args = arguments;
    }
  }
  class Derived extends Base {}

  new Derived;
  assertEquals(0, args.length);

  new Derived(1);
  assertEquals(1, args.length);
  assertEquals(1, args[0]);

  new Derived(1, 2, 3);
  assertEquals(3, args.length);
  assertEquals(1, args[0]);
  assertEquals(2, args[1]);
  assertEquals(3, args[2]);
})();