// Copyright 2015 the V8 project authors. All rights reserved.
// Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1.  Redistributions of source code must retain the above copyright
//     notice, this list of conditions and the following disclaimer.
// 2.  Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

description('Tests for ES6 class constructor return values');

// ES6
// - 9.2.2 [[Construct]] (ECMAScript Function Objects)
// - 12.3.5.1 Runtime Semantics: Evaluation (The super Keyword)
// - 14.5.14 Runtime Semantics: ClassDefinitionEvaluation (Default Constructor)

var globalVariable = {name:"globalVariable"};
var globalSymbol = Symbol();

debug('Base class');
class BaseNoReturn { constructor() { } };
class BaseReturnImplicit { constructor() { return; } };
class BaseReturnUndefined { constructor() { return undefined; } };
class BaseReturnThis { constructor() { return this; } };
class BaseReturnObject { constructor() { return {a:1}; } };
class BaseReturnObject2 { constructor() { return globalVariable; } };
class BaseReturnString { constructor() { return "test"; } };
class BaseReturnNumber { constructor() { return 1; } };
class BaseReturnNull { constructor() { return null; } };
class BaseReturnSymbol { constructor() { return Symbol(); } };
class BaseThrow { constructor() { throw "Thrown Exception String"; } };

// Base - Implicit => return this.
shouldBeTrue('(new BaseNoReturn) instanceof BaseNoReturn');

// Base - Early return => return this.
shouldBeTrue('(new BaseReturnImplicit) instanceof BaseReturnImplicit');
shouldBeTrue('(new BaseReturnImplicit) !== undefined');
shouldBeTrue('(new BaseReturnUndefined) instanceof BaseReturnUndefined');
shouldBeTrue('(new BaseReturnUndefined) !== undefined');

// Base - return this => return this.
shouldBeTrue('(new BaseReturnThis) instanceof BaseReturnThis');

// Base - return Object => return object, not instance.
shouldBeFalse('(new BaseReturnObject) instanceof BaseReturnObject');
shouldBeTrue('typeof (new BaseReturnObject) === "object"');
shouldBeFalse('(new BaseReturnObject2) instanceof BaseReturnObject');
shouldBeTrue('(new BaseReturnObject2) === globalVariable');

// Base - return non-Object => return this.
shouldBeTrue('(new BaseReturnString) instanceof BaseReturnString');
shouldBeTrue('typeof (new BaseReturnString) !== "string"');
shouldBeTrue('(new BaseReturnNumber) instanceof BaseReturnNumber');
shouldBeTrue('typeof (new BaseReturnNumber) !== "number"');
shouldBeTrue('(new BaseReturnNull) instanceof BaseReturnNull');
shouldBeTrue('(new BaseReturnNull) !== null');
shouldBeTrue('(new BaseReturnSymbol) instanceof BaseReturnSymbol');
shouldBeTrue('(new BaseReturnSymbol) !== globalSymbol');

// Base - throw => throw
shouldThrow('(new BaseThrow)');

// Same behavior for Functions.
debug(''); debug('Function constructor (non-class)');
function FunctionNoReturn() { };
function FunctionReturnImplicit() { return; };
function FunctionReturnUndefined() { return undefined; };
function FunctionReturnThis() { return this; };
function FunctionReturnObject() { return {a:1}; };
function FunctionReturnObject2() { return globalVariable; };
function FunctionReturnString() { return "test"; };
function FunctionReturnNumber() { return 1; };
function FunctionReturnNull() { return null; };
function FunctionReturnSymbol() { return Symbol(); };
function FunctionThrow() { throw "Thrown Exception String"; };

