Skip to content

feat(globalThis): expose built-in constructors as properties#965

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-agent-af738591529944d84
May 17, 2026
Merged

feat(globalThis): expose built-in constructors as properties#965
proggeramlug merged 1 commit into
mainfrom
worktree-agent-af738591529944d84

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

Summary

  • Populate the globalThis singleton with built-in constructor properties on first init so globalThis.Array / context.Object (lodash's runInContext pattern) no longer return undefined.
  • Route Expr::PropertyGet { GlobalGet(_), <Builtin> } through js_get_global_this + js_object_get_field_by_name_f64 so the static AST shape sees the same populated singleton (mirrors the IndexGet arm added in perry-runtime: Effect framework SIGSEGVs during Utils.ts module init — array-fast-path called with x0 = 0x1 (Effect end-to-end blocker, post-#592) #611).
  • Constructors get a ClosureHeader-backed sentinel (typeof === "function" via the existing CLOSURE_MAGIC tag); Math / JSON / Reflect get a plain ObjectHeader (typeof === "object"). Each constructor carries a prototype dynamic property pointing at an empty backing object so <Builtin>.prototype reads don't trip the spec-shaped TypeError: Cannot read properties of undefined.

Follows PR #963 (v0.5.977) which closed the Function('return this')() IIFE recogniser. Today's blocker for import _ from "lodash" was the next line down — var Array = context.Array; var arrayProto = Array.prototype — because the built-in constructors were never registered as properties on the singleton.

Test plan

  • test-files/test_globalthis_builtins.ts — byte-for-byte parity with node --experimental-strip-types for typeof globalThis.{Array,Object,Function,Math,JSON} + Array.prototype + new Array(3).length.
  • lodash repro: _.chunk([1,2,3,4], 2) still fails to load _, but the throw point advanced from lodash.js:1463 (var arrayProto = Array.prototype) to lodash.js:1493 (funcToString.call(Object)). The new blocker needs real Function.prototype.toString support — tracked alongside the broader lodash compile-as-package work.

Caveats

  • The backing objects are sentinels, not the real constructors. Array.prototype.toString === undefined (Node returns a function).
  • globalThis.Array === Array is false. Bare Array still lowers to Expr::GlobalGet(0); the singleton form returns a closure pointer. Unifying these requires widening Perry's first-class representation of built-in constructors.
  • Bare new Array(n) is unaffected — it flows through lower_new like before. The thunk underneath each sentinel is a no-op returning undefined, so globalThis.Array(3) as a call (not a new) returns undefined rather than constructing an array (best-effort, known gap).

Version

  • Cargo.toml [workspace.package].version0.5.978
  • CLAUDE.md **Current Version:**0.5.978
  • CHANGELOG.md — prepended ## v0.5.978 — feat(globalThis): ... block with full context

PR #963 closed the `Function('return this')()` recogniser so lodash's
module-init IIFE no longer threw before `runInContext` ran, but the next
blocker surfaced at lodash.js:1463 — `var Array = context.Array; var
arrayProto = Array.prototype` threw `TypeError: Cannot read properties
of undefined (reading 'prototype')` because the canonical JS built-ins
were never registered on `globalThis`.

Two coordinated pieces fix this:

1. Runtime (`crates/perry-runtime/src/object.rs`): on first access,
   `js_get_global_this`'s CAS winner now calls
   `populate_global_this_builtins(singleton)`. Constructors get a
   ClosureHeader-backed sentinel (`typeof === "function"` via the
   CLOSURE_MAGIC tag); `Math`/`JSON`/`Reflect` get a plain ObjectHeader
   (`typeof === "object"`). Each constructor carries a `prototype`
   dynamic property pointing at an empty ObjectHeader so the lodash
   `Array.prototype` chained read returns a real pointer instead of
   throwing.

2. Codegen (`crates/perry-codegen/src/expr.rs`): the
   `Expr::PropertyGet { GlobalGet(_), <name> }` arm consults
   `is_global_this_builtin_name(<name>)` and, when matched, routes
   the read through `js_get_global_this` + `js_object_get_field_by_name_f64`
   (mirrors the existing IndexGet arm from #611). The `typeof` short-
   circuit gains a parallel arm for the same names.

`test-files/test_globalthis_builtins.ts` validates the round-trip
byte-for-byte against `node --experimental-strip-types`. The lodash
`_.chunk` repro still fails to load `_` but the throw point moves from
line 1463 (`var arrayProto = Array.prototype`) to line 1493
(`funcToString.call(Object)`) — three reads + one call deeper into
`runInContext`.
@proggeramlug proggeramlug force-pushed the worktree-agent-af738591529944d84 branch from f23ff3a to 1b70ab8 Compare May 17, 2026 22:40
@proggeramlug proggeramlug merged commit e6202fc into main May 17, 2026
@proggeramlug proggeramlug deleted the worktree-agent-af738591529944d84 branch May 17, 2026 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant