Skip to content

fix: emit REQUIRE_TLA error when require() loads a module with top-level await#9071

Merged
IWANABETHATGUY merged 8 commits intorolldown:mainfrom
jaehafe:04-11-fix_require_tla_error
Apr 12, 2026
Merged

fix: emit REQUIRE_TLA error when require() loads a module with top-level await#9071
IWANABETHATGUY merged 8 commits intorolldown:mainfrom
jaehafe:04-11-fix_require_tla_error

Conversation

@jaehafe
Copy link
Copy Markdown
Contributor

@jaehafe jaehafe commented Apr 11, 2026

Summary

  • Add REQUIRE_TLA build error when require() loads a module with top-level await
  • This was a TODO in compute_tla.rs:46: "require TLA module should give a error"
  • Rolldown docs state "require TLA module is forbidden" but no error was emitted

Changes

  • Add RequireTlaError event kind and RequireTla diagnostic
  • After computing TLA status, second pass checks if any ImportKind::Require points to a TLA module
  • Add integration test in errors/require_tla/

Note

The existing esbuild test top_level_await_forbidden_require may fail because it expects require+TLA to bundle without error. This PR intentionally enforces the restriction documented in docs/in-depth/tla-in-rolldown.md. Please advise how to handle that test.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 11, 2026

Merging this PR will not alter performance

✅ 4 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing jaehafe:04-11-fix_require_tla_error (60bca76) with main (d938ed9)

Open in CodSpeed

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@jaehafe jaehafe force-pushed the 04-11-fix_require_tla_error branch from f95d667 to ea98816 Compare April 11, 2026 10:32
Comment thread crates/rolldown/src/stages/link_stage/compute_tla.rs Outdated
@jaehafe jaehafe force-pushed the 04-11-fix_require_tla_error branch from ea98816 to 1af89fb Compare April 11, 2026 13:22
@jaehafe jaehafe force-pushed the 04-11-fix_require_tla_error branch from 1af89fb to 6e2c298 Compare April 11, 2026 15:36
@IWANABETHATGUY
Copy link
Copy Markdown
Member

The overall direction seems good! I'll make a few tweaks to the code directly on your branch to ensure everything fits perfectly.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 11, 2026

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit fcc2910
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69da81575f07f000084b1557
😎 Deploy Preview https://deploy-preview-9071--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 11, 2026

Deploy Preview for rolldown-rs canceled.

Name Link
🔨 Latest commit 60bca76
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/69da9bcf3e52ec0008fead85

jaehafe and others added 7 commits April 12, 2026 03:04
Top-level await is rare in real-world apps, so storing the span on every
EcmaView wastes memory. Route it through NormalModuleTaskResult into a
FxHashMap<ModuleIdx, Span> on LinkStage, and look it up during compute_tla
when emitting require_tla diagnostics.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use ImportRecordIdx for span lookup to avoid ambiguity when multiple
import records share a module_request specifier. Pass ArcStr stable
ids directly instead of round-tripping through String, drop the
is_direct flag in favor of deriving it from import_chain.is_empty(),
and construct ImportChainNote directly without an intermediate tuple.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- debug_assert! on the Span::empty(0) fallbacks in compute_tla so a
  missing import span or missing tla_keyword_span surfaces in tests
  rather than rendering silently-broken diagnostics.
- Give the primary require_span label a non-empty text
  ("The require() call is here:").
- Register source files up front so the DiagnosticFileId returned by
  add_file can be looked up by reference without cloning.
- Collapse BuildDiagnostic::require_tla's 7 positional args into a
  single RequireTla struct argument so the call site names each field.

Snapshots updated for the new primary label text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a require() target is inside an import cycle that reaches TLA
through a forward edge, find_tla_source memoizes every node on the
cycle as `Some(tla_source_idx)` — including the back-edge target. The
previous chain builder used find_map on the import records and picked
the first match, which would walk into the back-edge and spin forever
on fixtures like:

    main.js: require('./a.js')
    a.js:    import './b.js'
    b.js:    import './a.js'; import './c.js'
    c.js:    await 0

Prefer the direct edge to tla_source_idx when one exists and track a
`seen` set so back-edges can never be chosen as the next hop. Also
document the pre-existing find_tla_source false-negative on SCCs that
reach TLA through an already-memoized sibling.

Adds a require_tla_cycle_back_edge fixture that previously hung.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@IWANABETHATGUY IWANABETHATGUY force-pushed the 04-11-fix_require_tla_error branch from e5f6a6f to 49c477b Compare April 11, 2026 19:04
The require_tla_cycle_back_edge fixture already exercises the happy
path (require() → import chain → top-level await module) in addition
to the cycle handling, so the original require_tla fixture is
redundant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@IWANABETHATGUY IWANABETHATGUY merged commit b3843b3 into rolldown:main Apr 12, 2026
32 checks passed
This was referenced Apr 15, 2026
shulaoda added a commit that referenced this pull request Apr 16, 2026
## [1.0.0-rc.16] - 2026-04-16

### 🚀 Features

