use informative stack traces instead of loops

This commit is contained in:
Josh Wolfe 2017-08-29 13:53:38 -07:00 committed by Rick Waldron
parent 29938e9525
commit 0f3f22f6ab
2 changed files with 156 additions and 188 deletions

View File

@ -6,249 +6,219 @@ description: |
operations like ToNumber. operations like ToNumber.
---*/ ---*/
function getValuesCoercibleToIntegerZero() { function testCoercibleToIntegerZero(test) {
var result = []; function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}
var primitiveValues = [ // ToNumber
// ToNumber testPrimitiveValue(null);
null, testPrimitiveValue(false);
false, testPrimitiveValue(0);
0, testPrimitiveValue("0");
"0",
// ToInteger: NaN -> +0 // ToInteger: NaN -> +0
undefined, testPrimitiveValue(undefined);
NaN, testPrimitiveValue(NaN);
"", testPrimitiveValue("");
"foo", testPrimitiveValue("foo");
"true", testPrimitiveValue("true");
// ToInteger: floor(abs(number)) // ToInteger: floor(abs(number))
0.9, testPrimitiveValue(0.9);
-0, testPrimitiveValue(-0);
-0.9, testPrimitiveValue(-0.9);
"0.9", testPrimitiveValue("0.9");
"-0", testPrimitiveValue("-0");
"-0.9", testPrimitiveValue("-0.9");
];
// ToPrimitive
primitiveValues.forEach(function(zero) {
result.push(zero);
result = result.concat(getPrimitiveWrappers(zero, "number"));
});
// Non-primitive values that coerce to 0: // Non-primitive values that coerce to 0:
// toString() returns a string that parses to NaN. // toString() returns a string that parses to NaN.
result = result.concat([ test({});
{}, test([]);
[],
]);
return result;
} }
function getValuesCoercibleToIntegerOne() { function testCoercibleToIntegerOne(test) {
var result = []; function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}
var primitiveValues = [ // ToNumber
// ToNumber testPrimitiveValue(true);
true, testPrimitiveValue(1);
1, testPrimitiveValue("1");
"1",
// ToInteger: floor(abs(number)) // ToInteger: floor(abs(number))
1.9, testPrimitiveValue(1.9);
"1.9", testPrimitiveValue("1.9");
];
// ToPrimitive
primitiveValues.forEach(function(value) {
result.push(value);
result = result.concat(getPrimitiveWrappers(value, "number"));
});
// Non-primitive values that coerce to 1: // Non-primitive values that coerce to 1:
// toString() returns a string that parses to 1. // toString() returns a string that parses to 1.
result = result.concat([ test([1]);
[1], test(["1"]);
["1"],
]);
return result;
} }
function getValuesCoercibleToIntegerFromInteger(nominalInteger) { function testCoercibleToIntegerFromInteger(nominalInteger, test) {
assert(Number.isInteger(nominalInteger)); 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)) // ToInteger: floor(abs(number))
if (nominalInteger >= 0) { if (nominalInteger >= 0) {
primitiveValues.push(nominalInteger + 0.9); testPrimitiveNumber(nominalInteger + 0.9);
} }
if (nominalInteger <= 0) { 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) { function testPrimitiveWrappers(primitiveValue, hint, test) {
assert(hint === "number" || hint === "string");
var result = [];
if (primitiveValue != null) { if (primitiveValue != null) {
// null and undefined result in {} rather than a proper wrapper, // null and undefined result in {} rather than a proper wrapper,
// so skip this case for those values. // 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 primitiveValue;
})); }, test);
return result;
} }
function getValuesCoercibleToPrimitiveWithMethod(hint, method) { function testCoercibleToPrimitiveWithMethod(hint, method, test) {
var methodNames; var methodNames;
if (hint === "number") { if (hint === "number") {
methodNames = ["valueOf", "toString"]; methodNames = ["valueOf", "toString"];
} else { } else if (hint === "string") {
methodNames = ["toString", "valueOf"]; methodNames = ["toString", "valueOf"];
} else {
throw new Test262Error();
} }
return [ // precedence order
// precedence order test({
{ [Symbol.toPrimitive]: method,
[Symbol.toPrimitive]: method, [methodNames[0]]: function() { throw new Test262Error(); },
[methodNames[0]]: function() { throw new Test262Error(); }, [methodNames[1]]: function() { throw new Test262Error(); },
[methodNames[1]]: function() { throw new Test262Error(); }, });
}, { test({
[methodNames[0]]: method, [methodNames[0]]: method,
[methodNames[1]]: function() { throw new Test262Error(); }, [methodNames[1]]: function() { throw new Test262Error(); },
}, { });
[methodNames[1]]: method, test({
}, [methodNames[1]]: method,
});
// GetMethod: if func is undefined or null, return undefined. // GetMethod: if func is undefined or null, return undefined.
{ test({
[Symbol.toPrimitive]: undefined, [Symbol.toPrimitive]: undefined,
[methodNames[0]]: method, [methodNames[0]]: method,
[methodNames[1]]: method, [methodNames[1]]: method,
}, { });
[Symbol.toPrimitive]: null, test({
[methodNames[0]]: method, [Symbol.toPrimitive]: null,
[methodNames[1]]: method, [methodNames[0]]: method,
}, [methodNames[1]]: method,
});
// if methodNames[0] is not callable, fallback to methodNames[1] // if methodNames[0] is not callable, fallback to methodNames[1]
{ test({
[methodNames[0]]: null, [methodNames[0]]: null,
[methodNames[1]]: method, [methodNames[1]]: method,
}, { });
[methodNames[0]]: 1, test({
[methodNames[1]]: method, [methodNames[0]]: 1,
}, { [methodNames[1]]: method,
[methodNames[0]]: {}, });
[methodNames[1]]: method, test({
}, [methodNames[0]]: {},
[methodNames[1]]: method,
});
// if methodNames[0] returns an object, fallback to methodNames[1] // if methodNames[0] returns an object, fallback to methodNames[1]
{ test({
[methodNames[0]]: function() { return {}; }, [methodNames[0]]: function() { return {}; },
[methodNames[1]]: method, [methodNames[1]]: method,
}, { });
[methodNames[0]]: function() { return Object(1); }, test({
[methodNames[1]]: method, [methodNames[0]]: function() { return Object(1); },
}, [methodNames[1]]: method,
]; });
} }
function getValuesNotCoercibleToInteger() { function testNotCoercibleToInteger(test) {
// ToInteger only throws from ToNumber. // ToInteger only throws from ToNumber.
return getValuesNotCoercibleToNumber(); return testNotCoercibleToNumber(test);
} }
function getValuesNotCoercibleToNumber() { function testNotCoercibleToNumber(test) {
var result = []; function testPrimitiveValue(value) {
test(TypeError, value);
// ToPrimitive
testPrimitiveWrappers(value, "number", function(value) {
test(TypeError, value);
});
}
// ToNumber: Symbol -> TypeError // ToNumber: Symbol -> TypeError
var primitiveValues = [ testPrimitiveValue(Symbol("1"));
Symbol("1"),
];
if (typeof BigInt !== "undefined") { if (typeof BigInt !== "undefined") {
// ToNumber: BigInt -> TypeError // 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 // ToPrimitive
result = result.concat(getValuesNotCoercibleToPrimitive("number")); testNotCoercibleToPrimitive("number", test);
return result;
} }
function getValuesNotCoercibleToPrimitive(hint) { function testNotCoercibleToPrimitive(hint, test) {
function MyError() {} 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) // ToPrimitive: input[@@toPrimitive] is not callable (and non-null)
result.push({error:TypeError, value:{[Symbol.toPrimitive]: 1}}); test(TypeError, {[Symbol.toPrimitive]: 1});
result.push({error:TypeError, value:{[Symbol.toPrimitive]: {}}}); test(TypeError, {[Symbol.toPrimitive]: {}});
// ToPrimitive: input[@@toPrimitive] returns object // ToPrimitive: input[@@toPrimitive] returns object
result.push({error:TypeError, value:{[Symbol.toPrimitive]: function() { return Object(1); }}}); test(TypeError, {[Symbol.toPrimitive]: function() { return Object(1); }});
result.push({error:TypeError, value:{[Symbol.toPrimitive]: function() { return {}; }}}); test(TypeError, {[Symbol.toPrimitive]: function() { return {}; }});
// ToPrimitive: input[@@toPrimitive] throws // 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 // OrdinaryToPrimitive: method throws
result = result.concat(getValuesCoercibleToPrimitiveWithMethod(hint, function() { testCoercibleToPrimitiveWithMethod(hint, function() {
throw new MyError(); throw new MyError();
}).map(function(value) { }, function(value) {
return {error:MyError, value:value}; test(MyError, 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; // 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 {}; });
} }

View File

@ -11,20 +11,18 @@ info: >
includes: [typeCoercion.js] includes: [typeCoercion.js]
---*/ ---*/
getValuesCoercibleToIntegerZero().forEach(function(zero) { testCoercibleToIntegerZero(function(zero) {
assert.sameValue("aaaa".indexOf("aa", zero), 0, "with value " + zero); assert.sameValue("aaaa".indexOf("aa", zero), 0);
}); });
getValuesCoercibleToIntegerOne().forEach(function(one) { testCoercibleToIntegerOne(function(one) {
assert.sameValue("aaaa".indexOf("aa", one), 1, "with value " + one); assert.sameValue("aaaa".indexOf("aa", one), 1);
}); });
getValuesCoercibleToIntegerFromInteger(2).forEach(function(two) { testCoercibleToIntegerFromInteger(2, function(two) {
assert.sameValue("aaaa".indexOf("aa", two), 2, "with value " + two); assert.sameValue("aaaa".indexOf("aa", two), 2);
}); });
getValuesNotCoercibleToInteger().forEach(function(pair) { testNotCoercibleToInteger(function(error, value) {
var error = pair.error;
var value = pair.value;
assert.throws(error, function() { "".indexOf("", value); }); assert.throws(error, function() { "".indexOf("", value); });
}); });