/*
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/licenses/publicdomain/
 */

//-----------------------------------------------------------------------------
var BUGNUMBER = 577325;
var summary = 'Implement the ES5 algorithm for processing function statements';

print(BUGNUMBER + ": " + summary);

/**************
 * BEGIN TEST *
 **************/

var outer, desc;
var isInShell = !("Window" in this);

///////////////////////////////////////////////////
// Function definitions over accessor properties //
///////////////////////////////////////////////////

var getCalled, setCalled;

// configurable properties get blown away

getCalled = false, setCalled = false;
Object.defineProperty(this, "acc1",
                      {
                        get: function() { getCalled = true; throw "FAIL get 1"; },
                        set: function(v) { setCalled = true; throw "FAIL set 1 " + v; },
                        configurable: true,
                        enumerable: false
                      });

// does not throw
outer = undefined;
eval("function acc1() { throw 'FAIL redefined 1'; } outer = acc1;");
assertEq(getCalled, false);
assertEq(setCalled, false);
assertEq(typeof acc1, "function");
assertEq(acc1, outer);
desc = Object.getOwnPropertyDescriptor(this, "acc1");
assertEq(desc.value, acc1);
assertEq(desc.writable, true);
assertEq(desc.enumerable, true);
assertEq(desc.configurable, true);


getCalled = false, setCalled = false;
Object.defineProperty(this, "acc2",
                      {
                        get: function() { getCalled = true; throw "FAIL get 2"; },
                        set: function(v) { setCalled = true; throw "FAIL set 2 " + v; },
                        configurable: true,
                        enumerable: true
                      });

// does not throw
outer = undefined;
eval("function acc2() { throw 'FAIL redefined 2'; } outer = acc2;");
assertEq(getCalled, false);
assertEq(setCalled, false);
assertEq(typeof acc2, "function");
assertEq(acc2, outer);
desc = Object.getOwnPropertyDescriptor(this, "acc2");
assertEq(desc.value, acc2);
assertEq(desc.writable, true);
assertEq(desc.enumerable, true);
assertEq(desc.configurable, true);


// non-configurable properties produce a TypeError.  We only test this in shell,
// since defining non-configurable properties on Window instances throws.
if (isInShell) {
    getCalled = false, setCalled = false;
    Object.defineProperty(this, "acc3",
			  {
                              get: function() { getCalled = true; throw "FAIL get 3"; },
                              set: function(v) { setCalled = true; throw "FAIL set 3 " + v; },
                              configurable: false,
                              enumerable: true
			  });

    outer = undefined;
    try
    {
	eval("function acc3() { throw 'FAIL redefined 3'; }; outer = acc3");
	throw new Error("should have thrown trying to redefine global function " +
			"over a non-configurable, enumerable accessor");
    }
    catch (e)
    {
	assertEq(e instanceof TypeError, true,
		 "global function definition, when that function would overwrite " +
		 "a non-configurable, enumerable accessor, must throw a TypeError " +
		 "per ES5+errata: " + e);
	desc = Object.getOwnPropertyDescriptor(this, "acc3");
	assertEq(typeof desc.get, "function");
	assertEq(typeof desc.set, "function");
	assertEq(desc.enumerable, true);
	assertEq(desc.configurable, false);
	assertEq(outer, undefined);
	assertEq(getCalled, false);
	assertEq(setCalled, false);
    }


    getCalled = false, setCalled = false;
    Object.defineProperty(this, "acc4",
			  {
                              get: function() { getCalled = true; throw "FAIL get 4"; },
                              set: function(v) { setCalled = true; throw "FAIL set 4 " + v; },
                              configurable: false,
                              enumerable: false
			  });

    outer = undefined;
    try
    {
	eval("function acc4() { throw 'FAIL redefined 4'; }; outer = acc4");
	throw new Error("should have thrown trying to redefine global function " +
			"over a non-configurable, non-enumerable accessor");
    }
    catch (e)
    {
	assertEq(e instanceof TypeError, true,
		 "global function definition, when that function would overwrite " +
		 "a non-configurable, non-enumerable accessor, must throw a " +
		 "TypeError per ES5+errata: " + e);
	desc = Object.getOwnPropertyDescriptor(this, "acc4");
	assertEq(typeof desc.get, "function");
	assertEq(typeof desc.set, "function");
	assertEq(desc.enumerable, false);
	assertEq(desc.configurable, false);
	assertEq(outer, undefined);
	assertEq(getCalled, false);
	assertEq(setCalled, false);
    }
}


///////////////////////////////////////////////
// Function definitions over data properties //
///////////////////////////////////////////////


// configurable properties, regardless of other attributes, get blown away

Object.defineProperty(this, "data1",
                      {
                        configurable: true,
                        enumerable: true,
                        writable: true,
                        value: "data1"
                      });

outer = undefined;
eval("function data1() { return 'data1 function'; } outer = data1;");
assertEq(typeof data1, "function");
assertEq(data1, outer);
desc = Object.getOwnPropertyDescriptor(this, "data1");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, true);
assertEq(desc.writable, true);
assertEq(desc.value, data1);