- const enum cross-module inlining support (#8796) by @Dunqing
- implement module tagging system for code splitting (#9045) by @hyf0

### 🐛 Bug Fixes

- rolldown_plugin_vite_manifest: handle duplicate chunk names for CSS entries (#9059) by @sapphi-red
- improve error message for invalid return values in function options (#9125) by @shulaoda
- await async export-star init wrappers (#9101) by @thezzisu
- never panic during diagnostic emission (#9091) by @IWANABETHATGUY
- include array rest pattern in binding_identifiers (#9112) by @IWANABETHATGUY
- rolldown: set worker thread count with ROLLDOWN_WORKER_THREADS (#9086) by @fpotter
- rolldown_plugin_lazy_compilation: escape request ID in proxy modules (#9102) by @h-a-n-a
- treat namespace member access as side-effect-free (#9099) by @IWANABETHATGUY
- relax overly conservative side-effect leak check in chunk optimizer (#9085) by @IWANABETHATGUY
- runtime: release `cb` reference after `__commonJS` factory initialization (#9067) by @hyf0-agent
- `@__NO_SIDE_EFFECTS__` wrapper should not remove dynamic imports (#9075) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use POSIX path join/normalize for glob resolution (#9077) by @shulaoda
- emit REQUIRE_TLA error when require() loads a module with top-level await (#9071) by @jaehafe
- emit namespace declaration for empty modules in manual chunks (#8993) by @privatenumber
- rolldown_plugin_vite_import_glob: keep common base on path segment boundary (#9070) by @shulaoda
- prevent circular runtime helper imports during facade elimination (#8989) (#9057) by @IWANABETHATGUY
- correct circular dependency check in facade elimination (#9047) by @h-a-n-a
- docs: correct dead link in CodeSplittingGroup.tags JSDoc (#9051) by @hyf0
- emit DUPLICATE_SHEBANG warning when banner contains shebang (#9026) by @IWANABETHATGUY

### 🚜 Refactor

- use semantic reference flags for member write detection (#9060) by @Dunqing
- extract UsedSymbolRefs newtype wrapper (#9130) by @IWANABETHATGUY
- dedupe await wrapping in export-star init emit (#9119) by @IWANABETHATGUY
- calculate side-effect-free function symbols on demand (#9120) by @IWANABETHATGUY
- extract duplicated top-level await handling into shared helper (#9087) by @IWANABETHATGUY
- rolldown_plugin_vite_import_glob: use split_first for get_common_base (#9069) by @shulaoda
- simplify ESM init deduplication with idiomatic insert check (#9044) by @IWANABETHATGUY

### 📚 Documentation

- document runtime module placement strategy in code-splitting design (#9062) by @IWANABETHATGUY
- clarify `options` hook behavior difference with Rollup in watch mode (#9053) by @sapphi-red
- meta/design: introduce module tags (#9017) by @hyf0

### ⚡ Performance

- convert `generate_transitive_esm_init` to iterative (#9046) by @IWANABETHATGUY

### 🧪 Testing

- merge strict/non_strict test variants using configVariants (#9089) by @IWANABETHATGUY

### ⚙️ Miscellaneous Tasks

- disable Renovate auto-updates for oxc packages (#9129) by @IWANABETHATGUY
- upgrade oxc@0.126.0 (#9127) by @Dunqing
- deps: update napi to v3.8.5 (#9126) by @renovate[bot]
- deps: update dependency @napi-rs/cli to v3.6.2 (#9123) by @renovate[bot]
- move lazy-compilation design doc (#9117) by @h-a-n-a
- deps: update dependency vite-plus to v0.1.18 (#9118) by @renovate[bot]
- deps: update dependency vite-plus to v0.1.17 (#9113) by @renovate[bot]
- deps: update oxc to v0.125.0 (#9094) by @renovate[bot]
- deps: update dependency follow-redirects to v1.16.0 [security] (#9103) by @renovate[bot]
- deps: update test262 submodule for tests (#9097) by @sapphi-red
- deps: update crate-ci/typos action to v1.45.1 (#9096) by @renovate[bot]
- deps: update rust crates (#9081) by @renovate[bot]
- deps: update npm packages (#9080) by @renovate[bot]
- remove outdated TODO in determine_module_exports_kind (#9072) by @jaehafe
- rust/test: support `extendedTests: false` shorthand in test config (#9050) by @hyf0
- ci: extract shared infra-changes anchor in path filters (#9054) by @hyf0
- add docs build check to catch dead links in PRs (#9052) by @hyf0

### ❤️ New Contributors

* @thezzisu made their first contribution in [#9101](#9101)
* @fpotter made their first contribution in [#9086](#9086)
* @jaehafe made their first contribution in [#9071](#9071)
* @privatenumber made their first contribution in [#8993](#8993)

Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
Comment on lines +225 to +227
if self.result.tla_keyword_span.is_none() {
self.result.tla_keyword_span = Some(it.span());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.result.tla_keyword_span.is_none() {
self.result.tla_keyword_span = Some(it.span());
}
self.result.tla_keyword_span.get_or_insert_with(|| it.span());

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.

4 participants