Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ES2019 Array.prototype.flat #1313

Merged
merged 7 commits into from
May 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion src/org/mozilla/javascript/NativeArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
71 changes: 71 additions & 0 deletions testsrc/jstests/harmony/array-flat.js
Original file line number Diff line number Diff line change
@@ -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";
1 change: 0 additions & 1 deletion testsrc/org/mozilla/javascript/tests/Test262SuiteTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public class Test262SuiteTest {
new HashSet<>(
Arrays.asList(
"Array.prototype.flatMap",
"Array.prototype.flat",
"Atomics",
"IsHTMLDDA",
"Proxy",
Expand Down
14 changes: 14 additions & 0 deletions testsrc/org/mozilla/javascript/tests/harmony/ArrayFlatTest.java
Original file line number Diff line number Diff line change
@@ -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 {}
15 changes: 13 additions & 2 deletions testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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]}
Expand Down Expand Up @@ -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]}
Expand Down