From 16cd3203660e6247fd35c6dc34cfc12fa7cafcff Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Mon, 28 Aug 2017 10:37:38 -0700 Subject: [PATCH 1/4] type coercion harness utilities --- harness/typeCoercion.js | 254 ++++++++++++++++++ .../prototype/indexOf/position-tointeger.js | 30 +++ 2 files changed, 284 insertions(+) create mode 100644 harness/typeCoercion.js create mode 100644 test/built-ins/String/prototype/indexOf/position-tointeger.js diff --git a/harness/typeCoercion.js b/harness/typeCoercion.js new file mode 100644 index 00000000000..1fd42ec1b42 --- /dev/null +++ b/harness/typeCoercion.js @@ -0,0 +1,254 @@ +// Copyright (C) 2017 Josh Wolfe. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +description: | + Functions to help generate test cases for testing type coercion abstract + operations like ToNumber. +---*/ + +function getValuesCoercibleToIntegerZero() { + var result = []; + + var primitiveValues = [ + // ToNumber + null, + false, + 0, + "0", + + // ToInteger: NaN -> +0 + undefined, + NaN, + "", + "foo", + "true", + + // ToInteger: floor(abs(number)) + 0.9, + -0, + -0.9, + "0.9", + "-0", + "-0.9", + ]; + + // ToPrimitive + primitiveValues.forEach(function(zero) { + result.push(zero); + result = result.concat(getPrimitiveWrappers(zero, "number")); + }); + + // Non-primitive values that coerce to 0: + // toString() returns a string that parses to NaN. + result = result.concat([ + {}, + [], + ]); + + return result; +} + +function getValuesCoercibleToIntegerOne() { + var result = []; + + var primitiveValues = [ + // ToNumber + true, + 1, + "1", + + // ToInteger: floor(abs(number)) + 1.9, + "1.9", + ]; + + // ToPrimitive + primitiveValues.forEach(function(value) { + result.push(value); + result = result.concat(getPrimitiveWrappers(value, "number")); + }); + + // Non-primitive values that coerce to 1: + // toString() returns a string that parses to 1. + result = result.concat([ + [1], + ["1"], + ]); + + return result; +} + +function getValuesCoercibleToIntegerFromInteger(nominalInteger) { + assert(Number.isInteger(nominalInteger)); + var result = []; + + var primitiveValues = [ nominalInteger ]; + + // ToInteger: floor(abs(number)) + if (nominalInteger >= 0) { + primitiveValues.push(nominalInteger + 0.9); + } + if (nominalInteger <= 0) { + primitiveValues.push(nominalInteger - 0.9); + } + + // ToNumber: String -> Number + primitiveValues = primitiveValues.concat(primitiveValues.map(function(number) { return number.toString(); })); + + // ToPrimitive + primitiveValues.forEach(function(value) { + result.push(value); + result = result.concat(getPrimitiveWrappers(value, "number")); + }); + + // Non-primitive values that coerce to the nominal integer: + // toString() returns a string that parsers to a primitive value. + result = result.concat(primitiveValues.map(function(number) { return [number]; })); + + return result; +} + +function getPrimitiveWrappers(primitiveValue, hint) { + assert(hint === "number" || hint === "string"); + var result = []; + + if (primitiveValue != null) { + // null and undefined result in {} rather than a proper wrapper, + // so skip this case for those values. + result.push(Object(primitiveValue)); + } + + result = result.concat(getValuesCoercibleToPrimitiveWithMethod(hint, function() { + return primitiveValue; + })); + return result; +} + +function getValuesCoercibleToPrimitiveWithMethod(hint, method) { + var methodNames; + if (hint === "number") { + methodNames = ["valueOf", "toString"]; + } else { + methodNames = ["toString", "valueOf"]; + } + return [ + // precedence order + { + [Symbol.toPrimitive]: method, + [methodNames[0]]: function() { throw new Test262Error(); }, + [methodNames[1]]: function() { throw new Test262Error(); }, + }, { + [methodNames[0]]: method, + [methodNames[1]]: function() { throw new Test262Error(); }, + }, { + [methodNames[1]]: method, + }, + + // GetMethod: if func is undefined or null, return undefined. + { + [Symbol.toPrimitive]: undefined, + [methodNames[0]]: method, + [methodNames[1]]: method, + }, { + [Symbol.toPrimitive]: null, + [methodNames[0]]: method, + [methodNames[1]]: method, + }, + + // if methodNames[0] is not callable, fallback to methodNames[1] + { + [methodNames[0]]: null, + [methodNames[1]]: method, + }, { + [methodNames[0]]: 1, + [methodNames[1]]: method, + }, { + [methodNames[0]]: {}, + [methodNames[1]]: method, + }, + + // if methodNames[0] returns an object, fallback to methodNames[1] + { + [methodNames[0]]: function() { return {}; }, + [methodNames[1]]: method, + }, { + [methodNames[0]]: function() { return Object(1); }, + [methodNames[1]]: method, + }, + ]; +} + +function getValuesNotCoercibleToInteger() { + // ToInteger only throws from ToNumber. + return getValuesNotCoercibleToNumber(); +} +function getValuesNotCoercibleToNumber() { + var result = []; + + // ToNumber: Symbol -> TypeError + var primitiveValues = [ + Symbol("1"), + ]; + if (typeof BigInt !== "undefined") { + // ToNumber: BigInt -> TypeError + primitiveValues.push(BigInt(0)); + } + primitiveValues.forEach(function(value) { + result.push({error:TypeError, value:value}); + getPrimitiveWrappers(value, "number").forEach(function(value) { + result.push({error:TypeError, value:value}); + }); + }); + + // ToPrimitive + result = result.concat(getValuesNotCoercibleToPrimitive("number")); + + return result; +} + +function getValuesNotCoercibleToPrimitive(hint) { + function MyError() {} + + var result = []; + + var methodNames; + if (hint === "number") { + methodNames = ["valueOf", "toString"]; + } else { + methodNames = ["toString", "valueOf"]; + } + + // ToPrimitive: input[@@toPrimitive] is not callable (and non-null) + result.push({error:TypeError, value:{[Symbol.toPrimitive]: 1}}); + result.push({error:TypeError, value:{[Symbol.toPrimitive]: {}}}); + + // ToPrimitive: input[@@toPrimitive] returns object + result.push({error:TypeError, value:{[Symbol.toPrimitive]: function() { return Object(1); }}}); + result.push({error:TypeError, value:{[Symbol.toPrimitive]: function() { return {}; }}}); + + // ToPrimitive: input[@@toPrimitive] throws + result.push({error:MyError, value:{[Symbol.toPrimitive]: function() { throw new MyError(); }}}); + + // OrdinaryToPrimitive: method throws + result = result.concat(getValuesCoercibleToPrimitiveWithMethod(hint, function() { + throw new MyError(); + }).map(function(value) { + return {error:MyError, value:value}; + })); + + // OrdinaryToPrimitive: both methods are unsuitable + var unsuitableMethods = [ + // not callable: + null, + 1, + {}, + // returns object: + function() { return Object(1); }, + function() { return {}; }, + ]; + unsuitableMethods.forEach(function(method) { + result.push({error:TypeError, value:{valueOf:method, toString:method}}); + }); + + return result; +} diff --git a/test/built-ins/String/prototype/indexOf/position-tointeger.js b/test/built-ins/String/prototype/indexOf/position-tointeger.js new file mode 100644 index 00000000000..74996827400 --- /dev/null +++ b/test/built-ins/String/prototype/indexOf/position-tointeger.js @@ -0,0 +1,30 @@ +// Copyright (C) 2017 Josh Wolfe. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-string.prototype.indexof +description: String.prototype.indexOf type coercion for position parameter +info: > + String.prototype.indexOf ( searchString [ , position ] ) + + 4. Let pos be ? ToInteger(position). + +includes: [typeCoercion.js] +---*/ + +getValuesCoercibleToIntegerZero().forEach(function(zero) { + assert.sameValue("aaaa".indexOf("aa", zero), 0, "with value " + zero); +}); + +getValuesCoercibleToIntegerOne().forEach(function(one) { + assert.sameValue("aaaa".indexOf("aa", one), 1, "with value " + one); +}); + +getValuesCoercibleToIntegerFromInteger(2).forEach(function(two) { + assert.sameValue("aaaa".indexOf("aa", two), 2, "with value " + two); +}); + +getValuesNotCoercibleToInteger().forEach(function(pair) { + var error = pair.error; + var value = pair.value; + assert.throws(error, function() { "".indexOf("", value); }); +}); From f58e962fd82f637c2025fd0d4fed0171dc267db2 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Tue, 29 Aug 2017 13:53:38 -0700 Subject: [PATCH 2/4] use informative stack traces instead of loops --- harness/typeCoercion.js | 338 ++++++++---------- .../prototype/indexOf/position-tointeger.js | 16 +- 2 files changed, 161 insertions(+), 193 deletions(-) diff --git a/harness/typeCoercion.js b/harness/typeCoercion.js index 1fd42ec1b42..504c56c3e35 100644 --- a/harness/typeCoercion.js +++ b/harness/typeCoercion.js @@ -6,249 +6,219 @@ description: | operations like ToNumber. ---*/ -function getValuesCoercibleToIntegerZero() { - var result = []; - - var primitiveValues = [ - // ToNumber - null, - false, - 0, - "0", - - // ToInteger: NaN -> +0 - undefined, - NaN, - "", - "foo", - "true", - - // ToInteger: floor(abs(number)) - 0.9, - -0, - -0.9, - "0.9", - "-0", - "-0.9", - ]; +function testCoercibleToIntegerZero(test) { + function testPrimitiveValue(value) { + test(value); + // ToPrimitive + testPrimitiveWrappers(value, "number", test); + } - // ToPrimitive - primitiveValues.forEach(function(zero) { - result.push(zero); - result = result.concat(getPrimitiveWrappers(zero, "number")); - }); + // ToNumber + testPrimitiveValue(null); + testPrimitiveValue(false); + testPrimitiveValue(0); + testPrimitiveValue("0"); + + // ToInteger: NaN -> +0 + testPrimitiveValue(undefined); + testPrimitiveValue(NaN); + testPrimitiveValue(""); + testPrimitiveValue("foo"); + testPrimitiveValue("true"); + + // ToInteger: floor(abs(number)) + testPrimitiveValue(0.9); + testPrimitiveValue(-0); + testPrimitiveValue(-0.9); + testPrimitiveValue("0.9"); + testPrimitiveValue("-0"); + testPrimitiveValue("-0.9"); // Non-primitive values that coerce to 0: // toString() returns a string that parses to NaN. - result = result.concat([ - {}, - [], - ]); - - return result; + test({}); + test([]); } -function getValuesCoercibleToIntegerOne() { - var result = []; - - var primitiveValues = [ - // ToNumber - true, - 1, - "1", +function testCoercibleToIntegerOne(test) { + function testPrimitiveValue(value) { + test(value); + // ToPrimitive + testPrimitiveWrappers(value, "number", test); + } - // ToInteger: floor(abs(number)) - 1.9, - "1.9", - ]; + // ToNumber + testPrimitiveValue(true); + testPrimitiveValue(1); + testPrimitiveValue("1"); - // ToPrimitive - primitiveValues.forEach(function(value) { - result.push(value); - result = result.concat(getPrimitiveWrappers(value, "number")); - }); + // ToInteger: floor(abs(number)) + testPrimitiveValue(1.9); + testPrimitiveValue("1.9"); // Non-primitive values that coerce to 1: // toString() returns a string that parses to 1. - result = result.concat([ - [1], - ["1"], - ]); - - return result; + test([1]); + test(["1"]); } -function getValuesCoercibleToIntegerFromInteger(nominalInteger) { +function testCoercibleToIntegerFromInteger(nominalInteger, test) { assert(Number.isInteger(nominalInteger)); - var result = []; - var primitiveValues = [ nominalInteger ]; + function testPrimitiveValue(value) { + test(value); + // ToPrimitive + testPrimitiveWrappers(value, "number", test); + + // Non-primitive values that coerce to the nominal integer: + // toString() returns a string that parsers to a primitive value. + test([value]); + } + + function testPrimitiveNumber(number) { + testPrimitiveValue(number); + // ToNumber: String -> Number + testPrimitiveValue(number.toString()); + } + + testPrimitiveNumber(nominalInteger); // ToInteger: floor(abs(number)) if (nominalInteger >= 0) { - primitiveValues.push(nominalInteger + 0.9); + testPrimitiveNumber(nominalInteger + 0.9); } if (nominalInteger <= 0) { - primitiveValues.push(nominalInteger - 0.9); + testPrimitiveNumber(nominalInteger - 0.9); } - - // ToNumber: String -> Number - primitiveValues = primitiveValues.concat(primitiveValues.map(function(number) { return number.toString(); })); - - // ToPrimitive - primitiveValues.forEach(function(value) { - result.push(value); - result = result.concat(getPrimitiveWrappers(value, "number")); - }); - - // Non-primitive values that coerce to the nominal integer: - // toString() returns a string that parsers to a primitive value. - result = result.concat(primitiveValues.map(function(number) { return [number]; })); - - return result; } -function getPrimitiveWrappers(primitiveValue, hint) { - assert(hint === "number" || hint === "string"); - var result = []; - +function testPrimitiveWrappers(primitiveValue, hint, test) { if (primitiveValue != null) { // null and undefined result in {} rather than a proper wrapper, // so skip this case for those values. - result.push(Object(primitiveValue)); + test(Object(primitiveValue)); } - result = result.concat(getValuesCoercibleToPrimitiveWithMethod(hint, function() { + testCoercibleToPrimitiveWithMethod(hint, function() { return primitiveValue; - })); - return result; + }, test); } -function getValuesCoercibleToPrimitiveWithMethod(hint, method) { +function testCoercibleToPrimitiveWithMethod(hint, method, test) { var methodNames; if (hint === "number") { methodNames = ["valueOf", "toString"]; - } else { + } else if (hint === "string") { methodNames = ["toString", "valueOf"]; + } else { + throw new Test262Error(); } - return [ - // precedence order - { - [Symbol.toPrimitive]: method, - [methodNames[0]]: function() { throw new Test262Error(); }, - [methodNames[1]]: function() { throw new Test262Error(); }, - }, { - [methodNames[0]]: method, - [methodNames[1]]: function() { throw new Test262Error(); }, - }, { - [methodNames[1]]: method, - }, - - // GetMethod: if func is undefined or null, return undefined. - { - [Symbol.toPrimitive]: undefined, - [methodNames[0]]: method, - [methodNames[1]]: method, - }, { - [Symbol.toPrimitive]: null, - [methodNames[0]]: method, - [methodNames[1]]: method, - }, - - // if methodNames[0] is not callable, fallback to methodNames[1] - { - [methodNames[0]]: null, - [methodNames[1]]: method, - }, { - [methodNames[0]]: 1, - [methodNames[1]]: method, - }, { - [methodNames[0]]: {}, - [methodNames[1]]: method, - }, - - // if methodNames[0] returns an object, fallback to methodNames[1] - { - [methodNames[0]]: function() { return {}; }, - [methodNames[1]]: method, - }, { - [methodNames[0]]: function() { return Object(1); }, - [methodNames[1]]: method, - }, - ]; + // precedence order + test({ + [Symbol.toPrimitive]: method, + [methodNames[0]]: function() { throw new Test262Error(); }, + [methodNames[1]]: function() { throw new Test262Error(); }, + }); + test({ + [methodNames[0]]: method, + [methodNames[1]]: function() { throw new Test262Error(); }, + }); + test({ + [methodNames[1]]: method, + }); + + // GetMethod: if func is undefined or null, return undefined. + test({ + [Symbol.toPrimitive]: undefined, + [methodNames[0]]: method, + [methodNames[1]]: method, + }); + test({ + [Symbol.toPrimitive]: null, + [methodNames[0]]: method, + [methodNames[1]]: method, + }); + + // if methodNames[0] is not callable, fallback to methodNames[1] + test({ + [methodNames[0]]: null, + [methodNames[1]]: method, + }); + test({ + [methodNames[0]]: 1, + [methodNames[1]]: method, + }); + test({ + [methodNames[0]]: {}, + [methodNames[1]]: method, + }); + + // if methodNames[0] returns an object, fallback to methodNames[1] + test({ + [methodNames[0]]: function() { return {}; }, + [methodNames[1]]: method, + }); + test({ + [methodNames[0]]: function() { return Object(1); }, + [methodNames[1]]: method, + }); } -function getValuesNotCoercibleToInteger() { +function testNotCoercibleToInteger(test) { // ToInteger only throws from ToNumber. - return getValuesNotCoercibleToNumber(); + return testNotCoercibleToNumber(test); } -function getValuesNotCoercibleToNumber() { - var result = []; +function testNotCoercibleToNumber(test) { + function testPrimitiveValue(value) { + test(TypeError, value); + // ToPrimitive + testPrimitiveWrappers(value, "number", function(value) { + test(TypeError, value); + }); + } // ToNumber: Symbol -> TypeError - var primitiveValues = [ - Symbol("1"), - ]; + testPrimitiveValue(Symbol("1")); + if (typeof BigInt !== "undefined") { // ToNumber: BigInt -> TypeError - primitiveValues.push(BigInt(0)); + testPrimitiveValue(BigInt(0)); } - primitiveValues.forEach(function(value) { - result.push({error:TypeError, value:value}); - getPrimitiveWrappers(value, "number").forEach(function(value) { - result.push({error:TypeError, value:value}); - }); - }); // ToPrimitive - result = result.concat(getValuesNotCoercibleToPrimitive("number")); - - return result; + testNotCoercibleToPrimitive("number", test); } -function getValuesNotCoercibleToPrimitive(hint) { +function testNotCoercibleToPrimitive(hint, test) { function MyError() {} - var result = []; - - var methodNames; - if (hint === "number") { - methodNames = ["valueOf", "toString"]; - } else { - methodNames = ["toString", "valueOf"]; - } - // ToPrimitive: input[@@toPrimitive] is not callable (and non-null) - result.push({error:TypeError, value:{[Symbol.toPrimitive]: 1}}); - result.push({error:TypeError, value:{[Symbol.toPrimitive]: {}}}); + test(TypeError, {[Symbol.toPrimitive]: 1}); + test(TypeError, {[Symbol.toPrimitive]: {}}); // ToPrimitive: input[@@toPrimitive] returns object - result.push({error:TypeError, value:{[Symbol.toPrimitive]: function() { return Object(1); }}}); - result.push({error:TypeError, value:{[Symbol.toPrimitive]: function() { return {}; }}}); + test(TypeError, {[Symbol.toPrimitive]: function() { return Object(1); }}); + test(TypeError, {[Symbol.toPrimitive]: function() { return {}; }}); // ToPrimitive: input[@@toPrimitive] throws - result.push({error:MyError, value:{[Symbol.toPrimitive]: function() { throw new MyError(); }}}); + test(MyError, {[Symbol.toPrimitive]: function() { throw new MyError(); }}); // OrdinaryToPrimitive: method throws - result = result.concat(getValuesCoercibleToPrimitiveWithMethod(hint, function() { + testCoercibleToPrimitiveWithMethod(hint, function() { throw new MyError(); - }).map(function(value) { - return {error:MyError, value:value}; - })); - - // OrdinaryToPrimitive: both methods are unsuitable - var unsuitableMethods = [ - // not callable: - null, - 1, - {}, - // returns object: - function() { return Object(1); }, - function() { return {}; }, - ]; - unsuitableMethods.forEach(function(method) { - result.push({error:TypeError, value:{valueOf:method, toString:method}}); + }, function(value) { + test(MyError, value); }); - return result; + // OrdinaryToPrimitive: both methods are unsuitable + function testUnsuitableMethod(method) { + test(TypeError, {valueOf:method, toString:method}); + } + // not callable: + testUnsuitableMethod(null); + testUnsuitableMethod(1); + testUnsuitableMethod({}); + // returns object: + testUnsuitableMethod(function() { return Object(1); }); + testUnsuitableMethod(function() { return {}; }); } diff --git a/test/built-ins/String/prototype/indexOf/position-tointeger.js b/test/built-ins/String/prototype/indexOf/position-tointeger.js index 74996827400..1405ddca74a 100644 --- a/test/built-ins/String/prototype/indexOf/position-tointeger.js +++ b/test/built-ins/String/prototype/indexOf/position-tointeger.js @@ -11,20 +11,18 @@ info: > includes: [typeCoercion.js] ---*/ -getValuesCoercibleToIntegerZero().forEach(function(zero) { - assert.sameValue("aaaa".indexOf("aa", zero), 0, "with value " + zero); +testCoercibleToIntegerZero(function(zero) { + assert.sameValue("aaaa".indexOf("aa", zero), 0); }); -getValuesCoercibleToIntegerOne().forEach(function(one) { - assert.sameValue("aaaa".indexOf("aa", one), 1, "with value " + one); +testCoercibleToIntegerOne(function(one) { + assert.sameValue("aaaa".indexOf("aa", one), 1); }); -getValuesCoercibleToIntegerFromInteger(2).forEach(function(two) { - assert.sameValue("aaaa".indexOf("aa", two), 2, "with value " + two); +testCoercibleToIntegerFromInteger(2, function(two) { + assert.sameValue("aaaa".indexOf("aa", two), 2); }); -getValuesNotCoercibleToInteger().forEach(function(pair) { - var error = pair.error; - var value = pair.value; +testNotCoercibleToInteger(function(error, value) { assert.throws(error, function() { "".indexOf("", value); }); }); From 2bcee866c2f75796403a3024792ad493752ae2c0 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Tue, 29 Aug 2017 17:28:55 -0700 Subject: [PATCH 3/4] more separation of ToInteger from ToNumber --- harness/typeCoercion.js | 58 +++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/harness/typeCoercion.js b/harness/typeCoercion.js index 504c56c3e35..7c32a2c8db6 100644 --- a/harness/typeCoercion.js +++ b/harness/typeCoercion.js @@ -7,59 +7,65 @@ description: | ---*/ function testCoercibleToIntegerZero(test) { + testCoercibleToNumberZero(test); + + testCoercibleToIntegerFromInteger(0, test); + + // NaN -> +0 + testCoercibleToNumberNan(test); + + // When toString() returns a string that parses to NaN: + test({}); + test([]); +} + +function testCoercibleToIntegerOne(test) { + testCoercibleToNumberOne(test); + + testCoercibleToIntegerFromInteger(1, test); + + // When toString() returns "1" + test([1]); + test(["1"]); +} + +function testCoercibleToNumberZero(test) { function testPrimitiveValue(value) { test(value); // ToPrimitive testPrimitiveWrappers(value, "number", test); } - // ToNumber testPrimitiveValue(null); testPrimitiveValue(false); testPrimitiveValue(0); testPrimitiveValue("0"); +} + +function testCoercibleToNumberNan(test) { + function testPrimitiveValue(value) { + test(value); + // ToPrimitive + testPrimitiveWrappers(value, "number", test); + } - // ToInteger: NaN -> +0 testPrimitiveValue(undefined); testPrimitiveValue(NaN); testPrimitiveValue(""); testPrimitiveValue("foo"); testPrimitiveValue("true"); - - // ToInteger: floor(abs(number)) - testPrimitiveValue(0.9); - testPrimitiveValue(-0); - testPrimitiveValue(-0.9); - testPrimitiveValue("0.9"); - testPrimitiveValue("-0"); - testPrimitiveValue("-0.9"); - - // Non-primitive values that coerce to 0: - // toString() returns a string that parses to NaN. - test({}); - test([]); } -function testCoercibleToIntegerOne(test) { +function testCoercibleToNumberOne(test) { function testPrimitiveValue(value) { test(value); // ToPrimitive testPrimitiveWrappers(value, "number", test); } - // ToNumber testPrimitiveValue(true); testPrimitiveValue(1); testPrimitiveValue("1"); - - // ToInteger: floor(abs(number)) - testPrimitiveValue(1.9); - testPrimitiveValue("1.9"); - - // Non-primitive values that coerce to 1: - // toString() returns a string that parses to 1. - test([1]); - test(["1"]); } function testCoercibleToIntegerFromInteger(nominalInteger, test) { From 7eb13e115ebdecd5ff29f0a4770ca5a9f0de32c5 Mon Sep 17 00:00:00 2001 From: Josh Wolfe Date: Tue, 29 Aug 2017 18:06:37 -0700 Subject: [PATCH 4/4] test for String.prototype.indexOf first parameter type coercion --- harness/typeCoercion.js | 61 ++++++++++++++++++- .../indexOf/searchstring-tostring.js | 25 ++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 test/built-ins/String/prototype/indexOf/searchstring-tostring.js diff --git a/harness/typeCoercion.js b/harness/typeCoercion.js index 7c32a2c8db6..296694fcfbf 100644 --- a/harness/typeCoercion.js +++ b/harness/typeCoercion.js @@ -129,9 +129,15 @@ function testCoercibleToPrimitiveWithMethod(hint, method, test) { [methodNames[0]]: method, [methodNames[1]]: function() { throw new Test262Error(); }, }); - test({ - [methodNames[1]]: method, - }); + if (hint === "number") { + // The default valueOf returns an object, which is unsuitable. + // The default toString returns a String, which is suitable. + // Therefore this test only works for valueOf falling back to toString. + test({ + // this is toString: + [methodNames[1]]: method, + }); + } // GetMethod: if func is undefined or null, return undefined. test({ @@ -228,3 +234,52 @@ function testNotCoercibleToPrimitive(hint, test) { testUnsuitableMethod(function() { return Object(1); }); testUnsuitableMethod(function() { return {}; }); } + +function testCoercibleToString(test) { + function testPrimitiveValue(value, expectedString) { + test(value, expectedString); + // ToPrimitive + testPrimitiveWrappers(value, "string", function(value) { + test(value, expectedString); + }); + } + + testPrimitiveValue(undefined, "undefined"); + testPrimitiveValue(null, "null"); + testPrimitiveValue(true, "true"); + testPrimitiveValue(false, "false"); + testPrimitiveValue(0, "0"); + testPrimitiveValue(-0, "0"); + testPrimitiveValue(Infinity, "Infinity"); + testPrimitiveValue(-Infinity, "-Infinity"); + testPrimitiveValue(123.456, "123.456"); + testPrimitiveValue(-123.456, "-123.456"); + testPrimitiveValue("", ""); + testPrimitiveValue("foo", "foo"); + + if (typeof BigInt !== "undefined") { + // BigInt -> TypeError + testPrimitiveValue(BigInt(0), "0"); + } + + // toString of a few objects + test([], ""); + test(["foo", "bar"], "foo,bar"); + test({}, "[object Object]"); +} + +function testNotCoercibleToString(test) { + function testPrimitiveValue(value) { + test(TypeError, value); + // ToPrimitive + testPrimitiveWrappers(value, "string", function(value) { + test(TypeError, value); + }); + } + + // Symbol -> TypeError + testPrimitiveValue(Symbol("1")); + + // ToPrimitive + testNotCoercibleToPrimitive("string", test); +} diff --git a/test/built-ins/String/prototype/indexOf/searchstring-tostring.js b/test/built-ins/String/prototype/indexOf/searchstring-tostring.js new file mode 100644 index 00000000000..e2dddda89a9 --- /dev/null +++ b/test/built-ins/String/prototype/indexOf/searchstring-tostring.js @@ -0,0 +1,25 @@ +// Copyright (C) 2017 Josh Wolfe. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. +/*--- +esid: sec-string.prototype.indexof +description: String.prototype.indexOf type coercion for searchString parameter +info: > + String.prototype.indexOf ( searchString [ , position ] ) + + 3. Let searchStr be ? ToString(searchString). + +includes: [typeCoercion.js] +---*/ + +testCoercibleToString(function(value, expectedString) { + if (expectedString.length === 0) { + assert.sameValue(("x_x_x").indexOf(value), 0); + } else { + assert.sameValue(expectedString.indexOf("\x00"), -1, "sanity check"); + assert.sameValue(("\x00\x00" + expectedString + "\x00\x00").indexOf(value), 2); + } +}); + +testNotCoercibleToString(function(error, value) { + assert.throws(error, function() { "".indexOf(value); }); +});