diff --git a/src/org/mozilla/javascript/NativeArray.java b/src/org/mozilla/javascript/NativeArray.java index 9ce2225983..b5d042beb4 100644 --- a/src/org/mozilla/javascript/NativeArray.java +++ b/src/org/mozilla/javascript/NativeArray.java @@ -291,6 +291,10 @@ protected void initPrototypeId(int id) { arity = 2; s = "copyWithin"; break; + case Id_flat: + arity = 0; + s = "flat"; + break; default: throw new IllegalArgumentException(String.valueOf(id)); } @@ -427,6 +431,9 @@ public Object execIdCall( case Id_copyWithin: return js_copyWithin(cx, scope, thisObj, args); + case Id_flat: + return js_flat(cx, scope, thisObj, args); + case Id_every: case Id_filter: case Id_forEach: @@ -1000,6 +1007,14 @@ private static void defineElem(Context cx, Scriptable target, long index, Object } } + private static void defineElemOrThrow(Context cx, Scriptable target, long index, Object value) { + if (index > NativeNumber.MAX_SAFE_INTEGER) { + throw ScriptRuntime.typeErrorById("msg.arraylength.too.big", String.valueOf(index)); + } else { + defineElem(cx, target, index, value); + } + } + private static void setElem(Context cx, Scriptable target, long index, Object value) { if (index > Integer.MAX_VALUE) { String id = Long.toString(index); @@ -1959,6 +1974,44 @@ private static Object js_copyWithin( return thisObj; } + private static Object js_flat(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + Scriptable o = ScriptRuntime.toObject(cx, scope, thisObj); + double depth; + if (args.length < 1 || Undefined.isUndefined(args[0])) { + depth = 1; + } else { + depth = ScriptRuntime.toInteger(args[0]); + } + + return flat(cx, scope, o, depth); + } + + private static Scriptable flat(Context cx, Scriptable scope, Scriptable source, double depth) { + long length = getLengthProperty(cx, source); + + Scriptable result; + result = cx.newArray(scope, 0); + long j = 0; + for (long i = 0; i < length; i++) { + Object elem = getRawElem(source, i); + if (elem == Scriptable.NOT_FOUND) { + continue; + } + if (depth >= 1 && js_isArray(elem)) { + Scriptable arr = flat(cx, scope, (Scriptable) elem, depth - 1); + long arrLength = getLengthProperty(cx, arr); + for (long k = 0; k < arrLength; k++) { + Object temp = getRawElem(arr, k); + defineElemOrThrow(cx, result, j++, temp); + } + } else { + defineElemOrThrow(cx, result, j++, elem); + } + } + setLengthProperty(cx, result, j); + return result; + } + /** Implements the methods "every", "filter", "forEach", "map", and "some". */ private static Object iterativeMethod( Context cx, @@ -2538,6 +2591,9 @@ protected int findPrototypeId(String s) { case "copyWithin": id = Id_copyWithin; break; + case "flat": + id = Id_flat; + break; default: id = 0; break; @@ -2576,7 +2632,8 @@ protected int findPrototypeId(String s) { Id_entries = 29, Id_includes = 30, Id_copyWithin = 31, - SymbolId_iterator = 32, + Id_flat = 32, + SymbolId_iterator = 33, MAX_PROTOTYPE_ID = SymbolId_iterator; private static final int ConstructorId_join = -Id_join, ConstructorId_reverse = -Id_reverse, diff --git a/testsrc/jstests/harmony/array-flat.js b/testsrc/jstests/harmony/array-flat.js new file mode 100644 index 0000000000..4cfad6b2b0 --- /dev/null +++ b/testsrc/jstests/harmony/array-flat.js @@ -0,0 +1,71 @@ +// Copyright 2018 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. +// +// Taken from https://github.com/v8/v8/blob/main/test/mjsunit/harmony/array-flat.js and changed due to Rhino errors +// TypeError: redeclaration of const input +load("testsrc/assert.js"); + +assertEquals(Array.prototype.flat.length, 0); +assertEquals(Array.prototype.flat.name, 'flat'); + +let input; + +{ + input = [1, [2], [[3]]]; + + assertEquals(input.flat(), [1, 2, [3]]); + assertEquals(input.flat(1), [1, 2, [3]]); + assertEquals(input.flat(true), [1, 2, [3]]); + assertEquals(input.flat(undefined), [1, 2, [3]]); + + assertEquals(input.flat(-Infinity), [1, [2], [[3]]]); + assertEquals(input.flat(-1), [1, [2], [[3]]]); + assertEquals(input.flat(-0), [1, [2], [[3]]]); + assertEquals(input.flat(0), [1, [2], [[3]]]); + assertEquals(input.flat(false), [1, [2], [[3]]]); + assertEquals(input.flat(null), [1, [2], [[3]]]); + assertEquals(input.flat(''), [1, [2], [[3]]]); + assertEquals(input.flat('foo'), [1, [2], [[3]]]); + assertEquals(input.flat(/./), [1, [2], [[3]]]); + assertEquals(input.flat([]), [1, [2], [[3]]]); + assertEquals(input.flat({}), [1, [2], [[3]]]); + //assertEquals( + // input.flat(new Proxy({}, {})), [1, [2], [[3]]]); + assertEquals(input.flat((x) => x), [1, [2], [[3]]]); + assertEquals( + input.flat(String), [1, [2], [[3]]]); + + assertEquals(input.flat(2), [1, 2, 3]); + assertEquals(input.flat(Infinity), [1, 2, 3]); + + assertThrows(() => { input.flat(Symbol()); }, TypeError); + assertThrows(() => { input.flat(Object.create(null)); }, TypeError); +} + +{ + input = { 0: 'a', 1: 'b', 2: 'c', length: 'wat' }; + assertEquals(Array.prototype.flat.call(input, Infinity), []); +} + +{ + let count = 0; + input = { + get length() { ++count; return 0; } + }; + const result = Array.prototype.flat.call(input, Infinity); + assertEquals(count, 1); +} + +{ + const descriptor = Object.getOwnPropertyDescriptor( + Array.prototype, + 'flat' + ); + assertEquals(descriptor.value, Array.prototype.flat); + assertEquals(descriptor.writable, true); + assertEquals(descriptor.enumerable, false); + assertEquals(descriptor.configurable, true); +} + +"success"; \ No newline at end of file diff --git a/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java b/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java index f333f86195..82670fedab 100644 --- a/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java +++ b/testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java @@ -89,7 +89,6 @@ public class Test262SuiteTest { new HashSet<>( Arrays.asList( "Array.prototype.flatMap", - "Array.prototype.flat", "Atomics", "IsHTMLDDA", "Proxy", diff --git a/testsrc/org/mozilla/javascript/tests/harmony/ArrayFlatTest.java b/testsrc/org/mozilla/javascript/tests/harmony/ArrayFlatTest.java new file mode 100644 index 0000000000..40017c051d --- /dev/null +++ b/testsrc/org/mozilla/javascript/tests/harmony/ArrayFlatTest.java @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript.tests.harmony; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.drivers.LanguageVersion; +import org.mozilla.javascript.drivers.RhinoTest; +import org.mozilla.javascript.drivers.ScriptTestsBase; + +@RhinoTest("testsrc/jstests/harmony/array-flat.js") +@LanguageVersion(Context.VERSION_ES6) +public class ArrayFlatTest extends ScriptTestsBase {} diff --git a/testsrc/test262.properties b/testsrc/test262.properties index bd416dff50..31a16efcc3 100644 --- a/testsrc/test262.properties +++ b/testsrc/test262.properties @@ -73,7 +73,18 @@ built-ins/Array 200/2670 (7.49%) prototype/findIndex/predicate-call-this-strict.js strict prototype/find/predicate-call-this-strict.js strict prototype/flatMap 21/21 (100.0%) - prototype/flat 17/17 (100.0%) + prototype/flat/array-like-objects.js + prototype/flat/bound-function-call.js + prototype/flat/empty-array-elements.js + prototype/flat/empty-object-elements.js + prototype/flat/non-numeric-depth-should-not-throw.js + prototype/flat/non-object-ctor-throws.js + prototype/flat/null-undefined-elements.js + prototype/flat/positive-infinity.js + prototype/flat/proxy-access-count.js + prototype/flat/target-array-non-extensible.js {unsupported: [Symbol.species]} + prototype/flat/target-array-with-non-configurable-property.js {unsupported: [Symbol.species]} + prototype/flat/target-array-with-non-writable-property.js {unsupported: [Symbol.species]} prototype/forEach/15.4.4.18-5-1-s.js non-strict prototype/includes/get-prop.js {unsupported: [Proxy]} prototype/indexOf/calls-only-has-on-prototype-after-length-zeroed.js {unsupported: [Proxy]} @@ -155,7 +166,7 @@ built-ins/Array 200/2670 (7.49%) prototype/toLocaleString/primitive_this_value.js strict prototype/toLocaleString/primitive_this_value_getter.js strict prototype/unshift/throws-with-string-receiver.js - prototype/methods-called-as-functions.js {unsupported: [Symbol.species, Array.prototype.flat, Array.prototype.flatMap]} + prototype/methods-called-as-functions.js {unsupported: [Symbol.species, Array.prototype.flatMap]} prototype/Symbol.iterator.js Expects a particular string value Symbol.species 4/4 (100.0%) proto-from-ctor-realm-one.js {unsupported: [Reflect]}