use std::sync::Arc;
use pedant_core::resolution::rust::{
RustResolutionSnapshot, RustResolutionUnit, RustSnapshotEdge, RustSnapshotUnitId,
RustTargetResolution, RustUnitBinding,
};
use pedant_types::{ResolutionRecord, ResolutionReport, ResolutionUnit, SymbolReference};
use crate::containment::ContainmentEdge;
use crate::error::GraphBuildError;
use crate::id::{GraphNodeId, index_of, position};
use crate::node::NodeDraft;
use super::index::ProjectionState;
#[derive(Clone, Copy, PartialEq, Eq)]
enum Visit {
Pending,
Active,
Done,
}
pub(crate) fn check_root_target(
snapshot: &RustResolutionSnapshot,
resolution: &RustTargetResolution,
) -> Result<(), GraphBuildError> {
match snapshot.root_target() == resolution.root_target() {
true => Ok(()),
false => Err(GraphBuildError::RootTargetMismatch),
}
}
pub(crate) fn check_snapshot_identity(
snapshot: &RustResolutionSnapshot,
resolution: &RustTargetResolution,
) -> Result<(), GraphBuildError> {
match snapshot.fingerprint() == resolution.snapshot_fingerprint() {
true => Ok(()),
false => Err(GraphBuildError::SnapshotFingerprintMismatch),
}
}
pub(crate) fn stated_binding(
resolution: &RustTargetResolution,
unit: &ResolutionUnit,
) -> Result<RustSnapshotUnitId, GraphBuildError> {
resolution
.unit(unit.id())
.map(RustUnitBinding::snapshot_unit)
.ok_or(GraphBuildError::MissingUnitBinding {
unit: unit.id().index(),
})
}
pub(crate) fn bound_snapshot_unit(
state: &ProjectionState,
unit: u32,
) -> Result<RustSnapshotUnitId, GraphBuildError> {
state
.snapshot_unit(unit)
.ok_or(GraphBuildError::MissingUnitBinding { unit })
}
pub(crate) fn snapshot_instance(
snapshot: &RustResolutionSnapshot,
unit: (RustSnapshotUnitId, u32),
) -> Result<&RustResolutionUnit, GraphBuildError> {
let (snapshot_unit, reported) = unit;
snapshot
.unit(snapshot_unit)
.ok_or(GraphBuildError::DanglingUnitBinding { unit: reported })
}
pub(crate) fn bind_unit(
state: &mut ProjectionState,
container: GraphNodeId,
unit: (RustSnapshotUnitId, u32),
) -> Result<(), GraphBuildError> {
let (snapshot_unit, reported) = unit;
match state.bind_unit(container, (snapshot_unit, reported)) {
None => Ok(()),
Some(held) => Err(GraphBuildError::SharedUnitBinding {
held,
unit: reported,
}),
}
}
pub(crate) fn unit_container(
state: &ProjectionState,
unit: u32,
) -> Result<GraphNodeId, GraphBuildError> {
state
.container(unit)
.ok_or(GraphBuildError::MissingUnitBinding { unit })
}
pub(crate) fn snapshot_container(
state: &ProjectionState,
unit: RustSnapshotUnitId,
stated: (u32, &RustSnapshotEdge),
) -> Result<GraphNodeId, GraphBuildError> {
let (edge, declaration) = stated;
state
.snapshot_container(unit)
.ok_or_else(|| GraphBuildError::MissingDependencyUnit {
edge,
alias: Box::from(declaration.name()),
})
}
pub(crate) fn qualified_source(
state: &mut ProjectionState,
unit: (RustSnapshotUnitId, u32),
path: &Arc<str>,
draft: NodeDraft,
) -> Result<GraphNodeId, GraphBuildError> {
let (snapshot_unit, reported) = unit;
state
.qualify_source(snapshot_unit, path, draft)?
.ok_or(GraphBuildError::MissingUnitBinding { unit: reported })
}
pub(crate) fn resolved_references(
report: &ResolutionReport,
) -> Result<Box<[(&SymbolReference, &ResolutionRecord)]>, GraphBuildError> {
let references = report.references();
let records = report.resolutions();
match references.len() == records.len() {
true => Ok(references.iter().zip(records).collect()),
false => Err(GraphBuildError::ReferenceRecordMismatch {
references: position(references.len()),
records: position(records.len()),
}),
}
}
pub(crate) fn source_node(
state: &ProjectionState,
unit: (RustSnapshotUnitId, u32),
path: &str,
) -> Result<GraphNodeId, GraphBuildError> {
let (snapshot_unit, reported) = unit;
state
.file_node(snapshot_unit, path)
.ok_or_else(|| GraphBuildError::MissingSourceNode {
unit: reported,
path: Box::from(path),
})
}
pub(crate) fn definition_node(
state: &ProjectionState,
definition: u32,
) -> Result<GraphNodeId, GraphBuildError> {
state
.definition_node(definition)
.ok_or(GraphBuildError::MissingDefinitionNode { definition })
}
pub(crate) fn check_containment_forest(
state: &ProjectionState,
units: usize,
) -> Result<(), GraphBuildError> {
let parents = stated_parents(state)?;
check_every_node_is_parented(&parents, units)?;
check_no_unit_root_is_contained(&parents, units)?;
check_acyclic(&parents)
}
fn stated_parents(state: &ProjectionState) -> Result<Box<[Option<GraphNodeId>]>, GraphBuildError> {
let mut parents: Vec<Option<GraphNodeId>> = vec![None; state.node_count()];
for edge in state.containment() {
let slot = parent_slot(&mut parents, edge)?;
match slot {
Some(existing) => {
return Err(GraphBuildError::MultiplyContained {
parent: existing.index(),
child: edge.child().index(),
});
}
None => *slot = Some(edge.parent()),
}
}
Ok(parents.into_boxed_slice())
}
fn parent_slot<'a>(
parents: &'a mut [Option<GraphNodeId>],
edge: &ContainmentEdge,
) -> Result<&'a mut Option<GraphNodeId>, GraphBuildError> {
let child = edge.child();
parents
.get_mut(index_of(child.index()))
.ok_or(GraphBuildError::UnknownContainmentNode {
node: child.index(),
})
}
fn check_every_node_is_parented(
parents: &[Option<GraphNodeId>],
units: usize,
) -> Result<(), GraphBuildError> {
parents
.iter()
.enumerate()
.skip(units)
.find(|(_, parent)| parent.is_none())
.map_or(Ok(()), |(index, _)| {
Err(GraphBuildError::UnparentedNode {
node: position(index),
})
})
}
fn check_no_unit_root_is_contained(
parents: &[Option<GraphNodeId>],
units: usize,
) -> Result<(), GraphBuildError> {
parents
.iter()
.enumerate()
.take(units)
.find_map(|(index, parent)| parent.map(|held| (index, held)))
.map_or(Ok(()), |(index, parent)| {
Err(GraphBuildError::RootHasParent {
root: position(index),
parent: parent.index(),
})
})
}
fn check_acyclic(parents: &[Option<GraphNodeId>]) -> Result<(), GraphBuildError> {
let mut visits = vec![Visit::Pending; parents.len()];
let mut chain: Vec<usize> = Vec::new();
for start in 0..parents.len() {
walk_ancestors(parents, &mut visits, &mut chain, start)?;
}
Ok(())
}
fn walk_ancestors(
parents: &[Option<GraphNodeId>],
visits: &mut [Visit],
chain: &mut Vec<usize>,
start: usize,
) -> Result<(), GraphBuildError> {
match visit_of(visits, start)? {
Visit::Pending => (),
Visit::Active | Visit::Done => return Ok(()),
}
chain.clear();
let mut current = Some(start);
let mut cycle = None;
while let Some(index) = current {
let visit = visit_of(visits, index)?;
cycle = (visit == Visit::Active).then_some(index);
match visit {
Visit::Pending => (),
Visit::Active | Visit::Done => break,
}
mark(visits, index, Visit::Active)?;
chain.push(index);
current = parents
.get(index)
.and_then(|parent| *parent)
.map(|parent| index_of(parent.index()));
}
for index in chain.drain(..) {
mark(visits, index, Visit::Done)?;
}
match cycle {
Some(index) => Err(GraphBuildError::ContainmentCycle {
node: position(index),
}),
None => Ok(()),
}
}
fn visit_of(visits: &[Visit], index: usize) -> Result<Visit, GraphBuildError> {
visits
.get(index)
.copied()
.ok_or(GraphBuildError::UnknownContainmentNode {
node: position(index),
})
}
fn mark(visits: &mut [Visit], index: usize, visit: Visit) -> Result<(), GraphBuildError> {
match visits.get_mut(index) {
Some(slot) => {
*slot = visit;
Ok(())
}
None => Err(GraphBuildError::UnknownContainmentNode {
node: position(index),
}),
}
}