Skip to content

Commit 041c1be

Browse files
committed
fix: emit JS for skipped modules when dependency error resolves
When a dependency has a compile error, process_in_waves breaks early and downstream modules in the closure are never attempted by bsc. These "skipped" modules were left at TypeChecked with no JS output. Track skipped modules (closure minus compiled) through ProcessResult and IncrementalBuildError, then register them as FullCompile intent in build_batch so they get compiled once the dependency is fixed. Box both `modules` and `skipped_modules` in IncrementalBuildError to stay under clippy's result_large_err size threshold.
1 parent a1cae90 commit 041c1be

10 files changed

Lines changed: 252 additions & 24 deletions

File tree

rewatch/src/build.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ pub fn parse_and_resolve(
266266
kind: IncrementalBuildErrorKind::SourceFileParseError,
267267
output_mode: build_config.output_mode.clone(),
268268
diagnostics: parse_diagnostics,
269-
modules: AHashSet::new(),
269+
modules: Box::default(),
270+
skipped_modules: Box::default(),
270271
});
271272
}
272273
};

rewatch/src/build/build_types.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ pub struct ProcessResult {
425425
pub compile_errors: String,
426426
pub compile_warnings: String,
427427
pub num_compiled_modules: usize,
428+
/// Modules that were in the compile universe but never attempted because
429+
/// the compile loop broke early (e.g. a dependency error aborted the loop
430+
/// before these modules' dependencies were satisfied).
431+
pub skipped_modules: AHashSet<String>,
428432
}
429433

430434
impl ProcessResult {
@@ -869,7 +873,12 @@ pub struct IncrementalBuildError {
869873
pub kind: IncrementalBuildErrorKind,
870874
pub diagnostics: Vec<super::diagnostics::BscDiagnostic>,
871875
/// The set of module names that participated in this compile cycle.
872-
pub modules: AHashSet<String>,
876+
/// Boxed (along with `skipped_modules`) to keep `IncrementalBuildError`
877+
/// under clippy's `result_large_err` size threshold.
878+
pub modules: Box<AHashSet<String>>,
879+
/// Modules that were in the closure but never attempted by bsc because
880+
/// the compile loop broke early due to a dependency error.
881+
pub skipped_modules: Box<AHashSet<String>>,
873882
}
874883

875884
#[derive(Debug, Clone)]

rewatch/src/build/compile.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,10 +800,17 @@ pub fn process_in_waves(
800800
}
801801
}
802802

803+
let skipped_modules = compile_params
804+
.modules
805+
.difference(&compiled_modules)
806+
.cloned()
807+
.collect();
808+
803809
Ok(ProcessResult {
804810
compile_errors,
805811
compile_warnings,
806812
num_compiled_modules,
813+
skipped_modules,
807814
})
808815
}
809816

