Bug 896608 - Implement ES6 %TypedArray%.{of, from}. r=evilpie, till

This commit is contained in:
ziyunfei 2015-01-14 10:06:00 +01:00
parent f7e80df874
commit cc5f6453a1
14 changed files with 892 additions and 3 deletions

View File

@ -540,3 +540,155 @@ function TypedArrayIncludes(searchElement, fromIndex = 0) {
// Step 11.
return false;
}
// ES6 draft rev30 (2014/12/24) 22.2.2.1 %TypedArray%.from(source[, mapfn[, thisArg]]).
function TypedArrayStaticFrom(source, mapfn = undefined, thisArg = undefined) {
// Step 1.
var C = this;
// Step 2.
if (!IsConstructor(C))
ThrowError(JSMSG_NOT_CONSTRUCTOR, DecompileArg(1, C));
// Step 3.
var f = mapfn;
// Step 4.
if (f !== undefined && !IsCallable(f))
ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(1, f));
// Steps 5-6.
return TypedArrayFrom(C, undefined, source, f, thisArg);
}
// ES6 draft rev30 (2014/12/24) 22.2.2.1.1 TypedArrayFrom().
function TypedArrayFrom(constructor, target, items, mapfn, thisArg) {
// Step 1.
var C = constructor;
// Step 2.
assert(C === undefined || target === undefined,
"Neither of 'constructor' and 'target' is undefined");
// Step 3.
assert(IsConstructor(C) || C === undefined,
"'constructor' is neither an constructor nor undefined");
// Step 4.
assert(target === undefined || IsTypedArray(target),
"'target' is neither a typed array nor undefined");
// Step 5.
assert(IsCallable(mapfn) || mapfn === undefined,
"'target' is neither a function nor undefined");
// Steps 6-7.
var mapping = mapfn !== undefined;
var T = thisArg;
// Steps 8-9.
var usingIterator = GetMethod(items, std_iterator);
// Step 10.
if (usingIterator !== undefined) {
// Steps 10.a-b.
var iterator = GetIterator(items, usingIterator);
// Step 10.c.
var values = new List();
// Steps 10.d-e.
while (true) {
// Steps 10.e.i-ii.
var next = iterator.next();
if (!IsObject(next))
ThrowError(JSMSG_NEXT_RETURNED_PRIMITIVE);
// Steps 10.e.iii-vi.
if (next.done)
break;
values.push(next.value);
}
// Step 10.f.
var len = values.length;
// Steps 10.g-h.
// There is no need to implement the 22.2.2.1.2 - TypedArrayAllocOrInit() method,
// since `%TypedArray%(object)` currently doesn't call this self-hosted TypedArrayFrom().
var targetObj = new C(len);
// Steps 10.i-j.
for (var k = 0; k < len; k++) {
// Steps 10.j.i-ii.
var kValue = values[k];
// Steps 10.j.iii-iv.
var mappedValue = mapping ? callFunction(mapfn, T, kValue, k) : kValue;
// Steps 10.j.v-vi.
targetObj[k] = mappedValue;
}
// Step 10.k.
// asserting that `values` is empty here would require removing them one by one from
// the list's start in the loop above. That would introduce unacceptable overhead.
// Additionally, the loop's logic is simple enough not to require the assert.
// Step 10.l.
return targetObj;
}
// Step 11 is an assertion: items is not an Iterator. Testing this is
// literally the very last thing we did, so we don't assert here.
// Steps 12-13.
var arrayLike = ToObject(items);
// Steps 14-16.
var len = ToLength(arrayLike.length);
// Steps 17-18.
// See comment for steps 10.g-h.
var targetObj = new C(len);
// Steps 19-20.
for (var k = 0; k < len; k++) {
// Steps 20.a-c.
var kValue = arrayLike[k];
// Steps 20.d-e.
var mappedValue = mapping ? callFunction(mapfn, T, kValue, k) : kValue;
// Steps 20.f-g.
targetObj[k] = mappedValue;
}
// Step 21.
return targetObj;
}
// ES6 draft rev30 (2014/12/24) 22.2.2.2 %TypedArray%.of(...items).
function TypedArrayStaticOf(/*...items*/) {
// Step 1.
var len = arguments.length;
// Step 2.
var items = arguments;
// Step 3.
var C = this;
// Steps 4-5.
if (!IsConstructor(C))
ThrowError(JSMSG_NOT_CONSTRUCTOR, typeof C);
var newObj = new C(len);
// Steps 6-7.
for (var k = 0; k < len; k++)
newObj[k] = items[k]
// Step 8.
return newObj;
}

