Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type coercion harness utilities + features flags + linting #1221

Merged
merged 13 commits into from
Sep 11, 2017
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ regexp-unicode-property-escapes

# Shared Memory and atomics
# https://github.com/tc39/ecmascript_sharedmem
Atomics
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it really make sense to have feature flags for parts of proposals? Atomics is part of the SharedArrayBuffer proposal. Do we expect implementations to do part of a language feature and separate it out this way?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm +1 to use a single tag for each proposal, some exceptions can be considered, but in this case I believe it's ok to use the SharedArrayBuffer tag.

Wrt the exceptions, I guess this could possibly apply to private class fields, but we should postpone this discussion to a later time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added Atomics just to be thorough, since it's a new global. Is the consensus here that I should remove?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of using a single name for features from the SharedArrayBuffer proposal. Even though, I believe we can change it as a follow up improvement.

If this is good to land, I don't want to block it, we can have a separate discussion to find an agreement for this specific part later.

SharedArrayBuffer

# Standard language features
Expand Down Expand Up @@ -80,6 +81,7 @@ DataView.prototype.setUint8
default-arg
default-parameters
destructuring-binding
for-of
Float64Array
generators
Int8Array
Expand Down
4 changes: 2 additions & 2 deletions harness/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ assert.throws = function (expectedErrorConstructor, func, message) {
};

assert.throws.early = function(err, code) {
let wrappedCode = `function wrapperFn() { ${code} }`;
let wrappedCode = 'function wrapperFn() { ' + code + ' }';
let ieval = eval;

assert.throws(err, function() { Function(wrappedCode); }, `Function: ${code}`);
assert.throws(err, function() { Function(wrappedCode); }, 'Function: ' + code);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @leobalter ;)

};
2 changes: 1 addition & 1 deletion harness/compareArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ function compareArray(a, b) {

assert.compareArray = function(actual, expected, message) {
assert(compareArray(actual, expected),
`Expected [${actual.join(", ")}] and [${expected.join(", ")}] to have the same contents. ${message}`);
'Expected [' + actual.join(', ') + '] and [' + expected.join(', ') + '] to have the same contents. ' + message);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry again @leobalter ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha

};
3 changes: 3 additions & 0 deletions harness/features.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
typeCoercion.js: [Symbol.toPrimitive, BigInt]
testAtomics.js: [ArrayBuffer, Atomics, DataView, SharedArrayBuffer, arrow-function, let, for-of]
testTypedArray.js: [TypedArray]
16 changes: 8 additions & 8 deletions harness/propertyHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function verifyProperty(obj, name, desc, options) {
assert.sameValue(
originalDesc,
undefined,
`obj['${nameStr}'] descriptor should be undefined`
"obj['" + nameStr + "'] descriptor should be undefined"
);

// desc and originalDesc are both undefined, problem solved;
Expand All @@ -29,47 +29,47 @@ function verifyProperty(obj, name, desc, options) {

assert(
Object.prototype.hasOwnProperty.call(obj, name),
`obj should have an own property ${nameStr}`
"obj should have an own property " + nameStr
);

assert.notSameValue(
desc,
null,
`The desc argument should be an object or undefined, null`
"The desc argument should be an object or undefined, null"
);

assert.sameValue(
typeof desc,
"object",
`The desc argument should be an object or undefined, ${String(desc)}`
"The desc argument should be an object or undefined, " + String(desc)
);

var failures = [];

if (Object.prototype.hasOwnProperty.call(desc, 'value')) {
if (desc.value !== originalDesc.value) {
failures.push(`descriptor value should be ${desc.value}`);
failures.push("descriptor value should be " + desc.value);
}
}

if (Object.prototype.hasOwnProperty.call(desc, 'enumerable')) {
if (desc.enumerable !== originalDesc.enumerable ||
desc.enumerable !== isEnumerable(obj, name)) {
failures.push(`descriptor should ${desc.enumerable ? '' : 'not '}be enumerable`);
failures.push('descriptor should ' + (desc.enumerable ? '' : 'not ') + 'be enumerable');
}
}

if (Object.prototype.hasOwnProperty.call(desc, 'writable')) {
if (desc.writable !== originalDesc.writable ||
desc.writable !== isWritable(obj, name)) {
failures.push(`descriptor should ${desc.writable ? '' : 'not '}be writable`);
failures.push('descriptor should ' + (desc.writable ? '' : 'not ') + 'be writable');
}
}

if (Object.prototype.hasOwnProperty.call(desc, 'configurable')) {
if (desc.configurable !== originalDesc.configurable ||
desc.configurable !== isConfigurable(obj, name)) {
failures.push(`descriptor should ${desc.configurable ? '' : 'not '}be configurable`);
failures.push('descriptor should ' + (desc.configurable ? '' : 'not ') + 'be configurable');
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, @leobalter I'm not really sorry ;)

}

Expand Down
285 changes: 285 additions & 0 deletions harness/typeCoercion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
// 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 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);
}

testPrimitiveValue(null);
testPrimitiveValue(false);
testPrimitiveValue(0);
testPrimitiveValue("0");
}

function testCoercibleToNumberNan(test) {
function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}

testPrimitiveValue(undefined);
testPrimitiveValue(NaN);
testPrimitiveValue("");
testPrimitiveValue("foo");
testPrimitiveValue("true");
}

