Skip to content

Commit 85e728e

Browse files
committed
util: capture Temporal state in pre-execution
Signed-off-by: Renegade334 <contact.9a5d6388@renegade334.me.uk>
1 parent 2bf9f64 commit 85e728e

3 files changed

Lines changed: 166 additions & 40 deletions

File tree

lib/internal/process/pre_execution.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ function prepareExecution(options) {
138138

139139
require('internal/dns/utils').initializeDns();
140140

141+
require('internal/util/temporal').initialize();
142+
141143
if (isMainThread) {
142144
assert(internalBinding('worker').isMainThread);
143145
// Worker threads will get the manifest in the message handler.

lib/internal/util/inspect.js

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ const {
195195
kValidateObjectAllowArray,
196196
} = require('internal/validators');
197197

198+
const temporal = require('internal/util/temporal');
199+
198200
let hexSlice;
199201
let internalUrl;
200202

@@ -208,40 +210,6 @@ function isURL(value) {
208210
return typeof value.href === 'string' && value instanceof internalUrl.URL;
209211
}
210212

211-
const temporalConstructors = [
212-
{ name: 'Duration', fullName: 'Temporal.Duration' },
213-
{ name: 'Instant', fullName: 'Temporal.Instant' },
214-
{ name: 'PlainDate', fullName: 'Temporal.PlainDate' },
215-
{ name: 'PlainDateTime', fullName: 'Temporal.PlainDateTime' },
216-
{ name: 'PlainMonthDay', fullName: 'Temporal.PlainMonthDay' },
217-
{ name: 'PlainTime', fullName: 'Temporal.PlainTime' },
218-
{ name: 'PlainYearMonth', fullName: 'Temporal.PlainYearMonth' },
219-
{ name: 'ZonedDateTime', fullName: 'Temporal.ZonedDateTime' },
220-
];
221-
222-
// Best-effort, using instanceof. Polyfills will likely pass these checks.
223-
// TODO: Hopefully the V8 API will expose typechecks at some point.
224-
function getTemporalInfo(value) {
225-
try {
226-
const { Temporal } = globalThis;
227-
if (Temporal != null) {
228-
for (let i = 0; i < temporalConstructors.length; i++) {
229-
const entry = temporalConstructors[i];
230-
const constructor = Temporal[entry.name];
231-
if (typeof constructor !== 'function' || !FunctionPrototypeSymbolHasInstance(constructor, value)) {
232-
continue;
233-
}
234-
const { prototype } = constructor;
235-
if (!ObjectPrototypeHasOwnProperty(prototype, 'toString')) {
236-
return;
237-
}
238-
const result = FunctionPrototypeCall(prototype.toString, value);
239-
return { ...entry, result };
240-
}
241-
}
242-
} catch { /* void */ }
243-
}
244-
245213
const builtInObjects = new SafeSet(
246214
ArrayPrototypeFilter(
247215
ObjectGetOwnPropertyNames(globalThis),
@@ -1355,7 +1323,6 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
13551323
if (noIterator) {
13561324
keys = getKeys(value, ctx.showHidden);
13571325
braces = ['{', '}'];
1358-
let temporalInfo;
13591326
if (typeof value === 'function') {
13601327
base = getFunctionBase(ctx, value, constructor, tag);
13611328
if (keys.length === 0 && protoProps === undefined)
@@ -1439,11 +1406,67 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
14391406
if (keys.length === 0 && protoProps === undefined) {
14401407
return base;
14411408
}
1442-
} else if ((temporalInfo = getTemporalInfo(value))) {
1443-
const prefix = constructor === temporalInfo.name && tag === temporalInfo.fullName ?
1444-
(temporalInfo.fullName + ' ') :
1445-
getPrefix(constructor, tag, temporalInfo.fullName);
1446-
base = `${prefix}${temporalInfo.result}`;
1409+
} else if (temporal.isTemporalDuration(value)) {
1410+
const prefix = constructor === 'Duration' && tag === 'Temporal.Duration' ?
1411+
('Temporal.Duration ') :
1412+
getPrefix(constructor, tag, 'Temporal.Duration');
1413+
base = `${prefix}${temporal.TemporalDurationPrototypeToString(value)}`;
1414+
if (keys.length === 0 && protoProps === undefined) {
1415+
return ctx.stylize(base, 'date');
1416+
}
1417+
} else if (temporal.isTemporalInstant(value)) {
1418+
const prefix = constructor === 'Instant' && tag === 'Temporal.Instant' ?
1419+
('Temporal.Instant ') :
1420+
getPrefix(constructor, tag, 'Temporal.Instant');
1421+
base = `${prefix}${temporal.TemporalInstantPrototypeToString(value)}`;
1422+
if (keys.length === 0 && protoProps === undefined) {
1423+
return ctx.stylize(base, 'date');
1424+
}
1425+
} else if (temporal.isTemporalPlainDate(value)) {
1426+
const prefix = constructor === 'PlainDate' && tag === 'Temporal.PlainDate' ?
1427+
('Temporal.PlainDate ') :
1428+
getPrefix(constructor, tag, 'Temporal.PlainDate');
1429+
base = `${prefix}${temporal.TemporalPlainDatePrototypeToString(value)}`;
1430+
if (keys.length === 0 && protoProps === undefined) {
1431+
return ctx.stylize(base, 'date');
1432+
}
1433+
} else if (temporal.isTemporalPlainDateTime(value)) {
1434+
const prefix = constructor === 'PlainDateTime' && tag === 'Temporal.PlainDateTime' ?
1435+
('Temporal.PlainDateTime ') :
1436+
getPrefix(constructor, tag, 'Temporal.PlainDateTime');
1437+
base = `${prefix}${temporal.TemporalPlainDateTimePrototypeToString(value)}`;
1438+
if (keys.length === 0 && protoProps === undefined) {
1439+
return ctx.stylize(base, 'date');
1440+
}
1441+
} else if (temporal.isTemporalPlainMonthDay(value)) {
1442+
const prefix = constructor === 'PlainMonthDay' && tag === 'Temporal.PlainMonthDay' ?
1443+
('Temporal.PlainMonthDay ') :
1444+
getPrefix(constructor, tag, 'Temporal.PlainMonthDay');
1445+
base = `${prefix}${temporal.TemporalPlainMonthDayPrototypeToString(value)}`;
1446+
if (keys.length === 0 && protoProps === undefined) {
1447+
return ctx.stylize(base, 'date');
1448+
}
1449+
} else if (temporal.isTemporalPlainTime(value)) {
1450+
const prefix = constructor === 'PlainTime' && tag === 'Temporal.PlainTime' ?
1451+
('Temporal.PlainTime ') :
1452+
getPrefix(constructor, tag, 'Temporal.PlainTime');
1453+
base = `${prefix}${temporal.TemporalPlainTimePrototypeToString(value)}`;
1454+
if (keys.length === 0 && protoProps === undefined) {
1455+
return ctx.stylize(base, 'date');
1456+
}
1457+
} else if (temporal.isTemporalPlainYearMonth(value)) {
1458+
const prefix = constructor === 'PlainYearMonth' && tag === 'Temporal.PlainYearMonth' ?
1459+
('Temporal.PlainYearMonth ') :
1460+
getPrefix(constructor, tag, 'Temporal.PlainYearMonth');
1461+
base = `${prefix}${temporal.TemporalPlainYearMonthPrototypeToString(value)}`;
1462+
if (keys.length === 0 && protoProps === undefined) {
1463+
return ctx.stylize(base, 'date');
1464+
}
1465+
} else if (temporal.isTemporalZonedDateTime(value)) {
1466+
const prefix = constructor === 'ZonedDateTime' && tag === 'Temporal.ZonedDateTime' ?
1467+
('Temporal.ZonedDateTime ') :
1468+
getPrefix(constructor, tag, 'Temporal.ZonedDateTime');
1469+
base = `${prefix}${temporal.TemporalZonedDateTimePrototypeToString(value)}`;
14471470
if (keys.length === 0 && protoProps === undefined) {
14481471
return ctx.stylize(base, 'date');
14491472
}

lib/internal/util/temporal.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
'use strict';
2+
3+
// This module is initialized in the pre-execution phase, before user code is
4+
// run. Since the module namespace is not populated at snapshot time, this
5+
// module should be not be imported with a destructuring pattern unless
6+
// imported lazily.
7+
8+
// TODO(Renegade334): Remove typecheck methods once available via V8 API.
9+
// Replace with primordials if/when temporal_rs becomes a mandatory V8
10+
// build dependency and the harmony_temporal flag is removed.
11+
12+
const {
13+
ArrayPrototypeForEach,
14+
FunctionPrototypeCall,
15+
ObjectEntries,
16+
ObjectGetOwnPropertyDescriptors,
17+
SafeMap,
18+
StringPrototypeSubstring,
19+
StringPrototypeToUpperCase,
20+
globalThis,
21+
uncurryThis,
22+
} = primordials;
23+
24+
// The keys of this Map are the Temporal constructor names.
25+
// The values are the names of the most lightweight brand-aware getter within
26+
// each prototype, which can be called speculatively as a brand check.
27+
const constructors = new SafeMap()
28+
.set('Duration', 'nanoseconds')
29+
.set('Instant', 'epochNanoseconds')
30+
.set('PlainDate', 'calendarId')
31+
.set('PlainDateTime', 'calendarId')
32+
.set('PlainMonthDay', 'calendarId')
33+
.set('PlainTime', 'nanosecond')
34+
.set('PlainYearMonth', 'calendarId')
35+
.set('ZonedDateTime', 'calendarId');
36+
37+
exports.initialize = () => {
38+
const { Temporal } = globalThis;
39+
if (typeof Temporal === 'undefined') {
40+
for (const name of constructors.keys()) {
41+
exports[`isTemporal${name}`] = () => false;
42+
}
43+
return;
44+
}
45+
46+
for (const { 0: name, 1: brandedGetter } of constructors.entries()) {
47+
const constructor = Temporal[name];
48+
exports[`Temporal${name}`] = constructor;
49+
50+
ArrayPrototypeForEach(
51+
ObjectEntries(ObjectGetOwnPropertyDescriptors(constructor)),
52+
({ 0: key, 1: desc }) => {
53+
if (typeof key !== 'string' || typeof desc.value !== 'function') return;
54+
55+
exports[`Temporal${name}${capitalizeKey(key)}`] = desc.value;
56+
},
57+
);
58+
59+
ArrayPrototypeForEach(
60+
ObjectEntries(ObjectGetOwnPropertyDescriptors(constructor.prototype)),
61+
({ 0: key, 1: desc }) => {
62+
if (typeof key !== 'string') return;
63+
64+
if ('get' in desc) {
65+
exports[`Temporal${name}PrototypeGet${capitalizeKey(key)}`] = uncurryThis(desc.get);
66+
if (key === brandedGetter) {
67+
exports[`isTemporal${name}`] = makeTypeCheckMethod(desc.get);
68+
}
69+
} else {
70+
exports[`Temporal${name}Prototype${capitalizeKey(key)}`] = typeof desc.value === 'function' ?
71+
uncurryThis(desc.value) :
72+
desc.value;
73+
}
74+
},
75+
);
76+
}
77+
78+
ArrayPrototypeForEach(
79+
ObjectEntries(ObjectGetOwnPropertyDescriptors(Temporal.Now)),
80+
({ 0: key, 1: desc }) => {
81+
if (typeof key !== 'string' || !('value' in desc)) return;
82+
83+
exports[`TemporalNow${capitalizeKey(key)}`] = desc.value;
84+
},
85+
);
86+
};
87+
88+
function makeTypeCheckMethod(getter) {
89+
return (value) => {
90+
try {
91+
FunctionPrototypeCall(getter, value);
92+
return true;
93+
} catch {
94+
return false;
95+
}
96+
};
97+
}
98+
99+
function capitalizeKey(key) {
100+
return `${StringPrototypeToUpperCase(key[0])}${StringPrototypeSubstring(key, 1)}`;
101+
}

0 commit comments

Comments
 (0)