View File

@ -43,7 +43,10 @@ var std_StopIteration = StopIteration;
/* Spec: ECMAScript Language Specification, 5.1 edition, 8.8 */
function List() {}
function List() {
this.length = 0;
}
{
let ListProto = std_Object_create(null);
ListProto.indexOf = std_Array_indexOf;
@ -103,7 +106,50 @@ function ToLength(v) {
return std_Math_min(v, 0x1fffffffffffff);
}
// Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4.
/* Spec: ECMAScript Draft, 6th edition Oct 14, 2014, 7.2.4 */
function SameValueZero(x, y) {
return x === y || (x !== x && y !== y);
}
/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.3.8 */
function GetMethod(O, P) {
// Step 1.
assert(IsPropertyKey(P), "Invalid property key");
// Steps 2-3.
var func = ToObject(O)[P];
// Step 4.
if (func === undefined || func === null)
return undefined;
// Step 5.
if (!IsCallable(func))
ThrowError(JSMSG_NOT_FUNCTION, typeof func);
// Step 6.
return func;
}
/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.2.7 */
function IsPropertyKey(argument) {
var type = typeof argument;
return type === "string" || type === "symbol";
}
/* Spec: ECMAScript Draft, 6th edition Dec 24, 2014, 7.4.1 */
function GetIterator(obj, method) {
// Steps 1-2.
if (arguments.length === 1)
method = GetMethod(obj, std_iterator);
// Steps 3-4.
var iterator = callFunction(method, obj);
// Step 5.
if (!IsObject(iterator))
ThrowError(JSMSG_NOT_ITERABLE, ToString(iterator));
// Step 6.
return iterator;
}

View File

@ -0,0 +1,54 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// 'from' method is identical for all typed array constructors.
assertEq(constructors[0].from === constructor.from, true);
// %TypedArray%.from copies arrays.
var src = new constructor([1, 2, 3]), copy = constructor.from(src);
assertEq(copy === src, false);
assertEq(copy instanceof constructor, true);
assertDeepEq(copy, src);
// Non-element properties are not copied.
var a = new constructor([0, 1]);
a.name = "lisa";
assertDeepEq(constructor.from(a), new constructor([0, 1]));
// %TypedArray%.from can copy non-iterable objects, if they're array-like.
src = {0: 0, 1: 1, length: 2};
copy = constructor.from(src);
assertEq(copy instanceof constructor, true);
assertDeepEq(copy, new constructor([0, 1]));
// Properties past the .length are not copied.
src = {0: "0", 1: "1", 2: "two", 9: "nine", name: "lisa", length: 2};
assertDeepEq(constructor.from(src), new constructor([0, 1]));
// If an object has neither an @@iterator method nor .length,
// then it's treated as zero-length.
assertDeepEq(constructor.from({}), new constructor());
// Primitives will be coerced to primitive wrapper first.
assertDeepEq(constructor.from(1), new constructor());
assertDeepEq(constructor.from("123"), new constructor([1, 2, 3]));
assertDeepEq(constructor.from(true), new constructor());
assertDeepEq(constructor.from(Symbol()), new constructor());
// Source object property order doesn't matter.
src = {length: 2, 1: "1", 0: "0"};
assertDeepEq(constructor.from(src), new constructor([0, 1]));
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,79 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// %TypedArray%.from can be applied to any constructor.
// For example, the Date builtin constructor.
// Unlike Array.from, %TypedArray%.from doesn't set the 'length' property at the end.
var d = constructor.from.call(Date, ["A", "B"]);
assertEq(d instanceof constructor, false);
assertEq(Object.prototype.toString.call(d), "[object Date]");
assertEq(Object.getPrototypeOf(d), Date.prototype);
assertEq(d.length, undefined);
assertEq(d[0], "A");
assertEq(d[1], "B");
// Or RegExp.
var obj = constructor.from.call(RegExp, [1]);
assertEq(obj instanceof constructor, false);
assertEq(Object.getPrototypeOf(obj), RegExp.prototype);
assertEq(Object.getOwnPropertyNames(obj).join(","),
"0,lastIndex,source,global,ignoreCase,multiline,sticky");
assertEq(obj.length, undefined);
// Or any JS function.
function C(arg) {
this.args = arguments;
}
var c = constructor.from.call(C, {length: 1, 0: "zero"});
assertEq(c instanceof C, true);
assertEq(c.args.length, 1);
assertEq(c.args[0], 1);
assertEq(c.length, undefined);
assertEq(c[0], "zero");
// Note %TypedArray%.from(iterable) calls 'this' with an argument whose value is
// `[...iterable].length`, while Array.from(iterable) doesn't pass any argument.
assertEq(constructor.from.call(Object, []) instanceof Number, true);
assertDeepEq(constructor.from.call(Object, []), new Number(0));
assertEq(constructor.from.call(Number,[1, , "a"]) + 1, 4);
constructor.from.call(function(len){
assertEq(len, 3);
}, Array(3));
// If the 'this' value passed to %TypedArray.from is not a constructor,
// then an exception is thrown, while Array.from will use Array as it's constructor.
var arr = [3, 4, 5];
var nonconstructors = [
{}, Math, Object.getPrototypeOf, undefined, 17,
() => ({}) // arrow functions are not constructors
];
for (var v of nonconstructors) {
assertThrowsInstanceOf(() => {
constructor.from.call(v, arr);
}, TypeError);
}
// %TypedArray%.from does not get confused if global constructors for typed arrays
// are replaced with another constructor.
function NotArray() {
}
var RealArray = constructor;
NotArray.from = constructor.from;
this[constructor.name] = NotArray;
assertEq(RealArray.from([1]) instanceof RealArray, true);
assertEq(NotArray.from([1]) instanceof NotArray, true);
this[constructor.name] = RealArray;
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,109 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// %TypedArray%.from throws if the argument is undefined or null.
assertThrowsInstanceOf(() => constructor.from(), TypeError);
assertThrowsInstanceOf(() => constructor.from(undefined), TypeError);
assertThrowsInstanceOf(() => constructor.from(null), TypeError);
// %TypedArray%.from throws if an element can't be defined on the new object.
function ObjectWithReadOnlyElement() {
Object.defineProperty(this, "0", {value: null});
this.length = 0;
}
ObjectWithReadOnlyElement.from = constructor.from;
assertDeepEq(ObjectWithReadOnlyElement.from([]), new ObjectWithReadOnlyElement);
assertThrowsInstanceOf(() => ObjectWithReadOnlyElement.from([1]), TypeError);
// The same, but via preventExtensions.
function InextensibleObject() {
Object.preventExtensions(this);
}
InextensibleObject.from = constructor.from;
assertThrowsInstanceOf(() => InextensibleObject.from([1]), TypeError);
// The same, but via a readonly property on its __proto__.
function ObjectWithReadOnlyElementOnProto() {
return Object.create({
get 0(){}
});
}
ObjectWithReadOnlyElementOnProto.from = constructor.from;
assertThrowsInstanceOf(() => ObjectWithReadOnlyElementOnProto.from([1]), TypeError);
// Unlike Array.from, %TypedArray%.from doesn't get or set the length property.
function ObjectWithThrowingLengthGetterSetter() {
Object.defineProperty(this, "length", {
configurable: true,
get() { throw new RangeError("getter!"); },
set() { throw new RangeError("setter!"); }
});
}
ObjectWithThrowingLengthGetterSetter.from = constructor.from;
assertEq(ObjectWithThrowingLengthGetterSetter.from(["foo"])[0], "foo");
// %TypedArray%.from throws if mapfn is neither callable nor undefined.
assertThrowsInstanceOf(() => constructor.from([3, 4, 5], {}), TypeError);
assertThrowsInstanceOf(() => constructor.from([3, 4, 5], "also not a function"), TypeError);
assertThrowsInstanceOf(() => constructor.from([3, 4, 5], null), TypeError);
// Even if the function would not have been called.
assertThrowsInstanceOf(() => constructor.from([], JSON), TypeError);
// If mapfn is not undefined and not callable, the error happens before anything else.
// Before calling the constructor, before touching the arrayLike.
var log = "";
function C() {
log += "C";
obj = this;
}
var p = new Proxy({}, {
has: function () { log += "1"; },
get: function () { log += "2"; },
getOwnPropertyDescriptor: function () { log += "3"; }
});
assertThrowsInstanceOf(() => constructor.from.call(C, p, {}), TypeError);
assertEq(log, "");
// If mapfn throws, the new object has already been created.
var arrayish = {
get length() { log += "l"; return 1; },
get 0() { log += "0"; return "q"; }
};
log = "";
var exc = {surprise: "ponies"};
assertThrowsValue(() => constructor.from.call(C, arrayish, () => { throw exc; }), exc);
assertEq(log, "lC0");
assertEq(obj instanceof C, true);
// It's a TypeError if the @@iterator property is a primitive (except null and undefined).
for (var primitive of ["foo", 17, Symbol(), true]) {
assertThrowsInstanceOf(() => constructor.from({[Symbol.iterator] : primitive}), TypeError);
}
assertDeepEq(constructor.from({[Symbol.iterator]: null}), new constructor());
assertDeepEq(constructor.from({[Symbol.iterator]: undefined}), new constructor());
// It's a TypeError if the iterator's .next() method returns a primitive.
for (var primitive of [undefined, null, "foo", 17, Symbol(), true]) {
assertThrowsInstanceOf(
() => constructor.from({
[Symbol.iterator]() {
return {next() { return primitive; }};
}
}),
TypeError);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,61 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// %TypedArray%.from works on arguments objects.
(function () {
assertDeepEq(constructor.from(arguments), new constructor(["0", "1", undefined]));
})("0", "1", undefined);
// If an object has both .length and [@@iterator] properties, [@@iterator] is used.
var a = ['0', '1', '2', '3', '4'];
a[Symbol.iterator] = function* () {
for (var i = 5; i--; )
yield this[i];
};
var log = '';
function f(x) {
log += x;
return x + x;
}
var b = constructor.from(a, f);
assertDeepEq(b, new constructor(['44', '33', '22', '11', '00']));
assertEq(log, '43210');
// In fact, if [@@iterator] is present, .length isn't queried at all.
var pa = new Proxy(a, {
has: function (target, id) {
if (id === "length")
throw new Error(".length should not be queried (has)");
return id in target;
},
get: function (target, id) {
if (id === "length")
throw new Error(".length should not be queried (get)");
return target[id];
},
getOwnPropertyDescriptor: function (target, id) {
if (id === "length")
throw new Error(".length should not be queried (getOwnPropertyDescriptor)");
return Object.getOwnPropertyDescriptor(target, id)
}
});
log = "";
b = constructor.from(pa, f);
assertDeepEq(b, new constructor(['44', '33', '22', '11', '00']));
assertEq(log, '43210');
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,56 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// If the mapfn argument to %TypedArray%.from is undefined, don't map.
assertDeepEq(constructor.from([3, 4, 5], undefined), new constructor([3, 4, 5]));
assertDeepEq(constructor.from([4, 5, 6], undefined, Math), new constructor([4, 5, 6]));
// mapfn is called with two arguments: value and index.
var log = [];
function f(...args) {
log.push(args);
return log.length;
}
assertDeepEq(constructor.from(['a', 'e', 'i', 'o', 'u'], f), new constructor([1, 2, 3, 4, 5]));
assertDeepEq(log, [['a', 0], ['e', 1], ['i', 2], ['o', 3], ['u', 4]]);
// If the object to be copied is non-iterable, mapfn is still called with two
// arguments.
log = [];
assertDeepEq(constructor.from({0: "zero", 1: "one", length: 2}, f), new constructor([1, 2]));
assertDeepEq(log, [["zero", 0], ["one", 1]]);
// If the object to be copied is iterable and the constructor is not Array,
// mapfn is still called with two arguments.
log = [];
function C() {}
C.from = constructor.from;
var c = new C;
c[0] = 1;
c[1] = 2;
assertDeepEq(C.from(["zero", "one"], f), c);
assertDeepEq(log, [["zero", 0], ["one", 1]]);
// The mapfn is called even if the value to be mapped is undefined.
assertDeepEq(constructor.from([0, 1, , 3], String), new constructor(["0", "1", "undefined", "3"]));
var arraylike = {length: 4, "0": 0, "1": 1, "3": 3};
assertDeepEq(constructor.from(arraylike, String), new constructor(["0", "1", "undefined", "3"]));
}
// %TypedArray%.from(obj, map) is not exactly the same as %TypedArray%.from(obj).map(mapFn).
assertDeepEq(Int8Array.from([150], v => v / 2), new Int8Array([75]));
// Uncomment the following line when we implement the .map method.
// assertDeepEq(Int8Array.from([150]).map(v => v / 2), new Int8Array([-53]));
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,69 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// Two tests involving %TypedArray%.from and a Proxy.
var log = [];
function LoggingProxy(target) {
log.push("target", target);
var h = {
defineProperty: function (t, id) {
log.push("define", id);
return undefined;
},
has: function (t, id) {
log.push("has", id);
return id in t;
},
get: function (t, id) {
log.push("get", id);
return t[id];
},
set: function (t, id, v) {
log.push("set", id);
t[id] = v;
}
};
return new Proxy(Object(target), h);
}
// Unlike Array.from, %TypedArray%.from uses [[Put]] instead of [[DefineOwnProperty]].
// Hence, it calls handler.set to create new elements rather than handler.defineProperty.
// Additionally, it doesn't set the length property at the end.
LoggingProxy.from = constructor.from;
LoggingProxy.from([3, 4, 5]);
assertDeepEq(log, ["target", 3, "set", "0", "set", "1", "set", "2"]);
// When the argument passed to %TypedArray%.from is a Proxy, %TypedArray%.from
// calls handler.get on it.
log = [];
assertDeepEq(constructor.from(new LoggingProxy([3, 4, 5])), new constructor([3, 4, 5]));
assertDeepEq(log, ["target", [3, 4, 5],
"get", Symbol.iterator,
"get", "length", "get", "0",
"get", "length", "get", "1",
"get", "length", "get", "2",
"get", "length"]);
// Array-like iteration only gets the length once.
log = [];
var arr = [5, 6, 7];
arr[Symbol.iterator] = undefined;
assertDeepEq(constructor.from(new LoggingProxy(arr)), new constructor([5, 6, 7]));
assertDeepEq(log, ["target", [5, 6, 7],
"get", Symbol.iterator,
"get", "length",
"get", "0", "get", "1", "get", "2"]);
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,44 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
if (typeof newGlobal !== 'function')
break;
// G[constructor.name].from, where G is any global, produces an array whose prototype
// is G[constructor.name].prototype.
var g = newGlobal();
var ga = g[constructor.name].from([1, 2, 3]);
assertEq(ga instanceof g[constructor.name], true);
// %TypedArray%.from can be applied to a constructor from another realm.
var p = constructor.from.call(g[constructor.name], [1, 2, 3]);
assertEq(p instanceof g[constructor.name], true);
var q = g[constructor.name].from.call(constructor, [3, 4, 5]);
assertEq(q instanceof constructor, true);
// The default 'this' value received by a non-strict mapping function is
// that function's global, not %TypedArray%.from's global or the caller's global.
var h = newGlobal(), result = undefined;
h.mainGlobal = this;
h.eval("function f() { mainGlobal.result = this; }");
g[constructor.name].from.call(constructor, [5, 6, 7], h.f);
// (Give each global in the test a name, for better error messages. But use
// globalName, because window.name is complicated.)
this.globalName = "main";
g.globalName = "g";
h.globalName = "h";
assertEq(result.globalName, "h");
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,23 @@
// %TypedArray%.from called on Array should also handle strings correctly.
var from = Int8Array.from.bind(Array);
// %TypedArray%.from on a string iterates over the string.
assertDeepEq(from("test string"),
['t', 'e', 's', 't', ' ', 's', 't', 'r', 'i', 'n', 'g']);
// %TypedArray%.from on a string handles surrogate pairs correctly.
var gclef = "\uD834\uDD1E"; // U+1D11E MUSICAL SYMBOL G CLEF
assertDeepEq(from(gclef), [gclef]);
assertDeepEq(from(gclef + " G"), [gclef, " ", "G"]);
// %TypedArray%.from on a string calls the @@iterator method.
String.prototype[Symbol.iterator] = function* () { yield 1; yield 2; };
assertDeepEq(from("anything"), [1, 2]);
// If the iterator method is deleted, Strings are still arraylike.
delete String.prototype[Symbol.iterator];
assertDeepEq(from("works"), ['w', 'o', 'r', 'k', 's']);
assertDeepEq(from(gclef), ['\uD834', '\uDD1E']);
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,24 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// Check superficial features of %TypeArray%.from.
var desc = Object.getOwnPropertyDescriptor(constructor.__proto__, "from");
assertEq(desc.configurable, true);
assertEq(desc.enumerable, false);
assertEq(desc.writable, true);
assertEq(constructor.from.length, 1);
assertThrowsInstanceOf(() => new constructor.from(), TypeError); // not a constructor
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,71 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
// The third argument to %TypedArray%.from is passed as the 'this' value to the
// mapping function.
var hits = 0, obj = {};
function f(x) {
assertEq(this, obj);
hits++;
}
constructor.from(["a", "b", "c"], f, obj);
assertEq(hits, 3);
// Without an argument, undefined is passed...
hits = 0;
function gs(x) {
"use strict";
assertEq(this, undefined);
hits++;
}
constructor.from("def", gs);
assertEq(hits, 3);
// ...and if the mapping function is non-strict, that means the global is
// passed.
var global = this;
hits = 0;
function g(x) {
assertEq(this, global);
hits++;
}
constructor.from("ghi", g);
assertEq(hits, 3);
// A primitive value can be passed.
for (var v of [0, "str", undefined]) {
hits = 0;
var mapfn = function h(x) {
"use strict";
assertEq(this, v);
hits++;
};
constructor.from("pq", mapfn, v);
assertEq(hits, 2);
}
// ...and if the mapping function is non-strict, primitive values will
// be wrapped to objects.
for (var v of [0, "str", true]) {
hits = 0;
var mapfn = function h(x) {
assertDeepEq(this, Object(v));
hits++;
};
constructor.from("pq", mapfn, v);
assertEq(hits, 2);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,100 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
for (var constructor of constructors) {
assertEq(constructor.of.length, 0);
assertDeepEq(Object.getOwnPropertyDescriptor(constructor.__proto__, "of"), {
configurable: true,
enumerable: false,
value: constructor.of,
writable: true
});
// Basic tests.
assertEq(constructor.of().constructor, constructor);
assertEq(constructor.of() instanceof constructor, true);
assertDeepEq(constructor.of(10), new constructor([10]));
assertDeepEq(constructor.of(1, 2, 3), new constructor([1, 2, 3]));
assertDeepEq(constructor.of("1", "2", "3"), new constructor([1, 2, 3]));
// This method can be transplanted to other constructors.
assertDeepEq(constructor.of.call(Array, 1, 2, 3), [1, 2, 3]);
var hits = 0;
assertDeepEq(constructor.of.call(function(len) {
assertEq(arguments.length, 1);
assertEq(len, 3);
hits++;
return {};
}, "a", "b", "c"), {
0: "a",
1: "b",
2: "c"
});
assertEq(hits, 1);
// Behavior across compartments.
if (typeof newGlobal === "function") {
var newC = newGlobal()[constructor.name];
assertEq(newC.of() instanceof newC, true);
assertEq(newC.of() instanceof constructor, false);
assertEq(newC.of.call(constructor) instanceof constructor, true);
}
// Throws if `this` isn't a constructor.
var invalidConstructors = [undefined, null, 1, false, "", Symbol(), [], {}, /./,
constructor.of, () => {}];
invalidConstructors.forEach(C => {
assertThrowsInstanceOf(() => {
constructor.of.call(C);
}, TypeError);
});
// FIXME: Should throw if `this` is a method definition or a getter/setter function, see bug 1059908.
constructor.of.call({method() {}}.method);
constructor.of.call(Object.getOwnPropertyDescriptor({get getter() {}}, "getter").get);
// Generators are also legal constructors.
assertEq(constructor.of.call(function*(len) {
return len;
}, "a", "b", "c").next().value, 3);
// An exception might be thrown in a strict assignment to the new object's indexed properties.
assertThrowsInstanceOf(() => {
constructor.of.call(function() {
return {get 0() {}};
}, "a");
}, TypeError);
assertThrowsInstanceOf(() => {
constructor.of.call(function() {
return Object("1");
}, "a");
}, TypeError);
assertThrowsInstanceOf(() => {
constructor.of.call(function() {
return Object.create({
set 0(v) {
throw new TypeError;
}
});
}, "a");
}, TypeError);
}
assertDeepEq(Float32Array.of(0.1, null, undefined, NaN), new Float32Array([0.1, 0, NaN, NaN]));
assertDeepEq(Float64Array.of(0.1, null, undefined, NaN), new Float64Array([0.1, 0, NaN, NaN]));
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -833,7 +833,8 @@ TypedArrayObject::protoFunctions[] = {
/* static */ const JSFunctionSpec
TypedArrayObject::staticFunctions[] = {
// Coming soon...
JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0),
JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0),
JS_FS_END
};