use std::collections::BTreeMap;
use std::sync::Arc;
use pedant_types::{
ResolutionReport, ResolutionUnit, ResolutionUnitId, SourceSpan, SymbolDefinition,
SymbolReference,
};
use crate::resolution::rust::identity::{PackageId, TargetId, position};
use crate::resolution::rust::snapshot::{
RustResolutionSnapshot, RustResolutionUnit, RustSnapshotUnitId,
};
use crate::resolution::rust::warning;
use super::coordinates::LineIndex;
use super::error::RustResolutionError;
pub(super) fn unit_key(unit: &RustResolutionUnit) -> Arc<str> {
warning::unit_key(unit)
}
pub(super) fn crate_name(unit: &RustResolutionUnit) -> Arc<str> {
Arc::from(unit.name().replace('-', "_"))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RustUnitBinding {
unit: ResolutionUnitId,
snapshot_unit: RustSnapshotUnitId,
package: PackageId,
target: TargetId,
}
impl RustUnitBinding {
pub fn unit(&self) -> ResolutionUnitId {
self.unit
}
pub fn snapshot_unit(&self) -> RustSnapshotUnitId {
self.snapshot_unit
}
pub fn package(&self) -> PackageId {
self.package
}
pub fn target(&self) -> TargetId {
self.target
}
}
#[derive(Debug, Clone)]
pub struct RustTargetResolution {
root_target: TargetId,
units: Box<[RustUnitBinding]>,
report: Arc<ResolutionReport>,
}
impl RustTargetResolution {
pub fn try_new(
snapshot: &RustResolutionSnapshot,
report: ResolutionReport,
) -> Result<Self, RustResolutionError> {
let units = bind_units(snapshot, report.units())?;
validate_sites(snapshot, &report)?;
Ok(Self {
root_target: snapshot.root_target(),
units,
report: Arc::new(report),
})
}
pub fn root_target(&self) -> TargetId {
self.root_target
}
pub fn units(&self) -> &[RustUnitBinding] {
&self.units
}
pub fn unit(&self, unit: ResolutionUnitId) -> Option<&RustUnitBinding> {
self.units.get(usize::try_from(unit.index()).ok()?)
}
pub fn report(&self) -> &ResolutionReport {
&self.report
}
pub fn shared_report(&self) -> Arc<ResolutionReport> {
Arc::clone(&self.report)
}
}
fn bind_units(
snapshot: &RustResolutionSnapshot,
units: &[ResolutionUnit],
) -> Result<Box<[RustUnitBinding]>, RustResolutionError> {
if units.len() != snapshot.units().len() {
return Err(RustResolutionError::UnitMapping {
unit: position(units.len()),
reason: Box::from("the report and the snapshot hold different unit counts"),
});
}
let keyed = keyed_units(snapshot);
units.iter().map(|unit| bind_unit(&keyed, unit)).collect()
}
fn keyed_units(snapshot: &RustResolutionSnapshot) -> BTreeMap<Arc<str>, &RustResolutionUnit> {
snapshot
.units()
.iter()
.map(|unit| (unit_key(unit), unit))
.collect()
}
fn bind_unit(
keyed: &BTreeMap<Arc<str>, &RustResolutionUnit>,
unit: &ResolutionUnit,
) -> Result<RustUnitBinding, RustResolutionError> {
let found = keyed
.get(unit.key())
.ok_or_else(|| RustResolutionError::UnitMapping {
unit: unit.id().index(),
reason: Box::from("no snapshot unit carries this key"),
})?;
Ok(RustUnitBinding {
unit: unit.id(),
snapshot_unit: found.id(),
package: found.package(),
target: found.target(),
})
}
fn validate_sites(
snapshot: &RustResolutionSnapshot,
report: &ResolutionReport,
) -> Result<(), RustResolutionError> {
let lines = indexed_sources(snapshot);
for definition in report.definitions() {
validate_span(snapshot, &lines, SymbolDefinition::span(definition))?;
}
for reference in report.references() {
validate_span(snapshot, &lines, SymbolReference::span(reference))?;
}
Ok(())
}
fn indexed_sources(snapshot: &RustResolutionSnapshot) -> BTreeMap<&str, LineIndex> {
snapshot
.sources()
.iter()
.map(|source| (source.path(), LineIndex::new(source.text())))
.collect()
}
fn validate_span(
snapshot: &RustResolutionSnapshot,
lines: &BTreeMap<&str, LineIndex>,
span: &SourceSpan,
) -> Result<(), RustResolutionError> {
let stated = snapshot
.source(span.file())
.zip(lines.get(span.file()))
.ok_or_else(|| RustResolutionError::UnknownFile {
file: Box::from(span.file()),
})?;
let (source, index) = stated;
for at in [span.start(), span.end()] {
if !index.holds(source.text(), at) {
return Err(RustResolutionError::InvalidCoordinate {
file: Box::from(span.file()),
line: at.line(),
column: at.column(),
});
}
}
Ok(())
}