b23c328f53
%AddElement is not intended for objects which are not arrays, and its behavior may go away with future refactorings. This patch gets rid of it if the receiver of from or of is not the intrinsic Array object. Array.of and Array.from previously papered over failures in calling [[DefineOwnProperty]] when setting array elements. This patch makes them lead to exceptions, and adds tests to assert that the appropriate exceptions are thrown. BUG=v8:4168 R=adamk CC=rossberg,verwaest LOG=Y Review URL: https://codereview.chromium.org/1181623003 Cr-Commit-Position: refs/heads/master@{#28969}
180 lines
6.1 KiB
JavaScript
180 lines
6.1 KiB
JavaScript
// Copyright 2014 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Flags: --harmony-arrays
|
|
(function() {
|
|
|
|
assertEquals(1, Array.from.length);
|
|
|
|
function assertArrayLikeEquals(value, expected, type) {
|
|
assertInstanceof(value, type);
|
|
assertEquals(expected.length, value.length);
|
|
for (var i=0; i<value.length; ++i) {
|
|
assertEquals(expected[i], value[i]);
|
|
}
|
|
}
|
|
|
|
// Assert that constructor is called with "length" for array-like objects
|
|
var myCollectionCalled = false;
|
|
function MyCollection(length) {
|
|
myCollectionCalled = true;
|
|
assertEquals(1, arguments.length);
|
|
assertEquals(5, length);
|
|
}
|
|
|
|
Array.from.call(MyCollection, {length: 5});
|
|
assertTrue(myCollectionCalled);
|
|
|
|
// Assert that calling mapfn with / without thisArg in sloppy and strict modes
|
|
// works as expected.
|
|
var global = this;
|
|
function non_strict(){ assertEquals(global, this); }
|
|
function strict(){ "use strict"; assertEquals(void 0, this); }
|
|
function strict_null(){ "use strict"; assertEquals(null, this); }
|
|
Array.from([1], non_strict);
|
|
Array.from([1], non_strict, void 0);
|
|
Array.from([1], non_strict, null);
|
|
Array.from([1], strict);
|
|
Array.from([1], strict, void 0);
|
|
Array.from([1], strict_null, null);
|
|
|
|
function testArrayFrom(thisArg, constructor) {
|
|
assertArrayLikeEquals(Array.from.call(thisArg, [], undefined), [],
|
|
constructor);
|
|
assertArrayLikeEquals(Array.from.call(thisArg, NaN), [], constructor);
|
|
assertArrayLikeEquals(Array.from.call(thisArg, Infinity), [], constructor);
|
|
assertArrayLikeEquals(Array.from.call(thisArg, 10000000), [], constructor);
|
|
assertArrayLikeEquals(Array.from.call(thisArg, 'test'), ['t', 'e', 's', 't'],
|
|
constructor);
|
|
|
|
assertArrayLikeEquals(Array.from.call(thisArg,
|
|
{ length: 1, '0': { 'foo': 'bar' } }), [{'foo': 'bar'}], constructor);
|
|
|
|
assertArrayLikeEquals(Array.from.call(thisArg,
|
|
{ length: -1, '0': { 'foo': 'bar' } }), [], constructor);
|
|
|
|
assertArrayLikeEquals(Array.from.call(thisArg,
|
|
[ 'foo', 'bar', 'baz' ]), ['foo', 'bar', 'baz'], constructor);
|
|
|
|
var kSet = new Set(['foo', 'bar', 'baz']);
|
|
assertArrayLikeEquals(Array.from.call(thisArg, kSet), ['foo', 'bar', 'baz'],
|
|
constructor);
|
|
|
|
var kMap = new Map(['foo', 'bar', 'baz'].entries());
|
|
assertArrayLikeEquals(Array.from.call(thisArg, kMap),
|
|
[[0, 'foo'], [1, 'bar'], [2, 'baz']], constructor);
|
|
|
|
|
|
function* generator() {
|
|
yield 'a';
|
|
yield 'b';
|
|
yield 'c';
|
|
}
|
|
|
|
assertArrayLikeEquals(Array.from.call(thisArg, generator()),
|
|
['a', 'b', 'c'], constructor);
|
|
|
|
// Mozilla:
|
|
// Array.from on a string handles surrogate pairs correctly.
|
|
var gclef = "\uD834\uDD1E"; // U+1D11E MUSICAL SYMBOL G CLEF
|
|
assertArrayLikeEquals(Array.from.call(thisArg, gclef), [gclef], constructor);
|
|
assertArrayLikeEquals(Array.from.call(thisArg, gclef + " G"),
|
|
[gclef, " ", "G"], constructor);
|
|
|
|
assertArrayLikeEquals(Array.from.call(thisArg, 'test', function(x) {
|
|
return this.filter(x);
|
|
}, {
|
|
filter: function(x) { return x.toUpperCase(); }
|
|
}), ['T', 'E', 'S', 'T'], constructor);
|
|
assertArrayLikeEquals(Array.from.call(thisArg, 'test', function(x) {
|
|
return x.toUpperCase();
|
|
}), ['T', 'E', 'S', 'T'], constructor);
|
|
|
|
assertThrows(function() { Array.from.call(thisArg, null); }, TypeError);
|
|
assertThrows(function() { Array.from.call(thisArg, undefined); }, TypeError);
|
|
assertThrows(function() { Array.from.call(thisArg, [], null); }, TypeError);
|
|
assertThrows(function() { Array.from.call(thisArg, [], "noncallable"); },
|
|
TypeError);
|
|
|
|
var nullIterator = {};
|
|
nullIterator[Symbol.iterator] = null;
|
|
assertArrayLikeEquals(Array.from.call(thisArg, nullIterator), [],
|
|
constructor);
|
|
|
|
var nonObjIterator = {};
|
|
nonObjIterator[Symbol.iterator] = function() { return "nonObject"; };
|
|
assertThrows(function() { Array.from.call(thisArg, nonObjIterator); },
|
|
TypeError);
|
|
|
|
assertThrows(function() { Array.from.call(thisArg, [], null); }, TypeError);
|
|
|
|
// Ensure iterator is only accessed once, and only invoked once
|
|
var called = false;
|
|
var arr = [1, 2, 3];
|
|
var obj = {};
|
|
|
|
// Test order --- only get iterator method once
|
|
function testIterator() {
|
|
assertFalse(called, "@@iterator should be called only once");
|
|
called = true;
|
|
assertEquals(obj, this);
|
|
return arr[Symbol.iterator]();
|
|
}
|
|
var getCalled = false;
|
|
Object.defineProperty(obj, Symbol.iterator, {
|
|
get: function() {
|
|
assertFalse(getCalled, "@@iterator should be accessed only once");
|
|
getCalled = true;
|
|
return testIterator;
|
|
},
|
|
set: function() {
|
|
assertUnreachable("@@iterator should not be set");
|
|
}
|
|
});
|
|
assertArrayLikeEquals(Array.from.call(thisArg, obj), [1, 2, 3], constructor);
|
|
}
|
|
|
|
function Other() {}
|
|
|
|
var boundFn = (function() {}).bind(Array, 27);
|
|
|
|
testArrayFrom(Array, Array);
|
|
testArrayFrom(null, Array);
|
|
testArrayFrom({}, Array);
|
|
testArrayFrom(Object, Object);
|
|
testArrayFrom(Other, Other);
|
|
testArrayFrom(Math.cos, Array);
|
|
testArrayFrom(Math.cos.bind(Math), Array);
|
|
testArrayFrom(boundFn, boundFn);
|
|
|
|
// Assert that [[DefineOwnProperty]] is used in ArrayFrom, meaning a
|
|
// setter isn't called, and a failed [[DefineOwnProperty]] will throw.
|
|
var setterCalled = 0;
|
|
function exotic() {
|
|
Object.defineProperty(this, '0', {
|
|
get: function() { return 2; },
|
|
set: function() { setterCalled++; }
|
|
});
|
|
}
|
|
// Non-configurable properties can't be overwritten with DefineOwnProperty
|
|
assertThrows(function () { Array.from.call(exotic, [1]); }, TypeError);
|
|
// The setter wasn't called
|
|
assertEquals(0, setterCalled);
|
|
|
|
// Check that array properties defined are writable, enumerable, configurable
|
|
function ordinary() { }
|
|
var x = Array.from.call(ordinary, [2]);
|
|
var xlength = Object.getOwnPropertyDescriptor(x, 'length');
|
|
assertEquals(1, xlength.value);
|
|
assertEquals(true, xlength.writable);
|
|
assertEquals(true, xlength.enumerable);
|
|
assertEquals(true, xlength.configurable);
|
|
var x0 = Object.getOwnPropertyDescriptor(x, 0);
|
|
assertEquals(2, x0.value);
|
|
assertEquals(true, xlength.writable);
|
|
assertEquals(true, xlength.enumerable);
|
|
assertEquals(true, xlength.configurable);
|
|
|
|
})();
|