1 // SPDX-License-Identifier: MPL-2.0
2 
3 //! Core model and functions
4 //! to write a functional PubGrub algorithm.
5 
6 use std::collections::HashSet as Set;
7 
8 use crate::error::PubGrubError;
9 use crate::internal::arena::Arena;
10 use crate::internal::incompatibility::{IncompId, Incompatibility, Relation};
11 use crate::internal::partial_solution::SatisfierSearch::{
12     DifferentDecisionLevels, SameDecisionLevels,
13 };
14 use crate::internal::partial_solution::{DecisionLevel, PartialSolution};
15 use crate::internal::small_vec::SmallVec;
16 use crate::package::Package;
17 use crate::report::DerivationTree;
18 use crate::solver::DependencyConstraints;
19 use crate::type_aliases::Map;
20 use crate::version::Version;
21 
22 /// Current state of the PubGrub algorithm.
23 #[derive(Clone)]
24 pub struct State<P: Package, V: Version> {
25     root_package: P,
26     root_version: V,
27 
28     incompatibilities: Map<P, Vec<IncompId<P, V>>>,
29 
30     /// Store the ids of incompatibilities that are already contradicted
31     /// and will stay that way until the next conflict and backtrack is operated.
32     contradicted_incompatibilities: rustc_hash::FxHashSet<IncompId<P, V>>,
33 
34     /// Partial solution.
35     /// TODO: remove pub.
36     pub partial_solution: PartialSolution<P, V>,
37 
38     /// The store is the reference storage for all incompatibilities.
39     pub incompatibility_store: Arena<Incompatibility<P, V>>,
40 
41     /// This is a stack of work to be done in `unit_propagation`.
42     /// It can definitely be a local variable to that method, but
43     /// this way we can reuse the same allocation for better performance.
44     unit_propagation_buffer: SmallVec<P>,
45 }
46 
47 impl<P: Package, V: Version> State<P, V> {
48     /// Initialization of PubGrub state.
init(root_package: P, root_version: V) -> Self49     pub fn init(root_package: P, root_version: V) -> Self {
50         let mut incompatibility_store = Arena::new();
51         let not_root_id = incompatibility_store.alloc(Incompatibility::not_root(
52             root_package.clone(),
53             root_version.clone(),
54         ));
55         let mut incompatibilities = Map::default();
56         incompatibilities.insert(root_package.clone(), vec![not_root_id]);
57         Self {
58             root_package,
59             root_version,
60             incompatibilities,
61             contradicted_incompatibilities: rustc_hash::FxHashSet::default(),
62             partial_solution: PartialSolution::empty(),
63             incompatibility_store,
64             unit_propagation_buffer: SmallVec::Empty,
65         }
66     }
67 
68     /// Add an incompatibility to the state.
add_incompatibility(&mut self, incompat: Incompatibility<P, V>)69     pub fn add_incompatibility(&mut self, incompat: Incompatibility<P, V>) {
70         let id = self.incompatibility_store.alloc(incompat);
71         self.merge_incompatibility(id);
72     }
73 
74     /// Add an incompatibility to the state.
add_incompatibility_from_dependencies( &mut self, package: P, version: V, deps: &DependencyConstraints<P, V>, ) -> std::ops::Range<IncompId<P, V>>75     pub fn add_incompatibility_from_dependencies(
76         &mut self,
77         package: P,
78         version: V,
79         deps: &DependencyConstraints<P, V>,
80     ) -> std::ops::Range<IncompId<P, V>> {
81         // Create incompatibilities and allocate them in the store.
82         let new_incompats_id_range = self
83             .incompatibility_store
84             .alloc_iter(deps.iter().map(|dep| {
85                 Incompatibility::from_dependency(package.clone(), version.clone(), dep)
86             }));
87         // Merge the newly created incompatibilities with the older ones.
88         for id in IncompId::range_to_iter(new_incompats_id_range.clone()) {
89             self.merge_incompatibility(id);
90         }
91         new_incompats_id_range
92     }
93 
94     /// Check if an incompatibility is terminal.
is_terminal(&self, incompatibility: &Incompatibility<P, V>) -> bool95     pub fn is_terminal(&self, incompatibility: &Incompatibility<P, V>) -> bool {
96         incompatibility.is_terminal(&self.root_package, &self.root_version)
97     }
98 
99     /// Unit propagation is the core mechanism of the solving algorithm.
100     /// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
unit_propagation(&mut self, package: P) -> Result<(), PubGrubError<P, V>>101     pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError<P, V>> {
102         self.unit_propagation_buffer.clear();
103         self.unit_propagation_buffer.push(package);
104         while let Some(current_package) = self.unit_propagation_buffer.pop() {
105             // Iterate over incompatibilities in reverse order
106             // to evaluate first the newest incompatibilities.
107             let mut conflict_id = None;
108             // We only care about incompatibilities if it contains the current package.
109             for &incompat_id in self.incompatibilities[&current_package].iter().rev() {
110                 if self.contradicted_incompatibilities.contains(&incompat_id) {
111                     continue;
112                 }
113                 let current_incompat = &self.incompatibility_store[incompat_id];
114                 match self.partial_solution.relation(current_incompat) {
115                     // If the partial solution satisfies the incompatibility
116                     // we must perform conflict resolution.
117                     Relation::Satisfied => {
118                         conflict_id = Some(incompat_id);
119                         break;
120                     }
121                     Relation::AlmostSatisfied(package_almost) => {
122                         self.unit_propagation_buffer.push(package_almost.clone());
123                         // Add (not term) to the partial solution with incompat as cause.
124                         self.partial_solution.add_derivation(
125                             package_almost,
126                             incompat_id,
127                             &self.incompatibility_store,
128                         );
129                         // With the partial solution updated, the incompatibility is now contradicted.
130                         self.contradicted_incompatibilities.insert(incompat_id);
131                     }
132                     Relation::Contradicted(_) => {
133                         self.contradicted_incompatibilities.insert(incompat_id);
134                     }
135                     _ => {}
136                 }
137             }
138             if let Some(incompat_id) = conflict_id {
139                 let (package_almost, root_cause) = self.conflict_resolution(incompat_id)?;
140                 self.unit_propagation_buffer.clear();
141                 self.unit_propagation_buffer.push(package_almost.clone());
142                 // Add to the partial solution with incompat as cause.
143                 self.partial_solution.add_derivation(
144                     package_almost,
145                     root_cause,
146                     &self.incompatibility_store,
147                 );
148                 // After conflict resolution and the partial solution update,
149                 // the root cause incompatibility is now contradicted.
150                 self.contradicted_incompatibilities.insert(root_cause);
151             }
152         }
153         // If there are no more changed packages, unit propagation is done.
154         Ok(())
155     }
156 
157     /// Return the root cause and the backtracked model.
158     /// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
conflict_resolution( &mut self, incompatibility: IncompId<P, V>, ) -> Result<(P, IncompId<P, V>), PubGrubError<P, V>>159     fn conflict_resolution(
160         &mut self,
161         incompatibility: IncompId<P, V>,
162     ) -> Result<(P, IncompId<P, V>), PubGrubError<P, V>> {
163         let mut current_incompat_id = incompatibility;
164         let mut current_incompat_changed = false;
165         loop {
166             if self.incompatibility_store[current_incompat_id]
167                 .is_terminal(&self.root_package, &self.root_version)
168             {
169                 return Err(PubGrubError::NoSolution(
170                     self.build_derivation_tree(current_incompat_id),
171                 ));
172             } else {
173                 let (package, satisfier_search_result) = self.partial_solution.satisfier_search(
174                     &self.incompatibility_store[current_incompat_id],
175                     &self.incompatibility_store,
176                 );
177                 match satisfier_search_result {
178                     DifferentDecisionLevels {
179                         previous_satisfier_level,
180                     } => {
181                         self.backtrack(
182                             current_incompat_id,
183                             current_incompat_changed,
184                             previous_satisfier_level,
185                         );
186                         return Ok((package, current_incompat_id));
187                     }
188                     SameDecisionLevels { satisfier_cause } => {
189                         let prior_cause = Incompatibility::prior_cause(
190                             current_incompat_id,
191                             satisfier_cause,
192                             &package,
193                             &self.incompatibility_store,
194                         );
195                         current_incompat_id = self.incompatibility_store.alloc(prior_cause);
196                         current_incompat_changed = true;
197                     }
198                 }
199             }
200         }
201     }
202 
203     /// Backtracking.
backtrack( &mut self, incompat: IncompId<P, V>, incompat_changed: bool, decision_level: DecisionLevel, )204     fn backtrack(
205         &mut self,
206         incompat: IncompId<P, V>,
207         incompat_changed: bool,
208         decision_level: DecisionLevel,
209     ) {
210         self.partial_solution
211             .backtrack(decision_level, &self.incompatibility_store);
212         self.contradicted_incompatibilities.clear();
213         if incompat_changed {
214             self.merge_incompatibility(incompat);
215         }
216     }
217 
218     /// Add this incompatibility into the set of all incompatibilities.
219     ///
220     /// Pub collapses identical dependencies from adjacent package versions
221     /// into individual incompatibilities.
222     /// This substantially reduces the total number of incompatibilities
223     /// and makes it much easier for Pub to reason about multiple versions of packages at once.
224     ///
225     /// For example, rather than representing
226     /// foo 1.0.0 depends on bar ^1.0.0 and
227     /// foo 1.1.0 depends on bar ^1.0.0
228     /// as two separate incompatibilities,
229     /// they are collapsed together into the single incompatibility {foo ^1.0.0, not bar ^1.0.0}
230     /// (provided that no other version of foo exists between 1.0.0 and 2.0.0).
231     /// We could collapse them into { foo (1.0.0 ∪ 1.1.0), not bar ^1.0.0 }
232     /// without having to check the existence of other versions though.
233     ///
234     /// Here we do the simple stupid thing of just growing the Vec.
235     /// It may not be trivial since those incompatibilities
236     /// may already have derived others.
merge_incompatibility(&mut self, id: IncompId<P, V>)237     fn merge_incompatibility(&mut self, id: IncompId<P, V>) {
238         for (pkg, _term) in self.incompatibility_store[id].iter() {
239             self.incompatibilities
240                 .entry(pkg.clone())
241                 .or_default()
242                 .push(id);
243         }
244     }
245 
246     // Error reporting #########################################################
247 
build_derivation_tree(&self, incompat: IncompId<P, V>) -> DerivationTree<P, V>248     fn build_derivation_tree(&self, incompat: IncompId<P, V>) -> DerivationTree<P, V> {
249         let shared_ids = self.find_shared_ids(incompat);
250         Incompatibility::build_derivation_tree(incompat, &shared_ids, &self.incompatibility_store)
251     }
252 
find_shared_ids(&self, incompat: IncompId<P, V>) -> Set<IncompId<P, V>>253     fn find_shared_ids(&self, incompat: IncompId<P, V>) -> Set<IncompId<P, V>> {
254         let mut all_ids = Set::new();
255         let mut shared_ids = Set::new();
256         let mut stack = vec![incompat];
257         while let Some(i) = stack.pop() {
258             if let Some((id1, id2)) = self.incompatibility_store[i].causes() {
259                 if all_ids.contains(&i) {
260                     shared_ids.insert(i);
261                 } else {
262                     all_ids.insert(i);
263                     stack.push(id1);
264                     stack.push(id2);
265                 }
266             }
267         }
268         shared_ids
269     }
270 }
271