Commit a5336471 authored by Caitlin Potter's avatar Caitlin Potter Committed by Commit Bot

[builtins] Implement Object.fromEntries

Adds the Object.fromEntries() method behind
--harmony-object-from-entries.


Includes an initial implementation of the new experimental builtin
Object.fromEntries implemented by Daniel Clifford, and
has been modified by Caitlin Potter to support a fast case to skip
the iterator protocol when it can be done unobservably in common cases.

There are some incidental changes: A number of CSA macros have been
updated to use TNodes, and some Context arguments have been
re-arranged to be implicit in Torque.


There are also a number of mjsunit tests written mirroring and
expanding on the test262 tests.

BUG=v8:8021

Change-Id: I1c12bee8a2f98c6297b77d5d723910a5e3b630cc
Co-authored-by: 's avatarDaniel Clifford <danno@chromium.org>
Co-authored-by: 's avatarCaitlin Potter <caitp@igalia.com>
Reviewed-on: https://chromium-review.googlesource.com/c/1337585
Commit-Queue: Daniel Clifford <danno@chromium.org>
Reviewed-by: 's avatarDaniel Clifford <danno@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57667}
parent b84766b3
......@@ -925,9 +925,11 @@ torque_files = [
"src/builtins/array-splice.tq",
"src/builtins/array-unshift.tq",
"src/builtins/collections.tq",
"src/builtins/typed-array.tq",
"src/builtins/data-view.tq",
"src/builtins/object.tq",
"src/builtins/object-fromentries.tq",
"src/builtins/iterator.tq",
"src/builtins/typed-array.tq",
"test/torque/test-torque.tq",
"third_party/v8/builtins/array-sort.tq",
]
......@@ -937,6 +939,7 @@ torque_namespaces = [
"array",
"collections",
"iterator",
"object",
"typed-array",
"data-view",
"test",
......
......@@ -4903,6 +4903,12 @@ void Genesis::InitializeGlobal_harmony_intl_segmenter() {
#endif // V8_INTL_SUPPORT
void Genesis::InitializeGlobal_harmony_object_from_entries() {
if (!FLAG_harmony_object_from_entries) return;
SimpleInstallFunction(isolate(), isolate()->object_function(), "fromEntries",
Builtins::kObjectFromEntries, 1, false);
}
Handle<JSFunction> Genesis::CreateArrayBuffer(
Handle<String> name, ArrayBufferKind array_buffer_kind) {
// Create the %ArrayBufferPrototype%
......
......@@ -41,10 +41,20 @@ type JSArgumentsObjectWithLength extends JSObject
generates 'TNode<JSArgumentsObjectWithLength>';
type JSArray extends JSArgumentsObjectWithLength
generates 'TNode<JSArray>';
// A HeapObject with a JSArray map, and either fast packed elements, or fast
// holey elements when the global NoElementsProtector is not invalidated.
transient type FastJSArray extends JSArray
generates 'TNode<JSArray>';
// A FastJSArray when the global ArraySpeciesProtector is not invalidated.
transient type FastJSArrayForCopy extends FastJSArray
generates 'TNode<JSArray>';
// A FastJSArray when the global ArrayIteratorProtector is not invalidated.
transient type FastJSArrayWithNoCustomIteration extends FastJSArray
generates 'TNode<JSArray>';
type JSFunction extends JSObject generates 'TNode<JSFunction>';
type JSBoundFunction extends JSObject generates 'TNode<JSBoundFunction>';
type Callable = JSFunction | JSBoundFunction | JSProxy;
......@@ -67,6 +77,8 @@ const ARRAY_JOIN_STACK_INDEX: constexpr NativeContextSlot
generates 'Context::ARRAY_JOIN_STACK_INDEX';
const OBJECT_FUNCTION_INDEX: constexpr NativeContextSlot
generates 'Context::OBJECT_FUNCTION_INDEX';
const ITERATOR_RESULT_MAP_INDEX: constexpr NativeContextSlot
generates 'Context::ITERATOR_RESULT_MAP_INDEX';
extern operator '[]' macro LoadContextElement(
NativeContext, NativeContextSlot): Object;
extern operator '[]=' macro StoreContextElement(
......@@ -843,6 +855,8 @@ extern macro RawCastObjectToJSArgumentsObjectWithLength(Object):
JSArgumentsObjectWithLength;
extern macro RawCastObjectToFastJSArray(Object): FastJSArray;
extern macro RawCastObjectToFastJSArrayForCopy(Object): FastJSArrayForCopy;
extern macro RawCastObjectToFastJSArrayWithNoCustomIteration(Object):
FastJSArrayWithNoCustomIteration;
macro BranchIfJSArgumentsObjectWithLength(implicit context: Context)(o: Object):
never
......@@ -911,6 +925,21 @@ Cast<FastJSArrayForCopy>(implicit context: Context)(o: Object):
}
}
UnsafeCast<FastJSArrayWithNoCustomIteration>(implicit context: Context)(
o: Object): FastJSArrayWithNoCustomIteration {
assert(BranchIfFastJSArrayWithNoCustomIteration(o));
return RawCastObjectToFastJSArrayWithNoCustomIteration(o);
}
Cast<FastJSArrayWithNoCustomIteration>(implicit context: Context)(o: Object):
FastJSArrayWithNoCustomIteration labels CastError {
if (BranchIfFastJSArrayWithNoCustomIteration(o)) {
return UnsafeCast<FastJSArrayWithNoCustomIteration>(o);
} else {
goto CastError;
}
}
UnsafeCast<JSFunction>(implicit context: Context)(o: Object): JSFunction {
assert(IsJSFunction(Cast<HeapObject>(o) otherwise unreachable));
return RawCastObjectToJSFunction(o);
......@@ -951,6 +980,9 @@ extern macro BranchIfFastJSArray(Object, Context): never
labels Taken, NotTaken;
extern macro BranchIfFastJSArrayForCopy(Object, Context): never
labels Taken, NotTaken;
extern macro BranchIfFastJSArrayWithNoCustomIteration(
implicit context: Context)(Object): never labels Taken,
NotTaken;
extern macro BranchIfNotFastJSArray(Object, Context): never
labels Taken, NotTaken;
macro BranchIfNotFastJSArrayForCopy(implicit context: Context)(o: Object): never
......@@ -1078,7 +1110,7 @@ extern macro CopyFixedArrayElements(
extern macro AllocateJSArray(constexpr ElementsKind, Map, intptr, Smi): JSArray;
extern macro AllocateJSArray(constexpr ElementsKind, Map, Smi, Smi): JSArray;
extern macro AllocateJSObjectFromMap(Map): HeapObject;
extern macro AllocateJSObjectFromMap(Map): JSObject;
extern operator '[]=' macro StoreFixedDoubleArrayElementSmi(
FixedDoubleArray, Smi, float64): void;
......@@ -1195,9 +1227,22 @@ extern macro IsNumber(Object): bool;
extern macro IsExtensibleMap(Map): bool;
extern macro IsCustomElementsReceiverInstanceType(int32): bool;
extern macro IsFastJSArray(Object, Context): bool;
extern macro IsFastJSArrayWithNoCustomIteration(implicit context: Context)(
Object): bool;
extern macro Typeof(Object): Object;
extern macro LoadTargetFromFrame(): JSFunction;
macro IsJSReceiver(o: Object): bool {
typeswitch (o) {
case (JSReceiver): {
return true;
}
case (Object): {
return false;
}
}
}
// Return true iff number is NaN.
macro NumberIsNaN(number: Number): bool {
typeswitch (number) {
......
......@@ -173,7 +173,7 @@ void BaseCollectionsAssembler::AddConstructorEntries(
Variant variant, TNode<Context> context, TNode<Context> native_context,
TNode<Object> collection, TNode<Object> initial_entries) {
TVARIABLE(BoolT, use_fast_loop,
IsFastJSArrayWithNoCustomIteration(initial_entries, context));
IsFastJSArrayWithNoCustomIteration(context, initial_entries));
TNode<IntPtrT> at_least_space_for =
EstimatedInitialSize(initial_entries, use_fast_loop.value());
Label allocate_table(this, &use_fast_loop), exit(this), fast_loop(this),
......@@ -193,8 +193,8 @@ void BaseCollectionsAssembler::AddConstructorEntries(
TNode<JSArray> initial_entries_jsarray =
UncheckedCast<JSArray>(initial_entries);
#if DEBUG
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(initial_entries_jsarray,
context));
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(
context, initial_entries_jsarray));
TNode<Map> original_initial_entries_map = LoadMap(initial_entries_jsarray);
#endif
......@@ -243,7 +243,7 @@ void BaseCollectionsAssembler::AddConstructorEntriesFromFastJSArray(
CSA_ASSERT(
this,
WordEqual(GetAddFunction(variant, native_context, collection), add_func));
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(fast_jsarray, context));
CSA_ASSERT(this, IsFastJSArrayWithNoCustomIteration(context, fast_jsarray));
TNode<IntPtrT> length = SmiUntag(LoadFastJSArrayLength(fast_jsarray));
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(length, IntPtrConstant(0)));
CSA_ASSERT(
......
......@@ -256,7 +256,7 @@ TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler) {
Label slow_path(this);
GotoIfNot(IsFastJSArrayWithNoCustomIteration(iterable, context), &slow_path);
GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable), &slow_path);
// The fast path will copy holes to the new array.
TailCallBuiltin(Builtins::kCloneFastJSArray, context, iterable);
......@@ -270,7 +270,7 @@ void IteratorBuiltinsAssembler::FastIterableToList(
TVariable<Object>* var_result, Label* slow) {
Label done(this), check_string(this), check_map(this), check_set(this);
GotoIfNot(IsFastJSArrayWithNoCustomIteration(iterable, context),
GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable),
&check_string);
// Fast path for fast JSArray.
......
// 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.
#ifndef V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
#define V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
#include "src/code-stub-assembler.h"
namespace v8 {
namespace internal {} // namespace internal
} // namespace v8
#endif // V8_BUILTINS_BUILTINS_OBJECT_GEN_H_
// 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.
namespace object {
transitioning macro ObjectFromEntriesFastCase(implicit context: Context)(
iterable: Object): JSObject labels IfSlow {
typeswitch (iterable) {
case (array: FastJSArrayWithNoCustomIteration): {
const elements: FixedArray =
Cast<FixedArray>(array.elements) otherwise IfSlow;
const length: Smi = array.length;
const result: JSObject = AllocateEmptyJSObject();
for (let k: Smi = 0; k < length; ++k) {
const value: Object = array::LoadElementOrUndefined(elements, k);
const pair: KeyValuePair =
collections::LoadKeyValuePairNoSideEffects(value)
otherwise IfSlow;
// Bail out if ToPropertyKey will attempt to load and call
// Symbol.toPrimitive, toString, and valueOf, which could
// invalidate assumptions about the iterable.
if (IsJSReceiver(pair.key)) goto IfSlow;
CreateDataProperty(result, pair.key, pair.value);
}
return result;
}
case (Object): {
goto IfSlow;
}
}
}
transitioning javascript builtin
ObjectFromEntries(implicit context: Context)(receiver: Object): Object {
const iterable: Object = receiver;
try {
if (IsNullOrUndefined(iterable)) goto Throw;
return ObjectFromEntriesFastCase(iterable) otherwise IfSlow;
}
label IfSlow {
const result: JSObject = AllocateEmptyJSObject();
const fastIteratorResultMap: Map =
Cast<Map>(LoadNativeContext(context)[ITERATOR_RESULT_MAP_INDEX])
otherwise unreachable;
let i: iterator::IteratorRecord = iterator::GetIterator(iterable);
try {
assert(!IsNullOrUndefined(i.object));
while (true) {
const step: Object = iterator::IteratorStep(i, fastIteratorResultMap)
otherwise return result;
const iteratorValue: Object =
iterator::IteratorValue(step, fastIteratorResultMap);
const pair: KeyValuePair =
collections::LoadKeyValuePair(iteratorValue);
CreateDataProperty(result, pair.key, pair.value);
}
return result;
} catch (e) deferred {
iterator::IteratorCloseOnException(i, e);
}
}
label Throw deferred {
ThrowTypeError(context, kNotIterable);
}
}
} // namespace object
// 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.
namespace object {
macro AllocateEmptyJSObject(implicit context: Context)(): JSObject {
const objectFunction: JSFunction = GetObjectFunction();
const map: Map = Cast<Map>(objectFunction.prototype_or_initial_map)
otherwise unreachable;
return AllocateJSObjectFromMap(map);
}
}
......@@ -1056,22 +1056,29 @@ TNode<BoolT> CodeStubAssembler::IsFastJSArray(SloppyTNode<Object> object,
return var_result.value();
}
TNode<BoolT> CodeStubAssembler::IsFastJSArrayWithNoCustomIteration(
TNode<Object> object, TNode<Context> context) {
Label if_false(this, Label::kDeferred), if_fast(this), exit(this);
TVARIABLE(BoolT, var_result);
BranchIfFastJSArray(object, context, &if_fast, &if_false, true);
void CodeStubAssembler::BranchIfFastJSArrayWithNoCustomIteration(
TNode<Context> context, TNode<Object> object, Label* if_true,
Label* if_false) {
Label if_fast(this);
BranchIfFastJSArray(object, context, &if_fast, if_false, true);
BIND(&if_fast);
{
// Check that the Array.prototype hasn't been modified in a way that would
// affect iteration.
Node* protector_cell = LoadRoot(RootIndex::kArrayIteratorProtector);
DCHECK(isolate()->heap()->array_iterator_protector()->IsPropertyCell());
var_result =
Branch(
WordEqual(LoadObjectField(protector_cell, PropertyCell::kValueOffset),
SmiConstant(Isolate::kProtectorValid));
Goto(&exit);
SmiConstant(Isolate::kProtectorValid)),
if_true, if_false);
}
}
TNode<BoolT> CodeStubAssembler::IsFastJSArrayWithNoCustomIteration(
TNode<Context> context, TNode<Object> object) {
Label if_false(this, Label::kDeferred), exit(this);
TVARIABLE(BoolT, var_result, Int32TrueConstant());
BranchIfFastJSArrayWithNoCustomIteration(context, object, &exit, &if_false);
BIND(&if_false);
{
var_result = Int32FalseConstant();
......
......@@ -416,6 +416,11 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
return TNode<JSArray>::UncheckedCast(p_o);
}
TNode<JSArray> RawCastObjectToFastJSArrayWithNoCustomIteration(
TNode<Object> p_o) {
return TNode<JSArray>::UncheckedCast(p_o);
}
TNode<JSFunction> RawCastObjectToJSFunction(TNode<Object> p_o) {
return TNode<JSFunction>::UncheckedCast(p_o);
}
......@@ -807,6 +812,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
}
void BranchIfFastJSArrayForCopy(Node* object, Node* context, Label* if_true,
Label* if_false);
void BranchIfFastJSArrayWithNoCustomIteration(TNode<Context> context,
TNode<Object> object,
Label* if_true,
Label* if_false);
// Branches to {if_true} when --force-slow-path flag has been passed.
// It's used for testing to ensure that slow path implementation behave
......@@ -2022,8 +2031,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<BoolT> IsExternalStringInstanceType(SloppyTNode<Int32T> instance_type);
TNode<BoolT> IsFastJSArray(SloppyTNode<Object> object,
SloppyTNode<Context> context);
TNode<BoolT> IsFastJSArrayWithNoCustomIteration(TNode<Object> object,
TNode<Context> context);
TNode<BoolT> IsFastJSArrayWithNoCustomIteration(TNode<Context> context,
TNode<Object> object);
TNode<BoolT> IsFeedbackCell(SloppyTNode<HeapObject> object);
TNode<BoolT> IsFeedbackVector(SloppyTNode<HeapObject> object);
TNode<BoolT> IsContext(SloppyTNode<HeapObject> object);
......
......@@ -199,7 +199,8 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields)
V(harmony_await_optimization, "harmony await taking 1 tick") \
V(harmony_private_methods, "harmony private methods in class literals") \
V(harmony_regexp_sequence, "RegExp Unicode sequence properties") \
V(harmony_weak_refs, "harmony weak references")
V(harmony_weak_refs, "harmony weak references") \
V(harmony_object_from_entries, "harmony Object.fromEntries()")
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \
......
// 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.
// Flags: --harmony-object-from-entries
const fromEntries = Object.fromEntries;
const ObjectPrototype = Object.prototype;
const ObjectPrototypeHasOwnProperty = ObjectPrototype.hasOwnProperty;
function hasOwnProperty(O, Name) {
if (O === undefined || O === null) return false;
return ObjectPrototypeHasOwnProperty.call(O, Name);
}
let test = {
methodExists() {
assertTrue(hasOwnProperty(Object, "fromEntries"));
assertEquals("function", typeof Object.fromEntries);
},
methodLength() {
assertEquals(1, Object.fromEntries.length);
},
methodName() {
assertEquals("fromEntries", Object.fromEntries.name);
},
methodPropertyDescriptor() {
let descriptor = Object.getOwnPropertyDescriptor(Object, "fromEntries");
assertFalse(descriptor.enumerable);
assertTrue(descriptor.configurable);
assertTrue(descriptor.writable);
assertEquals(descriptor.value, Object.fromEntries);
},
exceptionIfNotCoercible() {
assertThrows(() => fromEntries(null), TypeError);
assertThrows(() => fromEntries(undefined), TypeError);
},
exceptionIfNotIterable() {
let nonIterable = [1, 2, 3, 4, 5];
Object.defineProperty(nonIterable, Symbol.iterator, { value: undefined });
assertThrows(() => fromEntries(nonIterable), TypeError);
},
exceptionIfGetIteratorThrows() {
let iterable = [1, 2, 3, 4, 5];
class ThrewDuringGet {};
Object.defineProperty(iterable, Symbol.iterator, {
get() { throw new ThrewDuringGet(); }
});
assertThrows(() => fromEntries(iterable), ThrewDuringGet);
},
exceptionIfCallIteratorThrows() {
let iterable = [1, 2, 3, 4, 5];
class ThrewDuringCall {};
iterable[Symbol.iterator] = function() {
throw new ThrewDuringCall();
}
assertThrows(() => fromEntries(iterable), ThrewDuringCall);
},
exceptionIfIteratorNextThrows() {
let iterable = [1, 2, 3, 4, 5];
class ThrewDuringIteratorNext {}
iterable[Symbol.iterator] = function() {
return {
next() { throw new ThrewDuringIteratorNext; },
return() {
throw new Error(
"IteratorClose must not be performed if IteratorStep throws");
},
}
}
assertThrows(() => fromEntries(iterable), ThrewDuringIteratorNext);
},
exceptionIfIteratorCompleteThrows() {
let iterable = [1, 2, 3, 4, 5];
class ThrewDuringIteratorComplete {}
iterable[Symbol.iterator] = function() {
return {
next() {
return {
get value() { throw new Error(
"IteratorValue must not be performed before IteratorComplete");
},
get done() {
throw new ThrewDuringIteratorComplete();
}
}
throw new ThrewDuringIteratorNext;
},
return() {
throw new Error(
"IteratorClose must not be performed if IteratorStep throws");
},
}
}
assertThrows(() => fromEntries(iterable), ThrewDuringIteratorComplete);
},
exceptionIfEntryIsNotObject() {
{
// Fast path (Objects/Smis)
let iterables = [[null], [undefined], [1], [NaN], [false], [Symbol()],
[""]];
for (let iterable of iterables) {
assertThrows(() => fromEntries(iterable), TypeError);
}
}
{
// Fast path (Doubles)
let iterable = [3.7, , , 3.6, 1.1, -0.4];
assertThrows(() => fromEntries(iterable), TypeError);
}
{
// Slow path
let i = 0;
let values = [null, undefined, 1, NaN, false, Symbol(), ""];
let iterable = {
[Symbol.iterator]() { return this; },
next() {
return {
done: i >= values.length,
value: values[i++],
}
},
};
for (let k = 0; k < values.length; ++k) {
assertThrows(() => fromEntries(iterable), TypeError);
}
assertEquals({}, fromEntries(iterable));
}
},
returnIfEntryIsNotObject() {
// Only observable/verifiable in the slow path :(
let i = 0;
let didCallReturn = false;
let values = [null, undefined, 1, NaN, false, Symbol(), ""];
let iterable = {
[Symbol.iterator]() { return this; },
next() {
return {
done: i >= values.length,
value: values[i++],
}
},
return() { didCallReturn = true; throw new Error("Unused!"); }
};
for (let k = 0; k < values.length; ++k) {
didCallReturn = false;
assertThrows(() => fromEntries(iterable), TypeError);
assertTrue(didCallReturn);
}
assertEquals({}, fromEntries(iterable));
},
returnIfEntryKeyAccessorThrows() {
class ThrewDuringKeyAccessor {};
let entries = [{ get 0() { throw new ThrewDuringKeyAccessor(); },
get 1() { throw new Error("Unreachable!"); } }];
let didCallReturn = false;
let iterator = entries[Symbol.iterator]();
iterator.return = function() {
didCallReturn = true;
throw new Error("Unused!");
}
assertThrows(() => fromEntries(iterator), ThrewDuringKeyAccessor);
assertTrue(didCallReturn);
},
returnIfEntryKeyAccessorThrows() {
class ThrewDuringValueAccessor {};
let entries = [{ get 1() { throw new ThrewDuringValueAccessor(); },
0: "key",
}];
let didCallReturn = false;
let iterator = entries[Symbol.iterator]();
iterator.return = function() {
didCallReturn = true;
throw new Error("Unused!");
};
assertThrows(() => fromEntries(iterator), ThrewDuringValueAccessor);
assertTrue(didCallReturn);
},
returnIfKeyToStringThrows() {
class ThrewDuringKeyToString {};
let operations = [];
let entries = [{
get 0() {
operations.push("[[Get]] key");
return {
toString() {
operations.push("toString(key)");
throw new ThrewDuringKeyToString();
},
valueOf() {
operations.push("valueOf(key)");
}
};
},
get 1() {
operations.push("[[Get]] value");
return "value";
},
}];
let iterator = entries[Symbol.iterator]();
iterator.return = function() {
operations.push("IteratorClose");
throw new Error("Unused!");
};
assertThrows(() => fromEntries(iterator), ThrewDuringKeyToString);
assertEquals([
"[[Get]] key",
"[[Get]] value",
"toString(key)",
"IteratorClose",
], operations);
},
throwsIfIteratorValueThrows() {
let iterable = [1, 2, 3, 4, 5];
class ThrewDuringIteratorValue {}
iterable[Symbol.iterator] = function() {
return {
next() {
return {
get value() { throw new ThrewDuringIteratorValue(); },
get done() { return false; }
}
throw new ThrewDuringIteratorNext;
},
return() {
throw new Error(
"IteratorClose must not be performed if IteratorStep throws");
},
}
}
assertThrows(() => fromEntries(iterable), ThrewDuringIteratorValue);
},
emptyIterable() {
let iterables = [[], new Set(), new Map()];
for (let iterable of iterables) {
let result = fromEntries(iterable);
assertEquals({}, result);
assertEquals(ObjectPrototype, result.__proto__);
}
},
keyOrderFastPath() {
let entries = [
["z", 1],
["y", 2],
["x", 3],
["y", 4],
[100, 0],
];
let result = fromEntries(entries);
assertEquals({
100: 0,
z: 1,
y: 4,
x: 3,
}, result);
assertEquals(["100", "z", "y", "x"], Object.keys(result));
},
keyOrderSlowPath() {
let entries = [
["z", 1],
["y", 2],
["x", 3],
["y", 4],
[100, 0],
];
let i = 0;
let iterable = {
[Symbol.iterator]() { return this; },
next() {
return {
done: i >= entries.length,
value: entries[i++]
}
},
return() { throw new Error("Unreachable!"); }
};
let result = fromEntries(iterable);
assertEquals({
100: 0,
z: 1,
y: 4,
x: 3,
}, result);
assertEquals(["100", "z", "y", "x"], Object.keys(result));
},
doesNotUseIteratorForKeyValuePairFastCase() {
class Entry {
constructor(k, v) {
this[0] = k;
this[1] = v;
}
get [Symbol.iterator]() {
throw new Error("Should not load Symbol.iterator from Entry!");
}
}
function e(k, v) { return new Entry(k, v); }
let entries = [e(100, 0), e('z', 1), e('y', 2), e('x', 3), e('y', 4)];
let result = fromEntries(entries);
assertEquals({
100: 0,
z: 1,
y: 4,
x: 3,
}, result);
},
doesNotUseIteratorForKeyValuePairSlowCase() {
class Entry {
constructor(k, v) {
this[0] = k;
this[1] = v;
}
get [Symbol.iterator]() {
throw new Error("Should not load Symbol.iterator from Entry!");
}
}
function e(k, v) { return new Entry(k, v); }
let entries = new Set(
[e(100, 0), e('z', 1), e('y', 2), e('x', 3), e('y', 4)]);
let result = fromEntries(entries);
assertEquals({
100: 0,
z: 1,
y: 4,
x: 3,
}, result);
},
createDataPropertyFastCase() {
Object.defineProperty(ObjectPrototype, "property", {
configurable: true,
get() { throw new Error("Should not invoke getter on prototype!"); },
set() { throw new Error("Should not invoke setter on prototype!"); },
});
let entries = [["property", "value"]];
let result = fromEntries(entries);
assertEquals(result.property, "value");
delete ObjectPrototype.property;
},
createDataPropertySlowCase() {
Object.defineProperty(ObjectPrototype, "property", {
configurable: true,
get() { throw new Error("Should not invoke getter on prototype!"); },
set() { throw new Error("Should not invoke setter on prototype!"); },
});
let entries = new Set([["property", "value"]]);
let result = fromEntries(entries);
assertEquals(result.property, "value");
delete ObjectPrototype.property;
},
keyToPrimitiveMutatesArrayInFastCase() {
let mySymbol = Symbol();
let entries = [[0, 1], ["a", 2], [{
[Symbol.toPrimitive]() {
// The fast path should bail out if a key is a JSReceiver, otherwise
// assumptions about the structure of the iterable can change. If the
// fast path doesn't bail out, the 4th key would be "undefined".
delete entries[3][0];
entries[3].__proto__ = { 0: "shfifty", };
return mySymbol;
},
}, 3], [3, 4]];
let result = fromEntries(entries);
assertEquals({
0: 1,
"a": 2,
[mySymbol]: 3,
"shfifty": 4,
}, result);
assertEquals(["0", "a", "shfifty", mySymbol], Reflect.ownKeys(result));
},
keyToStringMutatesArrayInFastCase() {
let mySymbol = Symbol();
let entries = [[mySymbol, 1], [0, 2], [{
toString() {
delete entries[3][0];
entries[3].__proto__ = { 0: "shfifty", };
return "z";
},
valueOf() { throw new Error("Unused!"); }
}, 3], [3, 4]];
let result = fromEntries(entries);
assertEquals({
[mySymbol]: 1,
0: 2,
"z": 3,
"shfifty": 4,
}, result);
assertEquals(["0", "z", "shfifty", mySymbol], Reflect.ownKeys(result));
},
keyValueOfMutatesArrayInFastCase() {
let mySymbol = Symbol();
let entries = [[mySymbol, 1], ["z", 2], [{
toString: undefined,
valueOf() {
delete entries[3][0];
entries[3].__proto__ = { 0: "shfifty", };
return 0;
},
}, 3], [3, 4]];
let result = fromEntries(entries);
assertEquals({
[mySymbol]: 1,
"z": 2,
0: 3,
"shfifty": 4,
}, result);
assertEquals(["0", "z", "shfifty", mySymbol], Reflect.ownKeys(result));
},
}
for (let t of Reflect.ownKeys(test)) {
test[t]();
}
......@@ -57,10 +57,10 @@ FEATURE_FLAGS = {
'globalThis': '--harmony-global',
'well-formed-json-stringify': '--harmony-json-stringify',
'export-star-as-namespace-from-module': '--harmony-namespace-exports',
'Object.fromEntries': '--harmony-object-from-entries',
}
SKIPPED_FEATURES = set(['Object.fromEntries',
'class-fields-private',
SKIPPED_FEATURES = set(['class-fields-private',
'class-static-fields-private',
'class-methods-private',
'class-static-methods-private'])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment