use std::fmt::{Debug, Display};
use std::sync::Arc;
use crate::internal::{Arena, HashArena, Id, SmallMap};
use crate::{
DependencyProvider, DerivationTree, Derived, External, Map, Package, Set, Term, VersionSet,
term,
};
#[derive(Debug, Clone)]
pub(crate) struct Incompatibility<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
package_terms: SmallMap<Id<P>, Term<VS>>,
kind: Kind<P, VS, M>,
}
pub(crate) type IncompId<P, VS, M> = Id<Incompatibility<P, VS, M>>;
pub(crate) type IncompDpId<DP> = IncompId<
<DP as DependencyProvider>::P,
<DP as DependencyProvider>::VS,
<DP as DependencyProvider>::M,
>;
#[derive(Debug, Clone)]
enum Kind<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
NotRoot(Id<P>, VS::V),
NoVersions(Id<P>, VS),
FromDependencyOf(Id<P>, VS, Id<P>, VS),
DerivedFrom(IncompId<P, VS, M>, IncompId<P, VS, M>),
Custom(Id<P>, VS, M),
}
#[derive(Eq, PartialEq, Debug)]
pub(crate) enum Relation<P: Package> {
Satisfied,
Contradicted(Id<P>),
AlmostSatisfied(Id<P>),
Inconclusive,
}
impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibility<P, VS, M> {
pub(crate) fn not_root(package: Id<P>, version: VS::V) -> Self {
Self {
package_terms: SmallMap::One([(
package,
Term::Negative(VS::singleton(version.clone())),
)]),
kind: Kind::NotRoot(package, version),
}
}
pub(crate) fn no_versions(package: Id<P>, term: Term<VS>) -> Self {
let set = match &term {
Term::Positive(r) => r.clone(),
Term::Negative(_) => panic!("No version should have a positive term"),
};
Self {
package_terms: SmallMap::One([(package, term)]),
kind: Kind::NoVersions(package, set),
}
}
#[allow(dead_code)] pub(crate) fn custom_term(package: Id<P>, term: Term<VS>, metadata: M) -> Self {
let set = match &term {
Term::Positive(r) => r.clone(),
Term::Negative(_) => panic!("No version should have a positive term"),
};
Self {
package_terms: SmallMap::One([(package, term)]),
kind: Kind::Custom(package, set, metadata),
}
}
pub(crate) fn custom_version(package: Id<P>, version: VS::V, metadata: M) -> Self {
let set = VS::singleton(version);
let term = Term::Positive(set.clone());
Self {
package_terms: SmallMap::One([(package, term)]),
kind: Kind::Custom(package, set, metadata),
}
}
pub(crate) fn from_dependency(package: Id<P>, versions: VS, dep: (Id<P>, VS)) -> Self {
let (p2, set2) = dep;
Self {
package_terms: if set2 == VS::empty() {
SmallMap::One([(package, Term::Positive(versions.clone()))])
} else {
SmallMap::Two([
(package, Term::Positive(versions.clone())),
(p2, Term::Negative(set2.clone())),
])
},
kind: Kind::FromDependencyOf(package, versions, p2, set2),
}
}
pub(crate) fn as_dependency(&self) -> Option<(Id<P>, Id<P>)> {
match &self.kind {
Kind::FromDependencyOf(p1, _, p2, _) => Some((*p1, *p2)),
_ => None,
}
}
pub(crate) fn merge_dependents(&self, other: &Self) -> Option<Self> {
debug_assert!(self.as_dependency().is_some());
let self_pkgs = self.as_dependency()?;
if self_pkgs != other.as_dependency()? {
return None;
}
let (p1, p2) = self_pkgs;
if p1 == p2 {
return None;
}
let dep_term = self.get(p2);
if dep_term != other.get(p2) {
return None;
}
Some(Self::from_dependency(
p1,
self.get(p1)
.unwrap()
.unwrap_positive()
.union(other.get(p1).unwrap().unwrap_positive()), (
p2,
dep_term.map_or(VS::empty(), |v| v.unwrap_negative().clone()),
),
))
}
pub(crate) fn prior_cause(
incompat: Id<Self>,
satisfier_cause: Id<Self>,
package: Id<P>,
incompatibility_store: &Arena<Self>,
) -> Self {
let kind = Kind::DerivedFrom(incompat, satisfier_cause);
let (t1, mut package_terms) = incompatibility_store[incompat]
.package_terms
.split_one(&package)
.unwrap();
let satisfier_cause_terms = &incompatibility_store[satisfier_cause].package_terms;
package_terms.merge(
satisfier_cause_terms.iter().filter(|(p, _)| p != &&package),
|t1, t2| Some(t1.intersection(t2)),
);
let term = t1.union(satisfier_cause_terms.get(&package).unwrap());
if term != Term::any() {
package_terms.insert(package, term);
}
Self {
package_terms,
kind,
}
}
pub(crate) fn is_terminal(&self, root_package: Id<P>, root_version: &VS::V) -> bool {
if self.package_terms.len() == 0 {
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(crate) fn get(&self, package: Id<P>) -> Option<&Term<VS>> {
self.package_terms.get(&package)
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (Id<P>, &Term<VS>)> {
self.package_terms
.iter()
.map(|(package, term)| (*package, term))
}
pub(crate) fn causes(&self) -> Option<(Id<Self>, Id<Self>)> {
match self.kind {
Kind::DerivedFrom(id1, id2) => Some((id1, id2)),
_ => None,
}
}
pub(crate) fn build_derivation_tree(
self_id: Id<Self>,
shared_ids: &Set<Id<Self>>,
store: &Arena<Self>,
package_store: &HashArena<P>,
precomputed: &Map<Id<Self>, Arc<DerivationTree<P, VS, M>>>,
) -> DerivationTree<P, VS, M> {
match store[self_id].kind.clone() {
Kind::DerivedFrom(id1, id2) => {
let derived: Derived<P, VS, M> = Derived {
terms: store[self_id]
.package_terms
.iter()
.map(|(&a, b)| (package_store[a].clone(), b.clone()))
.collect(),
shared_id: shared_ids.get(&self_id).map(|id| id.into_raw()),
cause1: precomputed
.get(&id1)
.expect("Non-topological calls building tree")
.clone(),
cause2: precomputed
.get(&id2)
.expect("Non-topological calls building tree")
.clone(),
};
DerivationTree::Derived(derived)
}
Kind::NotRoot(package, version) => {
DerivationTree::External(External::NotRoot(package_store[package].clone(), version))
}
Kind::NoVersions(package, set) => DerivationTree::External(External::NoVersions(
package_store[package].clone(),
set.clone(),
)),
Kind::FromDependencyOf(package, set, dep_package, dep_set) => {
DerivationTree::External(External::FromDependencyOf(
package_store[package].clone(),
set.clone(),
package_store[dep_package].clone(),
dep_set.clone(),
))
}
Kind::Custom(package, set, metadata) => DerivationTree::External(External::Custom(
package_store[package].clone(),
set.clone(),
metadata.clone(),
)),
}
}
}
impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a>
Incompatibility<P, VS, M>
{
pub(crate) fn relation(&self, terms: impl Fn(Id<P>) -> Option<&'a Term<VS>>) -> Relation<P> {
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);
}
None | Some(term::Relation::Inconclusive) => {
if relation == Relation::Satisfied {
relation = Relation::AlmostSatisfied(package);
} else {
return Relation::Inconclusive;
}
}
}
}
relation
}
}
impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibility<P, VS, M> {
pub fn display<'a>(&'a self, package_store: &'a HashArena<P>) -> impl Display + 'a {
match self.iter().collect::<Vec<_>>().as_slice() {
[] => "version solving failed".into(),
[(package, Term::Positive(range))] => {
format!("{} {} is forbidden", package_store[*package], range)
}
[(package, Term::Negative(range))] => {
format!("{} {} is mandatory", package_store[*package], range)
}
[
(p_pos, Term::Positive(r_pos)),
(p_neg, Term::Negative(r_neg)),
]
| [
(p_neg, Term::Negative(r_neg)),
(p_pos, Term::Positive(r_pos)),
] => External::<_, _, M>::FromDependencyOf(
&package_store[*p_pos],
r_pos.clone(),
&package_store[*p_neg],
r_neg.clone(),
)
.to_string(),
slice => {
let str_terms: Vec<_> = slice
.iter()
.map(|(p, t)| format!("{} {}", package_store[*p], t))
.collect();
str_terms.join(", ") + " are incompatible"
}
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use proptest::prelude::*;
use std::cmp::Reverse;
use std::collections::BTreeMap;
use super::*;
use crate::internal::State;
use crate::term::tests::strategy as term_strat;
use crate::{OfflineDependencyProvider, Ranges};
proptest! {
#[test]
fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) {
let mut store = Arena::new();
let mut package_store = HashArena::new();
let p1 = package_store.alloc("p1");
let p2 = package_store.alloc("p2");
let p3 = package_store.alloc("p3");
let i1 = store.alloc(Incompatibility {
package_terms: SmallMap::Two([(p1, t1.clone()), (p2, t2.negate())]),
kind: Kind::<_, _, String>::FromDependencyOf(p1, Ranges::full(), p2, Ranges::full())
});
let i2 = store.alloc(Incompatibility {
package_terms: SmallMap::Two([(p2, t2), (p3, t3.clone())]),
kind: Kind::<_, _, String>::FromDependencyOf(p2, Ranges::full(), p3, Ranges::full())
});
let mut i3 = Map::default();
i3.insert(p1, t1);
i3.insert(p3, t3);
let i_resolution = Incompatibility::prior_cause(i1, i2, p2, &store);
assert_eq!(i_resolution.package_terms.iter().map(|(&k, v)|(k, v.clone())).collect::<Map<_, _>>(), i3);
}
}
#[test]
fn package_depend_on_self() {
let cases: &[Vec<(String, Ranges<usize>)>] = &[
vec![("foo".to_string(), Ranges::full())],
vec![
("foo".to_string(), Ranges::full()),
("foo".to_string(), Ranges::full()),
],
vec![
("foo".to_string(), Ranges::full()),
("foo".to_string(), Ranges::singleton(1usize)),
],
vec![
("foo".to_string(), Ranges::singleton(1usize)),
("foo".to_string(), Ranges::from_range_bounds(1usize..2)),
("foo".to_string(), Ranges::from_range_bounds(1usize..3)),
],
];
for case in cases {
let mut state: State<OfflineDependencyProvider<String, Ranges<usize>>> =
State::init("root".to_string(), 0);
state.unit_propagation(state.root_package).unwrap();
state.add_package_version_dependencies(
state.root_package,
0,
[("foo".to_string(), Ranges::singleton(1usize))],
);
state.unit_propagation(state.root_package).unwrap();
let (next, _) = state
.partial_solution
.pick_highest_priority_pkg(|_p, _r| (0, Reverse(0)))
.unwrap();
state.add_package_version_dependencies(next, 1, case.clone());
state.unit_propagation(next).unwrap();
assert!(
state
.partial_solution
.pick_highest_priority_pkg(|_p, _r| (0, Reverse(0)))
.is_none()
);
let solution: BTreeMap<String, usize> = state
.partial_solution
.extract_solution()
.map(|(p, v)| (state.package_store[p].clone(), v))
.collect();
let expected = BTreeMap::from([("root".to_string(), 0), ("foo".to_string(), 1)]);
assert_eq!(solution, expected, "{case:?}");
}
}
}