From 59ef8c5f3282f01b5990b3a46a2a3a829b699a13 Mon Sep 17 00:00:00 2001 From: dehrenberg Date: Tue, 19 May 2015 09:32:48 -0700 Subject: [PATCH] Implement %TypedArray%.prototype.{map,filter,some,reduce,reduceRight} This patch adds implementations for additional TypedArray methods from the ES6 spec, together with tests adapted from array code. R=arv@chromium.org BUG=v8:3578 LOG=Y Review URL: https://codereview.chromium.org/1139663005 Cr-Commit-Position: refs/heads/master@{#28488} --- src/array.js | 115 ++++++--- src/harmony-typedarray.js | 89 ++++++- test/mjsunit/harmony/typedarray-iteration.js | 196 ++++++++++++++ test/mjsunit/harmony/typedarray-reduce.js | 256 +++++++++++++++++++ 4 files changed, 612 insertions(+), 44 deletions(-) create mode 100644 test/mjsunit/harmony/typedarray-iteration.js create mode 100644 test/mjsunit/harmony/typedarray-reduce.js diff --git a/src/array.js b/src/array.js index 93378cfb00..a7c37e1ef8 100644 --- a/src/array.js +++ b/src/array.js @@ -12,6 +12,11 @@ var $arraySplice; var $arrayUnshift; var $innerArrayForEach; var $innerArrayEvery; +var $innerArrayFilter; +var $innerArrayMap; +var $innerArrayReduce; +var $innerArrayReduceRight; +var $innerArraySome; (function(global, shared, exports) { @@ -1150,14 +1155,7 @@ function ArraySort(comparefn) { // The following functions cannot be made efficient on sparse arrays while // preserving the semantics, since the calls to the receiver function can add // or delete elements from the array. -function ArrayFilter(f, receiver) { - CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter"); - - // Pull out the length so that modifications to the length in the - // loop will not affect the looping and side effects are visible. - var array = $toObject(this); - var length = $toUint32(array.length); - +function InnerArrayFilter(f, receiver, array, length) { if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f); var needs_wrapper = false; if (IS_NULL(receiver)) { @@ -1186,6 +1184,17 @@ function ArrayFilter(f, receiver) { return result; } +function ArrayFilter(f, receiver) { + CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter"); + + // Pull out the length so that modifications to the length in the + // loop will not affect the looping and side effects are visible. + var array = $toObject(this); + var length = $toUint32(array.length); + + return InnerArrayFilter(f, receiver, array, length); +} + function InnerArrayForEach(f, receiver, array, length) { if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f); var needs_wrapper = false; @@ -1219,16 +1228,7 @@ function ArrayForEach(f, receiver) { } -// Executes the function once for each element present in the -// array until it finds one where callback returns true. -function ArraySome(f, receiver) { - CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some"); - - // Pull out the length so that modifications to the length in the - // loop will not affect the looping and side effects are visible. - var array = $toObject(this); - var length = TO_UINT32(array.length); - +function InnerArraySome(f, receiver, array, length) { if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f); var needs_wrapper = false; if (IS_NULL(receiver)) { @@ -1252,6 +1252,19 @@ function ArraySome(f, receiver) { } +// Executes the function once for each element present in the +// array until it finds one where callback returns true. +function ArraySome(f, receiver) { + CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some"); + + // Pull out the length so that modifications to the length in the + // loop will not affect the looping and side effects are visible. + var array = $toObject(this); + var length = TO_UINT32(array.length); + return InnerArraySome(f, receiver, array, length); +} + + function InnerArrayEvery(f, receiver, array, length) { if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f); var needs_wrapper = false; @@ -1286,14 +1299,7 @@ function ArrayEvery(f, receiver) { } -function ArrayMap(f, receiver) { - CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map"); - - // Pull out the length so that modifications to the length in the - // loop will not affect the looping and side effects are visible. - var array = $toObject(this); - var length = TO_UINT32(array.length); - +function InnerArrayMap(f, receiver, array, length) { if (!IS_SPEC_FUNCTION(f)) throw MakeTypeError(kCalledNonCallable, f); var needs_wrapper = false; if (IS_NULL(receiver)) { @@ -1320,6 +1326,17 @@ function ArrayMap(f, receiver) { } +function ArrayMap(f, receiver) { + CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map"); + + // Pull out the length so that modifications to the length in the + // loop will not affect the looping and side effects are visible. + var array = $toObject(this); + var length = TO_UINT32(array.length); + return InnerArrayMap(f, receiver, array, length); +} + + function ArrayIndexOf(element, index) { CHECK_OBJECT_COERCIBLE(this, "Array.prototype.indexOf"); @@ -1430,21 +1447,14 @@ function ArrayLastIndexOf(element, index) { } -function ArrayReduce(callback, current) { - CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce"); - - // Pull out the length so that modifications to the length in the - // loop will not affect the looping and side effects are visible. - var array = $toObject(this); - var length = $toUint32(array.length); - +function InnerArrayReduce(callback, current, array, length, argumentsLength) { if (!IS_SPEC_FUNCTION(callback)) { throw MakeTypeError(kCalledNonCallable, callback); } var is_array = IS_ARRAY(array); var i = 0; - find_initial: if (%_ArgumentsLength() < 2) { + find_initial: if (argumentsLength < 2) { for (; i < length; i++) { if (HAS_INDEX(array, i, is_array)) { current = array[i++]; @@ -1467,21 +1477,27 @@ function ArrayReduce(callback, current) { } -function ArrayReduceRight(callback, current) { - CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight"); +function ArrayReduce(callback, current) { + CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce"); - // Pull out the length so that side effects are visible before the - // callback function is checked. + // Pull out the length so that modifications to the length in the + // loop will not affect the looping and side effects are visible. var array = $toObject(this); var length = $toUint32(array.length); + return InnerArrayReduce(callback, current, array, length, + %_ArgumentsLength()); +} + +function InnerArrayReduceRight(callback, current, array, length, + argumentsLength) { if (!IS_SPEC_FUNCTION(callback)) { throw MakeTypeError(kCalledNonCallable, callback); } var is_array = IS_ARRAY(array); var i = length - 1; - find_initial: if (%_ArgumentsLength() < 2) { + find_initial: if (argumentsLength < 2) { for (; i >= 0; i--) { if (HAS_INDEX(array, i, is_array)) { current = array[i--]; @@ -1503,6 +1519,18 @@ function ArrayReduceRight(callback, current) { return current; } + +function ArrayReduceRight(callback, current) { + CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight"); + + // Pull out the length so that side effects are visible before the + // callback function is checked. + var array = $toObject(this); + var length = $toUint32(array.length); + return InnerArrayReduceRight(callback, current, array, length, + %_ArgumentsLength()); +} + // ES5, 15.4.3.2 function ArrayIsArray(obj) { return IS_ARRAY(obj); @@ -1607,7 +1635,12 @@ $arraySlice = ArraySlice; $arraySplice = ArraySplice; $arrayUnshift = ArrayUnshift; -$innerArrayForEach = InnerArrayForEach; $innerArrayEvery = InnerArrayEvery; +$innerArrayFilter = InnerArrayFilter; +$innerArrayForEach = InnerArrayForEach; +$innerArrayMap = InnerArrayMap; +$innerArrayReduce = InnerArrayReduce; +$innerArrayReduceRight = InnerArrayReduceRight; +$innerArraySome = InnerArraySome; }); diff --git a/src/harmony-typedarray.js b/src/harmony-typedarray.js index 90679e0c1f..b1115f4fb4 100644 --- a/src/harmony-typedarray.js +++ b/src/harmony-typedarray.js @@ -30,6 +30,28 @@ DECLARE_GLOBALS(Array) // ------------------------------------------------------------------- +function ConstructTypedArray(constructor, array) { + // TODO(littledan): This is an approximation of the spec, which requires + // that only real TypedArray classes should be accepted (22.2.2.1.1) + if (!IS_SPEC_OBJECT(constructor) || IS_UNDEFINED(constructor.prototype) || + !%HasOwnProperty(constructor.prototype, "BYTES_PER_ELEMENT")) { + throw MakeTypeError(kNotTypedArray); + } + + // TODO(littledan): The spec requires that, rather than directly calling + // the constructor, a TypedArray is created with the proper proto and + // underlying size and element size, and elements are put in one by one. + // By contrast, this would allow subclasses to make a radically different + // constructor with different semantics. + return new constructor(array); +} + +function ConstructTypedArrayLike(typedArray, arrayContents) { + // TODO(littledan): The spec requires that we actuallly use + // typedArray.constructor[Symbol.species] (bug v8:4093) + return new typedArray.constructor(arrayContents); +} + function TypedArrayCopyWithin(target, start, end) { if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); @@ -61,7 +83,7 @@ function TypedArrayForEach(f, receiver) { %FunctionSetLength(TypedArrayForEach, 1); // ES6 draft 04-05-14 section 22.2.3.8 -function TypedArrayFill(value, start , end) { +function TypedArrayFill(value, start, end) { if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); var length = %_TypedArrayGetLength(this); @@ -70,6 +92,16 @@ function TypedArrayFill(value, start , end) { } %FunctionSetLength(TypedArrayFill, 1); +// ES6 draft 07-15-13, section 22.2.3.9 +function TypedArrayFilter(predicate, thisArg) { + if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); + + var length = %_TypedArrayGetLength(this); + var array = $innerArrayFilter(predicate, thisArg, this, length); + return ConstructTypedArrayLike(this, array); +} +%FunctionSetLength(TypedArrayFilter, 1); + // ES6 draft 07-15-13, section 22.2.3.10 function TypedArrayFind(predicate, thisArg) { if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); @@ -91,6 +123,52 @@ function TypedArrayFindIndex(predicate, thisArg) { %FunctionSetLength(TypedArrayFindIndex, 1); +// ES6 draft 07-15-13, section 22.2.3.18 +function TypedArrayMap(predicate, thisArg) { + if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); + + // TODO(littledan): Preallocate rather than making an intermediate + // array, for better performance. + var length = %_TypedArrayGetLength(this); + var array = $innerArrayMap(predicate, thisArg, this, length); + return ConstructTypedArrayLike(this, array); +} +%FunctionSetLength(TypedArrayMap, 1); + + +// ES6 draft 07-15-13, section 22.2.3.19 +function TypedArrayReduce(callback, current) { + if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); + + var length = %_TypedArrayGetLength(this); + return $innerArrayReduce(callback, current, this, length, + %_ArgumentsLength()); +} +%FunctionSetLength(TypedArrayReduce, 1); + + +// ES6 draft 07-15-13, section 22.2.3.19 +function TypedArrayReduceRight(callback, current) { + if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); + + var length = %_TypedArrayGetLength(this); + return $innerArrayReduceRight(callback, current, this, length, + %_ArgumentsLength()); +} +%FunctionSetLength(TypedArrayReduceRight, 1); + + +// ES6 draft 05-05-15, section 22.2.3.24 +function TypedArraySome(f, receiver) { + if (!%IsTypedArray(this)) throw MakeTypeError(kNotTypedArray); + + var length = %_TypedArrayGetLength(this); + + return $innerArraySome(f, receiver, this, length); +} +%FunctionSetLength(TypedArraySome, 1); + + // ES6 draft 08-24-14, section 22.2.2.2 function TypedArrayOf() { var length = %_ArgumentsLength(); @@ -137,10 +215,15 @@ macro EXTEND_TYPED_ARRAY(NAME) $installFunctions(GlobalNAME.prototype, DONT_ENUM, [ "copyWithin", TypedArrayCopyWithin, "every", TypedArrayEvery, - "forEach", TypedArrayForEach, + "fill", TypedArrayFill, + "filter", TypedArrayFilter, "find", TypedArrayFind, "findIndex", TypedArrayFindIndex, - "fill", TypedArrayFill + "forEach", TypedArrayForEach, + "map", TypedArrayMap, + "reduce", TypedArrayReduce, + "reduceRight", TypedArrayReduceRight, + "some", TypedArraySome ]); endmacro diff --git a/test/mjsunit/harmony/typedarray-iteration.js b/test/mjsunit/harmony/typedarray-iteration.js new file mode 100644 index 0000000000..85c509193d --- /dev/null +++ b/test/mjsunit/harmony/typedarray-iteration.js @@ -0,0 +1,196 @@ +// Copyright 2015 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 + +// Tests for standard TypedArray array iteration functions. + +var typedArrayConstructors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Uint8ClampedArray, + Float32Array, + Float64Array +]; + +function assertArrayLikeEquals(expected, value, type) { + assertEquals(value.__proto__, type.prototype); + assertEquals(expected.length, value.length); + for (var i = 0; i < value.length; ++i) { + assertEquals(expected[i], value[i]); + } +} + +for (var constructor of typedArrayConstructors) { + (function TypedArrayFilterTest() { + // Simple use. + var a = new constructor([0, 1]); + assertArrayLikeEquals([0], a.filter(function(n) { return n == 0; }), + constructor); + assertArrayLikeEquals([0, 1], a, constructor); + + // Use specified object as this object when calling the function. + var o = { value: 42 } + a = new constructor([1, 42, 3, 42, 4]); + assertArrayLikeEquals([42, 42], a.filter(function(n) { + return this.value == n + }, o), constructor); + + // Modify original array. + a = new constructor([1, 42, 3, 42, 4]); + assertArrayLikeEquals([42, 42], a.filter(function(n, index, array) { + array[index] = 43; return 42 == n; + }), constructor); + assertArrayLikeEquals([43, 43, 43, 43, 43], a, constructor); + + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + new constructor([1, 2]).filter(function() { a.push(this) }, ''); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + new constructor([1, 2]).filter(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + new constructor([1, 2]).filter(function() { + 'use strict'; + a.push(this); + }, ''); + assertEquals('', a[0]); + assertEquals(a[0], a[1]); + + // Calling this method on other types is a TypeError + assertThrows(function() { + constructor.prototype.filter.call([], function() {}); + }, TypeError); + + // Shadowing the length property doesn't change anything + a = new constructor([1, 2]); + Object.defineProperty(a, 'length', { value: 1 }); + assertArrayLikeEquals([2], a.filter(function(elt) { + return elt == 2; + }), constructor); + })(); + + (function TypedArrayMapTest() { + var a = new constructor([0, 1, 2, 3, 4]); + + // Simple use. + var result = [1, 2, 3, 4, 5]; + assertArrayLikeEquals(result, a.map(function(n) { return n + 1; }), + constructor); + assertEquals(a, a); + + // Use specified object as this object when calling the function. + var o = { delta: 42 } + result = [42, 43, 44, 45, 46]; + assertArrayLikeEquals(result, a.map(function(n) { + return this.delta + n; + }, o), constructor); + + // Modify original array. + a = new constructor([0, 1, 2, 3, 4]); + result = [1, 2, 3, 4, 5]; + assertArrayLikeEquals(result, a.map(function(n, index, array) { + array[index] = n + 1; + return n + 1; + }), constructor); + assertArrayLikeEquals(result, a, constructor); + + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + new constructor([1, 2]).map(function() { a.push(this) }, ''); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + new constructor([1, 2]).map(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + new constructor([1, 2]).map(function() { 'use strict'; a.push(this); }, ''); + assertEquals('', a[0]); + assertEquals(a[0], a[1]); + + // Test that the result is converted to the right type + assertArrayLikeEquals([3, 3], new constructor([1, 2]).map(function() { + return "3"; + }), constructor); + if (constructor !== Float32Array && constructor !== Float64Array) { + assertArrayLikeEquals([0, 0], new constructor([1, 2]).map(function() { + return NaN; + }), constructor); + } + })(); + + // + // %TypedArray%.prototype.some + // + (function TypedArraySomeTest() { + var a = new constructor([0, 1, 2, 3, 4]); + + // Simple use. + assertTrue(a.some(function(n) { return n == 3})); + assertFalse(a.some(function(n) { return n == 5})); + + // Use specified object as this object when calling the function. + var o = { element: 42 }; + a = new constructor([1, 42, 3]); + assertTrue(a.some(function(n) { return this.element == n; }, o)); + a = new constructor([1]); + assertFalse(a.some(function(n) { return this.element == n; }, o)); + + // Modify original array. + a = new constructor([0, 1, 2, 3]); + assertTrue(a.some(function(n, index, array) { + array[index] = n + 1; + return n == 2; + })); + assertArrayLikeEquals([1, 2, 3, 3], a, constructor); + + // Create a new object in each function call when receiver is a + // primitive value. See ECMA-262, Annex C. + a = []; + new constructor([1, 2]).some(function() { a.push(this) }, ''); + assertTrue(a[0] !== a[1]); + + // Do not create a new object otherwise. + a = []; + new constructor([1, 2]).some(function() { a.push(this) }, {}); + assertEquals(a[0], a[1]); + + // In strict mode primitive values should not be coerced to an object. + a = []; + new constructor([1, 2]).some(function() { + 'use strict'; + a.push(this); + }, ''); + assertEquals('', a[0]); + assertEquals(a[0], a[1]); + + // Calling this method on other types is a TypeError + assertThrows(function() { + constructor.prototype.some.call([], function() {}); + }, TypeError); + + // Shadowing the length property doesn't change anything + a = new constructor([1, 2]); + Object.defineProperty(a, 'length', { value: 1 }); + assertEquals(true, a.some(function(elt) { return elt == 2; })); + assertEquals(false, Array.prototype.some.call(a, function(elt) { + return elt == 2; + })); + })(); + +} diff --git a/test/mjsunit/harmony/typedarray-reduce.js b/test/mjsunit/harmony/typedarray-reduce.js new file mode 100644 index 0000000000..15bb0ba933 --- /dev/null +++ b/test/mjsunit/harmony/typedarray-reduce.js @@ -0,0 +1,256 @@ +// Copyright 2015 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 --allow-natives-syntax + +var typedArrayConstructors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Uint8ClampedArray, + Float32Array, + Float64Array +]; + +function clone(v) { + // Shallow-copies arrays, returns everything else verbatim. + if (v instanceof Array) { + // Shallow-copy an array. + var newArray = new Array(v.length); + for (var i in v) { + newArray[i] = v[i]; + } + return newArray; + } + return v; +} + + +// Creates a callback function for reduce/reduceRight that tests the number +// of arguments and otherwise behaves as "func", but which also +// records all calls in an array on the function (as arrays of arguments +// followed by result). +function makeRecorder(func, testName) { + var record = []; + var f = function recorder(a, b, i, s) { + assertEquals(4, arguments.length, + testName + "(number of arguments: " + arguments.length + ")"); + assertEquals("number", typeof(i), testName + "(index must be number)"); + assertEquals(s[i], b, testName + "(current argument is at index)"); + if (record.length > 0) { + var prevRecord = record[record.length - 1]; + var prevResult = prevRecord[prevRecord.length - 1]; + assertEquals(prevResult, a, + testName + "(prev result -> current input)"); + } + var args = [clone(a), clone(b), i, clone(s)]; + var result = func.apply(this, arguments); + args.push(clone(result)); + record.push(args); + return result; + }; + f.record = record; + return f; +} + + +function testReduce(type, + testName, + expectedResult, + expectedCalls, + array, + combine, + init) { + var rec = makeRecorder(combine); + var result; + var performsCall; + if (arguments.length > 6) { + result = array[type](rec, init); + } else { + result = array[type](rec); + } + var calls = rec.record; + assertEquals(expectedCalls.length, calls.length, + testName + " (number of calls)"); + for (var i = 0; i < expectedCalls.length; i++) { + assertEquals(expectedCalls[i], calls[i], + testName + " (call " + (i + 1) + ")"); + } + assertEquals(expectedResult, result, testName + " (result)"); +} + + +function sum(a, b) { return a + b; } +function prod(a, b) { return a * b; } +function dec(a, b, i, arr) { return a + b * Math.pow(10, arr.length - i - 1); } +function accumulate(acc, elem, i) { acc[i] = elem; return acc; } + +for (var constructor of typedArrayConstructors) { + // ---- Test Reduce[Left] + + var simpleArray = new constructor([2,4,6]) + + testReduce("reduce", "SimpleReduceSum", 12, + [[0, 2, 0, simpleArray, 2], + [2, 4, 1, simpleArray, 6], + [6, 6, 2, simpleArray, 12]], + simpleArray, sum, 0); + + testReduce("reduce", "SimpleReduceProd", 48, + [[1, 2, 0, simpleArray, 2], + [2, 4, 1, simpleArray, 8], + [8, 6, 2, simpleArray, 48]], + simpleArray, prod, 1); + + testReduce("reduce", "SimpleReduceDec", 246, + [[0, 2, 0, simpleArray, 200], + [200, 4, 1, simpleArray, 240], + [240, 6, 2, simpleArray, 246]], + simpleArray, dec, 0); + + testReduce("reduce", "SimpleReduceAccumulate", [2, 4, 6], + [[[], 2, 0, simpleArray, [2]], + [[2], 4, 1, simpleArray, [2, 4]], + [[2,4], 6, 2, simpleArray, [2, 4, 6]]], + simpleArray, accumulate, []); + + + testReduce("reduce", "EmptyReduceSum", 0, [], [], sum, 0); + testReduce("reduce", "EmptyReduceProd", 1, [], [], prod, 1); + testReduce("reduce", "EmptyReduceDec", 0, [], [], dec, 0); + testReduce("reduce", "EmptyReduceAccumulate", [], [], [], accumulate, []); + + testReduce("reduce", "EmptyReduceSumNoInit", 0, [], [0], sum); + testReduce("reduce", "EmptyReduceProdNoInit", 1, [], [1], prod); + testReduce("reduce", "EmptyReduceDecNoInit", 0, [], [0], dec); + testReduce("reduce", "EmptyReduceAccumulateNoInit", [], [], [[]], accumulate); + + // ---- Test ReduceRight + + testReduce("reduceRight", "SimpleReduceRightSum", 12, + [[0, 6, 2, simpleArray, 6], + [6, 4, 1, simpleArray, 10], + [10, 2, 0, simpleArray, 12]], + simpleArray, sum, 0); + + testReduce("reduceRight", "SimpleReduceRightProd", 48, + [[1, 6, 2, simpleArray, 6], + [6, 4, 1, simpleArray, 24], + [24, 2, 0, simpleArray, 48]], + simpleArray, prod, 1); + + testReduce("reduceRight", "SimpleReduceRightDec", 246, + [[0, 6, 2, simpleArray, 6], + [6, 4, 1, simpleArray, 46], + [46, 2, 0, simpleArray, 246]], + simpleArray, dec, 0); + + + testReduce("reduceRight", "EmptyReduceRightSum", 0, [], [], sum, 0); + testReduce("reduceRight", "EmptyReduceRightProd", 1, [], [], prod, 1); + testReduce("reduceRight", "EmptyReduceRightDec", 0, [], [], dec, 0); + testReduce("reduceRight", "EmptyReduceRightAccumulate", [], + [], [], accumulate, []); + + testReduce("reduceRight", "EmptyReduceRightSumNoInit", 0, [], [0], sum); + testReduce("reduceRight", "EmptyReduceRightProdNoInit", 1, [], [1], prod); + testReduce("reduceRight", "EmptyReduceRightDecNoInit", 0, [], [0], dec); + testReduce("reduceRight", "EmptyReduceRightAccumulateNoInit", + [], [], [[]], accumulate); + + // Ignore non-array properties: + + var arrayPlus = [1,2,3]; + arrayPlus[-1] = NaN; + arrayPlus[Math.pow(2,32)] = NaN; + arrayPlus[NaN] = NaN; + arrayPlus["00"] = NaN; + arrayPlus["02"] = NaN; + arrayPlus["-0"] = NaN; + + testReduce("reduce", "ArrayWithNonElementPropertiesReduce", 6, + [[0, 1, 0, arrayPlus, 1], + [1, 2, 1, arrayPlus, 3], + [3, 3, 2, arrayPlus, 6], + ], arrayPlus, sum, 0); + + testReduce("reduceRight", "ArrayWithNonElementPropertiesReduceRight", 6, + [[0, 3, 2, arrayPlus, 3], + [3, 2, 1, arrayPlus, 5], + [5, 1, 0, arrayPlus, 6], + ], arrayPlus, sum, 0); + + + // Test error conditions: + + var exception = false; + try { + new constructor([1]).reduce("not a function"); + } catch (e) { + exception = true; + assertTrue(e instanceof TypeError, + "reduce callback not a function not throwing TypeError"); + assertTrue(e.message.indexOf(" is not a function") >= 0, + "reduce non function TypeError type"); + } + assertTrue(exception); + + exception = false; + try { + new constructor([1]).reduceRight("not a function"); + } catch (e) { + exception = true; + assertTrue(e instanceof TypeError, + "reduceRight callback not a function not throwing TypeError"); + assertTrue(e.message.indexOf(" is not a function") >= 0, + "reduceRight non function TypeError type"); + } + assertTrue(exception); + + exception = false; + try { + new constructor([]).reduce(sum); + } catch (e) { + exception = true; + assertTrue(e instanceof TypeError, + "reduce no initial value not throwing TypeError"); + assertEquals("Reduce of empty array with no initial value", e.message, + "reduce no initial TypeError type"); + } + assertTrue(exception); + + exception = false; + try { + new constructor([]).reduceRight(sum); + } catch (e) { + exception = true; + assertTrue(e instanceof TypeError, + "reduceRight no initial value not throwing TypeError"); + assertEquals("Reduce of empty array with no initial value", e.message, + "reduceRight no initial TypeError type"); + } + assertTrue(exception); + + // Reduce fails when called on non-TypedArrays + assertThrows(function() { + constructor.prototype.reduce.call([], function() {}, null); + }, TypeError); + assertThrows(function() { + constructor.prototype.reduceRight.call([], function() {}, null); + }, TypeError); + + // Shadowing length doesn't affect every, unlike Array.prototype.every + var a = new constructor([1, 2]); + Object.defineProperty(a, 'length', {value: 1}); + assertEquals(a.reduce(sum, 0), 3); + assertEquals(Array.prototype.reduce.call(a, sum, 0), 1); + assertEquals(a.reduceRight(sum, 0), 3); + assertEquals(Array.prototype.reduceRight.call(a, sum, 0), 1); + + assertEquals(1, constructor.prototype.reduce.length); + assertEquals(1, constructor.prototype.reduceRight.length); +}