Repository navigation
runtime: expose standard global function values on globalThis #2905
Description
Summary
Node exposes the standard scalar/global helper APIs as function-valued properties on globalThis, and those properties are the same callable values as the bare bindings. Perry currently implements/lower-cases several of the bare calls, but does not populate the corresponding globalThis properties, so rebinding or property-based access observes undefined / non-callable behavior instead of Node-compatible functions.
Node behavior
On Node v25.9.0:
const names = [
'parseInt',
'parseFloat',
'isNaN',
'isFinite',
'encodeURI',
'decodeURI',
'encodeURIComponent',
'decodeURIComponent',
'escape',
'unescape',
];
for (const name of names) {
console.log(name, typeof globalThis[name], globalThis[name] === eval(name));
}
console.log(encodeURIComponent('a b?é'), decodeURIComponent('a%20b%3F%C3%A9'));
console.log(escape('a b?é'), unescape('a%20b%3F%E9'));prints function true for every listed global, followed by successful URI / legacy escape round-trips.
Perry evidence
Perry already has direct-call lowering/runtime helpers for several of these names:
crates/perry-hir/src/lower/expr_call/globals.rslowers bareparseInt,parseFloat,isNaN,isFinite,encodeURI,decodeURI,encodeURIComponent, anddecodeURIComponentcalls.crates/perry-runtime/src/builtins/numbers.rsimplementsjs_parse_int,js_parse_float,js_is_nan, andjs_is_finite.crates/perry-runtime/src/builtins/globals.rsimplementsjs_encode_uri,js_decode_uri,js_encode_uri_component, andjs_decode_uri_component.
But the globalThis population path only exposes a smaller callable set:
crates/perry-runtime/src/object/global_this.rsGLOBAL_THIS_BUILTIN_FUNCTIONSlists onlystructuredClone,atob,btoa, timers, immediates, andqueueMicrotask.- The matching thunk table in
populate_global_this_builtins()only dispatches those same names. - The codegen mirror in
crates/perry-codegen/src/expr/helpers.rs::is_global_this_builtin_name()omits these standard helper names, so property reads /typeof globalThis.parseIntdo not take the populated-global path either. escape/unescapeare also standard global function values in Node, but no global helper surface appears to be populated for them; source hits are querystring/lodash-specific helpers rather thanglobalThis.escape/globalThis.unescape.
That means code such as:
const parse = globalThis.parseInt;
const enc = globalThis.encodeURIComponent;
console.log(typeof parse, parse('10', 2));
console.log(typeof enc, enc('a b'));
console.log(globalThis.parseInt === parseInt);should work like Node but currently falls outside Perry's populated globalThis callable surface.
Acceptance criteria
globalThis.parseInt,parseFloat,isNaN,isFinite,encodeURI,decodeURI,encodeURIComponent,decodeURIComponent,escape, andunescapeare function-valued properties.- Rebound calls through those properties dispatch the same semantics as the bare built-ins.
globalThis.<name> === <name>matches Node for the listed helpers where Perry exposes bare globals.typeof globalThis.<name>matches Node's"function"result.
Related but separate: #2889 tracks rebound constructor values such as globalThis.Array / globalThis.Object; this issue is only for standard global helper functions.
Activity
fix(runtime): expose global builtin function values + rebound builtin…