Repository navigation
runtime: route rebound global built-in constructors through real semantics #2889
Description
Summary
globalThis exposes many built-in constructors as function-shaped values, but most of those values are no-op sentinels when they are called or rebound through a local. Node treats these as the real constructors/functions, so common patterns like const C = globalThis.Number; C("42") or const E = globalThis.Error; new E("x") should preserve the corresponding built-in semantics.
Node behavior
On local Node v25.9.0:
for (const [name,args] of Object.entries({
Array:[3], Object:[null], String:[123], Number:["42"], Boolean:[0],
Date:[0], RegExp:["a","g"], Error:["x"], Uint8Array:[3], ArrayBuffer:[4]
})) {
const F = globalThis[name];
const called = F(...args);
const constructed = new F(...args);
console.log(name, typeof F, Object.prototype.toString.call(called), Object.prototype.toString.call(constructed));
}Observed examples:
Arrayis a function;globalThis.Array(3)andnew globalThis.Array(3)both produce arrays.Numberis a function; calling it coerces to a number and constructing it produces a Number object.Boolean,String,Date,RegExp, andErrorall preserve their Node call/construct behavior when read fromglobalThis.- constructors such as
Map,Set, typed arrays, andArrayBufferthrow on bare call but construct correctly withnew.
Perry evidence
The current runtime intentionally installs most globalThis built-in constructors as no-op closure sentinels:
crates/perry-runtime/src/object/global_this.rs::GLOBAL_THIS_BUILTIN_CONSTRUCTORSlists core constructors includingArray,Object,String,Number,Boolean,RegExp,Date,Error,Map,Set, typed arrays,ArrayBuffer,DataView, etc.global_this_builtin_noop_thunk()documents that these values only maketypeof globalThis.Array === "function"andArray.prototypereads work, while direct calls likeglobalThis.Array(3)returnundefined.populate_global_this_builtins()usesglobal_this_builtin_noop_thunkfor every constructor exceptStringandBuffer.crates/perry-codegen/src/expr/new_dynamic.rsroutesnew <LocalGet>(...)/new <PropertyGet>(...)throughjs_new_function_construct(), so rebound constructors depend on runtime recovery.crates/perry-runtime/src/object/class_registry.rs::js_new_function_construct()recognizes the global no-op constructor closure, but only dispatches real construction forDate,Array,Object, andTextEncoderStream/TextDecoderStream; other recognized built-in names fall through to class-id-0 object allocation and then invoke the no-op thunk.
That means examples such as these cannot match Node today:
const NumberCtor = globalThis.Number;
const ErrorCtor = globalThis.Error;
const Uint8Ctor = globalThis.Uint8Array;
NumberCtor("42"); // should be 42, not undefined
new NumberCtor("42"); // should be a Number object, not an empty object
new ErrorCtor("boom"); // should be an Error instance with message/name/stack shape
new Uint8Ctor(3); // should be a length-3 Uint8Array viewExpected fix shape
Make the globalThis.<Builtin> values dispatch through the same call and construct paths as the direct syntax where possible:
- call-form constructors:
Array,Object,String,Number,Boolean,Date,RegExp,Errorsubclasses,Symbol,BigInt, etc. should match Node call semantics, including TypeErrors for non-callable constructors such asMap,Set, typed arrays, andArrayBuffer. - construct-form constructors reached through
new globalThis.X(...),const X = globalThis.X; new X(...), and.constructorshould reuse the real built-in constructor implementations and validation. - Keep direct
new X(...)lowering as-is, but avoid the no-op sentinel behavior for first-class global constructor values.
Related but not duplicate: #2586 tracks making URL / URLSearchParams global values real constructors, #2608 tracks Fetch API constructor globals, #2882 tracks the missing AggregateError global, and #2883 tracks direct new Symbol / new BigInt TypeErrors.
Activity
fix(runtime): expose global builtin function values + rebound builtin…
test: cover the unwrapped-CJS require-reassign and rebound-RegExp fixes
fix: two rebound-global-constructor gaps hit by rolldown-bundled CJS (#…