Skip to content

fix(link): route emitted js_event_emitter_* symbols to perry-ext-events (#5140)#5185

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-fix-5140-eventemitter3
Jun 15, 2026
Merged

fix(link): route emitted js_event_emitter_* symbols to perry-ext-events (#5140)#5185
proggeramlug merged 1 commit into
mainfrom
worktree-fix-5140-eventemitter3

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #5140 — compiling a program that imports eventemitter3 (or any package whose default/named export is an EventEmitter class) fails at link time with Undefined symbols: _js_event_emitter_*.

Root cause

new EventEmitter() / .on / .emit / .removeAllListeners lower to the native 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 was imported from. The canonical implementations live in perry-ext-events, the [bindings.events] well-known crate. import "events" flips that wrapper onto the link line by inserting "events" into ctx.native_module_imports.

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 perry-ext-events stays off the link line even though codegen emitted calls into it. The link then fails:

Undefined symbols for architecture arm64:
  "_js_event_emitter_emit"
  "_js_event_emitter_new_with_options"
  "_js_event_emitter_on"
  "_js_event_emitter_remove_all_listeners"

Fix

Tag the codegen-emitted core js_event_emitter_* symbols in the FFI provenance registry (ext_registry.rs) as WellKnown("events"). This makes the well-known flip fire off codegen provenance instead of imports — the exact same mechanism the existing #846 / #3954 http/net rows already use for symbols emitted without a matching source-level import.

Flipping "events" does three things, all already wired:

  • links perry-ext-events onto the link line,
  • strips the duplicate bundled-events perry-stdlib feature (so there's no duplicate-symbol conflict — optimized_libs.rs line ~366), and
  • activates external-events-construct, 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 (EventEmitterAsyncResource is node:events-only).

Testing

  • New unit test emitted_event_emitter_symbols_route_to_events (all 5 ext_registry tests pass).
  • Repro from the issue (import EventEmitter from "eventemitter3") now links and prints 5 (matching Node).
  • import { EventEmitter } from "events" still links and prints 5 (no regression).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved EventEmitter symbol resolution to ensure proper routing and binding resolution in all scenarios.
  • Tests

    • Added regression test to verify EventEmitter symbols are correctly identified and routed.

…ts (#5140)

`new EventEmitter()` / `.on` / `.emit` / `.removeAllListeners` lower to
`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 — and `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 for architecture arm64:
      "_js_event_emitter_emit"
      "_js_event_emitter_new_with_options"
      "_js_event_emitter_on"
      "_js_event_emitter_remove_all_listeners"

Tag the emitted core EventEmitter symbols in the codegen FFI provenance
registry as `WellKnown("events")` so the well-known flip fires off
codegen provenance instead of imports — the same mechanism the #846 /
#3954 http/net rows already use. Flipping `"events"` links
perry-ext-events, strips the duplicate `bundled-events` stdlib feature,
and activates `external-events-construct` (the default-import dynamic-new
path, #4995).

Verified: `import EventEmitter from "eventemitter3"; …` now links and
prints `5` (matching Node); `import { EventEmitter } from "events"` still
prints `5`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a5760d27-c162-47d4-b5c0-43d74f1808c1

📥 Commits

Reviewing files that changed from the base of the PR and between 39e2166 and 5c738b6.

📒 Files selected for processing (1)
  • crates/perry-codegen/src/ext_registry.rs

📝 Walkthrough

Walkthrough

FFI_REGISTRY in ext_registry.rs is extended with entries that map the full set of codegen-emitted js_event_emitter_* symbols to OwnerKind::WellKnown("events"). A new regression test verifies each listed symbol resolves to that well-known binding.

Changes

EventEmitter Symbol Routing

Layer / File(s) Summary
FFI_REGISTRY entries and routing test
crates/perry-codegen/src/ext_registry.rs
Maps js_event_emitter_* symbols (new, on, emit, listener management, etc.) to OwnerKind::WellKnown("events") in FFI_REGISTRY, and adds unit test emitted_event_emitter_symbols_route_to_events asserting each symbol resolves to that owner.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#5172: Addresses the complementary side of EventEmitterevents routing by detecting EventEmitter usage at the source level, while this PR handles the codegen-emitted FFI symbol side via FFI_REGISTRY.

Poem

🐰 Hop, hop, the registry grows,
js_event_emitter_* now in rows!
No more lost symbols in the link,
WellKnown("events") — quick as a wink.
The rabbit cheers, the tests all pass,
Each emit and on finds home at last! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the core fix (routing emitted js_event_emitter_* symbols to perry-ext-events) and directly matches the primary change in the changeset.
Description check ✅ Passed The description is comprehensive, covering summary, root cause analysis, the fix, testing approach, and key implementation details. All major sections from the template are addressed or reasonably omitted.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fix-5140-eventemitter3

Comment @coderabbitai help to get the list of available commands and usage tips.

@proggeramlug proggeramlug merged commit ad09dc8 into main Jun 15, 2026
15 checks passed
@proggeramlug proggeramlug deleted the worktree-fix-5140-eventemitter3 branch June 15, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

compilePackages: eventemitter3 fails to link — undefined _js_event_emitter_* symbols

1 participant