Skip to content

Commit

Permalink
Support ES2019 Array.prototype.flat (#1313)
Browse files Browse the repository at this point in the history
* feat: implement Array.prototype.flat

* tests: add (slightly modified) harmony test

* feat: throw TypeError if array is too big

* fix: remove constructor assigned Array.flat

* fix: just remove the comment about es2019

By other code looks like the Rhino version context is just ES6.

* tests: update test262

---------

Co-authored-by: RBRi <rbri@rbri.de>
  • Loading branch information
midgleyc and rbri authored May 31, 2023
1 parent 76c6124 commit ab3bd1f
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 4 deletions.
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

0 comments on commit ab3bd1f

Please sign in to comment.