Skip to content

Commit 465eccd

Browse files
committed
improve errors for circular dependencies
1 parent f3585e2 commit 465eccd

File tree

3 files changed

+73
-17
lines changed

3 files changed

+73
-17
lines changed

rewatch/src/build/compile.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,28 @@ pub fn compile(
356356
.collect::<Vec<(&String, &Module)>>(),
357357
);
358358

359-
compile_errors.push_str(&format!(
360-
"\n{}\n{}\n",
359+
let guidance = "\nHow to fix:\n- Extract shared code into a new module both depend on.\n- If a namespace entry is part of the cycle, import submodules directly instead of the entry.\n";
360+
let message = format!(
361+
"\n{}\n{}\n{}",
361362
style("Can't continue... Found a circular dependency in your code:").red(),
362-
dependency_cycle::format(&cycle)
363-
))
363+
dependency_cycle::format(&cycle, build_state),
364+
guidance
365+
);
366+
367+
// Append the error to the logs of all packages involved in the cycle,
368+
// so editor tooling can surface it from .compiler.log
369+
let mut touched_packages = AHashSet::<String>::new();
370+
for module_name in cycle.iter() {
371+
if let Some(module) = build_state.get_module(module_name) {
372+
if touched_packages.insert(module.package_name.clone()) {
373+
if let Some(package) = build_state.get_package(&module.package_name) {
374+
logs::append(package, &message);
375+
}
376+
}
377+
}
378+
}
379+
380+
compile_errors.push_str(&message)
364381
}
365382
if !compile_errors.is_empty() {
366383
break;

rewatch/src/build/compile/dependency_cycle.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use super::super::build_types::*;
22
use crate::helpers;
33
use ahash::AHashSet;
4-
use std::collections::{HashMap, HashSet, VecDeque};
4+
use std::{
5+
collections::{HashMap, HashSet, VecDeque},
6+
path::Path,
7+
};
58

69
pub fn find(modules: &Vec<(&String, &Module)>) -> Vec<String> {
710
find_shortest_cycle(modules)
@@ -139,15 +142,47 @@ fn find_cycle_bfs(
139142
None
140143
}
141144

142-
pub fn format(cycle: &[String]) -> String {
143-
let mut cycle = cycle.to_vec();
144-
cycle.reverse();
145-
// add the first module to the end of the cycle
146-
cycle.push(cycle[0].clone());
145+
pub fn format(cycle: &[String], build_state: &BuildCommandState) -> String {
146+
// Mirror dependency_cycle::format semantics: reverse then close the loop
147+
let mut nodes = cycle.to_vec();
148+
if nodes.is_empty() {
149+
return String::new();
150+
}
151+
nodes.reverse();
152+
nodes.push(nodes[0].clone());
153+
154+
let root = build_state.get_root_config().path.parent().unwrap();
147155

148-
cycle
156+
nodes
149157
.iter()
150-
.map(|s| helpers::format_namespaced_module_name(s))
158+
.map(|name| {
159+
let display_name = helpers::format_namespaced_module_name(name);
160+
161+
match build_state.get_module(name) {
162+
Some(Module {
163+
source_type: SourceType::SourceFile(source_file),
164+
package_name,
165+
..
166+
}) => {
167+
if let Some(package) = build_state.get_package(package_name) {
168+
let abs_path = Path::new(&package.path).join(&source_file.implementation.path);
169+
let rel_path = abs_path
170+
.strip_prefix(root)
171+
.unwrap_or(&abs_path)
172+
.to_string_lossy()
173+
.replace('\\', "/");
174+
format!("{} ({})", display_name, rel_path)
175+
} else {
176+
display_name
177+
}
178+
}
179+
Some(Module {
180+
source_type: SourceType::MlMap(_),
181+
..
182+
})
183+
| None => display_name,
184+
}
185+
})
151186
.collect::<Vec<String>>()
152187
.join("\n → ")
153188
}

rewatch/tests/snapshots/dependency-cycle.txt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ The field 'bsc-flags' found in the package config of '@testrepo/deprecated-confi
1212
Use 'compiler-flags' instead.
1313

1414
Can't continue... Found a circular dependency in your code:
15-
Dep01
16-
→ Dep02
17-
→ NS
18-
→ NewNamespace.NS_alias
19-
→ Dep01
15+
Dep01 (packages/dep01/src/Dep01.res)
16+
→ Dep02 (packages/dep02/src/Dep02.res)
17+
→ NS (packages/new-namespace/src/NS.res)
18+
→ NewNamespace.NS_alias (packages/new-namespace/src/NS_alias.res)
19+
→ Dep01 (packages/dep01/src/Dep01.res)
20+
21+
How to fix:
22+
- Extract shared code into a new module both depend on.
23+
- If a namespace entry is part of the cycle, import submodules directly instead of the entry.
2024

2125
Incremental build failed. Error:  Failed to Compile. See Errors Above

0 commit comments

Comments
 (0)