Implement ES6 @@isConcatSpreadable / Array.prototype.concat
Add support for Symbol.isConcatSpreadable in Array.prototype.concat. This enables spreading non-Array objects with the symbol. LOG=N R=dslomov@chromium.org BUG= Review URL: https://codereview.chromium.org/771483002 Cr-Commit-Position: refs/heads/master@{#25808}
This commit is contained in:
parent
10b38df268
commit
48054170e9
@ -192,6 +192,17 @@ function ArrayOf() {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
function HarmonyArrayExtendSymbolPrototype() {
|
||||
%CheckIsBootstrapping();
|
||||
|
||||
InstallConstants($Symbol, $Array(
|
||||
// TODO(dslomov, caitp): Move to symbol.js when shipping
|
||||
"isConcatSpreadable", symbolIsConcatSpreadable
|
||||
));
|
||||
}
|
||||
|
||||
HarmonyArrayExtendSymbolPrototype();
|
||||
|
||||
function HarmonyArrayExtendArrayPrototype() {
|
||||
%CheckIsBootstrapping();
|
||||
|
||||
|
@ -626,6 +626,15 @@ function IsPrimitive(x) {
|
||||
}
|
||||
|
||||
|
||||
// ES6, draft 10-14-14, section 22.1.3.1.1
|
||||
function IsConcatSpreadable(O) {
|
||||
if (!IS_SPEC_OBJECT(O)) return false;
|
||||
var spreadable = O[symbolIsConcatSpreadable];
|
||||
if (IS_UNDEFINED(spreadable)) return IS_ARRAY(O);
|
||||
return ToBoolean(spreadable);
|
||||
}
|
||||
|
||||
|
||||
// ECMA-262, section 8.6.2.6, page 28.
|
||||
function DefaultNumber(x) {
|
||||
if (!IS_SYMBOL_WRAPPER(x)) {
|
||||
|
@ -289,11 +289,11 @@ static uint32_t EstimateElementCount(Handle<JSArray> array) {
|
||||
|
||||
|
||||
template <class ExternalArrayClass, class ElementType>
|
||||
static void IterateExternalArrayElements(Isolate* isolate,
|
||||
Handle<JSObject> receiver,
|
||||
bool elements_are_ints,
|
||||
bool elements_are_guaranteed_smis,
|
||||
ArrayConcatVisitor* visitor) {
|
||||
static void IterateTypedArrayElements(Isolate* isolate,
|
||||
Handle<JSObject> receiver,
|
||||
bool elements_are_ints,
|
||||
bool elements_are_guaranteed_smis,
|
||||
ArrayConcatVisitor* visitor) {
|
||||
Handle<ExternalArrayClass> array(
|
||||
ExternalArrayClass::cast(receiver->elements()));
|
||||
uint32_t len = static_cast<uint32_t>(array->length());
|
||||
@ -440,7 +440,7 @@ static void CollectElementIndices(Handle<JSObject> object, uint32_t range,
|
||||
|
||||
|
||||
/**
|
||||
* A helper function that visits elements of a JSArray in numerical
|
||||
* A helper function that visits elements of a JSObject in numerical
|
||||
* order.
|
||||
*
|
||||
* The visitor argument called for each existing element in the array
|
||||
@ -449,9 +449,22 @@ static void CollectElementIndices(Handle<JSObject> object, uint32_t range,
|
||||
* length.
|
||||
* Returns false if any access threw an exception, otherwise true.
|
||||
*/
|
||||
static bool IterateElements(Isolate* isolate, Handle<JSArray> receiver,
|
||||
static bool IterateElements(Isolate* isolate, Handle<JSObject> receiver,
|
||||
ArrayConcatVisitor* visitor) {
|
||||
uint32_t length = static_cast<uint32_t>(receiver->length()->Number());
|
||||
uint32_t length = 0;
|
||||
|
||||
if (receiver->IsJSArray()) {
|
||||
Handle<JSArray> array(Handle<JSArray>::cast(receiver));
|
||||
length = static_cast<uint32_t>(array->length()->Number());
|
||||
} else {
|
||||
Handle<Object> val;
|
||||
Handle<Object> key(isolate->heap()->length_string(), isolate);
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, val,
|
||||
Runtime::GetObjectProperty(isolate, receiver, key), false);
|
||||
// TODO(caitp): implement ToLength() abstract operation for C++
|
||||
val->ToUint32(&length);
|
||||
}
|
||||
|
||||
switch (receiver->GetElementsKind()) {
|
||||
case FAST_SMI_ELEMENTS:
|
||||
case FAST_ELEMENTS:
|
||||
@ -552,55 +565,132 @@ static bool IterateElements(Isolate* isolate, Handle<JSArray> receiver,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UINT8_CLAMPED_ELEMENTS: {
|
||||
Handle<FixedUint8ClampedArray> pixels(
|
||||
FixedUint8ClampedArray::cast(receiver->elements()));
|
||||
for (uint32_t j = 0; j < length; j++) {
|
||||
Handle<Smi> e(Smi::FromInt(pixels->get_scalar(j)), isolate);
|
||||
visitor->visit(j, e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_INT8_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalInt8Array, int8_t>(
|
||||
IterateTypedArrayElements<ExternalInt8Array, int8_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case INT8_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedInt8Array, int8_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_UINT8_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalUint8Array, uint8_t>(
|
||||
IterateTypedArrayElements<ExternalUint8Array, uint8_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case UINT8_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedUint8Array, uint8_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_INT16_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalInt16Array, int16_t>(
|
||||
IterateTypedArrayElements<ExternalInt16Array, int16_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case INT16_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedInt16Array, int16_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_UINT16_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalUint16Array, uint16_t>(
|
||||
IterateTypedArrayElements<ExternalUint16Array, uint16_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case UINT16_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedUint16Array, uint16_t>(
|
||||
isolate, receiver, true, true, visitor);
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_INT32_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalInt32Array, int32_t>(
|
||||
IterateTypedArrayElements<ExternalInt32Array, int32_t>(
|
||||
isolate, receiver, true, false, visitor);
|
||||
break;
|
||||
}
|
||||
case INT32_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedInt32Array, int32_t>(
|
||||
isolate, receiver, true, false, visitor);
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_UINT32_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalUint32Array, uint32_t>(
|
||||
IterateTypedArrayElements<ExternalUint32Array, uint32_t>(
|
||||
isolate, receiver, true, false, visitor);
|
||||
break;
|
||||
}
|
||||
case UINT32_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedUint32Array, uint32_t>(
|
||||
isolate, receiver, true, false, visitor);
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_FLOAT32_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalFloat32Array, float>(
|
||||
IterateTypedArrayElements<ExternalFloat32Array, float>(
|
||||
isolate, receiver, false, false, visitor);
|
||||
break;
|
||||
}
|
||||
case FLOAT32_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedFloat32Array, float>(
|
||||
isolate, receiver, false, false, visitor);
|
||||
break;
|
||||
}
|
||||
case EXTERNAL_FLOAT64_ELEMENTS: {
|
||||
IterateExternalArrayElements<ExternalFloat64Array, double>(
|
||||
IterateTypedArrayElements<ExternalFloat64Array, double>(
|
||||
isolate, receiver, false, false, visitor);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
case FLOAT64_ELEMENTS: {
|
||||
IterateTypedArrayElements<FixedFloat64Array, double>(
|
||||
isolate, receiver, false, false, visitor);
|
||||
break;
|
||||
}
|
||||
case SLOPPY_ARGUMENTS_ELEMENTS: {
|
||||
ElementsAccessor* accessor = receiver->GetElementsAccessor();
|
||||
for (uint32_t index = 0; index < length; index++) {
|
||||
HandleScope loop_scope(isolate);
|
||||
if (accessor->HasElement(receiver, receiver, index)) {
|
||||
Handle<Object> element;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate, element, accessor->Get(receiver, receiver, index),
|
||||
false);
|
||||
visitor->visit(index, element);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
visitor->increase_index_offset(length);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool IsConcatSpreadable(Isolate* isolate, Handle<Object> obj) {
|
||||
HandleScope handle_scope(isolate);
|
||||
if (!obj->IsSpecObject()) return false;
|
||||
if (obj->IsJSArray()) return true;
|
||||
if (FLAG_harmony_arrays) {
|
||||
Handle<Symbol> key(isolate->factory()->is_concat_spreadable_symbol());
|
||||
Handle<Object> value;
|
||||
MaybeHandle<Object> maybeValue =
|
||||
i::Runtime::GetObjectProperty(isolate, obj, key);
|
||||
if (maybeValue.ToHandle(&value)) {
|
||||
return value->BooleanValue();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Array::concat implementation.
|
||||
* See ECMAScript 262, 15.4.4.4.
|
||||
@ -771,9 +861,11 @@ RUNTIME_FUNCTION(Runtime_ArrayConcat) {
|
||||
|
||||
for (int i = 0; i < argument_count; i++) {
|
||||
Handle<Object> obj(elements->get(i), isolate);
|
||||
if (obj->IsJSArray()) {
|
||||
Handle<JSArray> array = Handle<JSArray>::cast(obj);
|
||||
if (!IterateElements(isolate, array, &visitor)) {
|
||||
bool spreadable = IsConcatSpreadable(isolate, obj);
|
||||
if (isolate->has_pending_exception()) return isolate->heap()->exception();
|
||||
if (spreadable) {
|
||||
Handle<JSObject> object = Handle<JSObject>::cast(obj);
|
||||
if (!IterateElements(isolate, object, &visitor)) {
|
||||
return isolate->heap()->exception();
|
||||
}
|
||||
} else {
|
||||
|
413
test/mjsunit/harmony/array-concat.js
Normal file
413
test/mjsunit/harmony/array-concat.js
Normal file
@ -0,0 +1,413 @@
|
||||
// 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 --harmony-classes
|
||||
|
||||
(function testArrayConcatArity() {
|
||||
"use strict";
|
||||
assertEquals(1, Array.prototype.concat.length);
|
||||
})();
|
||||
|
||||
|
||||
(function testArrayConcatNoPrototype() {
|
||||
"use strict";
|
||||
assertEquals(void 0, Array.prototype.concat.prototype);
|
||||
})();
|
||||
|
||||
|
||||
(function testArrayConcatDescriptor() {
|
||||
"use strict";
|
||||
var desc = Object.getOwnPropertyDescriptor(Array.prototype, 'concat');
|
||||
assertEquals(false, desc.enumerable);
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatArrayLike() {
|
||||
"use strict";
|
||||
var obj = {
|
||||
"length": 6,
|
||||
"1": "A",
|
||||
"3": "B",
|
||||
"5": "C"
|
||||
};
|
||||
obj[Symbol.isConcatSpreadable] = true;
|
||||
var obj2 = { length: 3, "0": "0", "1": "1", "2": "2" };
|
||||
var arr = ["X", "Y", "Z"];
|
||||
assertEquals([void 0, "A", void 0, "B", void 0, "C",
|
||||
{ "length": 3, "0": "0", "1": "1", "2": "2" },
|
||||
"X", "Y", "Z"], Array.prototype.concat.call(obj, obj2, arr));
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatHoleyArray() {
|
||||
"use strict";
|
||||
var arr = [];
|
||||
arr[4] = "Item 4";
|
||||
arr[8] = "Item 8";
|
||||
var arr2 = [".", "!", "?"];
|
||||
assertEquals([void 0, void 0, void 0, void 0, "Item 4", void 0, void 0,
|
||||
void 0, "Item 8", ".", "!", "?"], arr.concat(arr2));
|
||||
})();
|
||||
|
||||
|
||||
(function testIsConcatSpreadableGetterThrows() {
|
||||
"use strict";
|
||||
function MyError() {}
|
||||
var obj = {};
|
||||
Object.defineProperty(obj, Symbol.isConcatSpreadable, {
|
||||
get: function() { throw new MyError(); }
|
||||
});
|
||||
|
||||
assertThrows(function() {
|
||||
[].concat(obj);
|
||||
}, MyError);
|
||||
|
||||
assertThrows(function() {
|
||||
Array.prototype.concat.call(obj, 1, 2, 3);
|
||||
}, MyError);
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatLengthThrows() {
|
||||
"use strict";
|
||||
function MyError() {}
|
||||
var obj = {};
|
||||
obj[Symbol.isConcatSpreadable] = true;
|
||||
Object.defineProperty(obj, "length", {
|
||||
get: function() { throw new MyError(); }
|
||||
});
|
||||
|
||||
assertThrows(function() {
|
||||
[].concat(obj);
|
||||
}, MyError);
|
||||
|
||||
assertThrows(function() {
|
||||
Array.prototype.concat.call(obj, 1, 2, 3);
|
||||
}, MyError);
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatArraySubclass() {
|
||||
"use strict";
|
||||
// TODO(caitp): when concat is called on instances of classes which extend
|
||||
// Array, they should:
|
||||
//
|
||||
// - return an instance of the class, rather than an Array instance (if from
|
||||
// same Realm)
|
||||
// - always treat such classes as concat-spreadable
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatNonArray() {
|
||||
"use strict";
|
||||
class NonArray {
|
||||
constructor() { Array.apply(this, arguments); }
|
||||
};
|
||||
|
||||
var obj = new NonArray(1,2,3);
|
||||
var result = Array.prototype.concat.call(obj, 4, 5, 6);
|
||||
assertEquals(Array, result.constructor);
|
||||
assertEquals([obj,4,5,6], result);
|
||||
assertFalse(result instanceof NonArray);
|
||||
})();
|
||||
|
||||
|
||||
function testConcatTypedArray(type, elems, modulo) {
|
||||
"use strict";
|
||||
var items = new Array(elems);
|
||||
var ta_by_len = new type(elems);
|
||||
for (var i = 0; i < elems; ++i) {
|
||||
ta_by_len[i] = items[i] = modulo === false ? i : elems % modulo;
|
||||
}
|
||||
var ta = new type(items);
|
||||
assertEquals([ta, ta], [].concat(ta, ta));
|
||||
ta[Symbol.isConcatSpreadable] = true;
|
||||
assertEquals(items, [].concat(ta));
|
||||
|
||||
assertEquals([ta_by_len, ta_by_len], [].concat(ta_by_len, ta_by_len));
|
||||
ta_by_len[Symbol.isConcatSpreadable] = true;
|
||||
assertEquals(items, [].concat(ta_by_len));
|
||||
}
|
||||
|
||||
(function testConcatSmallTypedArray() {
|
||||
var max = [2^8, 2^16, 2^32, false, false];
|
||||
[
|
||||
Uint8Array,
|
||||
Uint16Array,
|
||||
Uint32Array,
|
||||
Float32Array,
|
||||
Float64Array
|
||||
].forEach(function(ctor, i) {
|
||||
testConcatTypedArray(ctor, 1, max[i]);
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatLargeTypedArray() {
|
||||
var max = [2^8, 2^16, 2^32, false, false];
|
||||
[
|
||||
Uint8Array,
|
||||
Uint16Array,
|
||||
Uint32Array,
|
||||
Float32Array,
|
||||
Float64Array
|
||||
].forEach(function(ctor, i) {
|
||||
testConcatTypedArray(ctor, 4000, max[i]);
|
||||
});
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatStrictArguments() {
|
||||
var args = (function(a, b, c) { "use strict"; return arguments; })(1,2,3);
|
||||
args[Symbol.isConcatSpreadable] = true;
|
||||
assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args));
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatSloppyArguments() {
|
||||
var args = (function(a, b, c) { return arguments; })(1,2,3);
|
||||
args[Symbol.isConcatSpreadable] = true;
|
||||
assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args));
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatSloppyArgumentsWithDupes() {
|
||||
var args = (function(a, a, a) { return arguments; })(1,2,3);
|
||||
args[Symbol.isConcatSpreadable] = true;
|
||||
assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args));
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatSloppyArgumentsThrows() {
|
||||
function MyError() {}
|
||||
var args = (function(a) { return arguments; })(1,2,3);
|
||||
Object.defineProperty(args, 0, {
|
||||
get: function() { throw new MyError(); }
|
||||
});
|
||||
args[Symbol.isConcatSpreadable] = true;
|
||||
assertThrows(function() {
|
||||
return [].concat(args, args);
|
||||
}, MyError);
|
||||
})();
|
||||
|
||||
|
||||
(function testConcatHoleySloppyArguments() {
|
||||
var args = (function(a) { return arguments; })(1,2,3);
|
||||
delete args[1];
|
||||
args[Symbol.isConcatSpreadable] = true;
|
||||
assertEquals([1, void 0, 3, 1, void 0, 3], [].concat(args, args));
|
||||
})();
|
||||
|
||||
|
||||
// ES5 tests
|
||||
(function testArrayConcatES5() {
|
||||
"use strict";
|
||||
var poses;
|
||||
var pos;
|
||||
|
||||
poses = [140, 4000000000];
|
||||
while (pos = poses.shift()) {
|
||||
var a = new Array(pos);
|
||||
var array_proto = [];
|
||||
a.__proto__ = array_proto;
|
||||
assertEquals(pos, a.length);
|
||||
a.push('foo');
|
||||
assertEquals(pos + 1, a.length);
|
||||
var b = ['bar'];
|
||||
var c = a.concat(b);
|
||||
assertEquals(pos + 2, c.length);
|
||||
assertEquals("undefined", typeof(c[pos - 1]));
|
||||
assertEquals("foo", c[pos]);
|
||||
assertEquals("bar", c[pos + 1]);
|
||||
|
||||
// Can we fool the system by putting a number in a string?
|
||||
var onetwofour = "124";
|
||||
a[onetwofour] = 'doo';
|
||||
assertEquals(a[124], 'doo');
|
||||
c = a.concat(b);
|
||||
assertEquals(c[124], 'doo');
|
||||
|
||||
// If we put a number in the prototype, then the spec says it should be
|
||||
// copied on concat.
|
||||
array_proto["123"] = 'baz';
|
||||
assertEquals(a[123], 'baz');
|
||||
|
||||
c = a.concat(b);
|
||||
assertEquals(pos + 2, c.length);
|
||||
assertEquals("baz", c[123]);
|
||||
assertEquals("undefined", typeof(c[pos - 1]));
|
||||
assertEquals("foo", c[pos]);
|
||||
assertEquals("bar", c[pos + 1]);
|
||||
|
||||
// When we take the number off the prototype it disappears from a, but
|
||||
// the concat put it in c itself.
|
||||
array_proto["123"] = undefined;
|
||||
assertEquals("undefined", typeof(a[123]));
|
||||
assertEquals("baz", c[123]);
|
||||
|
||||
// If the element of prototype is shadowed, the element on the instance
|
||||
// should be copied, but not the one on the prototype.
|
||||
array_proto[123] = 'baz';
|
||||
a[123] = 'xyz';
|
||||
assertEquals('xyz', a[123]);
|
||||
c = a.concat(b);
|
||||
assertEquals('xyz', c[123]);
|
||||
|
||||
// Non-numeric properties on the prototype or the array shouldn't get
|
||||
// copied.
|
||||
array_proto.moe = 'joe';
|
||||
a.ben = 'jerry';
|
||||
assertEquals(a["moe"], 'joe');
|
||||
assertEquals(a["ben"], 'jerry');
|
||||
c = a.concat(b);
|
||||
// ben was not copied
|
||||
assertEquals("undefined", typeof(c.ben));
|
||||
|
||||
// When we take moe off the prototype it disappears from all arrays.
|
||||
array_proto.moe = undefined;
|
||||
assertEquals("undefined", typeof(c.moe));
|
||||
|
||||
// Negative indices don't get concated.
|
||||
a[-1] = 'minus1';
|
||||
assertEquals("minus1", a[-1]);
|
||||
assertEquals("undefined", typeof(a[0xffffffff]));
|
||||
c = a.concat(b);
|
||||
assertEquals("undefined", typeof(c[-1]));
|
||||
assertEquals("undefined", typeof(c[0xffffffff]));
|
||||
assertEquals(c.length, a.length + 1);
|
||||
}
|
||||
|
||||
poses = [140, 4000000000];
|
||||
while (pos = poses.shift()) {
|
||||
var a = new Array(pos);
|
||||
assertEquals(pos, a.length);
|
||||
a.push('foo');
|
||||
assertEquals(pos + 1, a.length);
|
||||
var b = ['bar'];
|
||||
var c = a.concat(b);
|
||||
assertEquals(pos + 2, c.length);
|
||||
assertEquals("undefined", typeof(c[pos - 1]));
|
||||
assertEquals("foo", c[pos]);
|
||||
assertEquals("bar", c[pos + 1]);
|
||||
|
||||
// Can we fool the system by putting a number in a string?
|
||||
var onetwofour = "124";
|
||||
a[onetwofour] = 'doo';
|
||||
assertEquals(a[124], 'doo');
|
||||
c = a.concat(b);
|
||||
assertEquals(c[124], 'doo');
|
||||
|
||||
// If we put a number in the prototype, then the spec says it should be
|
||||
// copied on concat.
|
||||
Array.prototype["123"] = 'baz';
|
||||
assertEquals(a[123], 'baz');
|
||||
|
||||
c = a.concat(b);
|
||||
assertEquals(pos + 2, c.length);
|
||||
assertEquals("baz", c[123]);
|
||||
assertEquals("undefined", typeof(c[pos - 1]));
|
||||
assertEquals("foo", c[pos]);
|
||||
assertEquals("bar", c[pos + 1]);
|
||||
|
||||
// When we take the number off the prototype it disappears from a, but
|
||||
// the concat put it in c itself.
|
||||
Array.prototype["123"] = undefined;
|
||||
assertEquals("undefined", typeof(a[123]));
|
||||
assertEquals("baz", c[123]);
|
||||
|
||||
// If the element of prototype is shadowed, the element on the instance
|
||||
// should be copied, but not the one on the prototype.
|
||||
Array.prototype[123] = 'baz';
|
||||
a[123] = 'xyz';
|
||||
assertEquals('xyz', a[123]);
|
||||
c = a.concat(b);
|
||||
assertEquals('xyz', c[123]);
|
||||
|
||||
// Non-numeric properties on the prototype or the array shouldn't get
|
||||
// copied.
|
||||
Array.prototype.moe = 'joe';
|
||||
a.ben = 'jerry';
|
||||
assertEquals(a["moe"], 'joe');
|
||||
assertEquals(a["ben"], 'jerry');
|
||||
c = a.concat(b);
|
||||
// ben was not copied
|
||||
assertEquals("undefined", typeof(c.ben));
|
||||
// moe was not copied, but we can see it through the prototype
|
||||
assertEquals("joe", c.moe);
|
||||
|
||||
// When we take moe off the prototype it disappears from all arrays.
|
||||
Array.prototype.moe = undefined;
|
||||
assertEquals("undefined", typeof(c.moe));
|
||||
|
||||
// Negative indices don't get concated.
|
||||
a[-1] = 'minus1';
|
||||
assertEquals("minus1", a[-1]);
|
||||
assertEquals("undefined", typeof(a[0xffffffff]));
|
||||
c = a.concat(b);
|
||||
assertEquals("undefined", typeof(c[-1]));
|
||||
assertEquals("undefined", typeof(c[0xffffffff]));
|
||||
assertEquals(c.length, a.length + 1);
|
||||
|
||||
}
|
||||
|
||||
a = [];
|
||||
c = a.concat('Hello');
|
||||
assertEquals(1, c.length);
|
||||
assertEquals("Hello", c[0]);
|
||||
assertEquals("Hello", c.toString());
|
||||
|
||||
// Check that concat preserves holes.
|
||||
var holey = [void 0,'a',,'c'].concat(['d',,'f',[0,,2],void 0])
|
||||
assertEquals(9, holey.length); // hole in embedded array is ignored
|
||||
for (var i = 0; i < holey.length; i++) {
|
||||
if (i == 2 || i == 5) {
|
||||
assertFalse(i in holey);
|
||||
} else {
|
||||
assertTrue(i in holey);
|
||||
}
|
||||
}
|
||||
|
||||
// Polluted prototype from prior tests.
|
||||
delete Array.prototype[123];
|
||||
|
||||
// Check that concat reads getters in the correct order.
|
||||
var arr1 = [,2];
|
||||
var arr2 = [1,3];
|
||||
var r1 = [].concat(arr1, arr2); // [,2,1,3]
|
||||
assertEquals([,2,1,3], r1);
|
||||
|
||||
// Make first array change length of second array.
|
||||
Object.defineProperty(arr1, 0, {get: function() {
|
||||
arr2.push("X");
|
||||
return undefined;
|
||||
}, configurable: true})
|
||||
var r2 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"]
|
||||
assertEquals([undefined,2,1,3,"X"], r2);
|
||||
|
||||
// Make first array change length of second array massively.
|
||||
arr2.length = 2;
|
||||
Object.defineProperty(arr1, 0, {get: function() {
|
||||
arr2[500000] = "X";
|
||||
return undefined;
|
||||
}, configurable: true})
|
||||
var r3 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"]
|
||||
var expected = [undefined,2,1,3];
|
||||
expected[500000 + 2] = "X";
|
||||
|
||||
assertEquals(expected, r3);
|
||||
|
||||
var arr3 = [];
|
||||
var trace = [];
|
||||
var expectedTrace = []
|
||||
function mkGetter(i) { return function() { trace.push(i); }; }
|
||||
arr3.length = 10000;
|
||||
for (var i = 0; i < 100; i++) {
|
||||
Object.defineProperty(arr3, i * i, {get: mkGetter(i)});
|
||||
expectedTrace[i] = i;
|
||||
expectedTrace[100 + i] = i;
|
||||
}
|
||||
var r4 = [0].concat(arr3, arr3);
|
||||
assertEquals(1 + arr3.length * 2, r4.length);
|
||||
assertEquals(expectedTrace, trace);
|
||||
})();
|
Loading…
Reference in New Issue
Block a user