Bug 1729563 - Implement change-array-by-copy methods r=mgaudet,arai

See https://tc39.es/proposal-change-array-by-copy/

    Added jit-test tests in change-array-by-copy.js

Depends on: D127201

Differential Revision: https://phabricator.services.mozilla.com/D126146
This commit is contained in:
Tim Chevalier 2021-10-22 18:38:10 +00:00
Родитель be12c6c29d
Коммит 7048979054
7 изменённых файлов: 537 добавлений и 93 удалений

Просмотреть файл

@ -31,6 +31,7 @@ const ErrorDocs = {
JSMSG_STMT_AFTER_RETURN: "Stmt_after_return",
JSMSG_NOT_A_CODEPOINT: "Not_a_codepoint",
JSMSG_BAD_SORT_ARG: "Array_sort_argument",
JSMSG_BAD_WITHSORTED_ARG: "Array_withSorted_argument",
JSMSG_UNEXPECTED_TYPE: "Unexpected_type",
JSMSG_NOT_DEFINED: "Not_defined",
JSMSG_NOT_FUNCTION: "Not_a_function",

Просмотреть файл

@ -57,6 +57,7 @@ MSG_DEF(JSMSG_INCOMPATIBLE_PROTO, 3, JSEXN_TYPEERR, "{0}.prototype.{1} call
MSG_DEF(JSMSG_INCOMPATIBLE_PROTO2, 3, JSEXN_TYPEERR, "{0}.prototype[{1}] called on incompatible {2}")
MSG_DEF(JSMSG_NO_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} has no constructor")
MSG_DEF(JSMSG_BAD_SORT_ARG, 0, JSEXN_TYPEERR, "invalid Array.prototype.sort argument")
MSG_DEF(JSMSG_BAD_WITHSORTED_ARG, 0, JSEXN_TYPEERR, "non-function passed to Array.prototype.withSorted")
MSG_DEF(JSMSG_READ_ONLY, 1, JSEXN_TYPEERR, "{0} is read-only")
MSG_DEF(JSMSG_CANT_DELETE, 1, JSEXN_TYPEERR, "property {0} is non-configurable and can't be deleted")
MSG_DEF(JSMSG_CANT_TRUNCATE_ARRAY, 0, JSEXN_TYPEERR, "can't delete non-configurable array element")

Просмотреть файл

@ -28,6 +28,7 @@
#include "js/experimental/JitInfo.h" // JSJitGetterOp, JSJitInfo
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/PropertyAndElement.h" // JS_DefineFunctions
#include "js/PropertySpec.h"
#include "util/Poison.h"
#include "util/StringBuffer.h"
@ -2769,6 +2770,69 @@ static bool CopyArrayElements(JSContext* cx, HandleObject obj, uint64_t begin,
return true;
}
/* Helpers for array_splice_impl() and array_with_spliced()
*
* Initialize variables common to splice() and withSpliced()
* GetActualStart() returns the index at which to start deleting elements.
* GetItemCount() returns the number of new elements being added.
* GetActualDeleteCount:() returns the number of elements being deleted.
*
*/
static bool GetActualStart(JSContext* cx, const CallArgs& args, uint64_t len,
uint64_t* result) {
double relativeStart;
if (!ToInteger(cx, args.get(0), &relativeStart)) {
return false;
}
if (relativeStart < 0) {
*result = uint64_t(std::max(double(len) + relativeStart, 0.0));
} else {
*result = uint64_t(std::min(relativeStart, double(len)));
}
return true;
}
static uint32_t GetItemCount(const CallArgs& args) {
if (args.length() < 2) {
return 0;
}
return (args.length() - 2);
}
static bool GetActualDeleteCount(JSContext* cx, const CallArgs& args,
HandleObject obj, uint64_t len,
uint64_t actualStart, uint64_t itemCount,
uint64_t* result) {
if (args.length() < 1) {
*result = 0;
} else if (args.length() < 2) {
*result = len - actualStart;
} else {
double deleteCount;
if (!ToInteger(cx, args.get(1), &deleteCount)) {
return false;
}
*result = uint64_t(std::min(std::max(0.0, deleteCount),
double(len) - double(actualStart)));
if (double(len + itemCount - *result) >= DOUBLE_INTEGRAL_PRECISION_LIMIT) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TOO_LONG_ARRAY);
return false;
}
}
MOZ_ASSERT(actualStart + *result <= len);
if (IsArraySpecies(cx, obj)) {
if (*result > UINT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_ARRAY_LENGTH);
return false;
}
}
return true;
}
static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
bool returnValueIsUsed) {
AutoGeckoProfilerEntry pseudoFrame(
@ -2788,58 +2852,27 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
return false;
}
/* Step 3. */
double relativeStart;
if (!ToInteger(cx, args.get(0), &relativeStart)) {
return false;
}
/* Step 4. */
/* Steps 3-6. */
/* actualStart is the index after which elements will be
deleted and/or new elements will be added */
uint64_t actualStart;
if (relativeStart < 0) {
actualStart = std::max(len + relativeStart, 0.0);
} else {
actualStart = std::min(relativeStart, double(len));
if (!GetActualStart(cx, args, len, &actualStart)) {
return false;
}
/* Step 5. */
/* Steps 7-10.*/
/* itemCount is the number of elements being added */
uint32_t itemCount = GetItemCount(args);
/* actualDeleteCount is the number of elements being deleted */
uint64_t actualDeleteCount;
if (args.length() == 0) {
/* Step 5.b. */
actualDeleteCount = 0;
} else if (args.length() == 1) {
/* Step 6.b. */
actualDeleteCount = len - actualStart;
} else {
/* Steps 7.b. */
double deleteCountDouble;
if (!ToInteger(cx, args[1], &deleteCountDouble)) {
if (!GetActualDeleteCount(cx, args, obj, len, actualStart, itemCount,
&actualDeleteCount)) {
return false;
}
/* Step 7.c. */
actualDeleteCount =
std::min(std::max(deleteCountDouble, 0.0), double(len - actualStart));
/* Step 8. */
uint32_t insertCount = args.length() - 2;
if (len + insertCount - actualDeleteCount >=
DOUBLE_INTEGRAL_PRECISION_LIMIT) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TOO_LONG_ARRAY);
return false;
}
}
MOZ_ASSERT(actualStart + actualDeleteCount <= len);
RootedObject arr(cx);
if (IsArraySpecies(cx, obj)) {
if (actualDeleteCount > UINT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_ARRAY_LENGTH);
return false;
}
uint32_t count = uint32_t(actualDeleteCount);
if (CanOptimizeForDenseStorage<ArrayAccess::Read>(obj,
@ -2848,7 +2881,7 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
"if actualStart + count <= UINT32_MAX, then actualStart <= "
"UINT32_MAX");
if (returnValueIsUsed) {
/* Steps 9-12. */
/* Steps 11-13. */
arr = CopyDenseArrayElements(cx, obj.as<NativeObject>(),
uint32_t(actualStart), count);
if (!arr) {
@ -2856,62 +2889,57 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
}
}
} else {
/* Step 9. */
/* Step 11. */
arr = NewDenseFullyAllocatedArray(cx, count);
if (!arr) {
return false;
}
/* Steps 10-11. */
/* Steps 12-13. */
if (!CopyArrayElements(cx, obj, actualStart, count,
arr.as<ArrayObject>())) {
return false;
}
/* Step 12 (implicit). */
}
} else {
/* Steps 9. */
/* Step 11. */
if (!ArraySpeciesCreate(cx, obj, actualDeleteCount, &arr)) {
return false;
}
/* Steps 10, 11, 11.d. */
/* Steps 12-13. */
RootedValue fromValue(cx);
for (uint64_t k = 0; k < actualDeleteCount; k++) {
/* Step 11.a (implicit). */
if (!CheckForInterrupt(cx)) {
return false;
}
/* Steps 11.b, 11.c.i. */
/* Steps 13.b, 13.c.i. */
bool hole;
if (!HasAndGetElement(cx, obj, actualStart + k, &hole, &fromValue)) {
return false;
}
/* Step 11.c. */
/* Step 13.c. */
if (!hole) {
/* Step 11.c.ii. */
/* Step 13.c.ii. */
if (!DefineArrayElement(cx, arr, k, fromValue)) {
return false;
}
}
}
/* Step 12. */
/* Step 14. */
if (!SetLengthProperty(cx, arr, actualDeleteCount)) {
return false;
}
}
/* Step 14. */
uint32_t itemCount = (args.length() >= 2) ? (args.length() - 2) : 0;
/* Step 15. */
uint64_t finalLength = len - actualDeleteCount + itemCount;
if (itemCount < actualDeleteCount) {
/* Step 15: the array is being shrunk. */
/* Step 16: the array is being shrunk. */
uint64_t sourceIndex = actualStart + actualDeleteCount;
uint64_t targetIndex = actualStart + itemCount;
@ -2921,14 +2949,14 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
MOZ_ASSERT(finalLength < len, "finalLength is strictly less than len");
MOZ_ASSERT(obj->is<NativeObject>());
/* Steps 15.a-b. */
/* Step 16.b. */
HandleArrayObject arr = obj.as<ArrayObject>();
if (targetIndex != 0 || !arr->tryShiftDenseElements(sourceIndex)) {
arr->moveDenseElements(uint32_t(targetIndex), uint32_t(sourceIndex),
uint32_t(len - sourceIndex));
}
/* Steps 15.c-d. */
/* Steps 20. */
SetInitializedLength(cx, arr, finalLength);
} else {
/*
@ -2938,7 +2966,7 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
* fallout.
*/
/* Steps 15.a-b. */
/* Step 16. */
RootedValue fromValue(cx);
for (uint64_t from = sourceIndex, to = targetIndex; from < len;
from++, to++) {
@ -2948,27 +2976,24 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
return false;
}
/* Steps 15.b.iii, 15.b.iv.1. */
/* Steps 16.b.iii-v */
bool hole;
if (!HasAndGetElement(cx, obj, from, &hole, &fromValue)) {
return false;
}
/* Steps 15.b.iv. */
if (hole) {
/* Steps 15.b.v.1. */
if (!DeletePropertyOrThrow(cx, obj, to)) {
return false;
}
} else {
/* Step 15.b.iv.2. */
if (!SetArrayElement(cx, obj, to, fromValue)) {
return false;
}
}
}
/* Steps 15.c-d. */
/* Step 16d. */
if (!DeletePropertiesOrThrow(cx, obj, len, finalLength)) {
return false;
}
@ -2977,7 +3002,7 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
MOZ_ASSERT(actualDeleteCount <= UINT32_MAX);
uint32_t deleteCount = uint32_t(actualDeleteCount);
/* Step 16. */
/* Step 17. */
// Fast path for when we can simply extend and move the dense elements.
auto extendElements = [len, itemCount, deleteCount](JSContext* cx,
@ -3033,7 +3058,7 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
arr->moveDenseElements(start + itemCount, start + deleteCount,
length - (start + deleteCount));
/* Steps 16.a-b. */
/* Step 20. */
SetInitializedLength(cx, arr, finalLength);
} else {
MOZ_ASSERT(res == DenseElementResult::Incomplete);
@ -3044,26 +3069,26 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
return false;
}
/* Step 16.b.i. */
/* Step 17.b.i. */
uint64_t from = k + actualDeleteCount - 1;
/* Step 16.b.ii. */
/* Step 17.b.ii. */
uint64_t to = k + itemCount - 1;
/* Steps 16.b.iii, 16.b.iv.1. */
/* Steps 17.b.iii, 17.b.iv.1. */
bool hole;
if (!HasAndGetElement(cx, obj, from, &hole, &fromValue)) {
return false;
}
/* Steps 16.b.iv. */
/* Steps 17.b.iv. */
if (hole) {
/* Step 16.b.v.1. */
/* Step 17.b.v.1. */
if (!DeletePropertyOrThrow(cx, obj, to)) {
return false;
}
} else {
/* Step 16.b.iv.2. */
/* Step 17.b.iv.2. */
if (!SetArrayElement(cx, obj, to, fromValue)) {
return false;
}
@ -3072,20 +3097,19 @@ static bool array_splice_impl(JSContext* cx, unsigned argc, Value* vp,
}
}
/* Step 13 (reordered). */
Value* items = args.array() + 2;
/* Steps 17-18. */
/* Steps 18-19. */
if (!SetArrayElements(cx, obj, actualStart, itemCount, items)) {
return false;
}
/* Step 19. */
/* Step 20. */
if (!SetLengthProperty(cx, obj, finalLength)) {
return false;
}
/* Step 20. */
/* Step 21. */
if (returnValueIsUsed) {
args.rval().setObject(*arr);
}
@ -3102,6 +3126,212 @@ static bool array_splice_noRetVal(JSContext* cx, unsigned argc, Value* vp) {
return array_splice_impl(cx, argc, vp, false);
}
#ifdef ENABLE_CHANGE_ARRAY_BY_COPY
static ArrayObject* NewDenseArray(JSContext* cx, const CallArgs& args,
uint64_t len) {
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Array, &proto)) {
return nullptr;
}
return NewDensePartlyAllocatedArrayWithProto(cx, len, proto);
}
/* Proposal
* https://github.com/tc39/proposal-change-array-by-copy
* Array.prototype.withSpliced()
*/
static bool array_with_spliced(JSContext* cx, unsigned argc, Value* vp) {
/* Currently doesn't use the optimizations array_splice() uses for
* dense arrays
*/
AutoGeckoProfilerEntry pseudoFrame(
cx, "Array.prototype.withSpliced", JS::ProfilingCategoryPair::JS,
uint32_t(ProfilingStackFrame::Flags::RELEVANT_FOR_JS));
CallArgs args = CallArgsFromVp(argc, vp);
/* Step 1. */
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj) {
return false;
}
/* Step 2. */
uint64_t len;
if (!GetLengthPropertyInlined(cx, obj, &len)) {
return false;
}
/* Steps 3-6. */
/* actualStart is the index after which elements will be
* deleted and/or new elements will be added
*/
uint64_t actualStart;
if (!GetActualStart(cx, args, len, &actualStart)) {
return false;
}
// insertCount is the number of elements being added
uint32_t insertCount = GetItemCount(args);
// actualDeleteCount is the number of elements being deleted
uint64_t actualDeleteCount;
if (!GetActualDeleteCount(cx, args, obj, len, actualStart, insertCount,
&actualDeleteCount)) {
return false;
}
/* Step 10. */
uint64_t newLen = len + insertCount - actualDeleteCount;
/* Step 11. */
RootedObject A(cx, NewDenseArray(cx, args, newLen));
if (!A) {
return false;
}
/* Steps 12-13. */
// Copy everything before start
uint64_t k = 0;
while (k < actualStart) {
RootedValue kValue(cx);
if (!GetArrayElement(cx, obj, k, &kValue)) {
return false;
}
if (!SetArrayElement(cx, A, k, kValue)) {
return false;
}
k++;
}
// result array now contains all elements before start
/* Steps 14-15.*/
// Copy new items
Value* items = args.array() + 2;
if (!SetArrayElements(cx, A, actualStart, insertCount, items)) {
return false;
}
k += insertCount;
/* Step 16. */
// Copy items after new items
while (k < newLen) {
uint64_t from = k + actualDeleteCount - insertCount;
RootedValue fromValue(cx);
if (!GetArrayElement(cx, obj, from, &fromValue)) {
return false;
}
if (!SetArrayElement(cx, A, k, fromValue)) {
return false;
}
k++;
}
/* Step 17. */
args.rval().setObject(*A);
return true;
}
bool IsIntegralNumber(JSContext* cx, HandleValue v, bool* result) {
double d;
if (!ToNumber(cx, v, &d)) {
return false;
}
if (mozilla::IsNaN(d) || !mozilla::IsFinite(d)) {
*result = false;
return true;
}
double integer = trunc(d);
*result = d - integer == 0;
return true;
}
/* Proposal
* https://github.com/tc39/proposal-change-array-by-copy
* Array.prototype.withAt()
*/
static bool array_with_at(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
/* Step 1. */
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj) {
return false;
}
/* Step 2. */
uint64_t len;
if (!GetLengthPropertyInlined(cx, obj, &len)) {
return false;
}
/* Step 3. */
int64_t index;
bool result;
if (!IsIntegralNumber(cx, args.get(0), &result)) {
return false;
}
if (!result) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
if (!ToInt64(cx, args.get(0), &index)) {
return false;
}
/* Step 4. */
if (index >= int64_t(len)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
/* Steps 5-6. */
int64_t actualIndex = index;
if (index < 0) {
actualIndex = int64_t(len + index);
}
/* Step 7. */
if (actualIndex < 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
// actualIndex must be non-negative at this point
/* Step 8 */
RootedObject A(cx, NewDenseArray(cx, args, len));
if (!A) {
return false;
}
/* Steps 9-10. */
for (uint64_t k = 0; k < len; k++) {
RootedValue fromValue(cx);
if (k == uint64_t(actualIndex)) {
fromValue = args.get(1);
} else {
if (!GetArrayElement(cx, obj, k, &fromValue)) {
return false;
}
}
if (!SetArrayElement(cx, A, k, fromValue)) {
return false;
}
}
/* Step 11. */
args.rval().setObject(*A);
return true;
}
#endif
struct SortComparatorIndexes {
bool operator()(uint32_t a, uint32_t b, bool* lessOrEqualp) {
*lessOrEqualp = (a <= b);
@ -3635,6 +3865,16 @@ static const JSFunctionSpec array_methods[] = {
JS_FS_END};
#ifdef ENABLE_CHANGE_ARRAY_BY_COPY
static const JSFunctionSpec change_array_by_copy_methods[] = {
JS_SELF_HOSTED_FN("withReversed", "ArrayWithReversed", 0, 0),
JS_SELF_HOSTED_FN("withSorted", "ArrayWithSorted", 1, 0),
JS_FN("withSpliced", array_with_spliced, 2, 0),
JS_FN("withAt", array_with_at, 2, 0),
JS_FS_END};
#endif
static const JSFunctionSpec array_static_methods[] = {
JS_INLINABLE_FN("isArray", array_isArray, 1, 0, ArrayIsArray),
JS_SELF_HOSTED_FN("from", "ArrayFrom", 3, 0), JS_FN("of", array_of, 0, 0),
@ -3884,6 +4124,21 @@ static bool array_proto_finish(JSContext* cx, JS::HandleObject ctor,
return false;
}
#ifdef ENABLE_CHANGE_ARRAY_BY_COPY
if (cx->options().changeArrayByCopy()) {
if (!DefineDataProperty(cx, unscopables, cx->names().withAt, value) ||
!DefineDataProperty(cx, unscopables, cx->names().withReversed, value) ||
!DefineDataProperty(cx, unscopables, cx->names().withSorted, value) ||
!DefineDataProperty(cx, unscopables, cx->names().withSpliced, value)) {
return false;
}
if (!JS_DefineFunctions(cx, proto, change_array_by_copy_methods)) {
return false;
}
}
#endif
RootedId id(cx, SYMBOL_TO_JSID(
cx->wellKnownSymbols().get(JS::SymbolCode::unscopables)));
value.setObject(*unscopables);

Просмотреть файл

@ -152,6 +152,10 @@ SetIsInlinableLargeFunction(ArraySome);
// ES2018 draft rev 3bbc87cd1b9d3bf64c3e68ca2fe9c5a3f2c304c0
// 22.1.3.25 Array.prototype.sort ( comparefn )
function ArraySort(comparefn) {
return SortArray(this, comparefn);
}
function SortArray(obj, comparefn) {
// Step 1.
if (comparefn !== undefined) {
if (!IsCallable(comparefn))
@ -159,7 +163,7 @@ function ArraySort(comparefn) {
}
// Step 2.
var O = ToObject(this);
var O = ToObject(obj);
// First try to sort the array in native code, if that fails, indicated by
// returning |false| from ArrayNativeSort, sort it in self-hosted code.
@ -1182,3 +1186,67 @@ function ArrayAt(index) {
}
// This function is only barely too long for normal inlining.
SetIsInlinableLargeFunction(ArrayAt);
#ifdef ENABLE_CHANGE_ARRAY_BY_COPY
// https://github.com/tc39/proposal-change-array-by-copy
// Array.prototype.withReversed()
function ArrayWithReversed() {
/* Step 1. */
var O = ToObject(this);
/* Step 2. */
var len = ToLength(O.length);
/* Step 3. */
var A = std_Array(len);
/* Steps 4-5. */
for (var k = 0; k < len; k++) {
/* Step 5a. */
var from = len - k - 1;
/* Step 5b - not necessary. */
/* Step 5c. */
var fromValue = O[from];
/* Step 5d. */
DefineDataProperty(A, k, fromValue);
}
/* Step 6. */
return A;
}
// https://github.com/tc39/proposal-change-array-by-copy
// Array.prototype.withSorted()
function ArrayWithSorted(comparefn) {
/* Step 1. */
if (comparefn !== undefined && !IsCallable(comparefn)) {
ThrowTypeError(JSMSG_BAD_WITHSORTED_ARG);
}
/* Step 2. */
var O = ToObject(this);
/* Step 3. */
var len = ToLength(O.length);
/* Step 4. */
var items = std_Array(len);
/* Steps 5-6. */
for(var k = 0; k < len; k++) {
DefineDataProperty(items, k, O[k]);
}
/* Step 7. */
SortArray(items, comparefn);
/* Steps 8-10 unnecessary */
/* Step 11. */
return items;
}
#endif

Просмотреть файл

@ -0,0 +1,103 @@
// |jit-test| --enable-change-array-by-copy; skip-if: !getBuildConfiguration()['change-array-by-copy']
load(libdir + 'array-compare.js');
load(libdir + "asserts.js");
var sequence = [1, 2, 3];
let reversedSequence = sequence.withReversed();
assertEq(arraysEqual(sequence, [1, 2, 3]), true);
assertEq(arraysEqual(reversedSequence, [3, 2, 1]), true);
sequence = [87, 3, 5, 888, 321, 42];
var sortedSequence = sequence.withSorted((x, y) => (x >= y));
assertEq(arraysEqual(sequence, [87, 3, 5, 888, 321, 42]), true);
assertEq(arraysEqual(sortedSequence, [3, 5, 42, 87, 321, 888]), true);
sequence = ["the", "quick", "fox", "jumped", "over", "the", "lazy", "dog"];
sortedSequence = sequence.withSorted();
assertEq(arraysEqual(sequence, ["the", "quick", "fox", "jumped", "over", "the", "lazy", "dog"]), true);
assertEq(arraysEqual(sortedSequence, ["dog", "fox", "jumped", "lazy", "over", "quick", "the", "the"]), true);
/* Test that the correct exception is thrown with a
non-function comparefn argument */
assertThrowsInstanceOf(() => sequence.withSorted([1, 2, 3]), TypeError);
/* withSpliced */
/* examples from:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice */
function unchanged(a) {
assertEq(arraysEqual(a, ['angel', 'clown', 'mandarin', 'sturgeon']), true);
}
// Remove no elements before index 2, insert "drum"
let myFish = ['angel', 'clown', 'mandarin', 'sturgeon']
var myFishSpliced = myFish.withSpliced(2, 0, 'drum')
unchanged(myFish);
assertEq(arraysEqual(myFishSpliced, ['angel', 'clown', 'drum', 'mandarin', 'sturgeon']), true);
// Remove no elements before index 2, insert "drum" and "guitar"
var myFishSpliced = myFish.withSpliced(2, 0, 'drum', 'guitar');
unchanged(myFish);
assertEq(arraysEqual(myFishSpliced, ['angel', 'clown', 'drum', 'guitar', 'mandarin', 'sturgeon']), true);
// Remove 1 element at index 3
let myFish1 = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon']
myFishSpliced = myFish1.withSpliced(3, 1);
assertEq(arraysEqual(myFish1, ['angel', 'clown', 'drum', 'mandarin', 'sturgeon']), true);
assertEq(arraysEqual(myFishSpliced, ['angel', 'clown', 'drum', 'sturgeon']), true);
// Remove 1 element at index 2, and insert 'trumpet'
let myFish2 = ['angel', 'clown', 'drum', 'sturgeon']
myFishSpliced = myFish2.withSpliced(2, 1, 'trumpet');
assertEq(arraysEqual(myFish2, ['angel', 'clown', 'drum', 'sturgeon']), true);
assertEq(arraysEqual(myFishSpliced, ['angel', 'clown', 'trumpet', 'sturgeon']), true);
// Remove 2 elements at index 0, and insert 'parrot', 'anemone', and 'blue'
let myFish3 = ['angel', 'clown', 'trumpet', 'sturgeon']
myFishSpliced = myFish3.withSpliced(0, 2, 'parrot', 'anemone', 'blue');
assertEq(arraysEqual(myFish3, ['angel', 'clown', 'trumpet', 'sturgeon']), true);
assertEq(arraysEqual(myFishSpliced, ['parrot', 'anemone', 'blue', 'trumpet', 'sturgeon']), true);
// Remove 2 elements, starting at index 2
let myFish4 = ['parrot', 'anemone', 'blue', 'trumpet', 'sturgeon']
myFishSpliced = myFish4.withSpliced(2, 2);
assertEq(arraysEqual(myFish4, ['parrot', 'anemone', 'blue', 'trumpet', 'sturgeon']), true);
assertEq(arraysEqual(myFishSpliced, ['parrot', 'anemone', 'sturgeon']), true);
// Remove 1 element from index -2
myFishSpliced = myFish.withSpliced(-2, 1);
unchanged(myFish);
assertEq(arraysEqual(myFishSpliced, ['angel', 'clown', 'sturgeon']), true);
// Remove all elements, starting from index 2
myFishSpliced = myFish.withSpliced(2);
unchanged(myFish);
assertEq(arraysEqual(myFishSpliced, ['angel', 'clown']), true);
/* withAt */
sequence = [1, 2, 3];
let seq_withAt = sequence.withAt(1, 42);
assertEq(arraysEqual(sequence, [1, 2, 3]), true);
assertEq(arraysEqual(seq_withAt, [1, 42, 3]), true);
/* false/true => index 0/1 */
assertEq(arraysEqual(sequence.withAt(false, 42), [42, 2, 3]), true);
assertEq(arraysEqual(sequence.withAt(true, 42), [1, 42, 3]), true);
/* null => 0 */
assertEq(arraysEqual(sequence.withAt(null, 42), [42, 2, 3]), true);
/* [] => 0 */
assertEq(arraysEqual(sequence.withAt([], 42), [42, 2, 3]), true);
assertEq(arraysEqual(sequence.withAt("2", 42), [1, 2, 42]), true);
assertThrowsInstanceOf(() => sequence.withAt(3, 42), RangeError);
assertThrowsInstanceOf(() => sequence.withAt(5, 42), RangeError);
assertThrowsInstanceOf(() => sequence.withAt(-10, 42), RangeError);
assertThrowsInstanceOf(() => sequence.withAt("monkeys", 42), RangeError);
assertThrowsInstanceOf(() => sequence.withAt(Infinity, 42), RangeError);
assertThrowsInstanceOf(() => sequence.withAt(undefined, 42), RangeError);
assertThrowsInstanceOf(() => sequence.withAt(function() {}, 42), RangeError);

Просмотреть файл

@ -20,8 +20,7 @@ assertDeepEq(desc2, {
let keys = Reflect.ownKeys(Array_unscopables);
assertDeepEq(keys, [
"at",
let expectedKeys = ["at",
"copyWithin",
"entries",
"fill",
@ -31,8 +30,21 @@ assertDeepEq(keys, [
"flatMap",
"includes",
"keys",
"values"
]);
"values"];
if (typeof getBuildConfiguration === "undefined") {
var getBuildConfiguration = SpecialPowers.Cu.getJSTestingFunctions().getBuildConfiguration;
}
if (typeof getRealmConfiguration === "undefined") {
var getRealmConfiguration = SpecialPowers.Cu.getJSTestingFunctions().getRealmConfiguration;
}
if (getBuildConfiguration()['change-array-by-copy'] && getRealmConfiguration().enableChangeArrayByCopy) {
expectedKeys.push("withAt", "withReversed", "withSorted", "withSpliced");
}
assertDeepEq(keys, expectedKeys);
for (let key of keys)
assertEq(Array_unscopables[key], true);

Просмотреть файл

@ -542,6 +542,10 @@
MACRO_(weekend, weekend, "weekend") \
MACRO_(while, while_, "while") \
MACRO_(with, with, "with") \
MACRO_(withAt, withAt, "withAt") \
MACRO_(withReversed, withReversed, "withReversed") \
MACRO_(withSorted, withSorted, "withSorted") \
MACRO_(withSpliced, withSpliced, "withSpliced") \
MACRO_(writable, writable, "writable") \
MACRO_(write, write, "write") \
MACRO_(year, year, "year") \