use std::collections::BTreeSet;
use rolldown_common::{Module, ModuleIdx, ModuleTable};
use super::Bundler;
impl Bundler {
pub fn assert_scan_state_parity_with(&self, fresh: &Bundler) {
fn module_id_at(table: &ModuleTable, idx: ModuleIdx) -> String {
table.modules.get(idx).map_or_else(|| format!("<unknown {idx:?}>"), |m| m.id().to_string())
}
if self.options().experimental.is_lazy_barrel_enabled() {
return;
}
let incremental_cache = &self.cache;
let fresh_cache = &fresh.cache;
assert!(
incremental_cache.has_snapshot() && fresh_cache.has_snapshot(),
"state parity needs both bundlers to have completed a build with incremental build enabled"
);
let incremental = incremental_cache.get_snapshot();
let fresh_snapshot = fresh_cache.get_snapshot();
let mut diffs = Vec::new();
let fresh_module_ids = fresh_snapshot
.module_table
.modules
.iter()
.map(|module| module.id().to_string())
.collect::<BTreeSet<_>>();
for fresh_module in &fresh_snapshot.module_table.modules {
let id = fresh_module.id();
let Some(state) = incremental_cache.module_id_to_idx.get(id) else {
diffs.push(format!("`{id}` is missing in the incremental state"));
continue;
};
let Some(incremental_module) = incremental.module_table.modules.get(state.idx()) else {
diffs.push(format!("`{id}` has an idx but no module in the incremental snapshot"));
continue;
};
match (fresh_module, incremental_module) {
(Module::Normal(fresh_module), Module::Normal(incremental_module)) => {
let fresh_importers =
fresh_module.importers.iter().map(ToString::to_string).collect::<BTreeSet<_>>();
let incremental_importers = incremental_module
.importers
.iter()
.map(ToString::to_string)
.filter(|importer| fresh_module_ids.contains(importer))
.collect::<BTreeSet<_>>();
if fresh_importers != incremental_importers {
diffs.push(format!(
"`{id}` importers differ: fresh {fresh_importers:?}, incremental {incremental_importers:?}"
));
}
let fresh_dynamic_importers =
fresh_module.dynamic_importers.iter().map(ToString::to_string).collect::<BTreeSet<_>>();
let incremental_dynamic_importers = incremental_module
.dynamic_importers
.iter()
.map(ToString::to_string)
.filter(|importer| fresh_module_ids.contains(importer))
.collect::<BTreeSet<_>>();
if fresh_dynamic_importers != incremental_dynamic_importers {
diffs.push(format!(
"`{id}` dynamic importers differ: fresh {fresh_dynamic_importers:?}, incremental {incremental_dynamic_importers:?}"
));
}
let fresh_importer_ids = fresh_module
.importers_idx
.iter()
.map(|idx| module_id_at(&fresh_snapshot.module_table, *idx))
.collect::<BTreeSet<_>>();
let incremental_importer_ids = incremental_module
.importers_idx
.iter()
.map(|idx| module_id_at(&incremental.module_table, *idx))
.filter(|importer| fresh_module_ids.contains(importer))
.collect::<BTreeSet<_>>();
if fresh_importer_ids != incremental_importer_ids {
diffs.push(format!(
"`{id}` importers_idx differ: fresh {fresh_importer_ids:?}, incremental {incremental_importer_ids:?}"
));
}
if fresh_module.import_records.len() == incremental_module.import_records.len() {
for (index, (fresh_record, incremental_record)) in
fresh_module.import_records.iter().zip(&incremental_module.import_records).enumerate()
{
let fresh_target = fresh_record
.resolved_module
.map(|idx| module_id_at(&fresh_snapshot.module_table, idx));
let incremental_target = incremental_record
.resolved_module
.map(|idx| module_id_at(&incremental.module_table, idx));
if fresh_record.kind != incremental_record.kind || fresh_target != incremental_target
{
diffs.push(format!(
"`{id}` import record {index} differs: fresh ({:?}, {fresh_target:?}), incremental ({:?}, {incremental_target:?})",
fresh_record.kind, incremental_record.kind
));
}
}
} else {
diffs.push(format!(
"`{id}` import record count differs: fresh {}, incremental {}",
fresh_module.import_records.len(),
incremental_module.import_records.len()
));
}
}
(Module::External(_), Module::External(_)) => {}
_ => {
diffs.push(format!("`{id}` module kind differs between fresh and incremental state"));
}
}
}
let incremental_entries = incremental
.entry_points
.iter()
.map(|entry| {
(format!("{:?}", entry.kind), module_id_at(&incremental.module_table, entry.idx))
})
.collect::<BTreeSet<_>>();
for entry in &fresh_snapshot.entry_points {
let entry =
(format!("{:?}", entry.kind), module_id_at(&fresh_snapshot.module_table, entry.idx));
if !incremental_entries.contains(&entry) {
diffs.push(format!("entry point {entry:?} is missing in the incremental state"));
}
}
assert!(
diffs.is_empty(),
"incremental scan state diverges from a fresh full build:\n{}",
diffs.join("\n")
);
}
}