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 3 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
62 changes: 61 additions & 1 deletion src/org/mozilla/javascript/NativeArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ protected void fillConstructorProperties(IdFunctionObject ctor) {
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_findIndex, "findIndex", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reduce, "reduce", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reduceRight, "reduceRight", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_flat, "flat", 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code adds the method to the constructor(Array.flat), not the prototype(Array.prototype.flat).
It is an old specification called Array generic methods.
https://lia.disi.unibo.it/materiale/JS/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array.html#Array_generic_methods

I do not think this is necessary as it is not in latest ECMA262 specification.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I just added these because I saw other functions doing this -- e.g. Array.reduceRight doesn't exist either, just Array.prototype.reduceRight.

I've removed ConstructorId_flat entirely.

addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_isArray, "isArray", 1);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_of, "of", 0);
addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_from, "from", 1);
Expand Down Expand Up @@ -291,6 +292,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 @@ -329,6 +334,7 @@ public Object execIdCall(
case ConstructorId_findIndex:
case ConstructorId_reduce:
case ConstructorId_reduceRight:
case ConstructorId_flat:
{
// this is a small trick; we will handle all the ConstructorId_xxx calls
// the same way the object calls are processed
Expand Down Expand Up @@ -427,6 +433,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 +1009,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 +1976,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 +2593,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 +2634,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 All @@ -2599,6 +2658,7 @@ protected int findPrototypeId(String s) {
ConstructorId_findIndex = -Id_findIndex,
ConstructorId_reduce = -Id_reduce,
ConstructorId_reduceRight = -Id_reduceRight,
ConstructorId_flat = -Id_flat,
ConstructorId_isArray = -26,
ConstructorId_of = -27,
ConstructorId_from = -28;
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";
15 changes: 15 additions & 0 deletions testsrc/org/mozilla/javascript/tests/harmony/ArrayFlatTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* 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")
// TODO: ES2019?
@LanguageVersion(Context.VERSION_ES6)
public class ArrayFlatTest extends ScriptTestsBase {}