ecma402
ecma402 copied to clipboard
ToDateTimeOptions should avoid making an observable internal-use object
trafficstars
(created from https://github.com/tc39/ecma402/pull/493#discussion_r465537106 )
ToDateTimeOptions creates an object inheriting from provided options and performs some observable writes upon it, which is unnecessarily weird.
/* Make options a proxy that spies on lots of operations until disabled. */
let spy = {};
const wrapperLabels = new Map();
const options = new Proxy({ignored: true}, {
...Object.fromEntries(
["getOwnPropertyDescriptor", "defineProperty", "has", "get", "ownKeys"].map(trapName => {
spy[trapName] = new Map();
return [trapName, (...args) => {
const result = Reflect[trapName](...args);
if (spy) {
const m = spy[trapName];
const propKey = args[1];
const n = (m.get(propKey) || 0) + 1;
m.set(propKey, n);
let details = args.slice(1);
/* Use the get trap to discover system-created objects. */
if (trapName === "get") {
const receiver = args[2];
let receiverLabel;
if (receiver === options) {
receiverLabel = "options";
} else if (Object.getPrototypeOf(receiver) === options) {
if (!wrapperLabels.has(receiver)) {
wrapperLabels.set(receiver, "wrapper" + wrapperLabels.size);
}
receiverLabel = wrapperLabels.get(receiver);
} else {
receiverLabel = "unknown receiver " + String(receiver);
}
details = ["@" + receiverLabel, propKey];
}
print(n, trapName, ...details, "=>", result);
}
return result;
}];
})
)
});
new Intl.DateTimeFormat(undefined, options);
spy = null;
for (const [obj, label] of new Map([[options, "options"], ...wrapperLabels.entries()])) {
print(`${label} = {\n${
Reflect.ownKeys(obj).map(k => `\t${String(k)}: ${String(obj[k])},\n`).join("")
}}`);
}
$ eshost -s demo.js
#### GraalJS, JavaScriptCore, SpiderMonkey, V8
1 get @wrapper0 weekday => undefined
1 get @wrapper0 year => undefined
1 get @wrapper0 month => undefined
1 get @wrapper0 day => undefined
1 get @wrapper0 dayPeriod => undefined
1 get @wrapper0 hour => undefined
1 get @wrapper0 minute => undefined
1 get @wrapper0 second => undefined
1 get @wrapper0 fractionalSecondDigits => undefined
1 get @wrapper0 dateStyle => undefined
1 get @wrapper0 timeStyle => undefined
1 get @wrapper0 localeMatcher => undefined
1 get @wrapper0 calendar => undefined
1 get @wrapper0 numberingSystem => undefined
1 get @wrapper0 hour12 => undefined
1 get @wrapper0 hourCycle => undefined
1 get @wrapper0 timeZone => undefined
2 get @wrapper0 weekday => undefined
1 get @wrapper0 era => undefined
2 get @wrapper0 dayPeriod => undefined
2 get @wrapper0 hour => undefined
2 get @wrapper0 minute => undefined
2 get @wrapper0 second => undefined
2 get @wrapper0 fractionalSecondDigits => undefined
1 get @wrapper0 timeZoneName => undefined
1 get @wrapper0 formatMatcher => undefined
2 get @wrapper0 dateStyle => undefined
2 get @wrapper0 timeStyle => undefined
options = {
ignored: true,
}
wrapper0 = {
year: numeric,
month: numeric,
day: numeric,
}
https://github.com/tc39/ecma402/issues/237#issuecomment-389232483 has a link to patch which avoids creating an intermediate object.
Fixed in #709.