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

//-----------------------------------------------------------------------------
var BUGNUMBER = 619283;
var summary =
  "ECMAScript built-in methods that immediately throw when |this| is " +
  "|undefined| or |null| (due to CheckObjectCoercible, ToObject, or ToString)";

print(BUGNUMBER + ": " + summary);

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

// We can't just exhaustively loop over everything because 1) method properties
// might be extensions with special |this| handling, and 2) some methods don't
// *quite* immediately throw a TypeError, first thing, if |this| is |undefined|
// or |null|, or their algorithms are very slightly ambiguous about whether they
// do.  Why?  Ipse-dixitism.  *shrug*

var ClassToMethodMap =
  {
    Object:  [/* "toString" has special |this| handling */
              "toLocaleString", "valueOf", "hasOwnProperty",
              /*
               * "isPrototypeOf" has special |this| handling already tested in
               * ecma_5/Object/isPrototypeOf.js.
               */
              /*
               * "isPrototypeOf" has special |this| handling already tested in
               * ecma_5/Object/propertyIsEnumerable.js.
               */],
    // Function methods often don't ToObject(this) as their very first step,
    // and they're already stepwise well-tested such that manual tests here
    // would be redundant.
    Array:   ["toString", "toLocaleString", "concat", "join", "pop", "push",
              "reverse", "shift", "slice", "sort", "splice", "unshift",
              "indexOf", "lastIndexOf", "every", "some", "forEach", "map",
              "filter", "reduce", "reduceRight"],
    String:  ["toString", "valueOf", "charAt", "charCodeAt", "concat",
              "indexOf", "lastIndexOf", "localeCompare", "match", "replace",
              "search", "slice", "split", "substring", "toLowerCase",
              "toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase", "trim",
              /*
               * "trimLeft" and "trimRight" are non-standard and thus are tested
               * in ecma_5/extensions/trim-extensions.js.
               */
              ],
    Boolean: ["toString", "valueOf"],
    Number:  ["toString", "toLocaleString", "valueOf",
              /*
               * toFixed doesn't *immediately* test |this| for number or
               * Number-ness, but because the ToInteger(void 0) which arguably
               * precedes it in the toFixed algorithm won't throw in this test,
               * we don't need to specially test it.
               */
              "toFixed",
              "toExponential", "toPrecision"],
    Date:    ["toDateString", "toTimeString", "toLocaleString",
              "toLocaleDateString", "toLocaleTimeString", "valueOf", "getTime",
              "getFullYear", "getUTCFullYear", "getMonth", "getUTCMonth",
              "getDate", "getUTCDate", "getDay", "getUTCDay", "getHours",
              "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds",
              "getUTCSeconds", "getMilliseconds", "getUTCMilliseconds",
              /*
               * toFixed doesn't *immediately* test |this| for number or
               * Number-ness, but because the TimeClip(ToNumber(void 0)) which
               * arguably precedes it in the setTime algorithm won't throw in
               * this test, we don't need to specially test it.
               */
              "setTime",
              "getTimezoneOffset", "setMilliseconds", "setUTCMilliseconds",
              "setSeconds", "setUTCSeconds", "setMinutes", "setUTCMinutes",
              "setHours", "setUTCHours", "setDate", "setUTCDate",  "setMonth",
              "setUTCMonth", "setFullYear", "setUTCFullYear", "toUTCString",
              "toISOString", "toJSON"],
    RegExp:  ["exec", "test", "toString"],
    Error:   ["toString"],
  };

var badThisValues = [null, undefined];

function testMethod(Class, className, method)
{
  var expr;

  // Try out explicit this values
  for (var i = 0, sz = badThisValues.length; i < sz; i++)
  {
    var badThis = badThisValues[i];

    expr = className + ".prototype." + method + ".call(" + badThis + ")";
    try
    {
      Class.prototype[method].call(badThis);
      throw new Error(expr + " didn't throw a TypeError");
    }
    catch (e)
    {
      assertEq(e instanceof TypeError, true,
               "wrong error for " + expr + ", instead threw " + e);
    }

    expr = className + ".prototype." + method + ".apply(" + badThis + ")";
    try
    {
      Class.prototype[method].apply(badThis);
      throw new Error(expr + " didn't throw a TypeError");
    }
    catch (e)
    {
      assertEq(e instanceof TypeError, true,
               "wrong error for " + expr + ", instead threw " + e);
    }
  }

  // ..and for good measure..

  expr = "(0, " + className + ".prototype." + method + ")()"
  try
  {
    // comma operator to call GetValue() on the method and de-Reference it
    (0, Class.prototype[method])();
    throw new Error(expr + " didn't throw a TypeError");
  }
  catch (e)
  {
    assertEq(e instanceof TypeError, true,
             "wrong error for " + expr + ", instead threw " + e);
  }
}

for (var className in ClassToMethodMap)
{
  var Class = this[className];

  var methodNames = ClassToMethodMap[className];
  for (var i = 0, sz = methodNames.length; i < sz; i++)
  {
    var method = methodNames[i];
    testMethod(Class, className, method);
  }
}

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

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

print("All tests passed!");