shouldBeTrue('(new FunctionNoReturn) instanceof FunctionNoReturn');
shouldBeTrue('(new FunctionReturnImplicit) instanceof FunctionReturnImplicit');
shouldBeTrue('(new FunctionReturnImplicit) !== undefined');
shouldBeTrue('(new FunctionReturnUndefined) instanceof FunctionReturnUndefined');
shouldBeTrue('(new FunctionReturnUndefined) !== undefined');
shouldBeTrue('(new FunctionReturnThis) instanceof FunctionReturnThis');
shouldBeFalse('(new FunctionReturnObject) instanceof FunctionReturnObject');
shouldBeTrue('typeof (new FunctionReturnObject) === "object"');
shouldBeFalse('(new FunctionReturnObject2) instanceof FunctionReturnObject');
shouldBeTrue('(new FunctionReturnObject2) === globalVariable');
shouldBeTrue('(new FunctionReturnString) instanceof FunctionReturnString');
shouldBeTrue('typeof (new FunctionReturnString) !== "string"');
shouldBeTrue('(new FunctionReturnNumber) instanceof FunctionReturnNumber');
shouldBeTrue('typeof (new FunctionReturnNumber) !== "number"');
shouldBeTrue('(new FunctionReturnNull) instanceof FunctionReturnNull');
shouldBeTrue('(new FunctionReturnNull) !== null');
shouldBeTrue('(new FunctionReturnSymbol) instanceof FunctionReturnSymbol');
shouldBeTrue('(new FunctionReturnSymbol) !== globalSymbol');
shouldThrow('(new FunctionThrow)');


debug(''); debug('Derived class calling super()');
class DerivedNoReturn extends BaseNoReturn { constructor() { super(); } };
class DerivedReturnImplicit extends BaseNoReturn { constructor() { super(); return; } };
class DerivedReturnUndefined extends BaseNoReturn { constructor() { super(); return undefined; } };
class DerivedReturnThis extends BaseNoReturn { constructor() { super(); return this; } };
class DerivedReturnObject extends BaseNoReturn { constructor() { super(); return {a:1}; } };
class DerivedReturnObject2 extends BaseNoReturn { constructor() { super(); return globalVariable; } };
class DerivedReturnString extends BaseNoReturn { constructor() { super(); return "test"; } };
class DerivedReturnNumber extends BaseNoReturn { constructor() { super(); return 1; } };
class DerivedReturnNull extends BaseNoReturn { constructor() { super(); return null; } };
class DerivedReturnSymbol extends BaseNoReturn { constructor() { super(); return globalSymbol; } };
class DerivedThrow extends BaseNoReturn { constructor() { super(); throw "Thrown Exception String"; } };

// Derived - Implicit => return this.
shouldBeTrue('(new DerivedNoReturn) instanceof DerivedNoReturn');

// Derived - Early return => return this.
shouldBeTrue('(new DerivedReturnImplicit) instanceof DerivedReturnImplicit');
shouldBeTrue('(new DerivedReturnImplicit) !== undefined');
shouldBeTrue('(new DerivedReturnUndefined) instanceof DerivedReturnUndefined');
shouldBeTrue('(new DerivedReturnUndefined) !== undefined');

// Derived - return this => return this.
shouldBeTrue('(new DerivedReturnThis) instanceof DerivedReturnThis');

// Derived - return Object => return object, not instance.
shouldBeFalse('(new DerivedReturnObject) instanceof DerivedReturnObject');
shouldBeTrue('typeof (new DerivedReturnObject) === "object"');
shouldBeFalse('(new DerivedReturnObject2) instanceof DerivedReturnObject2');
shouldBeTrue('(new DerivedReturnObject2) === globalVariable');

// Derived - return non-Object => exception.
shouldThrow('(new DerivedReturnString)');
shouldThrow('(new DerivedReturnNumber)');
shouldThrow('(new DerivedReturnNull)');
shouldThrow('(new DerivedReturnSymbol)');
shouldThrow('(new DerivedThrow)');


debug(''); debug('Derived class not calling super()');
class DerivedNoSuperNoReturn extends BaseNoReturn { constructor() { } };
class DerivedNoSuperReturn extends BaseNoReturn { constructor() { return; } };
class DerivedNoSuperReturnUndefined extends BaseNoReturn { constructor() { return undefined; } };
class DerivedNoSuperReturnObject extends BaseNoReturn { constructor() { return {a:1}; } };
class DerivedNoSuperReturnObject2 extends BaseNoReturn { constructor() { return globalVariable; } };
class DerivedNoSuperReturnThis extends BaseNoReturn { constructor() { return this; } };
class DerivedNoSuperReturnString extends BaseNoReturn { constructor() { return "test"; } };
class DerivedNoSuperReturnNumber extends BaseNoReturn { constructor() { return 1; } };
class DerivedNoSuperReturnNull extends BaseNoReturn { constructor() { return null; } };
class DerivedNoSuperReturnSymbol extends BaseNoReturn { constructor() { return globalSymbol; } };
class DerivedNoSuperThrow extends BaseNoReturn { constructor() { throw "Thrown Exception String"; } };

