fix(#665): compilePackages override + 3 follow-ups to make rate-limiter-flexible work end-to-end - #735
Merged
Merged
Conversation
proggeramlug
force-pushed
the
worktree-issue-665-resolver-opt-in
branch
from
May 13, 2026 05:48
39ac69c to
7b649e7
Compare
…g (v0.5.895) Two-site override (resolve.rs::resolve_import + collect_modules.rs) so a package listed in `perry.compilePackages` is compiled from its node_modules source instead of routed to the built-in Rust FFI binding. Unblocks users of packages whose native binding is incomplete (e.g. `rate-limiter-flexible`, where `perry-ext-ratelimit` only wires `consume` through codegen and the codegen-declared `js_ratelimit_create(I64)` doesn't match the real two-arg `js_ratelimit_new(points, duration_secs)` impl). Default behavior unchanged: stock projects without `compilePackages` go through the exact same path. Opt-in and package-scoped. Refs #665. Renumbered from v0.5.892 → v0.5.895 (parallel PRs #734/#736/#737 landed on main). See CHANGELOG.md for full root-cause notes.
… cross-class method symbols (v0.5.896) When two different classes are both default-imported in the same file, the `class_ids` / `imported_class_prefix` / `imported_class_source_name` side maps in `compile_module` overwrote each other (last-writer-wins) while `class_table` kept the first stub (first-writer-wins). The mismatch produced method symbols mangled with FIRST stub's method list + LAST writer's class name + LAST writer's source prefix — names the linker can't resolve since Phase F only declares each ic's REAL methods. Flip the three side-map `.insert()` calls to `.entry().or_insert()` so all four maps agree on first-writer-wins. Fixes the `@perry_method_..._RateLimiterRes_js__RateLimiterRes__delete` / `__get` / `__set` / `__block` undefined-value errors that surfaced in rate-limiter-flexible's 5 leaf files once v0.5.895's routing opt-in let them reach codegen. Refs #665. See CHANGELOG.md for full root-cause walkthrough.
…trailing args (v0.5.897) Existing `"setTimeout" if args.len() == 2` arm only handled the 2-arg shape; 3+ arg call sites (e.g. `setTimeout(resolve, delay, res)` inside Promise executors per ECMA-262 §27.5.4.1) fell through to the generic ExternFuncRef fallthrough and emitted `call @settimeout(...)` against a symbol nothing in stdlib defines. Discovering call site: `RateLimiterMemory.consume()`'s Promise executor in rate-limiter-flexible. Adds: - `CallbackTimer::args: Vec<f64>` (empty for 2-arg shape — back-compat). - `js_set_timeout_callback_args(callback, delay_ms, args_ptr, n_args)` runtime entry that copies the buffer in. - `js_callback_timer_tick` dispatch via `match args.len()` to `js_closure_call0..js_closure_call9`, clamping at 9 trailing args. - Codegen `"setTimeout" if args.len() >= 3` arm: alloca a stack buffer of doubles for the trailing args, GEP for the base, call the new runtime entry. Refs #665. See CHANGELOG.md for full root-cause + scope notes.
…ce lowering (v0.5.898)
HIR's `is_native_module` only consulted the NATIVE_MODULES manifest, so
`rate-limiter-flexible` was lowered as a native import regardless of the
user's `compilePackages` opt-in. That cascaded:
register_native_module(local, "rate-limiter-flexible", ...)
→ new RateLimiterMemory(...) triggers
register_native_instance(limiter, "rate-limiter-flexible", "RateLimiterMemory")
→ limiter.consume in expr_member::lower_member emits
NativeMethodCall { module, object: Some(limiter), method: "consume", args: [] }
→ codegen routes to js_native_call_method → no FFI entry → 0.0
So `typeof limiter.consume === "number"`. For genuine native bindings
(req.method on http.IncomingMessage) this zero-arg-getter lowering is
correct; for compile-package-overridden classes it's wrong because
`consume` is a real method, not a getter.
Fix: add a thread-local `COMPILE_PACKAGES_OVERRIDE: HashSet<String>` in
perry-hir, set by the compiler driver via
`set_compile_packages_override(ctx.compile_packages.clone())` before
each `lower_module_full` invocation and cleared after.
`is_native_module` parses the package name (supports `node:` prefix +
`@scope/pkg` scoped form) and returns false when the package is in the
override even if it appears in NATIVE_MODULES.
Validation: rate-limiter-flexible probe now reports `typeof
limiter.consume: function` (was "number"). Workspace tests green.
Refs #665. See CHANGELOG.md for full root-cause walkthrough.
proggeramlug
force-pushed
the
worktree-issue-665-resolver-opt-in
branch
from
May 13, 2026 06:17
7b649e7 to
5cbade5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
Four-commit stack closing #665's investigation. After this PR, the issue's original repro
import { RateLimiterMemory } from "rate-limiter-flexible"; new RateLimiterMemory({...})produces an instance whose methods are reachable as function values (no moretypeof limiter.consume === "number").v0.5.892 — resolver opt-in.
perry.compilePackagesnow overrides the built-in NATIVE_MODULES routing. Two-site change inresolve.rs+collect_modules.rs.v0.5.893 — codegen side-map consistency. Three side maps in
compile_moduleused last-writer-wins.insert()whileclass_tableused first-writer-wins. When two classes are both default-imported into the same file, the mismatch produced phantom method symbols (@perry_method_..._RateLimiterRes_js__RateLimiterRes__deleteetc.). Flipped to.entry().or_insert().v0.5.894 — setTimeout trailing args.
setTimeout(fn, delay, ...args)only had a 2-arg codegen arm; 3+ arg calls (e.g.setTimeout(resolve, delay, res)inside Promise executors) fell through and emitted a baresetTimeoutsymbol the linker couldn't resolve. Addedjs_set_timeout_callback_argsruntime entry + dispatch viajs_closure_call0..9.v0.5.895 — HIR native-instance override.
is_native_moduleonly checked the NATIVE_MODULES manifest, so a compile-package-overridden class still got lowered as a native instance. That madeinstance.method(bare property read) emitNativeMethodCall { method, args: [] }— a zero-arg FFI getter call that returned0.0because the binding had no matching FFI entry, sotypeof instance.methodreturned"number". Fixed via thread-localCOMPILE_PACKAGES_OVERRIDEset by the compiler driver around each HIR lowering.End-to-end validation
Test plan
cargo build --release -p perry -p perry-runtime -p perry-stdlib— cleancargo test --release -p perry— 152 passedcargo test --release --workspace --exclude perry-ui-{ios,tvos,watchos,visionos,android,windows,gtk4}— exit 0, 30 buckets, 0 FAILEDtypeof limiter.consume === "function"setTimeout(fn, delay)and 3-argsetTimeout(fn, delay, ...args)byte-equal to BunScope
Closes all four layers of #665 that this investigation surfaced. Not in scope:
await limiter.consume(...)end-to-end through Promise resolution — bound-method dispatch via CLASS_VTABLE_REGISTRY works for the drizzle/hono fixtures so it should work here, but I haven't probed the full chain.perry-ext-ratelimit's native surface for users who'd prefer the FFI path overcompilePackages.setTimeout(would need a variadic-dispatcher closure-call helper).Refs #665.