From 4e2500aa020ad876149932b8b082174f6be80e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 21 Aug 2026 19:15:27 +0200 Subject: [PATCH] fix(compile): treat require() as a stdlib requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ctx.native_module_imports, which drives needs_stdlib and therefore the link mode, was populated only by the ESM import walk. A CommonJS require("http") never landed in it, so the link came out runtime-only, perry-stdlib's dispatch init never ran, JS_NATIVE_HTTP_DISPATCH stayed null, and the createServer arm returned undefined. Not http-specific: any stdlib-backed builtin reached via require behaved the same. The lowered HIR cannot answer this — the CJS shim resolves the name through a generated runtime switch containing every builtin — so the requirement is recovered from the literal call sites via the existing cjs_wrap::extract_require_specifiers. Programs that reference no stdlib-backed builtin keep linking runtime-only. --- CLAUDE.md | 2 +- Cargo.lock | 152 +++++++++--------- Cargo.toml | 2 +- changelog.d/8548-require-stdlib-link.md | 28 ++++ .../src/commands/compile/cjs_wrap/mod.rs | 6 +- .../src/commands/compile/collect_modules.rs | 19 +++ 6 files changed, 129 insertions(+), 80 deletions(-) create mode 100644 changelog.d/8548-require-stdlib-link.md diff --git a/CLAUDE.md b/CLAUDE.md index 836816ae11..4d80c7f41a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1514 +**Current Version:** 0.5.1515 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 457cd7e4dc..66eccccb6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5522,7 +5522,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "base64 0.22.1", @@ -5582,7 +5582,7 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-dispatch", "serde", @@ -5590,7 +5590,7 @@ dependencies = [ [[package]] name = "perry-audio-miniaudio" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "cc", "libc", @@ -5599,7 +5599,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "inkwell", @@ -5616,7 +5616,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-hir", @@ -5624,7 +5624,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-hir", @@ -5632,7 +5632,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-dispatch", @@ -5641,7 +5641,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-hir", @@ -5649,7 +5649,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "base64 0.22.1", @@ -5661,7 +5661,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-hir", @@ -5669,7 +5669,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "async-trait", @@ -5698,14 +5698,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "serde", "serde_json", @@ -5713,7 +5713,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1514" +version = "0.5.1515" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5724,7 +5724,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "clap", @@ -5739,7 +5739,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "block2", "objc2", @@ -5749,7 +5749,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "argon2", "perry-ffi", @@ -5758,7 +5758,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "reqwest", @@ -5767,7 +5767,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "bcrypt", "perry-ffi", @@ -5775,7 +5775,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "rusqlite", @@ -5783,7 +5783,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "scraper", @@ -5791,7 +5791,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "perry-runtime", @@ -5799,7 +5799,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "chrono", "cron", @@ -5809,7 +5809,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "chrono", "perry-ffi", @@ -5817,7 +5817,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "rust_decimal", @@ -5825,7 +5825,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "serde_json", @@ -5833,7 +5833,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5841,7 +5841,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "perry-runtime", @@ -5849,14 +5849,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "bytes", "http-body-util", @@ -5874,7 +5874,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "bytes", "lazy_static", @@ -5887,7 +5887,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "bytes", @@ -5912,7 +5912,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "lazy_static", "perry-ffi", @@ -5922,7 +5922,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "jsonwebtoken", @@ -5933,7 +5933,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "lru", "perry-ffi", @@ -5942,7 +5942,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "chrono", "perry-ffi", @@ -5950,7 +5950,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "bson", "futures-util", @@ -5962,7 +5962,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "chrono", "perry-ffi", @@ -5972,7 +5972,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "nanoid", "perry-ffi", @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "bytes", "perry-ffi", @@ -5994,7 +5994,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6013,7 +6013,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "lettre", "perry-ffi", @@ -6023,7 +6023,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "printpdf", @@ -6031,7 +6031,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "sqlx", @@ -6040,7 +6040,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "governor", "perry-ffi", @@ -6048,7 +6048,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "fast_image_resize", "image", @@ -6058,14 +6058,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "lazy_static", "perry-ffi", @@ -6074,7 +6074,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "perry-runtime", @@ -6083,7 +6083,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "uuid", @@ -6091,7 +6091,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ffi", "regex", @@ -6101,7 +6101,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "futures-util", "lazy_static", @@ -6114,7 +6114,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "brotli", "flate2", @@ -6124,7 +6124,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "dashmap", "once_cell", @@ -6133,7 +6133,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-api-manifest", @@ -6151,7 +6151,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-diagnostics", @@ -6163,7 +6163,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "base64 0.22.1", @@ -6205,14 +6205,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6307,14 +6307,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "perry-hir", @@ -6323,14 +6323,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "itoa", @@ -6348,7 +6348,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "rand 0.10.1", "serde", @@ -6358,7 +6358,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "cairo-rs 0.22.0", @@ -6381,7 +6381,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "block2", @@ -6398,7 +6398,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "block2", @@ -6414,7 +6414,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1514" +version = "0.5.1515" [[package]] name = "perry-ui-test" @@ -6425,11 +6425,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1514" +version = "0.5.1515" [[package]] name = "perry-ui-tvos" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "block2", @@ -6446,7 +6446,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "block2", @@ -6463,7 +6463,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "block2", "libc", @@ -6477,7 +6477,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "base64 0.22.1", "libc", @@ -6496,14 +6496,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "anyhow", "base64 0.22.1", @@ -6519,7 +6519,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1514" +version = "0.5.1515" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index aa90f80873..b8d1071854 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1514" +version = "0.5.1515" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/8548-require-stdlib-link.md b/changelog.d/8548-require-stdlib-link.md new file mode 100644 index 0000000000..926c7d457d --- /dev/null +++ b/changelog.d/8548-require-stdlib-link.md @@ -0,0 +1,28 @@ +**`require('http')` linked runtime-only, so every stdlib-backed builtin reached that way returned `undefined` (#8547).** + +Same program, two import forms, opposite outcomes: + +``` +import * as http from 'node:http'; → Linking (with stdlib)... typeof createServer() === "object" +const http = require('http'); → Linking (runtime-only)... typeof createServer() === undefined +``` + +**Cause.** `ctx.native_module_imports` — which drives `needs_stdlib` and therefore the link mode — was populated only by the ESM import walk in `collect_modules.rs`. A CommonJS `require("http")` never landed in it, so the link came out runtime-only, `perry-stdlib`'s `common/dispatch/init.rs` never ran, `JS_NATIVE_HTTP_DISPATCH` stayed null, and the `("http", "createServer")` arm in `native_module_dispatch/dispatch_d_i.rs` took its documented null branch and returned `undefined`. Nothing about this was http-specific: any stdlib-backed builtin reached through `require` behaved the same way. + +**Why the obvious fix does not work.** Recovering the requirement from the lowered HIR — the way `uses_dgram` does — fails, and it is worth writing down so nobody retries it. For `require('http')` the module name never becomes a `module:` marker; the CJS shim emits a *runtime* dispatcher that switches over builtin names as string literals, and that switch contains a case for **every** builtin regardless of what the program uses. A static scan of the HIR would match the table rather than the call and link the stdlib into every CJS program. + +The fix therefore reads the literal call sites, which is exactly what `cjs_wrap::extract_require_specifiers` already extracts for CJS wrapping: any `require("")` whose specifier satisfies `perry_hir::requires_stdlib` now feeds `needs_stdlib` / `native_module_imports` alongside the import walk. + +**Scope, verified by measurement** — the change can only add stdlib linking for a program that genuinely references a stdlib-backed builtin: + +| program | link mode | binary | +|---|---|---| +| `console.log(...)` only | runtime-only (unchanged) | 7.7 MB | +| `require("path")` (runtime-only module) | runtime-only (unchanged) | 7.8 MB | +| `require("http")` | with stdlib (was runtime-only) | 14 MB | + +Dynamic `require(someVar)` remains statically undetectable and is unchanged; the generated dispatcher can reach any builtin, so whether that case should force stdlib linking is a separate product decision, noted on #8547. + +`crates/perry/tests/issue_4903_listen_callback_deferred.rs` goes 0/2 → 2/2, which clears the last sweep-tier `cargo-test` failure on `main`. + +Closes #8547. diff --git a/crates/perry/src/commands/compile/cjs_wrap/mod.rs b/crates/perry/src/commands/compile/cjs_wrap/mod.rs index 76face80f3..184a342fdd 100644 --- a/crates/perry/src/commands/compile/cjs_wrap/mod.rs +++ b/crates/perry/src/commands/compile/cjs_wrap/mod.rs @@ -54,9 +54,11 @@ use extract_exports::{ extract_object_literal_exports_from_require, extract_single_module_exports_assignment, module_reexport_specs, }; +// #8547: the stdlib-link decision needs the literal `require()` specifiers. +pub(crate) use extract_requires::extract_require_specifiers; use extract_requires::{ - extract_export_star_specs, extract_require_aliases_with_ranges, extract_require_specifiers, - function_local_specs, identifier_is_declared_binding, identifier_is_reassigned, + extract_export_star_specs, extract_require_aliases_with_ranges, function_local_specs, + identifier_is_declared_binding, identifier_is_reassigned, }; use hoist_classes::{ extract_top_level_class_decls, rewrite_module_exports_class_expression, diff --git a/crates/perry/src/commands/compile/collect_modules.rs b/crates/perry/src/commands/compile/collect_modules.rs index af32e694db..2b10ba7074 100644 --- a/crates/perry/src/commands/compile/collect_modules.rs +++ b/crates/perry/src/commands/compile/collect_modules.rs @@ -386,6 +386,25 @@ fn collect_module_one( &ctx.compile_packages, canonical.parent().unwrap_or_else(|| Path::new(".")), ); + + // #8547: a builtin reached through `require("http")` never appears in the + // ESM import walk below, so `needs_stdlib` stayed false, the link came out + // runtime-only, `perry-stdlib`'s dispatch init never ran, and every + // stdlib-backed export returned `undefined` (`http.createServer` was the + // reported case; the bug is not http-specific). The lowered body cannot + // answer this — the CJS shim resolves the name through a generated runtime + // switch that contains EVERY builtin — so recover it from the literal + // call sites, which is exactly what `extract_require_specifiers` already + // finds for CJS wrapping. Dynamic `require(someVar)` remains undetectable + // and is unchanged. + for spec in super::cjs_wrap::extract_require_specifiers(&source) { + if !perry_hir::requires_stdlib(&spec) { + continue; + } + ctx.needs_stdlib = true; + let normalized = spec.strip_prefix("node:").unwrap_or(&spec).to_string(); + ctx.native_module_imports.insert(normalized); + } if was_cjs_wrapped && ctx.debug_symbols { if let Some(prefix_lines) = cjs_wrap_body_prefix_lines { let lines_after_transform = source.bytes().filter(|&b| b == b'\n').count();