Repository navigation
fix(cjs-wrap): stop HIR from dropping built-in require bindings in wrapped modules - #8343
Merged
proggeramlug merged 2 commits intoAug 18, 2026
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #8341. sdxgen's
ReferenceError: node_process is not definedis eliminated. The HIR was dropping the built-in require binding before the wrap's runtimecreateRequirepath could run.Root cause (mechanism (a))
#8341 made the CJS wrap route built-in requires (
require("process")) through the synthetic require'screateRequirearm instead of the hoisted static import binding, and skipped alias adoption/blanking for built-ins. That logic is correct, but the binding was being dropped before the wrap's runtime path could use it.The HIR's destructuring
var/let/constpass (register_native_fetch_and_streams/register_destructured_stream_ctors) rewriteslet node_process = require("process")into a native-module namespace binding (register_require_namespace_binding→remove_local_binding), mirroringimport * as node_process from "process". This pass runs before call lowering, so thelookup_local("require")guard intry_require_literal(which would otherwise bail because the wrap's syntheticfunction requireshadows the global) never fires. The codegen does not initialize native-module import bindings inside CJS-wrapped modules, sonode_processresolved to nothing at runtime — theReferenceError.So the mechanism is (a): the HIR drops the built-in require binding via the destructuring native-require fast path, which is out of sync with the wrap fix — it matches the bare
requireident even when shadowed by the wrap's syntheticfunction require, unliketry_require_literalwhich checkslookup_local("require"). This is NOT (b) (the synthetic require'screateRequirefallback is reached and works) and NOT (c) (no separate blanking path — #8341's blanking skip is effective).Fix (three parts)
HIR (
var_decl_sources.rs/native_fetch.rs): gate the destructuring native-require fast paths onrequirebeing the bare global (not shadowed by the wrap's syntheticfunction require), via a newrequire_is_shadowed_by_localhelper that mirrorstry_require_literal's guard. When shadowed, therequire("<builtin>")call flows through to the synthetic require, which resolves builtins viacreateRequire.wrap (
wrap.rs): stop emittingimport _req_N from '<builtin>'for built-in specs — the binding is never initialized in a CJS-wrapped module and is now unreferenced.wrap (
wrap.rs): the per-spec require case for builtins never references the (now nonexistent) import local — always go through thecreateRequire-backedrequired_value, including the try-site branch (thetypeof {local} === 'boolean'sentinel guard does not apply to builtins).Verification
ReferenceError: node_process is not definedis gone — confirmed by running the compiled sdxgen.darwin:const p = require("process"); console.log(p.platform)(and os/path)__toESMshape (let node_process = require("process"); node_process = __toESM(node_process, 1), with the realObject.create(Object.getPrototypeOf(mod))helper)const { platform } = require("process")cjs_wrap_builtin_require_not_hoisted_as_static_import) and end-to-end (cjs_wrap_builtin_require.rs— 3 tests, all green).cargo testfor touched crates green, measured:perry-hirlib 315 passed;perrycjs_wrap unit 112 passed;createrequire_builtin_modulesintegration 5 passed (no regression in thecreateRequirepath).Separate newly-revealed blocker (not this PR's scope)
With the
ReferenceErrorfixed, sdxgen now reveals a separate downstream runtime error:TypeError: Object prototype may only be an Object or null: -2atexternal-pack.js(from__toESM'sObject.create(Object.getPrototypeOf(mod)), whereObject.getPrototypeOfreturns-2for some reified CJS chunk exports object). This is a different Perry runtime bug (Object prototype reification /getPrototypeOfreturning a tagged sentinel for a specific reified object), not the CJS-wrap/HIR-drop mechanism this PR fixes. It was masked by theReferenceErrorand needs its own follow-up. The assigned blocker (ReferenceError: node_process is not defined) is resolved.