fix(codegen): re-export-only modules must populate their namespace / follow re-export hops (#6304, #5916)#6309
Merged
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
🚧 Files skipped from review as they are similar to previous changes (9)
📝 WalkthroughWalkthroughThe PR resolves binding origins across import and re-export chains, bounds namespace re-export traversal, normalizes non-native import sources, and adds regression coverage for pure dynamic-import chunks and namespace-valued barrel re-exports. ChangesRe-export resolution
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant CompilePipeline
participant HIR
participant ModuleGraph
participant DynamicImport
CompilePipeline->>HIR: normalize import and export sources
HIR->>ModuleGraph: follow import and re-export bindings
ModuleGraph-->>HIR: resolved defining module and local binding
HIR-->>CompilePipeline: flattened export origin
DynamicImport->>CompilePipeline: load pure re-export chunk
CompilePipeline-->>DynamicImport: expose resolved bindings
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…#6304, #5916) Two bugs with the same shape: Perry resolved an exported name to the module that *mentions* it rather than the module that *defines* it, and so never followed the re-export hop to the real binding. #6304 — dynamic import() namespace of a pure re-export chunk was empty. esbuild/bun emit a shared chunk under `--splitting` as a two-statement re-export file: // agent-VP4LHHJR.js import { run } from "./chunk-XXXX.js"; export { run }; `flatten_exports` treated `export { run }` as a LOCAL export of the agent module. The driver then searched the agent's own HIR for a function/class/ global named `run`, found nothing (it is an IMPORT binding, not a definition), and fell back to a `ForeignVar` getter on `perry_fn_<agent>__run` — a symbol the #461 stub loop claims with an undefined-returning stub. Net effect: the whole namespace of a re-export-only chunk came out `undefined`, and an unguarded `ns.run()` printed `undefined` instead of throwing — a SILENT wrong answer that quietly broke every code-split bundle. A chunk containing real code was unaffected, which is why this hid for so long. Fix: `flatten_exports` now resolves an `Export::Named` whose local is an import binding to the module that actually defines it, following chains of import/re-export hops to the ultimate owner. Native imports and sources with no HIR in the graph are left alone (the resolver returns `None` and callers keep their prior behaviour), so this can only move an entry CLOSER to a real definition, never invent one. #5916 — barrel re-export of a namespace value failed to LINK. // selfns.ts export * as Token from "./selfns" // reexport_selfns.ts export { Token, estimate } from "./selfns" // main.ts import { Token } from "./reexport_selfns"; Token.estimate("hi") Undefined symbols: ___perry_wrap_perry_fn_reexport_selfns_ts__Token The consumer-side namespace detection only inspected the imported-from module's OWN exports for a `NamespaceReExport`. Across a `export { X } from` hop it saw a plain `ReExport`, fell through to the ordinary value path, and `Token.estimate(...)` lowered to a `StaticMethodCall` against a closure wrapper no module emits. Fix: walk the re-export chain first, so the scan runs against the module that actually DECLARES the namespace, under the name it declares it with. When no hop applies (the common case) the scan target is unchanged. Note: the issue's stated hypothesis — that the `Export::Named`-only destructuring in artifacts.rs's wrapper loops is the cause — does not hold. A `ReExport` of an ordinary function/const across the same hop already links and runs correctly today; it is specifically a NAMESPACE-valued binding that needs the namespace routing. The two issues are therefore different roots in the same family, and are fixed independently here. Verification: * #6304 repro built with real `esbuild --bundle --splitting --format=esm`: perry output is now byte-identical to `node out/index.js`. * #5916 repro from the issue links and prints `3 / 3`, matching node. * New gap fixtures (parity vs `node --experimental-strip-types`): test_gap_dynamic_import_reexport_chunk.ts (re-export-only chunk in a dynamic-import namespace, incl. renamed re-export + a real-code control chunk) and test_gap_namespace_reexport_barrel.ts (#5916 link case). * 6 new flatten_exports unit tests (rename, multi-hop chain, local-definition precedence, native-import exclusion, cycle termination); perry-hir lib suite green (229 passed). * Static-import barrels through both shapes still work (no regression), and all 10 pre-existing dynamic-import gap tests still pass.
e280550 to
a871399
Compare
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.
Fixes #6304. Fixes #5916.
Two bugs with the same shape: Perry resolved an exported name to the module that mentions it rather than the module that defines it, and so never followed the re-export hop to the real binding.
#6304 — dynamic
import()namespace of a pure re-export chunk was emptyesbuild/bun emit a shared chunk under
--splittingas a two-statement re-export file:flatten_exportstreatedexport { run }as a local export of the agent module. The driver then searched the agent's own HIR for a function/class/global namedrun, found nothing — it is an import binding, not a definition — and fell back to aForeignVargetter onperry_fn_<agent>__run, a symbol the #461 stub loop claims with an undefined-returning stub.Net effect: the namespace of a re-export-only chunk came out empty, and an unguarded
ns.run()printedundefinedrather than throwing — a silent wrong answer that quietly breaks every code-split bundle. A chunk containing real code was unaffected, which is why this hid for so long.Fix:
flatten_exportsresolves anExport::Namedwhose local is an import binding to the module that actually defines it, following chains of import / re-export hops to the ultimate owner. Native imports and sources with no HIR in the graph are left alone (the resolver returnsNone, callers keep prior behaviour), so the change can only move an entry closer to a real definition — never invent one.typeof ns.runfunctionundefinedfunctionns.run()"[shared-util] agent""[shared-util] agent"#5916 — barrel re-export of a namespace value failed to LINK
The consumer-side namespace detection only inspected the imported-from module's own exports for a
NamespaceReExport. Across anexport { X } fromhop it saw a plainReExport, fell through to the ordinary value path, andToken.estimate(...)lowered to aStaticMethodCallagainst a closure wrapper no module emits.Fix: walk the re-export chain first, so the scan runs against the module that actually declares the namespace, under the name it declares it with. When no hop applies (the common case) the scan target is unchanged.
Correcting the issue's hypothesis
#5916 suggests the cause is the
Export::Named-only destructuring inartifacts.rs's wrapper-emission loops. That hypothesis does not hold, and it is also not the cause of #6304:ReExportof an ordinary function/const across the same hop already links and runs correctly today (verified). It is specifically a namespace-valued binding that needs the namespace routing.flatten_exports.So the two issues are different roots in the same family (export-origin resolution not following re-export hops), and are fixed independently here rather than by a single mechanical change to those loops.
Verification
esbuild --bundle --splitting --format=esmchunk set; perry output is now byte-identical tonode out/index.js.3 / 3, matching node.node --experimental-strip-types):test_gap_dynamic_import_reexport_chunk.ts— re-export-only chunk in a dynamic-import namespace, incl. a renamed re-export and a real-code control chunk.test_gap_namespace_reexport_barrel.ts— the Barrel re-export of a namespace value (export { X } from "mod") never gets a __perry_wrap_perry_fn wrapper — linker failure #5916 link case.flatten_exportsunit tests: import rename, multi-hop chain, local-definition precedence, native-import exclusion, cycle termination.perry-hirlib suite green (229 passed).test_gap_events_import_4995andtest_gap_module_const_local_shadowmismatch, but both were A/B-verified as failing identically on pristinemain(the latter is already inknown_failures.json).Summary by CodeRabbit
Bug Fixes
export { ... }-style redirects resolve correctly.Tests