fix(hir): anonymous export default function () {} — emit __default symbol#885
Merged
Conversation
…` symbol
Pre-fix `export default function () { ... }` (no name binding) was
dropped entirely by the HIR lowerer in `ExportDefaultDecl::DefaultDecl::Fn`
when `fn_expr.ident == None`. No HIR Function was created, codegen never
emitted `perry_fn_<src>__default`, and any consumer link-failed with
`Undefined symbols: _perry_fn_<src>__default`. The
`__perry_wrap_perry_fn_<src>__default` rename wrapper from #837 had
nothing to point at either. This blocked zod (`v4/locales/en.ts`) and
vitest under `perry.compilePackages` — the remaining-1-symbol case from
the #836 closeout.
When `fn_expr.ident == None` and the function has a body, synthesize an
`ast::FnDecl` with ident `default`, then run it through the same flow
that `ExportDecl::Fn` uses: lower the body via `lower_fn_decl`, flip
`is_exported = true`, register defaults and `exported_functions`, push
`Export::Named { local: "default", exported: "default" }`. The HIR
function name is `default`, so the LLVM symbol is
`perry_fn_<src>__default` — exactly what consumers ask for. Since
`f.name == exported_name`, the alias-wrapper loop is a no-op and the
undefined-stub fallback at `codegen.rs:2310` skips emission because the
real symbol exists.
Scope-narrow: this only touches the `fn_expr.ident == None` branch.
Named-default (`export default function foo() {}`) has its own separate
bug — function body also dropped — that's left untouched per the issue's
scope-narrow directive.
Regression test: `test-files/test_issue_anonymous_default_export.ts` +
`test-files/test_issue_anonymous_default_export_pkg/{producer,consumer}.ts`.
Matches node `--experimental-strip-types` byte-for-byte. Zod link-error
from #836's closeout is gone (`perry main.ts -o out` succeeds against
the canonical zod repro).
Refs #793, #836, #837.
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
export default function () { ... }(no name binding) entirely, so codegen never emittedperry_fn_<src>__defaultand consumers link-failed withUndefined symbols: _perry_fn_<src>__default. The__perry_wrap_perry_fn_<src>__defaultrename wrapper from uuid (v4) fails to compile:defaultexport name not wired up under compilePackages #837 also had nothing to point at.fn_expr.ident == Noneand the function has a body, synthesize anast::FnDeclwith identdefaultand run it through the same lower-and-export path thatExportDecl::Fnuses (lower_fn_decl→is_exported→func_defaults+exported_functions+Export::Named { local: "default", exported: "default" }). The HIR function namedefaultflows throughsanitize()unchanged, so the LLVM symbol isperry_fn_<src>__default— what consumers ask for. Sincef.name == exported_name, the alias-wrapper loop is a no-op and the undefined-stub fallback atcodegen.rs:2310skips emission because the real symbol exists.fn_expr.ident == Nonebranch changes. Named-default (export default function foo() {}) has a separate bug (body also dropped) that is left for a follow-up per the issue directive. Closes the remaining-1-symbol case from zod fails to compile under perry.compilePackages: cross-module class re-exports unresolved #836's closeout.Test plan
test-files/test_issue_anonymous_default_export.ts+test-files/test_issue_anonymous_default_export_pkg/{producer,consumer}.ts(producer exports an anonymous default fn returning 42; consumer imports and invokes it; expected output42).node --experimental-strip-typesbyte-for-byte (42).perry.compilePackages: ["zod"],import { z } from "zod"; console.log(z.object({a:z.string()}).parse({a:'hi'}).a);) compiles + links successfully (runtime divergence remains, separate).test_issue_836_zod_class_reexports.ts) still passes.cargo build --release -p perry-runtime -p perry-stdlib -p perryclean.cargo test --release -p perry-hir -p perry-codegenclean.cargo test --release --workspace(with the usual UI-crate excludes) exits 0.Refs #793, #836, #837.