8a9cbdacad
This is a reland of a5336471f2
Original change's description:
> [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: Daniel Clifford <danno@chromium.org>
> Co-authored-by: Caitlin Potter <caitp@igalia.com>
> Reviewed-on: https://chromium-review.googlesource.com/c/1337585
> Commit-Queue: Daniel Clifford <danno@chromium.org>
> Reviewed-by: Daniel Clifford <danno@chromium.org>
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#57667}
Bug: v8:8021
Change-Id: I706e2d87bfc2f688e833c1b7d40ca82f5d80f5a2
Reviewed-on: https://chromium-review.googlesource.com/c/1346630
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Commit-Queue: Caitlin Potter <caitp@igalia.com>
Cr-Commit-Position: refs/heads/master@{#57798}
440 lines
12 KiB
JavaScript
440 lines
12 KiB
JavaScript
// 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]();
|
|
}
|