-
-
Notifications
You must be signed in to change notification settings - Fork 158
fix(compile): drop init-call back-edges the topo sort already broke (#6463) #6466
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2148,6 +2148,36 @@ pub fn run_with_parse_cache( | |
| } | ||
| } | ||
| } | ||
| // Drop init-call back-edges (#6463). `topo_sort_non_entry_modules` | ||
| // breaks import cycles at the back-edge and the eager main | ||
| // sequence runs inits in that order — but the wrapper's nested | ||
| // dep-init calls re-derive the order dynamically at runtime. | ||
| // When the cycle member the sort placed FIRST runs, its broken | ||
| // edge to the member placed second pulled that module's BODY in | ||
| // early, before this module's own body had populated anything. | ||
| // Effect's web.ts died on this: find-my-way-ts | ||
| // internal/router.ts has `import * as Router from "../index.js"` | ||
| // used only in type positions (no `type` keyword, so it is a | ||
| // value edge), forming a cycle index ⇄ internal. The sort | ||
| // correctly placed internal first — matching node's ESM | ||
| // evaluation order from the entry — but internal's wrapper then | ||
| // called index's init, whose body copied | ||
| // `export const make = internal.make` while internal's global | ||
| // was still undefined. `FindMyWay.make` stayed undefined | ||
| // forever: "TypeError: value is not a function" at | ||
| // Layer.launch. Keeping only forward edges (dep positioned | ||
| // before this module) is exactly ESM's behavior of skipping a | ||
| // module already on the evaluation stack. A dep missing from | ||
| // the position map keeps its edge (conservative). | ||
| let init_pos: std::collections::HashMap<String, usize> = | ||
| non_entry_module_names | ||
| .iter() | ||
| .enumerate() | ||
| .map(|(i, name)| (sanitize_name(name), i)) | ||
| .collect(); | ||
| if let Some(&self_pos) = init_pos.get(&sanitize_name(&hir_module.name)) { | ||
| deps.retain(|dep| init_pos.get(dep).map_or(true, |&p| p < self_pos)); | ||
| } | ||
|
Comment on lines
+2172
to
+2180
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win Move Building the Precompute the map once before the parallel iterator and share it by reference: // Add this before `ctx.native_modules.par_iter()` (around line 1976):
let non_entry_module_init_pos: std::collections::HashMap<String, usize> = non_entry_module_names
.iter()
.enumerate()
.map(|(i, name)| (sanitize_module_name(name), i))
.collect();⚡ Proposed fix for the parallel map closure- let init_pos: std::collections::HashMap<String, usize> =
- non_entry_module_names
- .iter()
- .enumerate()
- .map(|(i, name)| (sanitize_name(name), i))
- .collect();
- if let Some(&self_pos) = init_pos.get(&sanitize_name(&hir_module.name)) {
- deps.retain(|dep| init_pos.get(dep).map_or(true, |&p| p < self_pos));
+ if let Some(&self_pos) = non_entry_module_init_pos.get(&sanitize_name(&hir_module.name)) {
+ deps.retain(|dep| non_entry_module_init_pos.get(dep).map_or(true, |&p| p < self_pos));
}🤖 Prompt for AI Agents |
||
| deps | ||
| }; | ||
| // Build import → source-prefix table for cross-module | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Include entry module re-exports in the root DFS traversal.
The new logic successfully roots the DFS at the entry module's imports to preserve order, but it currently overlooks re-exports (e.g.,
export * from './b.js'). As a result, modules reached only via entry-level re-exports will fall back to alphabetical ordering, defeating the cycle-breaking fix for those branches. Add an iteration overentry_module.exportsidentical to how non-entry modules collect export dependencies.🐛 Proposed fix to traverse entry re-exports
} } + + for export in &entry_module.exports { + let source = match export { + perry_hir::Export::ExportAll { source } => Some(source), + perry_hir::Export::ReExport { source, .. } => Some(source), + perry_hir::Export::NamespaceReExport { source, .. } => Some(source), + perry_hir::Export::Named { .. } => None, + }; + if let Some(src) = source { + if let Some((resolved_path, _)) = resolve_import( + src, + entry_path, + &ctx.project_root, + &ctx.compile_packages, + &ctx.compile_package_dirs, + ) { + if path_to_name.contains_key(&resolved_path) { + dfs_visit( + &resolved_path, + &deps, + &path_to_name, + &mut visited, + &mut visiting, + &mut sorted, + ); + } + } + } + } }📝 Committable suggestion
🤖 Prompt for AI Agents