Skip to content

fix: #837 — emit wrapper for default/null/undefined-named exports#870

Merged
proggeramlug merged 1 commit into
mainfrom
fix/837-836-reserved-name-wrappers
May 16, 2026
Merged

fix: #837 — emit wrapper for default/null/undefined-named exports#870
proggeramlug merged 1 commit into
mainfrom
fix/837-836-reserved-name-wrappers

Conversation

@proggeramlug
Copy link
Copy Markdown
Contributor

@proggeramlug proggeramlug commented May 16, 2026

Summary

  • The wrapper-emission site at crates/perry-codegen/src/codegen.rs:2552-2586 keyed __perry_wrap_<...> on each function's local HIR name, while the consumer-side reference at crates/perry-codegen/src/expr.rs:~3819 builds the same symbol from the exported name. For renamed exports — export { local as exported } or export default function NAME — the exported wrapper symbol was never defined and the link failed.
  • A new emission pass right after the regular wrapper loop walks hir.exports, picks out Named { local, exported } entries with exported != local, and emits __perry_wrap_perry_fn_<src>__<sanitize(exported)> with external linkage.
    • Function rename → forwards to perry_fn_<src>__<local> using the closure-call ABI.
    • Variable / class / type rename, or export default function NAME whose body the HIR lowerer never recorded (crates/perry-hir/src/lower.rs:5296 TODO) → no-op returning undefined, mirroring the existing __perry_wrap_perry_unknown_func fallback.

Reproductions (pre-fix)

#837 — uuid's dist/esm-browser/v35.js uses export default function v35(...). Consumers in v3.js/v5.js pass v35 as a closure value, which transitively pulls the wrapper symbol:

Undefined symbols for architecture arm64:
  ___perry_wrap_perry_fn_node_modules_uuid_dist_esm_browser_v35_js__default, referenced from:
      ___perry_wrap_perry_fn_node_modules_uuid_dist_esm_browser_v3_js__v3
      ___perry_wrap_perry_fn_node_modules_uuid_dist_esm_browser_v5_js__v5

#836 (partial) — zod's src/v4/core/regexes.ts uses const _null = /.../; export { _null as null }; (same for undefined). A consumer that reads regexes.null / regexes.undefined as a value falls through to the function-shape branch and references:

___perry_wrap_perry_fn_node_modules_zod_src_v4_core_regexes_ts__null
___perry_wrap_perry_fn_node_modules_zod_src_v4_core_regexes_ts__undefined

Validation

  • Post-fix nm on node_modules_uuid_dist_esm_browser_v35_js.o now shows T ___perry_wrap_perry_fn_<v35.js>__default. uuid repro reaches the linker successfully (sha1.js's Call callee shape not supported (PropertyGet) with 20 args is a separate codegen gap and surfaces independently; the wrapper-emission failure no longer blocks).
  • Post-fix nm on node_modules_zod_src_v4_core_regexes_ts.o now shows T ___perry_wrap_..._null and T ___perry_wrap_..._undefined. The repro program (import * as regexes from "zod/v4/core/regexes"; console.log(typeof regexes.null);) links and runs, printing function (the closure handle, per js_closure_alloc_singleton semantics).
  • cargo build --release clean.
  • cargo test --release --workspace --exclude perry-ui-ios --exclude perry-ui-tvos --exclude perry-ui-watchos --exclude perry-ui-visionos --exclude perry-ui-android --exclude perry-ui-windows --exclude perry-ui-gtk4 — all green (no regressions in the 231-test perry-codegen / perry suites or anywhere else in the workspace).
  • cargo fmt --all clean.

Scope notes

  • The uuid v35.js runtime ("v3() returns undefined" rather than a uuid string) is a separate HIR-level bug: crates/perry-hir/src/lower.rs:5296 has a literal // TODO: properly lower function expression in the ExportDefaultDecl::Fn arm and never registers the function body in module.functions. This PR's scope is codegen-only; the runtime fix needs an HIR change and a different PR.
  • #836's cross-module class re-exports are out of scope per the task brief — that's a separate PR.

Refs #837 (link error only — runtime gaps tracked in #871).
Refs #836 (partial — null/undefined wrappers fixed; cross-module class re-exports remain a separate PR).

Test plan

  • Reproduce both link failures pre-fix (see above).
  • Confirm __perry_wrap_..._default is emitted with external linkage in v35.js's .o.
  • Confirm __perry_wrap_..._null / __perry_wrap_..._undefined are emitted in regexes.ts's .o.
  • Confirm uuid repro reaches linker (separate sha1.js codegen issue surfaces post-fix; no regression).
  • Confirm zod regex repro compiles and runs.
  • Workspace cargo test clean.

…ports

The wrapper-emission asymmetry: regular wrappers (codegen.rs:2552-2586)
key on the local HIR function name, while consumer references
(expr.rs:~3819) build wrapper symbols from the *exported* name. For
`export { local as exported }` renames, the consumer-facing
`__perry_wrap_perry_fn_<src>__<exported>` had no definition and the link
failed.

This adds a parallel emission pass right after the regular wrapper loop
that walks `hir.exports`, picks out `Named { local, exported }` entries
where `exported != local`, and emits the renamed wrapper:

- Function rename → forward to `perry_fn_<src>__<local>` (closure-call
  ABI, mirroring the regular wrapper).
- Variable/class/type rename or `export default function NAME` whose
  body the HIR lowerer skipped (lower.rs:5296 TODO) → no-op returning
  undefined, mirroring `__perry_wrap_perry_unknown_func`.

Validated:
- `__perry_wrap_perry_fn_<v35.js>__default` now emitted (issue #837).
- `__perry_wrap_perry_fn_<regexes.ts>__null` /  `__undefined` now
  emitted (issue #836 partial — the variable-rename leg).

Refs #836 (partial — cross-module class re-exports remain a separate PR).
@proggeramlug proggeramlug merged commit 1dd9f47 into main May 16, 2026
9 checks passed
@proggeramlug proggeramlug deleted the fix/837-836-reserved-name-wrappers branch May 16, 2026 16:15
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.

1 participant