// Derived without super() - Implicit => return this => TDZ.
shouldThrow('(new DerivedNoSuperNoReturn)');

// Derived without super() - Early return => return this => TDZ.
shouldThrow('(new DerivedNoSuperReturnImplicit)');
shouldThrow('(new DerivedNoSuperReturnUndefined)');

// Derived without super() - return this => return this => TDZ
shouldThrow('(new DerivedNoSuperReturnThis)');

// Derived without super() - return Object => no this access => return object, not instance
shouldNotThrow('(new DerivedNoSuperReturnObject)');
shouldNotThrow('(new DerivedNoSuperReturnObject2)');

// Derived without super() - return non-Object => exception
shouldThrow('(new DerivedNoSuperReturnString)'); // TypeError
shouldThrow('(new DerivedNoSuperReturnNumber)'); // TypeError
shouldThrow('(new DerivedNoSuperReturnNull)'); // TypeError
shouldThrow('(new DerivedNoSuperReturnSymbol)'); // TypeError
shouldThrow('(new DerivedNoSuperThrow)'); // Thrown exception


debug(''); debug('Derived class with default constructor and base class returning different values');
class DerivedDefaultConstructorWithBaseNoReturn extends BaseNoReturn { };
class DerivedDefaultConstructorWithBaseReturnImplicit extends BaseReturnImplicit { };
class DerivedDefaultConstructorWithBaseReturnUndefined extends BaseReturnUndefined { };
class DerivedDefaultConstructorWithBaseReturnThis extends BaseReturnThis { };
class DerivedDefaultConstructorWithBaseReturnObject extends BaseReturnObject { };
class DerivedDefaultConstructorWithBaseReturnObject2 extends BaseReturnObject2 { };
class DerivedDefaultConstructorWithBaseReturnString extends BaseReturnString { };
class DerivedDefaultConstructorWithBaseReturnNumber extends BaseReturnNumber { };
class DerivedDefaultConstructorWithBaseReturnNull extends BaseReturnNull { };
class DerivedDefaultConstructorWithBaseReturnSymbol extends BaseReturnSymbol { };
class DerivedDefaultConstructorWithBaseThrow extends BaseThrow { };

// Derived default constructor - implicit "super(...arguments)" return the result of the base (Object or this).
shouldBeTrue('(new DerivedDefaultConstructorWithBaseNoReturn) instanceof DerivedDefaultConstructorWithBaseNoReturn');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnImplicit) instanceof DerivedDefaultConstructorWithBaseReturnImplicit');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnUndefined) instanceof DerivedDefaultConstructorWithBaseReturnUndefined');
shouldBeFalse('(new DerivedDefaultConstructorWithBaseReturnObject) instanceof DerivedDefaultConstructorWithBaseReturnObject');
shouldBeTrue('typeof (new DerivedDefaultConstructorWithBaseReturnObject) === "object"');
shouldBeFalse('(new DerivedDefaultConstructorWithBaseReturnObject2) instanceof DerivedDefaultConstructorWithBaseReturnObject2');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnObject2) === globalVariable');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnThis) instanceof DerivedDefaultConstructorWithBaseReturnThis');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnString) instanceof DerivedDefaultConstructorWithBaseReturnString');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnNumber) instanceof DerivedDefaultConstructorWithBaseReturnNumber');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnNull) instanceof DerivedDefaultConstructorWithBaseReturnNull');
shouldBeTrue('(new DerivedDefaultConstructorWithBaseReturnSymbol) instanceof DerivedDefaultConstructorWithBaseReturnSymbol');
shouldThrow('(new DerivedDefaultConstructorWithBaseThrow)');

var successfullyParsed = true;