use super::prelude::*;
use super::{SemanticSnapshotClaim, SemanticSnapshotMismatch, SemanticUnitClaim};
pub(super) struct CrateIndex {
entries: Box<[(FileId, Crate)]>,
}
impl CrateIndex {
pub(super) fn build(context: &SemanticContext) -> Self {
let db = context.host.raw_database();
let mut entries: Vec<(FileId, Crate)> = Crate::all(db)
.into_iter()
.map(|krate| (krate.root_file(db), krate))
.collect();
entries.sort_by_key(|(file, _)| *file);
Self {
entries: entries.into_boxed_slice(),
}
}
fn at(&self, file: FileId) -> &[(FileId, Crate)] {
let start = self.entries.partition_point(|(root, _)| *root < file);
let rooted = self.entries.get(start..).unwrap_or_default();
let shared = rooted.iter().take_while(|(root, _)| *root == file).count();
rooted.get(..shared).unwrap_or_default()
}
}
pub(super) fn bind_units(
context: &SemanticContext,
claim: &SemanticSnapshotClaim,
index: &CrateIndex,
) -> Result<Box<[Crate]>, SemanticSnapshotMismatch> {
let requested = claim.units.get(claim.requested).ok_or_else(|| {
SemanticSnapshotMismatch::MalformedClaim {
detail: format!(
"the requested unit {} is not one of the {} units the claim holds",
claim.requested,
claim.units.len()
)
.into_boxed_str(),
}
})?;
let bound = bound_crate(context, requested, index)?.ok_or_else(|| {
SemanticSnapshotMismatch::RequestedTarget {
name: Box::from(&*requested.name),
}
})?;
claim
.units
.iter()
.enumerate()
.map(|(position, unit)| match position == claim.requested {
true => Ok(bound),
false => bind_unit(context, unit, index),
})
.collect()
}
fn bind_unit(
context: &SemanticContext,
unit: &SemanticUnitClaim,
index: &CrateIndex,
) -> Result<Crate, SemanticSnapshotMismatch> {
bound_crate(context, unit, index)?.ok_or_else(|| missing(unit.conditional, &unit.name))
}
fn bound_crate(
context: &SemanticContext,
unit: &SemanticUnitClaim,
index: &CrateIndex,
) -> Result<Option<Crate>, SemanticSnapshotMismatch> {
let db = context.host.raw_database();
let Some(file) = context.file_id(&context.absolute(&unit.crate_root)) else {
return Ok(None);
};
let krate = match index.at(file) {
[] => return Ok(None),
[(_, only)] => *only,
rooted => return Err(ambiguous_root(&unit.crate_root, rooted.len())),
};
let named = krate
.display_name(db)
.map(|name| name.canonical_name().as_str().replace('-', "_"));
Ok(named.filter(|name| *name == *unit.name).map(|_| krate))
}
fn ambiguous_root(crate_root: &str, rooted: usize) -> SemanticSnapshotMismatch {
SemanticSnapshotMismatch::UnitGraph {
detail: format!("the semantic database roots {rooted} crates at {crate_root}")
.into_boxed_str(),
}
}
pub(super) fn missing(conditional: bool, name: &str) -> SemanticSnapshotMismatch {
match conditional {
true => SemanticSnapshotMismatch::Activation {
name: Box::from(name),
},
false => SemanticSnapshotMismatch::UnitGraph {
detail: format!("no crate answers for unit {name}").into_boxed_str(),
},
}
}