Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion crates/perry-codegen/src/codegen/module_globals_emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,45 @@ pub(crate) fn emit_module_globals(
// globals + getter functions for cross-module access.
let exported_var_names: std::collections::HashSet<String> =
hir.exported_objects.iter().cloned().collect();
for s in &hir.init {
// #6649: module-level array-destructuring declarations (`var [Prime, Size]
// = [BigInt(...), BigInt(...)]` — TypeBox's FNV-1a table in the pi bundle)
// lower their leaf `Stmt::Let`s inside the iterator-protocol `Stmt::Try`
// scaffolding (IteratorClose on abrupt completion), so the previous
// top-level-only scan never saw them. The leaves then stayed
// un-globalized and every function/method/closure reference compiled to
// the not-in-scope fallback (`undefined`): TypeBox's `Accumulator * Prime`
// saw `ToNumeric(undefined) = NaN` and threw a spurious "Cannot mix BigInt
// and other types" during pi-native init. Walk through Try scaffolding
// (body/catch/finally, transitively for nested patterns) when collecting
// candidate lets — a module-init try body runs at most once, so its
// bindings are single-instance and safe to promote. Loop and if bodies
// intentionally stay out of the walk: their `let`s are genuinely
// block-scoped (fresh binding per iteration) and remain handled by the
// boxed-capture machinery.
fn collect_init_lets<'a>(stmts: &'a [perry_hir::Stmt], out: &mut Vec<&'a perry_hir::Stmt>) {
for s in stmts {
match s {
perry_hir::Stmt::Let { .. } => out.push(s),
perry_hir::Stmt::Try {
body,
catch,
finally,
} => {
collect_init_lets(body, out);
if let Some(c) = catch {
collect_init_lets(&c.body, out);
}
if let Some(f) = finally {
collect_init_lets(f, out);
}
}
_ => {}
}
}
}
let mut init_lets: Vec<&perry_hir::Stmt> = Vec::new();
collect_init_lets(&hir.init, &mut init_lets);
for s in init_lets {
if let perry_hir::Stmt::Let { id, name, ty, .. } = s {
// Always record the declared type for module-level lets
// so all functions see it (not just the entry function).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ pub(crate) const NODE_CORE_MODULE_SEA_TLS_TEST_ROWS: &[NativeModSig] = &[
has_receiver: false,
method: "createRequire",
class_filter: None,
runtime: "js_module_create_require",
// #6644: the devirt wrapper arms the nm/submod install-all hooks (the
// returned require closure resolves builtins from a runtime string, so
// codegen can't emit precise per-module installs). Mirrors
// js_process_get_builtin_module_devirt.
runtime: "js_module_create_require_devirt",
args: &[NA_F64],
ret: NR_F64,
},
Expand Down
3 changes: 3 additions & 0 deletions crates/perry-codegen/src/runtime_decls/strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,9 @@ pub fn declare_phase_b_strings(module: &mut LlModule) {
// #5389 Tier 2: synchronous ambient require(spec) resolution — the codegen
// fallthrough when a computed require() didn't const-fold to a compiled target.
module.declare_function("js_module_ambient_require_apply", DOUBLE, &[DOUBLE]);
// #6644: `module.createRequire(...)` devirt entry — arms the nm/submod
// install-all hooks before delegating (see js_process_get_builtin_module_devirt).
module.declare_function("js_module_create_require_devirt", DOUBLE, &[DOUBLE]);
// Non-throwing global read for `typeof <unresolved>` + global read-modify-
// write for `i++`/`i--` on a sloppy implicit global (#3575).
module.declare_function("js_global_get_optional", DOUBLE, &[DOUBLE]);
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/lower/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ impl LoweringContext {
next_anon_shape_id: 0,
class_method_return_types: Vec::new(),
class_captures: Vec::new(),
body_class_expr_captures: Vec::new(),
let_class_aliases: Vec::new(),
global_this_aliases: HashSet::new(),
prototype_aliases: HashMap::new(),
Expand Down
42 changes: 42 additions & 0 deletions crates/perry-hir/src/lower/expr_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ pub(super) fn lower_arrow(ctx: &mut LoweringContext, arrow: &ast::ArrowExpr) ->
// #4101: retain source text for `fn.toString()`.
capture_function_source(ctx, func_id, &arrow.span, arrow.is_async);
let scope_mark = ctx.enter_scope();
// #6604: truncate mark for capturing class expressions recorded while
// lowering THIS arrow — placed at scope entry so default-param
// expressions are covered too; see the truncate below the body match.
let body_class_expr_captures_mark = ctx.body_class_expr_captures.len();
let strict = ctx.current_strict_mode()
|| match &*arrow.body {
ast::BlockStmtOrExpr::BlockStmt(block) => {
Expand Down Expand Up @@ -377,6 +381,17 @@ pub(super) fn lower_arrow(ctx: &mut LoweringContext, arrow: &ast::ArrowExpr) ->
vec![Stmt::Return(Some(return_expr))]
}
};
// #6604: a capturing class expression in an EXPRESSION-bodied arrow
// (`x => new (class { … })(x)`) records a body-class-expr entry that no
// body twin will drain (the block-bodied arm drains its own inside
// `lower_fn_body_block_stmt`; default-param entries are self-truncated by
// `get_param_default`). Truncate on exit so the entry — whose ids are
// only meaningful in the arrow's own local numbering — never leaks into
// the ENCLOSING body's refresh statements. Nothing is lost: a
// single-expression body has no later statements that could reassign the
// class's captured locals.
ctx.body_class_expr_captures
.truncate(body_class_expr_captures_mark);
ctx.current_strict = outer_strict;

// Prepend destructuring statements to body
Expand Down Expand Up @@ -579,6 +594,13 @@ fn lower_fn_expr_anon(ctx: &mut LoweringContext, fn_expr: &ast::FnExpr) -> Resul
fn_expr.function.is_async,
);
let scope_mark = ctx.enter_scope();
// #6604: capturing class EXPRESSIONS lowered in THIS function register
// from here for the end-of-body refresh (twin of
// `lower_fn_body_block_stmt`); the mark sits at scope entry so nothing
// recorded for this function can leak into the enclosing body.
// (Default-param entries never reach the drain — `get_param_default`
// self-truncates.)
let body_class_expr_captures_mark = ctx.body_class_expr_captures.len();
// A plain function has its own `arguments` object, so a direct `eval`
// inside its body may reference `arguments` even when the function sits
// in a class field initializer. Cleared here, restored at the end.
Expand Down Expand Up @@ -1221,6 +1243,26 @@ fn lower_fn_expr_anon(ctx: &mut LoweringContext, fn_expr: &ast::FnExpr) -> Resul
}
}
}
// #6604: capturing class EXPRESSIONS lowered directly in this body —
// the semver/esbuild `__commonJS` wrapper shape `var Comparator =
// class _Comparator { … }; …; var parseOptions = require_…()` — join
// the same refresh machinery as class declarations, so the snapshot
// tracks captured vars assigned AFTER the class. Recorded by
// `lower_class_expr` under the RESOLVED registration name; see the
// block-body twin (`lower_fn_body_block_stmt`) for why no
// `append_new_args_stmt` pass runs for expressions.
for (cname, ids) in ctx
.body_class_expr_captures
.split_off(body_class_expr_captures_mark)
{
let captures: Vec<Expr> = ids.iter().map(|id| Expr::LocalGet(*id)).collect();
let re_reg = Stmt::Expr(Expr::RegisterClassCaptures {
class_name: cname,
captures,
});
re_reg_capsets.push((re_reg.clone(), ids.iter().copied().collect()));
re_regs.push(re_reg);
}
if !re_regs.is_empty() {
// Audit P0-B twin of the block-body path: refresh after every
// same-body assignment to a captured local so mid-body constructs
Expand Down
17 changes: 17 additions & 0 deletions crates/perry-hir/src/lower/lower_expr/arm_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ pub(crate) fn lower_class_expr(
// expressions inside a function body (factories like effect's
// `make()`), which produce a distinct class object per call.
let at_module_top = ctx.scope_depth == 0 && ctx.inside_block_scope == 0;
// #6604: register this capturing class EXPRESSION with the enclosing
// body's end-of-body capture-refresh machinery (#6037/#6052), which
// previously scanned class DECLARATION statements only. Without the
// refresh, a captured var assigned AFTER the class expression (semver's
// `var Comparator = class _Comparator { … }; …; var parseOptions =
// require_parse_options()`) stays `undefined` in the decl-site snapshot,
// and dynamic construction of the escaped class value replays that stale
// snapshot. Recording the RESOLVED registration name here (post
// rename/dedup) sidesteps re-deriving it from the AST at body end. Module
// top is skipped — module-level ids are stripped from capture lists by
// `filter_module_level_captures`, so there is nothing to refresh.
if !at_module_top && !captured_args.is_empty() {
if let Some(ids) = ctx.lookup_class_captures(&synthetic_name) {
ctx.body_class_expr_captures
.push((synthetic_name.clone(), ids.to_vec()));
}
}
Comment on lines +189 to +205

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep class-expression refreshes scoped to the evaluated class object.

The new bookkeeping is keyed by the shared template name, so later or skipped evaluations can corrupt the capture fallback used by an earlier ClassExprFresh.

  • crates/perry-hir/src/lower/lower_expr/arm_class.rs#L189-L205: retain per-evaluation identity when recording the captured class.
  • crates/perry-hir/src/lower/expr_function.rs#L1246-L1265: refresh the evaluated class instance/live boxes instead of an unconditional shared-name entry.
  • crates/perry-hir/src/lower_decl/block.rs#L1300-L1323: apply the same per-evaluation and control-flow-aware refresh behavior.
📍 Affects 3 files
  • crates/perry-hir/src/lower/lower_expr/arm_class.rs#L189-L205 (this comment)
  • crates/perry-hir/src/lower/expr_function.rs#L1246-L1265
  • crates/perry-hir/src/lower_decl/block.rs#L1300-L1323
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-hir/src/lower/lower_expr/arm_class.rs` around lines 189 - 205,
Scope class-expression capture refreshes to the specific evaluated class object
rather than the shared template name. In
crates/perry-hir/src/lower/lower_expr/arm_class.rs:189-205, retain
per-evaluation identity when recording captured classes; in
crates/perry-hir/src/lower/expr_function.rs:1246-1265 and
crates/perry-hir/src/lower_decl/block.rs:1300-1323, refresh only the evaluated
class instance/live boxes with control-flow-aware handling, avoiding
unconditional shared-name updates that can affect earlier ClassExprFresh
evaluations.

if !at_module_top
&& (!named_statics.is_empty()
|| !static_symbol_registrations.is_empty()
Expand Down
17 changes: 17 additions & 0 deletions crates/perry-hir/src/lower/lowering_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,23 @@ pub struct LoweringContext {
/// here so the `Expr::New { class_name }` lowering can append
/// `LocalGet(id)` for each captured id at every construction site.
pub(crate) class_captures: Vec<(String, Vec<LocalId>)>,
/// #6604: capturing class EXPRESSIONS lowered while the CURRENT function
/// body is being lowered — `(registration_name, captured_outer_ids)`,
/// pushed by `lower_class_expr` (skipped at module top, where
/// `filter_module_level_captures` already strips module-level ids). The
/// #6037/#6052 end-of-body capture-refresh machinery previously scanned
/// only `ast::Decl::Class` DECLARATION statements, so `var Comparator =
/// class _Comparator { … }` (semver's shape in every bundled class file)
/// never got refresh statements: a captured var assigned AFTER the class
/// (`var parseOptions = require_parse_options()` at file bottom) stayed
/// `undefined` in the snapshot forever, and dynamic construction of the
/// escaped class value threw "value is not a function" at pi-native init.
/// Both body twins (`lower_fn_body_block_stmt` and `lower_fn_expr`) mark
/// this list's length at entry and drain their own suffix at body end;
/// every other body-lowering path must truncate back to its entry mark so
/// entries (whose ids are only meaningful in THEIR OWN function scope)
/// never leak into an enclosing body's refresh statements.
pub(crate) body_class_expr_captures: Vec<(String, Vec<LocalId>)>,
/// Issue #740: `let_name → class_name` for `let/const/var <name> = <ClassRef>`
/// initializers. Lets `Expr::New { class_name }` (where `class_name` is
/// the source-level identifier of an alias binding) resolve to the
Expand Down
30 changes: 30 additions & 0 deletions crates/perry-hir/src/lower_decl/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,10 @@ pub fn lower_fn_body_block_stmt(
// Used by the Phase 1.6 forward `let`/`const` pre-registration so a const
// that shadows an outer binding still gets a fresh this-body local.
let body_entry_locals_len = ctx.locals.len();
// #6604: entries pushed while lowering THIS body belong to THIS body's
// capture-refresh pass (their ids are this function's locals); drain the
// suffix at body end, truncate on the error path so nothing leaks upward.
let body_class_expr_captures_mark = ctx.body_class_expr_captures.len();
let hoisted_var_slots = predefine_var_bindings_in_function_body(ctx, block);

// Phase 1: pre-define hoisted FnDecl locals so forward references in
Expand Down Expand Up @@ -1224,6 +1228,8 @@ pub fn lower_fn_body_block_stmt(
let mut body = match lower_block_stmt(ctx, block) {
Ok(body) => body,
Err(err) => {
ctx.body_class_expr_captures
.truncate(body_class_expr_captures_mark);
ctx.current_strict = parent_strict;
ctx.forward_class_names = saved_forward_class_names;
ctx.forward_class_decl_depth = saved_forward_class_decl_depth;
Expand Down Expand Up @@ -1291,6 +1297,30 @@ pub fn lower_fn_body_block_stmt(
}
}
}
// #6604: capturing class EXPRESSIONS lowered directly in this body
// (`var Comparator = class _Comparator { … }`, argument-position
// `register(class { … })`, …) need the same assignment-tracking
// refresh as class declarations: semver assigns the captured
// `parseOptions`/`debug` vars AFTER the class, so the snapshot (and
// the per-evaluation `__perry_ctor_caps` array, whose stale-undefined
// slots the runtime construct path now backfills from this snapshot)
// must be re-registered with the live values. Entries were recorded
// by `lower_class_expr` under the RESOLVED registration name; no
// `append_new_args_stmt` pass — a class expression's construct sites
// are either static (binding-name `new C()`, live locals appended at
// the site) or dynamic (replayed through the snapshot).
for (cname, ids) in ctx
.body_class_expr_captures
.split_off(body_class_expr_captures_mark)
{
let captures: Vec<Expr> = ids.iter().map(|id| Expr::LocalGet(*id)).collect();
let re_reg = Stmt::Expr(Expr::RegisterClassCaptures {
class_name: cname,
captures,
});
re_reg_capsets.push((re_reg.clone(), ids.iter().copied().collect()));
re_regs.push(re_reg);
}
if !re_regs.is_empty() {
// Audit P0-B: the decl-site snapshot is authoritative at
// construct time, so keep it TRACKING same-body assignments —
Expand Down
12 changes: 12 additions & 0 deletions crates/perry-hir/src/lower_patterns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1454,7 +1454,19 @@ pub(crate) fn get_param_default(ctx: &mut LoweringContext, pat: &ast::Pat) -> Re
}
}
ast::Pat::Assign(assign) => {
// #6604: a capturing class EXPRESSION used as a default value
// (`function f(C = class { … }) {}`) must NOT register with the
// enclosing body's end-of-body capture-refresh machinery: param
// defaults are lowered BEFORE the callee's own body twin takes
// its list mark (fn-decl / ctor / method param sites), so the
// entry would be drained by the WRONG (enclosing) body and its
// ids interpreted in the wrong function's local numbering.
// Truncate whatever this default expression recorded — the
// default is re-evaluated at every call anyway, so its
// evaluation-time snapshot is per-call fresh.
let mark = ctx.body_class_expr_captures.len();
let default_expr = lower_expr(ctx, &assign.right)?;
ctx.body_class_expr_captures.truncate(mark);
Ok(Some(default_expr))
}
_ => Ok(None),
Expand Down
39 changes: 38 additions & 1 deletion crates/perry-runtime/src/module_require.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@ fn supported_require_builtin(specifier: &str) -> Option<&str> {
// allowlist (they have runtime registry buckets + dispatch, but
// `require('tls')` etc. via createRequire was rejected as "package/file").
| "dgram" | "domain" | "inspector" | "inspector/promises" | "repl"
| "sqlite" => Some(name),
| "sqlite"
// #6644: implemented as a node_submodules spec (real pub/sub channel
// registry in node_submodules/diagnostics.rs) but missing here, so
// `require('node:diagnostics_channel')` through createRequire (the
// esbuild banner shim in any ESM bundle of CJS deps — lru-cache's node
// build in the pi bundle) was rejected as "package/file".
| "diagnostics_channel" => Some(name),
_ => None,
}
}
Expand All @@ -123,6 +129,19 @@ fn require_builtin_value(module_name: &str) -> f64 {
)
};
}
// #6644: diagnostics_channel lives in the node_submodules registry (not a
// native-module dispatch bucket); route it there like timers/promises so
// `require('diagnostics_channel')` / `require('node:diagnostics_channel')`
// return the real channel/subscribe/tracingChannel exports instead of an
// empty native-module namespace.
if module_name == "diagnostics_channel" {
return unsafe {
crate::node_submodules::js_node_submodule_namespace(
b"diagnostics_channel".as_ptr(),
"diagnostics_channel".len() as u32,
)
};
}
crate::object::native_module_get_builtin_module_value(module_name)
}

