diff --git a/src/harmony-array.js b/src/harmony-array.js index 18cda170549..934d3b77c7f 100644 --- a/src/harmony-array.js +++ b/src/harmony-array.js @@ -13,6 +13,59 @@ var GlobalSymbol = global.Symbol; // ------------------------------------------------------------------- +// ES6 draft 03-17-15, section 22.1.3.3 +function ArrayCopyWithin(target, start, end) { + CHECK_OBJECT_COERCIBLE(this, "Array.prototype.copyWithin"); + + var array = TO_OBJECT_INLINE(this); + var length = ToLength(array.length); + + target = TO_INTEGER(target); + var to; + if (target < 0) { + to = $max(length + target, 0); + } else { + to = $min(target, length); + } + + start = TO_INTEGER(start); + var from; + if (start < 0) { + from = $max(length + start, 0); + } else { + from = $min(start, length); + } + + end = IS_UNDEFINED(end) ? length : TO_INTEGER(end); + var final; + if (end < 0) { + final = $max(length + end, 0); + } else { + final = $min(end, length); + } + + var count = $min(final - from, length - to); + var direction = 1; + if (from < to && to < (from + count)) { + direction = -1; + from = from + count - 1; + to = to + count - 1; + } + + while (count > 0) { + if (from in array) { + array[to] = array[from]; + } else { + delete array[to]; + } + from = from + direction; + to = to + direction; + count--; + } + + return array; +} + // ES6 draft 07-15-13, section 15.4.3.23 function ArrayFind(predicate /* thisArg */) { // length == 1 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.find"); @@ -216,6 +269,7 @@ InstallConstants(GlobalSymbol, [ "isConcatSpreadable", symbolIsConcatSpreadable ]); +%FunctionSetLength(ArrayCopyWithin, 2); %FunctionSetLength(ArrayFrom, 1); // Set up non-enumerable functions on the Array object. @@ -226,6 +280,7 @@ InstallFunctions(GlobalArray, DONT_ENUM, [ // Set up the non-enumerable functions on the Array prototype object. InstallFunctions(GlobalArray.prototype, DONT_ENUM, [ + "copyWithin", ArrayCopyWithin, "find", ArrayFind, "findIndex", ArrayFindIndex, "fill", ArrayFill diff --git a/test/mjsunit/harmony/array-copywithin.js b/test/mjsunit/harmony/array-copywithin.js new file mode 100644 index 00000000000..c3a0c14663b --- /dev/null +++ b/test/mjsunit/harmony/array-copywithin.js @@ -0,0 +1,338 @@ +// 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 copyWithinArity() { + assertEquals(Array.prototype.copyWithin.length, 2); +})(); + + +(function copyWithinTargetAndStart() { + // works with two arguemnts + assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3)); + assertArrayEquals([1, 4, 5, 4, 5], [1, 2, 3, 4, 5].copyWithin(1, 3)); + assertArrayEquals([1, 3, 4, 5, 5], [1, 2, 3, 4, 5].copyWithin(1, 2)); + assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(2, 2)); +})(); + + +(function copyWithinTargetStartAndEnd() { + // works with three arguments + assertArrayEquals([1, 2, 3, 4, 5].copyWithin(0, 3, 4), [4, 2, 3, 4, 5]); + assertArrayEquals([1, 2, 3, 4, 5].copyWithin(1, 3, 4), [1, 4, 3, 4, 5]); + assertArrayEquals([1, 2, 3, 4, 5].copyWithin(1, 2, 4), [1, 3, 4, 4, 5]); +})(); + + +(function copyWithinNegativeRelativeOffsets() { + // works with negative arguments + assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, -2)); + assertArrayEquals([4, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, -2, -1)); + assertArrayEquals([1, 3, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-4, -3, -2)); + assertArrayEquals([1, 3, 4, 4, 5], [1, 2, 3, 4, 5].copyWithin(-4, -3, -1)); + assertArrayEquals([1, 3, 4, 5, 5], [1, 2, 3, 4, 5].copyWithin(-4, -3)); + // test with arguments equal to -this.length + assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-5, 0)); +})(); + + +(function copyWithinArrayLikeValues() { + // works with array-like values + var args = (function () { return arguments; }(1, 2, 3)); + Array.prototype.copyWithin.call(args, -2, 0); + assertArrayEquals([1, 1, 2], Array.prototype.slice.call(args)); + + // [[Class]] does not change + assertArrayEquals("[object Arguments]", Object.prototype.toString.call(args)); +})(); + + +(function copyWithinNullThis() { + // throws on null/undefined values + assertThrows(function() { + return Array.prototype.copyWithin.call(null, 0, 3); + }, TypeError); +})(); + + +(function copyWithinUndefinedThis() { + assertThrows(function() { + return Array.prototype.copyWithin.call(undefined, 0, 3); + }, TypeError); +})(); + + +// TODO(caitp): indexed properties of String are read-only and setting them +// should throw in strict mode. See bug v8:4042 +// (function copyWithinStringThis() { +// // test with this value as string +// assertThrows(function() { +// return Array.prototype.copyWithin.call("hello world", 0, 3); +// }, TypeError); +// })(); + + +(function copyWithinNumberThis() { + // test with this value as number + assertEquals(34, Array.prototype.copyWithin.call(34, 0, 3).valueOf()); +})(); + + +(function copyWithinSymbolThis() { + // test with this value as number + var sym = Symbol("test"); + assertEquals(sym, Array.prototype.copyWithin.call(sym, 0, 3).valueOf()); +})(); + + +(function copyyWithinTypedArray() { + // test with this value as TypedArray + var buffer = new ArrayBuffer(16); + var int32View = new Int32Array(buffer); + for (var i=0; i start on 2 arguments + assertArrayEquals([1, 2, 3, 1, 2], [1, 2, 3, 4, 5].copyWithin(3, 0)); + + // test with target > start on 3 arguments + assertArrayEquals([1, 2, 3, 1, 2], [1, 2, 3, 4, 5].copyWithin(3, 0, 4)); +})(); + + +(function copyWithinArrayWithHoles() { + // test on array with holes + var arr = new Array(6); + for (var i = 0; i < arr.length; i += 2) { + arr[i] = i; + } + assertArrayEquals([, 4, , , 4, , ], arr.copyWithin(0, 3)); +})(); + + +(function copyWithinArrayLikeWithHoles() { + // test on array-like object with holes + assertArrayEquals({ + length: 6, + 1: 4, + 4: 4 + }, Array.prototype.copyWithin.call({ + length: 6, + 0: 0, + 2: 2, + 4: 4 + }, 0, 3)); +})(); + + +(function copyWithinNonIntegerRelativeOffsets() { + // test on fractional arguments + assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0.2, 3.9)); +})(); + + +(function copyWithinNegativeZeroTarget() { + // test with -0 + assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-0, 3)); +})(); + + +(function copyWithinTargetOutsideStart() { + // test with arguments more than this.length + assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 7)); + + // test with arguments less than -this.length + assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(-7, 0)); +})(); + + +(function copyWithinEmptyArray() { + // test on empty array + assertArrayEquals([], [].copyWithin(0, 3)); +})(); + + +(function copyWithinTargetCutOff() { + // test with target range being shorter than end - start + assertArrayEquals([1, 2, 2, 3, 4], [1, 2, 3, 4, 5].copyWithin(2, 1, 4)); +})(); + + +(function copyWithinOverlappingRanges() { + // test overlapping ranges + var arr = [1, 2, 3, 4, 5]; + arr.copyWithin(2, 1, 4); + assertArrayEquals([1, 2, 2, 2, 3], arr.copyWithin(2, 1, 4)); +})(); + + +(function copyWithinStrictDelete() { + // check that [[Delete]] is strict (non-extensible via freeze) + assertThrows(function() { + return Object.freeze([1, , 3, , 4, 5]).copyWithin(2, 1, 4); + }, TypeError); + + // check that [[Delete]] is strict (non-extensible via seal) + assertThrows(function() { + return Object.seal([1, , 3, , 4, 5]).copyWithin(2, 1, 4); + }, TypeError); + + // check that [[Delete]] is strict (non-extensible via preventExtensions) + assertThrows(function() { + return Object.preventExtensions([1, , 3, , 4, 5]).copyWithin(2, 1, 4); + }, TypeError); +})(); + + +(function copyWithinStrictSet() { + // check that [[Set]] is strict (non-extensible via freeze) + assertThrows(function() { + return Object.freeze([1, 2, 3, 4, 5]).copyWithin(0, 3); + }, TypeError); + + // check that [[Set]] is strict (non-extensible via seal) + assertThrows(function() { + return Object.seal([, 2, 3, 4, 5]).copyWithin(0, 3); + }, TypeError); + + // check that [[Set]] is strict (non-extensible via preventExtensions) + assertThrows(function() { + return Object.preventExtensions([ , 2, 3, 4, 5]).copyWithin(0, 3); + }, TypeError); +})(); + + +(function copyWithinSetterThrows() { + function Boom() {} + // test if we throw in between + var arr = Object.defineProperty([1, 2, 3, 4, 5], 1, { + set: function () { + throw new Boom(); + } + }); + + assertThrows(function() { + return arr.copyWithin(1, 3); + }, Boom); + + assertArrayEquals([1, , 3, 4, 5], arr); +})(); + + +(function copyWithinDefaultEnd() { + // undefined as third argument + assertArrayEquals( + [4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3, undefined)); +})(); + + +(function copyWithinGetLengthOnce() { + // test that this.length is called only once + var count = 0; + var arr = Object.defineProperty({ 0: 1, 1: 2, 2: 3, 3: 4, 4: 5 }, "length", { + get: function () { + count++; + return 5; + } + }); + Array.prototype.copyWithin.call(arr, 1, 3); + assertEquals(1, count); + + Array.prototype.copyWithin.call(arr, 1, 3, 4); + assertEquals(2, count); +})(); + + +(function copyWithinLargeArray() { + var large = 10000; + + // test on a large array + var arr = new Array(large); + assertArrayEquals(arr, arr.copyWithin(45, 9000)); + + var expected = new Array(large); + // test on floating point numbers + for (var i = 0; i < large; i++) { + arr[i] = Math.random(); + expected[i] = arr[i]; + if (i >= 9000) { + expected[(i - 9000) + 45] = arr[i]; + } + } + assertArrayEquals(expected, arr.copyWithin(45, 9000)); + + // test on array of objects + for (var i = 0; i < large; i++) { + arr[i] = { num: Math.random() }; + } + 45 + arr.copyWithin(45, 9000); + + // test copied by reference + for (var i = 9000; i < large; ++i) { + assertSame(arr[(i - 9000) + 45], arr[i]); + } + + // test array length remains same + assertEquals(large, arr.length); +})(); + + +(function copyWithinSuperLargeLength() { + // 2^53 - 1 is the maximum value returned from ToLength() + var large = Math.pow(2, 53) - 1; + var object = { length: large }; + + // Initialize last 10 entries + for (var i = 1; i <= 10; ++i) { + object[(large - 11) + i] = { num: i }; + } + + Array.prototype.copyWithin.call(object, 1, large - 10); + + // Test copied values + for (var i = 1; i <= 10; ++i) { + var old_ref = object[(large - 11) + i]; + var new_ref = object[i]; + assertSame(old_ref, new_ref); + assertSame(new_ref.num, i); + } + + // Assert length has not changed + assertEquals(large, object.length); +})(); + + +(function copyWithinNullEnd() { + // test null on third argument is converted to +0 + assertArrayEquals([1, 2, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3, null)); +})(); + + +(function copyWithinElementsInObjectsPrototype() { + // tamper the global Object prototype and test this works + Object.prototype[2] = 1; + assertArrayEquals([4, 5, 3, 4, 5], [1, 2, 3, 4, 5].copyWithin(0, 3)); + delete Object.prototype[2]; + + Object.prototype[3] = "FAKE"; + assertArrayEquals(["FAKE", 5, 3, "FAKE", 5], [1, 2, 3, , 5].copyWithin(0, 3)); + delete Object.prototype[3]; +})();