function testCoercibleToNumberOne(test) {
function testPrimitiveValue(value) {
test(value);
// ToPrimitive
testPrimitiveWrappers(value, "number", test);
}

testPrimitiveValue(true);
testPrimitiveValue(1);
testPrimitiveValue("1");
}

function testCoercibleToIntegerFromInteger(nominalInteger, test) {
assert(Number.isInteger(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) {
testPrimitiveNumber(nominalInteger + 0.9);
}
if (nominalInteger <= 0) {
testPrimitiveNumber(nominalInteger - 0.9);
}
}

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.
test(Object(primitiveValue));
}

testCoercibleToPrimitiveWithMethod(hint, function() {
return primitiveValue;
}, test);
}

function testCoercibleToPrimitiveWithMethod(hint, method, test) {
var methodNames;
if (hint === "number") {
methodNames = ["valueOf", "toString"];
} else if (hint === "string") {
methodNames = ["toString", "valueOf"];
} else {
throw new Test262Error();
}
// 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(); },
});
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({
[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 testNotCoercibleToInteger(test) {
// ToInteger only throws from ToNumber.
return testNotCoercibleToNumber(test);
}
function testNotCoercibleToNumber(test) {
function testPrimitiveValue(value) {
test(TypeError, value);
// ToPrimitive
testPrimitiveWrappers(value, "number", function(value) {
test(TypeError, value);
});
}

// ToNumber: Symbol -> TypeError
testPrimitiveValue(Symbol("1"));

if (typeof BigInt !== "undefined") {
// ToNumber: BigInt -> TypeError
testPrimitiveValue(BigInt(0));
}

// ToPrimitive
testNotCoercibleToPrimitive("number", test);
}

function testNotCoercibleToPrimitive(hint, test) {
function MyError() {}

// ToPrimitive: input[@@toPrimitive] is not callable (and non-null)
test(TypeError, {[Symbol.toPrimitive]: 1});
test(TypeError, {[Symbol.toPrimitive]: {}});

// ToPrimitive: input[@@toPrimitive] returns object
test(TypeError, {[Symbol.toPrimitive]: function() { return Object(1); }});
test(TypeError, {[Symbol.toPrimitive]: function() { return {}; }});

// ToPrimitive: input[@@toPrimitive] throws
test(MyError, {[Symbol.toPrimitive]: function() { throw new MyError(); }});

// OrdinaryToPrimitive: method throws
testCoercibleToPrimitiveWithMethod(hint, function() {
throw new MyError();
}, function(value) {
test(MyError, value);
});

// 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 {}; });
}

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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ info: >
8. If _a_ has a [[TypedArrayName]] internal slot, then
a. If IsDetachedBuffer(_a_.[[ViewedArrayBuffer]]) is *true*, throw a *TypeError* exception.
includes: [testTypedArray.js, detachArrayBuffer.js]
features: [TypedArray]
---*/

testWithTypedArrayConstructors(TA => {
Expand Down
Loading