Expand Down Expand Up @@ -223,6 +242,24 @@ pub extern "C" fn js_module_create_require(filename_or_url: f64) -> f64 {
make_require(undefined())
}

/// Devirt codegen entry for `module.createRequire(...)` (#6644). The require
/// closure it returns resolves builtins from a RUNTIME string, so — exactly like
/// `js_process_get_builtin_module_devirt` — codegen could not emit the precise
/// per-module dispatch installs. Arm both install-all hooks so a dynamically
/// required module's methods (`require('node:diagnostics_channel').channel(...)`,
/// `require('tls').connect(...)`) can dispatch. Codegen targets THIS symbol, so
/// the all-buckets `js_nm_install_all` / `js_node_submod_install_all` are
/// referenced only by programs whose source actually calls `createRequire`; the
/// plain `js_module_create_require` (reachable from the always-pinned ambient
/// require keepalives via the module dispatch bucket) stays free of that
/// reference, preserving per-module stripping.
#[no_mangle]
pub extern "C" fn js_module_create_require_devirt(filename_or_url: f64) -> f64 {
crate::object::js_nm_enable_install_all();
crate::node_submodules::js_node_submod_enable_install_all();
js_module_create_require(filename_or_url)
}

/// Next.js wall 54: registry mapping an AOT-compiled CJS module's absolute
/// source path to its evaluated `module.exports`, so a RUNTIME
/// `require(absolutePath.js)` (Next.js / turbopack load page + chunk modules by
Expand Down
50 changes: 43 additions & 7 deletions crates/perry-runtime/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,49 @@ pub(crate) fn is_function_value(value: f64) -> bool {

pub(crate) fn supported_builtin_module_name(name: &str) -> Option<&str> {
match name {
"assert" | "assert/strict" | "async_hooks" | "buffer" | "child_process" | "cluster"
| "console" | "constants" | "crypto" | "dns" | "dns/promises" | "events" | "fs"
| "http" | "http2" | "https" | "module" | "net" | "os" | "path" | "perf_hooks"
| "process" | "punycode" | "querystring" | "readline" | "readline/promises" | "sea"
| "stream" | "stream/promises" | "string_decoder" | "sys" | "test" | "test/reporters"
| "timers" | "timers/promises" | "tty" | "url" | "util" | "util/types" | "vm"
| "worker_threads" | "zlib" => Some(name),
"assert"
| "assert/strict"
| "async_hooks"
| "buffer"
| "child_process"
| "cluster"
| "console"
| "constants"
| "crypto"
| "diagnostics_channel"
| "dns"
| "dns/promises"
| "events"
| "fs"
| "http"
| "http2"
| "https"
| "module"
| "net"
| "os"
| "path"
| "perf_hooks"
| "process"
| "punycode"
| "querystring"
| "readline"
| "readline/promises"
| "sea"
| "stream"
| "stream/promises"
| "string_decoder"
| "sys"
| "test"
| "test/reporters"
| "timers"
| "timers/promises"
| "tty"
| "url"
| "util"
| "util/types"
| "vm"
| "worker_threads"
| "zlib" => Some(name),
Comment on lines +105 to +147

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include missing supported native modules to maintain parity with require.

While "diagnostics_channel" was added to this list (and "sqlite" was added to the require allowlist in module_require.rs), several modules natively supported by require are currently missing from this getBuiltinModule allowlist. This causes process.getBuiltinModule("node:sqlite") (and others like dgram, domain) to incorrectly return undefined.

Consider adding "sqlite", "dgram", "domain", "inspector", "inspector/promises", and "repl" to ensure consistent behavior across module resolution paths.

🛠️ Proposed fix to sync with `supported_require_builtin`
         | "crypto"
+        | "dgram"
         | "diagnostics_channel"
         | "dns"
         | "dns/promises"
+        | "domain"
         | "events"
         | "fs"
         | "http"
         | "http2"
         | "https"
+        | "inspector"
+        | "inspector/promises"
         | "module"
         | "net"
         | "os"
         | "path"
         | "perf_hooks"
         | "process"
         | "punycode"
         | "querystring"
         | "readline"
         | "readline/promises"
+        | "repl"
         | "sea"
+        | "sqlite"
         | "stream"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"assert"
| "assert/strict"
| "async_hooks"
| "buffer"
| "child_process"
| "cluster"
| "console"
| "constants"
| "crypto"
| "diagnostics_channel"
| "dns"
| "dns/promises"
| "events"
| "fs"
| "http"
| "http2"
| "https"
| "module"
| "net"
| "os"
| "path"
| "perf_hooks"
| "process"
| "punycode"
| "querystring"
| "readline"
| "readline/promises"
| "sea"
| "stream"
| "stream/promises"
| "string_decoder"
| "sys"
| "test"
| "test/reporters"
| "timers"
| "timers/promises"
| "tty"
| "url"
| "util"
| "util/types"
| "vm"
| "worker_threads"
| "zlib" => Some(name),
"assert"
| "assert/strict"
| "async_hooks"
| "buffer"
| "child_process"
| "cluster"
| "console"
| "constants"
| "crypto"
| "dgram"
| "diagnostics_channel"
| "dns"
| "dns/promises"
| "domain"
| "events"
| "fs"
| "http"
| "http2"
| "https"
| "inspector"
| "inspector/promises"
| "module"
| "net"
| "os"
| "path"
| "perf_hooks"
| "process"
| "punycode"
| "querystring"
| "readline"
| "readline/promises"
| "repl"
| "sea"
| "sqlite"
| "stream"
| "stream/promises"
| "string_decoder"
| "sys"
| "test"
| "test/reporters"
| "timers"
| "timers/promises"
| "tty"
| "url"
| "util"
| "util/types"
| "vm"
| "worker_threads"
| "zlib" => Some(name),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/process.rs` around lines 105 - 147, Update the
builtin-module allowlist in the getBuiltinModule implementation to include
sqlite, dgram, domain, inspector, inspector/promises, and repl, matching the
modules accepted by supported_require_builtin in module_require.rs. Preserve the
existing return behavior for recognized and unrecognized module names.

_ => None,
}
}
Expand Down
Loading
Loading