rewatch/src/build/compile_dependencies.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ pub fn compile_dependencies(
3939
let result = match compile::process_in_waves(build_state, &params, || {}, |_| {}) {
4040
Ok(result) => result,
4141
Err(e) => {
42+
// process_in_waves itself failed — the entire closure is skipped.
43+
let skipped_modules = Box::new(params.modules.clone());
4244
return Err(IncrementalBuildError {
4345
kind: IncrementalBuildErrorKind::CompileError(Some(e.to_string())),
4446
output_mode: OutputMode::Silent,
4547
diagnostics: vec![],
46-
modules: params.modules,
48+
modules: Box::new(params.modules),
49+
skipped_modules,
4750
});
4851
}
4952
};
@@ -54,7 +57,8 @@ pub fn compile_dependencies(
5457
kind: IncrementalBuildErrorKind::CompileError(None),
5558
output_mode: OutputMode::Silent,
5659
diagnostics: result.to_diagnostics(),
57-
modules: params.modules,
60+
modules: Box::new(params.modules),
61+
skipped_modules: Box::new(result.skipped_modules),
5862
})
5963
} else {
6064
Ok(IncrementalBuildResult {

rewatch/src/build/full_build.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ pub fn full_build(
8484
kind: IncrementalBuildErrorKind::CompileError(Some(e.to_string())),
8585
output_mode: output_mode.clone(),
8686
diagnostics: vec![],
87-
modules: params.modules,
87+
modules: Box::new(params.modules),
88+
skipped_modules: Box::default(),
8889
});
8990
}
9091
};
@@ -128,7 +129,8 @@ pub fn full_build(
128129
kind: IncrementalBuildErrorKind::CompileError(None),
129130
output_mode: output_mode.clone(),
130131
diagnostics: result.to_diagnostics(),
131-
modules: params.modules,
132+
modules: Box::new(params.modules),
133+
skipped_modules: Box::default(),
132134
})
133135
} else {
134136
if show_progress {

rewatch/src/build/full_typecheck.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ pub fn full_typecheck(
3131
kind: IncrementalBuildErrorKind::CompileError(Some(e.to_string())),
3232
output_mode: OutputMode::Silent,
3333
diagnostics: vec![],
34-
modules: params.modules,
34+
modules: Box::new(params.modules),
35+
skipped_modules: Box::default(),
3536
});
3637
}
3738
};
@@ -42,7 +43,8 @@ pub fn full_typecheck(
4243
kind: IncrementalBuildErrorKind::CompileError(None),
4344
output_mode: OutputMode::Silent,
4445
diagnostics: result.to_diagnostics(),
45-
modules: params.modules,
46+
modules: Box::new(params.modules),
47+
skipped_modules: Box::default(),
4648
})
4749
} else {
4850
write_compiler_info(build_state, OutputTarget::Lsp);

rewatch/src/build/typecheck_dependents.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ pub fn typecheck_dependents(
5050
kind: IncrementalBuildErrorKind::CompileError(Some(e.to_string())),
5151
output_mode: OutputMode::Silent,
5252
diagnostics: vec![],
53-
modules: params.modules,
53+
modules: Box::new(params.modules),
54+
skipped_modules: Box::default(),
5455
});
5556
}
5657
};
@@ -61,7 +62,8 @@ pub fn typecheck_dependents(
6162
kind: IncrementalBuildErrorKind::CompileError(None),
6263
output_mode: OutputMode::Silent,
6364
diagnostics: result.to_diagnostics(),
64-
modules: params.modules,
65+
modules: Box::new(params.modules),
66+
skipped_modules: Box::default(),
6567
})
6668
} else {
6769
Ok(IncrementalBuildResult {

rewatch/src/lsp/queue/file_build.rs

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,14 @@ fn build_batch(
237237
) -> BatchBuildResult {
238238
let mut diagnostics = Vec::new();
239239
let mut touched_files = HashSet::new();
240+
let mut skipped_modules = HashSet::new();
240241

241242
if !module_names.is_empty() {
242243
// Steps 1–3: compile saved files, typecheck dependents, resolve errors.
243-
let (dep_diags, dep_touched) = compile_dependencies(build_state, &module_names);
244-
diagnostics.extend(dep_diags);
245-
touched_files.extend(dep_touched);
244+
let dep_result = compile_dependencies(build_state, &module_names);
245+
diagnostics.extend(dep_result.diagnostics);
246+
touched_files.extend(dep_result.touched_files);
247+
skipped_modules = dep_result.skipped_modules;
246248

247249
// Snapshot modules at CompileError(FullCompile) before typechecking
248250
// dependents. These are modules the user saved but that failed to
@@ -281,13 +283,22 @@ fn build_batch(
281283
}
282284

283285
// Step 4: drain full_compile_intent — compile intent modules that weren't
284-
// already handled by steps 1–3. Modules already at Built (compiled as part
285-
// of the dependency closure or via hash promotion) are skipped. Modules not
286-
// yet at TypeChecked (e.g. still at CompileError) are kept for future flushes.
287-
let remaining_intent = if let Some(names) = intent {
288-
drain_full_compile_intent(build_state, names, &mut diagnostics, &mut touched_files)
289-
} else {
286+
// already handled by steps 1–3, plus any modules skipped because step 1
287+
// broke early due to a dependency error. Modules already at Built are
288+
// skipped. Modules not yet at TypeChecked (e.g. still at CompileError)
289+
// are kept for future flushes.
290+
let mut intent_names = intent.unwrap_or_default();
291+
if !skipped_modules.is_empty() {
292+
tracing::debug!(
293+
skipped_count = skipped_modules.len(),
294+
"build_batch: registering skipped modules as FullCompile intent"
295+
);
296+
intent_names.extend(skipped_modules);
297+
}
298+
let remaining_intent = if intent_names.is_empty() {
290299
HashSet::new()
300+
} else {
301+
drain_full_compile_intent(build_state, intent_names, &mut diagnostics, &mut touched_files)
291302
};
292303

293304
record_error_count(&diagnostics);
@@ -305,11 +316,20 @@ fn build_batch(
305316
/// them, restricting to only the dependency closure.
306317
/// This produces type information and JavaScript output for just the
307318
/// saved file and its imports, without touching the rest of the codebase.
319+
/// Result of the compile_dependencies step.
320+
struct CompileDependenciesResult {
321+
diagnostics: Vec<BscDiagnostic>,
322+
touched_files: HashSet<PathBuf>,
323+
/// Modules that were in the dependency closure but never attempted by bsc
324+
/// because the compile loop broke early (a dependency had errors).
325+
skipped_modules: HashSet<String>,
326+
}
327+
308328
#[instrument(name = "lsp.flush.file_build.compile_dependencies", skip_all, fields(error_count = tracing::field::Empty))]
309329
fn compile_dependencies(
310330
build_state: &mut BuildCommandState,
311331
module_names: &[String],
312-
) -> (Vec<BscDiagnostic>, HashSet<PathBuf>) {
332+
) -> CompileDependenciesResult {
313333
use crate::build::build_types::BuildConfig;
314334

315335
let build_config = BuildConfig {
@@ -333,12 +353,16 @@ fn compile_dependencies(
333353
Err(e) => {
334354
// Parse failure — return diagnostics for the affected files, skip compilation
335355
let touched: HashSet<PathBuf> = e.diagnostics.iter().map(|d| d.file.clone()).collect();
336-
return (e.diagnostics, touched);
356+
return CompileDependenciesResult {
357+
diagnostics: e.diagnostics,
358+
touched_files: touched,
359+
skipped_modules: HashSet::new(),
360+
};
337361
}
338362
};
339363

340364
let module_set: AHashSet<String> = module_names.iter().cloned().collect();
341-
let (diagnostics, touched_files) =
365+
let (diagnostics, touched_files, skipped_modules) =
342366
match build::compile_dependencies::compile_dependencies(build_state, &module_set) {
343367
Ok(result) => {
344368
tracing::debug!(
@@ -348,19 +372,29 @@ fn compile_dependencies(
348372
(
349373
result.diagnostics,
350374
module_names_to_paths(build_state, &result.modules),
375+
HashSet::new(),
351376
)
352377
}
353378
Err(e) => {
354379
tracing::warn!("Incremental build completed with errors: {e}");
355-
(e.diagnostics, module_names_to_paths(build_state, &e.modules))
380+
let skipped: HashSet<String> = e.skipped_modules.into_iter().collect();
381+
(
382+
e.diagnostics,
383+
module_names_to_paths(build_state, &e.modules),
384+
skipped,
385+
)
356386
}
357387
};
358388

359389
let mut all_diagnostics = parse_diagnostics;
360390
all_diagnostics.extend(diagnostics);
361391
record_error_count(&all_diagnostics);
362392

363-
(all_diagnostics, touched_files)
393+
CompileDependenciesResult {
394+
diagnostics: all_diagnostics,
395+
touched_files,
396+
skipped_modules,
397+
}
364398
}
365399

366400
/// Re-typecheck all modules that import the saved modules (recursively).

tests/rewatch_tests/tests/lsp/__snapshots__/stale-js.test.mjs.snap

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,94 @@ exports[`lsp stale JS output > does not recompile errored dependents when saving
6565
]
6666
`;
6767

68+
exports[`lsp stale JS output > produces JS for skipped modules when a dependency error is resolved 1`] = `
69+
[
70+
"rewatch.lsp",
71+
" lsp.initialized",
72+
" lsp.register_watchers[watcher_count=3]",
73+
" lsp.discover_package[name=@rewatch-test/dep-chain]",
74+
" lsp.source_dir[dir=packages/dep-chain/src, recursive=true]",
75+
" lsp.initial_build[project=@rewatch-test/dep-chain]",
76+
" build.load_package_sources[package=@rewatch-test/dep-chain]",
77+
" packages.parse_packages",
78+
" clean.cleanup_previous_build",
79+
" build.parse[dirty_modules=4]",
80+
" build.parse_file[module=App, package=@rewatch-test/dep-chain]",
81+
" build.parse_file[module=Button, package=@rewatch-test/dep-chain]",
82+
" build.parse_file[module=Leaf, package=@rewatch-test/dep-chain]",
83+
" build.parse_file[module=Main, package=@rewatch-test/dep-chain]",
84+
" full_typecheck[module_count=4, output=lsp]",
85+
" build.typecheck",
86+
" build.typecheck_wave[file_count=2]",
87+
" build.typecheck_file[module=Button, package=@rewatch-test/dep-chain]",
88+
" build.typecheck_file[module=Leaf, package=@rewatch-test/dep-chain]",
89+
" build.typecheck_wave[file_count=1]",
90+
" build.typecheck_file[module=App, package=@rewatch-test/dep-chain]",
91+
" build.typecheck_wave[file_count=1]",
92+
" build.typecheck_file[module=Main, package=@rewatch-test/dep-chain]",
93+
" lsp.did_save[file=packages/dep-chain/src/Leaf.res]",
94+
" lsp.flush[incremental_builds=packages/dep-chain/src/Leaf.res]",
95+
" lsp.flush.file_build.batch[modules=["Leaf"], error_count=1]",
96+
" lsp.flush.file_build.compile_dependencies[error_count=1]",
97+
" build.parse[dirty_modules=1]",
98+
" build.parse_file[module=Leaf, package=@rewatch-test/dep-chain]",
99+
" compile_dependencies[module_count=4, output=lsp]",
100+
" build.compile",
101+
" build.compile_wave[file_count=1]",
102+
" build.compile_file[module=Leaf, package=@rewatch-test/dep-chain, suffix=.mjs, module_system=esmodule]",
103+
" build.compile_error",
104+
" lsp.flush.file_build.typecheck_dependents[dependent_count=2]",
105+
" typecheck_dependents[module_count=4, output=lsp]",
106+
" build.typecheck",
107+
" build.typecheck_wave[file_count=1]",
108+
" build.typecheck_file[module=App, package=@rewatch-test/dep-chain]",
109+
" build.typecheck_wave[file_count=1]",
110+
" build.typecheck_file[module=Main, package=@rewatch-test/dep-chain]",
111+
" lsp.flush.file_build.compile_resolved",
112+
" lsp.did_save[file=packages/dep-chain/src/App.res]",
113+
" lsp.flush[incremental_builds=packages/dep-chain/src/App.res]",
114+
" lsp.flush.file_build.batch[modules=["App"], error_count=1]",
115+
" lsp.flush.file_build.compile_dependencies[error_count=1]",
116+
" build.parse[dirty_modules=1]",
117+
" build.parse_file[module=App, package=@rewatch-test/dep-chain]",
118+
" compile_dependencies[module_count=4, output=lsp]",
119+
" build.compile",
120+
" build.compile_wave[file_count=2]",
121+
" build.compile_file[module=Button, package=@rewatch-test/dep-chain, suffix=.mjs, module_system=esmodule]",
122+
" build.compile_file[module=Leaf, package=@rewatch-test/dep-chain, suffix=.mjs, module_system=esmodule]",
123+
" build.compile_error",
124+
" lsp.flush.file_build.typecheck_dependents[dependent_count=1]",
125+
" typecheck_dependents[module_count=4, output=lsp]",
126+
" build.typecheck",
127+
" build.typecheck_wave[file_count=1]",
128+
" build.typecheck_file[module=Main, package=@rewatch-test/dep-chain]",
129+
" lsp.flush.file_build.compile_resolved",
130+
" lsp.did_save[file=packages/dep-chain/src/Leaf.res]",
131+
" lsp.flush[incremental_builds=packages/dep-chain/src/Leaf.res]",
132+
" lsp.flush.file_build.batch[modules=["Leaf"]]",
133+
" lsp.flush.file_build.compile_dependencies",
134+
" build.parse[dirty_modules=1]",
135+
" build.parse_file[module=Leaf, package=@rewatch-test/dep-chain]",
136+
" compile_dependencies[module_count=4, output=lsp]",
137+
" build.compile",
138+
" build.compile_wave[file_count=1]",
139+
" build.compile_file[module=Leaf, package=@rewatch-test/dep-chain, suffix=.mjs, module_system=esmodule]",
140+
" lsp.flush.file_build.typecheck_dependents[dependent_count=2]",
141+
" typecheck_dependents[module_count=4, output=lsp]",
142+
" build.typecheck",
143+
" build.typecheck_wave[file_count=1]",
144+
" build.typecheck_file[module=App, package=@rewatch-test/dep-chain]",
145+
" build.typecheck_wave[file_count=1]",
146+
" build.typecheck_file[module=Main, package=@rewatch-test/dep-chain]",
147+
" lsp.flush.file_build.compile_resolved",
148+
" compile_dependencies[module_count=4, output=lsp]",
149+
" build.compile",
150+
" build.compile_wave[file_count=2]",
151+
" build.compile_wave[file_count=1]",
152+
" build.compile_file[module=App, package=@rewatch-test/dep-chain, suffix=.mjs, module_system=esmodule]",
153+
]
154+
`;
155+
68156
exports[`lsp stale JS output > regenerates JS for dependents when a dependency fix resolves their errors 1`] = `
69157
[
70158
"rewatch.lsp",

0 commit comments

Comments
 (0)