fix(codegen): #678 — re-export rename resolves to origin export name#785
Merged
Conversation
When a TypeScript module imports a name that traverses a re-export
rename — `import { Box } from "ink"` where `ink/build/index.js` does
`export { default as Box } from './components/Box.js'` — the codegen
was forming the cross-module extern as
`perry_fn_<components_Box_js>__Box`. The origin module emits the symbol
under its own export name (`default`), so the linker failed with
`Undefined symbols: _perry_fn_..._Box`. Every compile-package that
shapes its barrel as `export { default as <Name> } from "./<Name>.js"`
(ink, hono adapters, drizzle subpackages, react sub-bundles, ...) hit
this on any named import.
The fix tracks the *origin name* alongside the origin path across
re-export chains and threads it through codegen so every
`perry_fn_<src>__<suffix>` construction site picks the right suffix:
* New `import_function_origin_names: HashMap<String, String>` on
`CompileOptions` parallels `import_function_prefixes`. Entries are
inserted only when origin_name != consumer_name (sparse map, identity
fallback at the call site).
* The CLI driver's per-module loop builds the map by consulting
`all_module_export_origin_names` (a parallel to `all_module_exports`
populated during the export-propagation loop). Named, ReExport,
ExportAll, and NamespaceReExport branches all surface deeper origin
names through any number of transitive hops.
* `import_origin_suffix()` helper in `perry-codegen` returns the
override-or-identity suffix. Every `perry_fn_<src>__<name>`
construction site (lower_call.rs, expr.rs ExternFuncRef + namespace
member dispatch, codegen.rs FuncRef-as-value wrapper emission) now
routes through it.
* `imported_vars` classification probes BOTH `(origin, exported_name)`
AND `(origin, origin_name)` so a `const X = ...; export default X`
re-exported under a different name still gets recognized as a
variable and routed through the closure-getter call path instead of
being treated as a direct function call.
* Object cache key includes the new map so two builds with the same
modules but different re-export shapes don't share cached `.o`.
Validation:
* New regression fixture `test-files/test_issue_678_reexport_default.ts`
+ `test-files/fixtures/issue_678_pkg/` exercises all three shapes
(default-as-rename of a const closure, default-as-rename of a
function declaration, named-as-rename of a function declaration);
byte-for-byte parity with `node --experimental-strip-types`.
* `cargo test --release -p perry -p perry-codegen` — 157 + 29 tests pass.
* `/tmp/run_gap_tests.sh` — 34/36 (same two pre-existing failures:
`test_gap_console_methods`, `test_gap_regexp_advanced`).
* Existing `test_issue_310_namespace_reexport.ts` still passes (the
ExportAll/NamespaceReExport branches keep working).
* The minimal ink repro from #678 now resolves Box / render correctly;
remaining link failures (Text — `export default function`-body not
lowered, react.development.js compile error, ink/devtools namespace
global) are pre-existing bugs in separate code paths, untouched by
this PR and not introduced by it.
Refs: #678
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
Closes the symbol-name-mismatch path of #678. When a consumer imports a name that traverses a re-export rename (
import { Box } from \"ink\"whereink/build/index.jsdoesexport { default as Box } from './components/Box.js'), the codegen was forming the cross-module extern asperry_fn_<components_Box_js>__Box. The origin module emits the symbol under its own export name (default), so the linker failed withUndefined symbols: _perry_fn_..._Box.Every compile-package shaped as
export { default as <Name> } from \"./<Name>.js\"(ink components, hono adapters, drizzle subpackages, react sub-bundles, ...) hit this on any named import. The original issue's hypothesis pointed at V8-fallback demotion, but reduction to a single-package fixture shows the actual root cause is the lost rename across the re-export hop, not the V8 fallback path.Root cause
all_module_exports[ink/index]carried\"Box\" → components/Box.js pathbut not the second piece of information needed — what name does Box.js export it under?. Every codegen call/wrapper/getter site that builtperry_fn_<src>__<suffix>reused the consumer's imported name (Box) verbatim. The origin module emits the symbol under its own local name (default, because Box.js doesconst Box = ...; export default Box).Fix approach
Track origin-name alongside origin-path across re-export chains. Threaded through end-to-end:
HIR-to-driver: new parallel map
all_module_export_origin_names: BTreeMap<String, BTreeMap<String, String>>populated alongsideall_module_exportsduring the export propagation loop. Sparse — only populated when origin_name differs from export_name. ReExport, Named, ExportAll, and NamespaceReExport branches all walk one extra hop to surface the deepest origin name through any chain depth.Driver-to-codegen: new
CompileOptions::import_function_origin_namesfield, copied intoCrossModuleCtx, exposed onFnCtx. Cache key inobject_cache.rsincludes it so re-shape changes invalidate cached.o.Codegen sites: new
import_origin_suffix(map, name)helper returns the override-or-identity suffix. Everyperry_fn_<src>__<name>construction site routes through it —lower_call.rs(direct extern call + namespace member call),expr.rs(ExternFuncRef value path, getter path for imported vars, namespace static method dispatch, property access on imported namespace),codegen.rs(FuncRef-as-value wrapper target).var-vs-function classification: the
imported_varslookup now probes BOTH(origin, exported_name)AND(origin, origin_name)so aconst X = ...; export default Xre-exported as a different name still routes through the closure-getter call path instead of a direct function call.Validation
test-files/test_issue_678_reexport_default.ts+test-files/fixtures/issue_678_pkg/covers three shapes (default-as-rename of const closure, default-as-rename of function decl, named-as-rename of function decl). Byte-for-byte parity with `node --experimental-strip-types`.Test plan
export * as X from ...) #310 regression test still passes