Skip to content
Merged
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
77 changes: 77 additions & 0 deletions crates/perry-codegen/src/ext_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,47 @@ const FFI_REGISTRY: &[(&str, OwnerKind)] = &[
("js_url_create_object_url", OwnerKind::Stdlib { feature: Some("http-client") }),
("js_url_revoke_object_url", OwnerKind::Stdlib { feature: Some("http-client") }),
("js_buffer_resolve_object_url", OwnerKind::Stdlib { feature: Some("http-client") }),

// ── #5140: EventEmitter without a source-level `import "events"` ──
// `new EventEmitter()` / `.on` / `.emit` / … lower to these
// `js_event_emitter_*` helpers purely off the class NAME being
// `EventEmitter` (`lower_call/builtin.rs` + the events native-table
// rows), regardless of which package the binding came from. The
// canonical implementations live in `perry-ext-events` (the
// `[bindings.events]` well-known crate); `import "events"` flips that
// wrapper onto the link line via `ctx.native_module_imports`. But a
// program that gets `EventEmitter` from a *different* package — e.g.
// `import EventEmitter from "eventemitter3"` under
// `perry.compilePackages` — never inserts `"events"` into the import
// set, so the wrapper stays off the link line and the build fails with
// `Undefined symbols: _js_event_emitter_new_with_options` (and the
// `_on` / `_emit` / `_remove_all_listeners` companions). Tagging the
// emitted symbols here makes the well-known flip fire off codegen
// provenance instead of imports — same mechanism as the #846 / #3954
// http/net rows above. Flipping `"events"` also activates perry-stdlib's
// `external-events-construct` feature (see optimized_libs.rs), which the
// default-import dynamic-`new` path relies on (#4995).
//
// Only the core surface defined by perry-ext-events is listed; the
// `js_event_emitter_async_resource_*` helpers live in perry-stdlib and
// are out of scope here (`EventEmitterAsyncResource` is node:events-only).
("js_event_emitter_new", OwnerKind::WellKnown("events")),
("js_event_emitter_new_with_options", OwnerKind::WellKnown("events")),
("js_event_emitter_on", OwnerKind::WellKnown("events")),
("js_event_emitter_once", OwnerKind::WellKnown("events")),
("js_event_emitter_prepend_listener", OwnerKind::WellKnown("events")),
("js_event_emitter_prepend_once_listener", OwnerKind::WellKnown("events")),
("js_event_emitter_emit", OwnerKind::WellKnown("events")),
("js_event_emitter_emit0", OwnerKind::WellKnown("events")),
("js_event_emitter_remove_listener", OwnerKind::WellKnown("events")),
("js_event_emitter_remove_all_listeners", OwnerKind::WellKnown("events")),
("js_event_emitter_listener_count", OwnerKind::WellKnown("events")),
("js_event_emitter_listeners", OwnerKind::WellKnown("events")),
("js_event_emitter_raw_listeners", OwnerKind::WellKnown("events")),
("js_event_emitter_event_names", OwnerKind::WellKnown("events")),
("js_event_emitter_set_max_listeners", OwnerKind::WellKnown("events")),
("js_event_emitter_get_max_listeners", OwnerKind::WellKnown("events")),
("js_event_emitter_domain_value", OwnerKind::WellKnown("events")),
];

/// Process-wide collector of provider keys observed during codegen.
Expand Down Expand Up @@ -632,6 +673,42 @@ mod tests {
}
}

/// #5140 regression: `new EventEmitter()` / `.on` / `.emit` /
/// `.removeAllListeners` lower to `js_event_emitter_*` helpers off the
/// class name alone, so a program that imports `EventEmitter` from a
/// non-`events` package (`import EventEmitter from "eventemitter3"`)
/// emits these symbols without ever inserting `"events"` into the import
/// set. Each must route to `WellKnown("events")` so the well-known flip
/// pulls `perry-ext-events` onto the link line; before the fix the build
/// failed with `Undefined symbols: _js_event_emitter_new_with_options`.
#[test]
fn emitted_event_emitter_symbols_route_to_events() {
let _guard = PROVIDER_TEST_LOCK
.lock()
.expect("provider test lock poisoned");
for symbol in [
"js_event_emitter_new",
"js_event_emitter_new_with_options",
"js_event_emitter_on",
"js_event_emitter_once",
"js_event_emitter_prepend_listener",
"js_event_emitter_prepend_once_listener",
"js_event_emitter_emit",
"js_event_emitter_emit0",
"js_event_emitter_remove_listener",
"js_event_emitter_remove_all_listeners",
"js_event_emitter_listener_count",
"js_event_emitter_listeners",
"js_event_emitter_raw_listeners",
"js_event_emitter_event_names",
"js_event_emitter_set_max_listeners",
"js_event_emitter_get_max_listeners",
"js_event_emitter_domain_value",
] {
assert_symbol_routes_to(symbol, OwnerKind::WellKnown("events"));
}
}

/// #2013 regression: net validation fixtures emit provider-only
/// `perry-ext-net` symbols through native-table lowering. Each one must
/// route to the net well-known archive so `PERRY_NO_AUTO_OPTIMIZE=1`
Expand Down