use std::collections::HashSet as Set;
use std::fmt;
use crate::package::Package;
use crate::range::Range;
use crate::report::{DefaultStringReporter, DerivationTree, Derived, External};
use crate::solver::DependencyConstraints;
use crate::term::{self, Term};
use crate::type_aliases::Map;
use crate::version::Version;
#[derive(Debug, Clone)]
pub struct Incompatibility<P: Package, V: Version> {
pub id: usize,
package_terms: Map<P, Term<V>>,
kind: Kind<P, V>,
}
#[derive(Debug, Clone)]
enum Kind<P: Package, V: Version> {
NotRoot(P, V),
NoVersions(P, Range<V>),
UnavailableDependencies(P, Range<V>),
FromDependencyOf(P, Range<V>, P, Range<V>),
DerivedFrom(usize, usize),
}
pub type PackageTerm<P, V> = (P, Term<V>);
#[derive(Eq, PartialEq)]
pub enum Relation<P: Package, V: Version> {
Satisfied,
Contradicted(PackageTerm<P, V>),
AlmostSatisfied(P),
Inconclusive,
}
impl<P: Package, V: Version> Incompatibility<P, V> {
pub fn not_root(id: usize, package: P, version: V) -> Self {
let mut package_terms = Map::with_capacity_and_hasher(1, Default::default());
package_terms.insert(
package.clone(),
Term::Negative(Range::exact(version.clone())),
);
Self {
id,
package_terms,
kind: Kind::NotRoot(package, version),
}
}
pub fn no_versions(id: usize, package: P, term: Term<V>) -> Self {
let range = match &term {
Term::Positive(r) => r.clone(),
Term::Negative(_) => panic!("No version should have a positive term"),
};
let mut package_terms = Map::with_capacity_and_hasher(1, Default::default());
package_terms.insert(package.clone(), term);
Self {
id,
package_terms,
kind: Kind::NoVersions(package, range),
}
}
pub fn unavailable_dependencies(id: usize, package: P, version: V) -> Self {
let range = Range::exact(version);
let mut package_terms = Map::with_capacity_and_hasher(1, Default::default());
package_terms.insert(package.clone(), Term::Positive(range.clone()));
Self {
id,
package_terms,
kind: Kind::UnavailableDependencies(package, range),
}
}
pub fn from_dependencies(
start_id: usize,
package: P,
version: V,
deps: &DependencyConstraints<P, V>,
) -> Vec<Self> {
deps.iter()
.enumerate()
.map(|(i, dep)| {
Self::from_dependency(start_id + i, package.clone(), version.clone(), dep)
})
.collect()
}
fn from_dependency(id: usize, package: P, version: V, dep: (&P, &Range<V>)) -> Self {
let mut package_terms = Map::with_capacity_and_hasher(2, Default::default());
let range1 = Range::exact(version);
package_terms.insert(package.clone(), Term::Positive(range1.clone()));
let (p2, range2) = dep;
package_terms.insert(p2.clone(), Term::Negative(range2.clone()));
Self {
id,
package_terms,
kind: Kind::FromDependencyOf(package, range1, p2.clone(), range2.clone()),
}
}
fn intersection(i1: &Map<P, Term<V>>, i2: &Map<P, Term<V>>) -> Map<P, Term<V>> {
Self::merge(i1, i2, |t1, t2| Some(t1.intersection(t2)))
}
fn merge<T: Clone, F: Fn(&T, &T) -> Option<T>>(
map_1: &Map<P, T>,
map_2: &Map<P, T>,
f: F,
) -> Map<P, T> {
let mut merged_map = map_1.clone();
merged_map.reserve(map_2.len());
let mut to_delete = Vec::new();
for (key, val_2) in map_2.iter() {
match merged_map.get_mut(key) {
None => {
merged_map.insert(key.clone(), val_2.clone());
}
Some(val_1) => match f(val_1, val_2) {
None => to_delete.push(key),
Some(merged_value) => *val_1 = merged_value,
},
}
}
for key in to_delete.iter() {
merged_map.remove(key);
}
merged_map
}
pub fn merge_into(self, incompatibilities: &mut Vec<Self>) {
incompatibilities.push(self);
}
pub fn prior_cause(id: usize, incompat: &Self, satisfier_cause: &Self, package: &P) -> Self {
let kind = Kind::DerivedFrom(incompat.id, satisfier_cause.id);
let mut incompat1 = incompat.package_terms.clone();
let mut incompat2 = satisfier_cause.package_terms.clone();
let t1 = incompat1.remove(package).unwrap();
let t2 = incompat2.remove(package).unwrap();
let mut package_terms = Self::intersection(&incompat1, &incompat2);
let term = t1.union(&t2);
if term != Term::any() {
package_terms.insert(package.clone(), term);
}
Self {
id,
package_terms,
kind,
}
}
pub fn relation(&self, mut terms: impl FnMut(&P) -> Option<Term<V>>) -> Relation<P, V> {
let mut relation = Relation::Satisfied;
for (package, incompat_term) in self.package_terms.iter() {
match terms(package).map(|term| incompat_term.relation_with(&term)) {
Some(term::Relation::Satisfied) => {}
Some(term::Relation::Contradicted) => {
return Relation::Contradicted((package.clone(), incompat_term.clone()));
}
None | Some(term::Relation::Inconclusive) => {
if relation == Relation::Satisfied {
relation = Relation::AlmostSatisfied(package.clone());
} else {
relation = Relation::Inconclusive;
}
}
}
}
relation
}
pub fn is_terminal(&self, root_package: &P, root_version: &V) -> bool {
if self.package_terms.is_empty() {
true
} else if self.package_terms.len() > 1 {
false
} else {
let (package, term) = self.package_terms.iter().next().unwrap();
(package == root_package) && term.contains(&root_version)
}
}
pub fn get(&self, package: &P) -> Option<&Term<V>> {
self.package_terms.get(package)
}
pub fn iter(&self) -> impl Iterator<Item = (&P, &Term<V>)> {
self.package_terms.iter()
}
pub fn causes(&self) -> Option<(usize, usize)> {
match self.kind {
Kind::DerivedFrom(id1, id2) => Some((id1, id2)),
_ => None,
}
}
pub fn build_derivation_tree(
&self,
shared_ids: &Set<usize>,
store: &[Self],
) -> DerivationTree<P, V> {
match &self.kind {
Kind::DerivedFrom(id1, id2) => {
let cause1 = store[*id1].build_derivation_tree(shared_ids, store);
let cause2 = store[*id2].build_derivation_tree(shared_ids, store);
let derived = Derived {
terms: self.package_terms.clone(),
shared_id: shared_ids.get(&self.id).cloned(),
cause1: Box::new(cause1),
cause2: Box::new(cause2),
};
DerivationTree::Derived(derived)
}
Kind::NotRoot(package, version) => {
DerivationTree::External(External::NotRoot(package.clone(), version.clone()))
}
Kind::NoVersions(package, range) => {
DerivationTree::External(External::NoVersions(package.clone(), range.clone()))
}
Kind::UnavailableDependencies(package, range) => DerivationTree::External(
External::UnavailableDependencies(package.clone(), range.clone()),
),
Kind::FromDependencyOf(package, range, dep_package, dep_range) => {
DerivationTree::External(External::FromDependencyOf(
package.clone(),
range.clone(),
dep_package.clone(),
dep_range.clone(),
))
}
}
}
}
impl<P: Package, V: Version> fmt::Display for Incompatibility<P, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
DefaultStringReporter::string_terms(&self.package_terms)
)
}
}
impl<P: Package, V: Version> IntoIterator for Incompatibility<P, V> {
type Item = (P, Term<V>);
type IntoIter = std::collections::hash_map::IntoIter<P, Term<V>>;
fn into_iter(self) -> Self::IntoIter {
self.package_terms.into_iter()
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::term::tests::strategy as term_strat;
use proptest::prelude::*;
proptest! {
#[test]
fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) {
let mut i1 = Map::default();
i1.insert("p1", t1.clone());
i1.insert("p2", t2.negate());
let i1 = Incompatibility { id: 0, package_terms: i1, kind: Kind::DerivedFrom(0,0) };
let mut i2 = Map::default();
i2.insert("p2", t2.clone());
i2.insert("p3", t3.clone());
let i2 = Incompatibility { id: 0, package_terms: i2, kind: Kind::DerivedFrom(0,0) };
let mut i3 = Map::default();
i3.insert("p1", t1);
i3.insert("p3", t3);
let i_resolution = Incompatibility::prior_cause(0, &i1, &i2, &"p2");
assert_eq!(i_resolution.package_terms, i3);
}
}
}