Object.defineProperty(this, "data2",
                      {
                        configurable: true,
                        enumerable: true,
                        writable: false,
                        value: "data2"
                      });

outer = undefined;
eval("function data2() { return 'data2 function'; } outer = data2;");
assertEq(typeof data2, "function");
assertEq(data2, outer);
desc = Object.getOwnPropertyDescriptor(this, "data2");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, true);
assertEq(desc.writable, true);
assertEq(desc.value, data2);


Object.defineProperty(this, "data3",
                      {
                        configurable: true,
                        enumerable: false,
                        writable: true,
                        value: "data3"
                      });

outer = undefined;
eval("function data3() { return 'data3 function'; } outer = data3;");
assertEq(typeof data3, "function");
assertEq(data3, outer);
desc = Object.getOwnPropertyDescriptor(this, "data3");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, true);
assertEq(desc.writable, true);
assertEq(desc.value, data3);


Object.defineProperty(this, "data4",
                      {
                        configurable: true,
                        enumerable: false,
                        writable: false,
                        value: "data4"
                      });

outer = undefined;
eval("function data4() { return 'data4 function'; } outer = data4;");
assertEq(typeof data4, "function");
assertEq(data4, outer);
desc = Object.getOwnPropertyDescriptor(this, "data4");
assertEq(desc.value, data4);
assertEq(desc.writable, true);
assertEq(desc.enumerable, true);
assertEq(desc.configurable, true);


// non-configurable data properties are trickier.  Again, we test these only in shell.

if (isInShell) {
    Object.defineProperty(this, "data5",
			  {
                              configurable: false,
                              enumerable: true,
                              writable: true,
                              value: "data5"
			  });

    outer = undefined;
    eval("function data5() { return 'data5 function'; } outer = data5;");
    assertEq(typeof data5, "function");
    assertEq(data5, outer);
    desc = Object.getOwnPropertyDescriptor(this, "data5");
    assertEq(desc.configurable, false);
    assertEq(desc.enumerable, true);
    assertEq(desc.writable, true);
    assertEq(desc.value, data5);


    Object.defineProperty(this, "data6",
			  {
                              configurable: false,
                              enumerable: true,
                              writable: false,
                              value: "data6"
			  });

    outer = undefined;
    try
    {
	eval("function data6() { return 'data6 function'; } outer = data6;");
	throw new Error("should have thrown trying to redefine global function " +
			"over a non-configurable, enumerable, non-writable accessor");
    }
    catch (e)
    {
	assertEq(e instanceof TypeError, true,
		 "global function definition, when that function would overwrite " +
		 "a non-configurable, enumerable, non-writable data property, must " +
		 "throw a TypeError per ES5+errata: " + e);
	assertEq(data6, "data6");
	assertEq(outer, undefined);
	desc = Object.getOwnPropertyDescriptor(this, "data6");
	assertEq(desc.configurable, false);
	assertEq(desc.enumerable, true);
	assertEq(desc.writable, false);
	assertEq(desc.value, "data6");
    }


    Object.defineProperty(this, "data7",
			  {
                              configurable: false,
                              enumerable: false,
                              writable: true,
                              value: "data7"
			  });

    outer = undefined;
    try
    {
	eval("function data7() { return 'data7 function'; } outer = data7;");
	throw new Error("should have thrown trying to redefine global function " +
			"over a non-configurable, non-enumerable, writable data" +
			"property");
    }
    catch (e)
    {
	assertEq(e instanceof TypeError, true,
		 "global function definition, when that function would overwrite " +
		 "a non-configurable, non-enumerable, writable data property, must " +
		 "throw a TypeError per ES5+errata: " + e);
	assertEq(data7, "data7");
	assertEq(outer, undefined);
	desc = Object.getOwnPropertyDescriptor(this, "data7");
	assertEq(desc.configurable, false);
	assertEq(desc.enumerable, false);
	assertEq(desc.writable, true);
	assertEq(desc.value, "data7");
    }


    Object.defineProperty(this, "data8",
			  {
                              configurable: false,
                              enumerable: false,
                              writable: false,
                              value: "data8"
			  });

    outer = undefined;
    try
    {
	eval("function data8() { return 'data8 function'; } outer = data8;");
	throw new Error("should have thrown trying to redefine global function " +
			"over a non-configurable, non-enumerable, non-writable data" +
			"property");
    }
    catch (e)
    {
	assertEq(e instanceof TypeError, true,
		 "global function definition, when that function would overwrite " +
		 "a non-configurable, non-enumerable, non-writable data property, " +
		 "must throw a TypeError per ES5+errata: " + e);
	assertEq(data8, "data8");
	assertEq(outer, undefined);
	desc = Object.getOwnPropertyDescriptor(this, "data8");
	assertEq(desc.configurable, false);
	assertEq(desc.enumerable, false);
	assertEq(desc.writable, false);
	assertEq(desc.value, "data8");
    }
}

/******************************************************************************/

if (typeof reportCompare === "function")
  reportCompare(true, true);

print("All tests passed!");