1use std::fmt::{Debug, Display};
7use std::sync::Arc;
8
9use crate::internal::{Arena, HashArena, Id, SmallMap};
10use crate::{
11 DependencyProvider, DerivationTree, Derived, External, Map, Package, Set, Term, VersionSet,
12 term,
13};
14
15#[derive(Debug, Clone)]
31pub struct Incompatibility<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
32 package_terms: SmallMap<Id<P>, Term<VS>>,
33 pub kind: Kind<P, VS, M>,
35}
36
37pub type IncompId<P, VS, M> = Id<Incompatibility<P, VS, M>>;
39
40pub(crate) type IncompDpId<DP> = IncompId<
41 <DP as DependencyProvider>::P,
42 <DP as DependencyProvider>::VS,
43 <DP as DependencyProvider>::M,
44>;
45
46#[derive(Debug, Clone)]
48pub enum Kind<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
49 NotRoot(Id<P>, VS::V),
54 NoVersions(Id<P>, VS),
59 FromDependencyOf(Id<P>, VS, Id<P>, VS),
67 DerivedFrom(IncompId<P, VS, M>, IncompId<P, VS, M>),
71 Custom(Id<P>, VS, M),
77}
78
79#[derive(Eq, PartialEq, Debug)]
82pub(crate) enum Relation<P: Package> {
83 Satisfied,
86 Contradicted(Id<P>),
89 AlmostSatisfied(Id<P>),
92 Inconclusive,
94}
95
96impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibility<P, VS, M> {
97 pub(crate) fn not_root(package: Id<P>, version: VS::V) -> Self {
99 Self {
100 package_terms: SmallMap::One([(
101 package,
102 Term::Negative(VS::singleton(version.clone())),
103 )]),
104 kind: Kind::NotRoot(package, version),
105 }
106 }
107
108 pub fn no_versions(package: Id<P>, term: Term<VS>) -> Self {
110 let set = match &term {
111 Term::Positive(r) => r.clone(),
112 Term::Negative(_) => panic!("No version should have a positive term"),
113 };
114 Self {
115 package_terms: SmallMap::One([(package, term)]),
116 kind: Kind::NoVersions(package, set),
117 }
118 }
119
120 #[allow(dead_code)] pub fn custom_term(package: Id<P>, term: Term<VS>, metadata: M) -> Self {
123 let set = match &term {
124 Term::Positive(r) => r.clone(),
125 Term::Negative(_) => panic!("No version should have a positive term"),
126 };
127 Self {
128 package_terms: SmallMap::One([(package, term)]),
129 kind: Kind::Custom(package, set, metadata),
130 }
131 }
132
133 pub fn custom_version(package: Id<P>, version: VS::V, metadata: M) -> Self {
135 let set = VS::singleton(version);
136 let term = Term::Positive(set.clone());
137 Self {
138 package_terms: SmallMap::One([(package, term)]),
139 kind: Kind::Custom(package, set, metadata),
140 }
141 }
142
143 pub fn from_dependency(package: Id<P>, versions: VS, dep: (Id<P>, VS)) -> Self {
145 let (p2, set2) = dep;
146 Self {
147 package_terms: if set2 == VS::empty() {
148 SmallMap::One([(package, Term::Positive(versions.clone()))])
149 } else {
150 SmallMap::Two([
151 (package, Term::Positive(versions.clone())),
152 (p2, Term::Negative(set2.clone())),
153 ])
154 },
155 kind: Kind::FromDependencyOf(package, versions, p2, set2),
156 }
157 }
158
159 pub(crate) fn as_dependency(&self) -> Option<(Id<P>, Id<P>)> {
160 match &self.kind {
161 Kind::FromDependencyOf(p1, _, p2, _) => Some((*p1, *p2)),
162 _ => None,
163 }
164 }
165
166 pub(crate) fn merge_dependents(&self, other: &Self) -> Option<Self> {
176 debug_assert!(self.as_dependency().is_some());
178 let self_pkgs = self.as_dependency()?;
181 if self_pkgs != other.as_dependency()? {
182 return None;
183 }
184 let (p1, p2) = self_pkgs;
185 if p1 == p2 {
191 return None;
192 }
193 let dep_term = self.get(p2);
194 if dep_term != other.get(p2) {
197 return None;
198 }
199 Some(Self::from_dependency(
200 p1,
201 self.get(p1)
202 .unwrap()
203 .unwrap_positive()
204 .union(other.get(p1).unwrap().unwrap_positive()), (
206 p2,
207 dep_term.map_or(VS::empty(), |v| v.unwrap_negative().clone()),
208 ),
209 ))
210 }
211
212 pub(crate) fn prior_cause(
214 incompat: Id<Self>,
215 satisfier_cause: Id<Self>,
216 package: Id<P>,
217 incompatibility_store: &Arena<Self>,
218 ) -> Self {
219 let kind = Kind::DerivedFrom(incompat, satisfier_cause);
220 let (t1, mut package_terms) = incompatibility_store[incompat]
222 .package_terms
223 .split_one(&package)
224 .unwrap();
225 let satisfier_cause_terms = &incompatibility_store[satisfier_cause].package_terms;
226 package_terms.merge(
227 satisfier_cause_terms.iter().filter(|(p, _)| p != &&package),
228 |t1, t2| Some(t1.intersection(t2)),
229 );
230 let term = t1.union(satisfier_cause_terms.get(&package).unwrap());
231 if term != Term::any() {
232 package_terms.insert(package, term);
233 }
234 Self {
235 package_terms,
236 kind,
237 }
238 }
239
240 pub(crate) fn is_terminal(&self, root_package: Id<P>, root_version: &VS::V) -> bool {
243 if self.package_terms.len() == 0 {
244 true
245 } else if self.package_terms.len() > 1 {
246 false
247 } else {
248 let (package, term) = self.package_terms.iter().next().unwrap();
249 (package == &root_package) && term.contains(root_version)
250 }
251 }
252
253 pub(crate) fn get(&self, package: Id<P>) -> Option<&Term<VS>> {
255 self.package_terms.get(&package)
256 }
257
258 pub fn iter(&self) -> impl Iterator<Item = (Id<P>, &Term<VS>)> {
260 self.package_terms
261 .iter()
262 .map(|(package, term)| (*package, term))
263 }
264
265 pub(crate) fn causes(&self) -> Option<(Id<Self>, Id<Self>)> {
269 match self.kind {
270 Kind::DerivedFrom(id1, id2) => Some((id1, id2)),
271 _ => None,
272 }
273 }
274
275 pub(crate) fn build_derivation_tree(
277 self_id: Id<Self>,
278 shared_ids: &Set<Id<Self>>,
279 store: &Arena<Self>,
280 package_store: &HashArena<P>,
281 precomputed: &Map<Id<Self>, Arc<DerivationTree<P, VS, M>>>,
282 ) -> DerivationTree<P, VS, M> {
283 match store[self_id].kind.clone() {
284 Kind::DerivedFrom(id1, id2) => {
285 let derived: Derived<P, VS, M> = Derived {
286 terms: store[self_id]
287 .package_terms
288 .iter()
289 .map(|(&a, b)| (package_store[a].clone(), b.clone()))
290 .collect(),
291 shared_id: shared_ids.get(&self_id).map(|id| id.into_raw()),
292 cause1: precomputed
293 .get(&id1)
294 .expect("Non-topological calls building tree")
295 .clone(),
296 cause2: precomputed
297 .get(&id2)
298 .expect("Non-topological calls building tree")
299 .clone(),
300 };
301 DerivationTree::Derived(derived)
302 }
303 Kind::NotRoot(package, version) => {
304 DerivationTree::External(External::NotRoot(package_store[package].clone(), version))
305 }
306 Kind::NoVersions(package, set) => DerivationTree::External(External::NoVersions(
307 package_store[package].clone(),
308 set.clone(),
309 )),
310 Kind::FromDependencyOf(package, set, dep_package, dep_set) => {
311 DerivationTree::External(External::FromDependencyOf(
312 package_store[package].clone(),
313 set.clone(),
314 package_store[dep_package].clone(),
315 dep_set.clone(),
316 ))
317 }
318 Kind::Custom(package, set, metadata) => DerivationTree::External(External::Custom(
319 package_store[package].clone(),
320 set.clone(),
321 metadata.clone(),
322 )),
323 }
324 }
325}
326
327impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a>
328 Incompatibility<P, VS, M>
329{
330 pub(crate) fn relation(&self, terms: impl Fn(Id<P>) -> Option<&'a Term<VS>>) -> Relation<P> {
332 let mut relation = Relation::Satisfied;
333 for (&package, incompat_term) in self.package_terms.iter() {
334 match terms(package).map(|term| incompat_term.relation_with(term)) {
335 Some(term::Relation::Satisfied) => {}
336 Some(term::Relation::Contradicted) => {
337 return Relation::Contradicted(package);
338 }
339 None | Some(term::Relation::Inconclusive) => {
340 if relation == Relation::Satisfied {
346 relation = Relation::AlmostSatisfied(package);
347 } else {
348 return Relation::Inconclusive;
349 }
350 }
351 }
352 }
353 relation
354 }
355}
356
357impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibility<P, VS, M> {
358 pub fn display<'a>(&'a self, package_store: &'a HashArena<P>) -> impl Display + 'a {
360 match self.iter().collect::<Vec<_>>().as_slice() {
361 [] => "version solving failed".into(),
362 [(package, Term::Positive(range))] => {
364 format!("{} {} is forbidden", package_store[*package], range)
365 }
366 [(package, Term::Negative(range))] => {
367 format!("{} {} is mandatory", package_store[*package], range)
368 }
369 [
370 (p_pos, Term::Positive(r_pos)),
371 (p_neg, Term::Negative(r_neg)),
372 ]
373 | [
374 (p_neg, Term::Negative(r_neg)),
375 (p_pos, Term::Positive(r_pos)),
376 ] => External::<_, _, M>::FromDependencyOf(
377 &package_store[*p_pos],
378 r_pos.clone(),
379 &package_store[*p_neg],
380 r_neg.clone(),
381 )
382 .to_string(),
383 slice => {
384 let str_terms: Vec<_> = slice
385 .iter()
386 .map(|(p, t)| format!("{} {}", package_store[*p], t))
387 .collect();
388 str_terms.join(", ") + " are incompatible"
389 }
390 }
391 }
392}
393
394#[cfg(test)]
397pub(crate) mod tests {
398 use proptest::prelude::*;
399 use std::cmp::Reverse;
400 use std::collections::BTreeMap;
401
402 use super::*;
403 use crate::internal::State;
404 use crate::term::tests::strategy as term_strat;
405 use crate::{OfflineDependencyProvider, Ranges};
406
407 proptest! {
408
409 #[test]
417 fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) {
418 let mut store = Arena::new();
419 let mut package_store = HashArena::new();
420 let p1 = package_store.alloc("p1");
421 let p2 = package_store.alloc("p2");
422 let p3 = package_store.alloc("p3");
423 let i1 = store.alloc(Incompatibility {
424 package_terms: SmallMap::Two([(p1, t1.clone()), (p2, t2.negate())]),
425 kind: Kind::<_, _, String>::FromDependencyOf(p1, Ranges::full(), p2, Ranges::full())
426 });
427
428 let i2 = store.alloc(Incompatibility {
429 package_terms: SmallMap::Two([(p2, t2), (p3, t3.clone())]),
430 kind: Kind::<_, _, String>::FromDependencyOf(p2, Ranges::full(), p3, Ranges::full())
431 });
432
433 let mut i3 = Map::default();
434 i3.insert(p1, t1);
435 i3.insert(p3, t3);
436
437 let i_resolution = Incompatibility::prior_cause(i1, i2, p2, &store);
438 assert_eq!(i_resolution.package_terms.iter().map(|(&k, v)|(k, v.clone())).collect::<Map<_, _>>(), i3);
439 }
440
441 }
442
443 #[test]
450 fn package_depend_on_self() {
451 let cases: &[Vec<(String, Ranges<usize>)>] = &[
452 vec![("foo".to_string(), Ranges::full())],
453 vec![
454 ("foo".to_string(), Ranges::full()),
455 ("foo".to_string(), Ranges::full()),
456 ],
457 vec![
458 ("foo".to_string(), Ranges::full()),
459 ("foo".to_string(), Ranges::singleton(1usize)),
460 ],
461 vec![
462 ("foo".to_string(), Ranges::singleton(1usize)),
463 ("foo".to_string(), Ranges::from_range_bounds(1usize..2)),
464 ("foo".to_string(), Ranges::from_range_bounds(1usize..3)),
465 ],
466 ];
467
468 for case in cases {
469 let mut state: State<OfflineDependencyProvider<String, Ranges<usize>>> =
470 State::init("root".to_string(), 0);
471 state.unit_propagation(state.root_package).unwrap();
472
473 state.add_package_version_dependencies(
475 state.root_package,
476 0,
477 [("foo".to_string(), Ranges::singleton(1usize))],
478 );
479 state.unit_propagation(state.root_package).unwrap();
480
481 let (next, _) = state
483 .partial_solution
484 .pick_highest_priority_pkg(|_p, _r| (0, Reverse(0)))
485 .unwrap();
486 state.add_package_version_dependencies(next, 1, case.clone());
487 state.unit_propagation(next).unwrap();
488
489 assert!(
490 state
491 .partial_solution
492 .pick_highest_priority_pkg(|_p, _r| (0, Reverse(0)))
493 .is_none()
494 );
495
496 let solution: BTreeMap<String, usize> = state
497 .partial_solution
498 .extract_solution()
499 .map(|(p, v)| (state.package_store[p].clone(), v))
500 .collect();
501 let expected = BTreeMap::from([("root".to_string(), 0), ("foo".to_string(), 1)]);
502
503 assert_eq!(solution, expected, "{case:?}");
504 }
505 }
506}