Skip to main content

vivacity_resolver/
solver.rs

1//! Port of `Composer\DependencyResolver\Solver` (CDCL) and of the skeleton
2//! of `Problem` (the offending rules; messages come with R5).
3
4use crate::decisions::{Decisions, SolverBug};
5use crate::package::Package;
6use crate::platform_filter::PlatformRequirementFilter;
7use crate::policy::DefaultPolicy;
8use crate::pool::{Pool, Request};
9use crate::rule::{Reason, Rule, RuleKind, RuleSet, RuleType};
10use crate::rules_gen::RuleSetGenerator;
11use crate::transaction::LockTransaction;
12use crate::watch::{RuleWatchGraph, RuleWatchNode};
13use std::collections::{HashMap, HashSet};
14
15/// `Composer\DependencyResolver\Problem`: rule sections.
16#[derive(Debug, Clone, Default)]
17pub struct Problem {
18    /// section -> rules (ids, or out-of-band rules for unsatisfiable root
19    /// requests). A section only exists from its first rule on
20    /// (`nextSection` merely advances the index).
21    pub sections: Vec<Vec<ProblemRule>>,
22    seen: HashSet<usize>,
23    pending_section: bool,
24}
25
26#[derive(Debug, Clone)]
27pub enum ProblemRule {
28    InSet(usize),
29    Detached(Rule),
30}
31
32impl Problem {
33    pub fn new() -> Problem {
34        Problem {
35            sections: Vec::new(),
36            seen: HashSet::new(),
37            pending_section: true,
38        }
39    }
40
41    fn push(&mut self, rule: ProblemRule) {
42        if self.pending_section || self.sections.is_empty() {
43            self.sections.push(Vec::new());
44            self.pending_section = false;
45        }
46        let last = self.sections.len() - 1;
47        self.sections[last].push(rule);
48    }
49
50    pub fn add_rule(&mut self, rule: usize) {
51        if !self.seen.insert(rule) {
52            return;
53        }
54        self.push(ProblemRule::InSet(rule));
55    }
56
57    pub fn add_detached(&mut self, rule: Rule) {
58        self.push(ProblemRule::Detached(rule));
59    }
60
61    pub fn next_section(&mut self) {
62        self.pending_section = true;
63    }
64}
65
66#[derive(Debug, thiserror::Error)]
67pub enum SolveError {
68    #[error("{0}")]
69    Bug(String),
70    #[error("Your requirements could not be resolved to an installable set of packages.")]
71    Problems(Vec<Problem>),
72}
73
74impl From<SolverBug> for SolveError {
75    fn from(e: SolverBug) -> SolveError {
76        SolveError::Bug(e.0)
77    }
78}
79
80pub struct Solver<'a> {
81    pool: &'a Pool,
82    arena: &'a [Package],
83    pub rules: RuleSet,
84    watch_graph: RuleWatchGraph,
85    pub decisions: Decisions,
86    fixed_map: HashSet<usize>,
87    propagate_index: usize,
88    /// `(literals, level)`.
89    branches: Vec<(Vec<i64>, i64)>,
90    pub problems: Vec<Problem>,
91    /// `learnedPool[why]`: rules that led to a learned rule.
92    learned_pool: Vec<Vec<usize>>,
93    /// learned rule -> `why`.
94    learned_why: HashMap<usize, usize>,
95}
96
97impl<'a> Solver<'a> {
98    pub fn new(pool: &'a Pool, arena: &'a [Package]) -> Solver<'a> {
99        Solver {
100            pool,
101            arena,
102            rules: RuleSet::new(),
103            watch_graph: RuleWatchGraph::new(),
104            decisions: Decisions::new(pool.len()),
105            fixed_map: HashSet::new(),
106            propagate_index: 0,
107            branches: Vec::new(),
108            problems: Vec::new(),
109            learned_pool: Vec::new(),
110            learned_why: HashMap::new(),
111        }
112    }
113
114    pub fn rule_set_size(&self) -> usize {
115        self.rules.len()
116    }
117
118    /// `makeAssertionRuleDecisions`.
119    fn make_assertion_rule_decisions(&mut self) -> Result<(), SolveError> {
120        let decision_start = self.decisions.len() as i64 - 1;
121        let rules_count = self.rules.len();
122        let mut rule_index: i64 = 0;
123        while (rule_index as usize) < rules_count {
124            let id = self.rules.rule_by_id[rule_index as usize];
125            rule_index += 1;
126            let rule = &self.rules.rules[id];
127            if !rule.is_assertion() || rule.disabled {
128                continue;
129            }
130            let literal = rule.literals[0];
131            if !self.decisions.decided(literal) {
132                self.decisions.decide(literal, 1, id)?;
133                continue;
134            }
135            if self.decisions.satisfy(literal) {
136                continue;
137            }
138            if rule.rule_type == Some(RuleType::Learned) {
139                self.rules.rules[id].disabled = true;
140                continue;
141            }
142            let conflict = self.decisions.decision_rule(literal)?;
143            if self.rules.rules[conflict].rule_type == Some(RuleType::Package) {
144                let mut problem = Problem::new();
145                problem.add_rule(id);
146                problem.add_rule(conflict);
147                self.rules.rules[id].disabled = true;
148                self.problems.push(problem);
149                continue;
150            }
151            let mut problem = Problem::new();
152            problem.add_rule(id);
153            problem.add_rule(conflict);
154            for &assert_id in self.rules.ids_of_type(RuleType::Request).to_vec().iter() {
155                let assert_rule = &self.rules.rules[assert_id];
156                if assert_rule.disabled || !assert_rule.is_assertion() {
157                    continue;
158                }
159                if literal.abs() != assert_rule.literals[0].abs() {
160                    continue;
161                }
162                problem.add_rule(assert_id);
163                self.rules.rules[assert_id].disabled = true;
164            }
165            self.problems.push(problem);
166            self.decisions.reset_to_offset(decision_start);
167            rule_index = 0;
168        }
169        Ok(())
170    }
171
172    /// `checkForFilterListRemovedLockedPackages`: a locked package whose
173    /// version was removed by a filter list.
174    fn check_for_filter_list_removed_locked_packages(&mut self, request: &Request) {
175        for idx in request.locked_packages_all() {
176            let p = &self.arena[idx];
177            if !self.pool.is_filter_list_removed(&p.name, &p.version) {
178                continue;
179            }
180            let mut problem = Problem::new();
181            problem.add_detached(Rule::generic(
182                Vec::new(),
183                Reason::LockedFilterListRemoved { package: idx },
184            ));
185            self.problems.push(problem);
186        }
187    }
188
189    /// `checkForRootRequireProblems`.
190    fn check_for_root_require_problems(
191        &mut self,
192        request: &Request,
193        filter: &PlatformRequirementFilter,
194    ) {
195        for (name, constraint) in request.requires.iter() {
196            if filter.is_ignored(name) {
197                continue;
198            }
199            let constraint = filter.filter_constraint(name, constraint, true);
200            if self
201                .pool
202                .what_provides(self.arena, name, Some(&constraint))
203                .is_empty()
204            {
205                let mut problem = Problem::new();
206                problem.add_detached(Rule::generic(
207                    Vec::new(),
208                    Reason::RootRequire {
209                        package_name: name.clone(),
210                        constraint,
211                    },
212                ));
213                self.problems.push(problem);
214            }
215        }
216    }
217
218    /// `solve`.
219    pub fn solve(
220        &mut self,
221        request: &Request,
222        policy: &mut DefaultPolicy,
223        filter: &PlatformRequirementFilter,
224    ) -> Result<LockTransaction, SolveError> {
225        self.fixed_map = request
226            .fixed_packages
227            .iter()
228            .filter_map(|&idx| self.pool.id_of(idx))
229            .collect();
230        let generator = RuleSetGenerator::new(self.pool, self.arena);
231        self.rules = generator
232            .rules_for(request, filter)
233            .map_err(|e| SolveError::Bug(e.0))?;
234        self.check_for_root_require_problems(request, filter);
235        self.check_for_filter_list_removed_locked_packages(request);
236        self.decisions = Decisions::new(self.pool.len());
237        self.watch_graph = RuleWatchGraph::new();
238        for id in self.rules.ids_in_iterator_order() {
239            let node = RuleWatchNode::new(&self.rules, id);
240            self.watch_graph.insert(&self.rules, node);
241        }
242        self.make_assertion_rule_decisions()?;
243        self.run_sat(policy)?;
244        if !self.problems.is_empty() {
245            return Err(SolveError::Problems(std::mem::take(&mut self.problems)));
246        }
247        Ok(LockTransaction::new(
248            self.pool,
249            self.arena,
250            request,
251            &self.decisions,
252        ))
253    }
254
255    /// `propagate`.
256    fn propagate(&mut self, level: i64) -> Result<Option<usize>, SolveError> {
257        while self.decisions.valid_offset(self.propagate_index) {
258            let decision = self.decisions.at_offset(self.propagate_index);
259            let conflict = self.watch_graph.propagate_literal(
260                &self.rules,
261                decision.literal,
262                level,
263                &mut self.decisions,
264            )?;
265            self.propagate_index += 1;
266            if conflict.is_some() {
267                return Ok(conflict);
268            }
269        }
270        Ok(None)
271    }
272
273    /// `revert`.
274    fn revert(&mut self, level: i64) {
275        while !self.decisions.is_empty() {
276            let literal = self.decisions.last_literal();
277            if self.decisions.undecided(literal) {
278                break;
279            }
280            let decision_level = self.decisions.decision_level(literal);
281            if decision_level <= level {
282                break;
283            }
284            self.decisions.revert_last();
285            self.propagate_index = self.decisions.len();
286        }
287        while self.branches.last().is_some_and(|(_, l)| *l >= level) {
288            self.branches.pop();
289        }
290    }
291
292    /// `setPropagateLearn`.
293    fn set_propagate_learn(
294        &mut self,
295        mut level: i64,
296        literal: i64,
297        rule: usize,
298    ) -> Result<i64, SolveError> {
299        level += 1;
300        self.decisions.decide(literal, level, rule)?;
301        loop {
302            let Some(conflict) = self.propagate(level)? else {
303                break;
304            };
305            if level == 1 {
306                self.analyze_unsolvable(conflict);
307                return Ok(0);
308            }
309            let (learn_literal, new_level, new_rule, why) = self.analyze(level, conflict)?;
310            if new_level <= 0 || new_level >= level {
311                return Err(SolveError::Bug(format!(
312                    "Trying to revert to invalid level {new_level} from level {level}."
313                )));
314            }
315            level = new_level;
316            self.revert(level);
317            // A duplicate does not enter the RuleSet but still serves as a
318            // watch node and as a reason (`add` returns without doing
319            // anything, the rest of the PHP code carries on with the object).
320            let (new_id, _) = self.rules.add(new_rule, RuleType::Learned);
321            self.learned_why.insert(new_id, why);
322            let mut node = RuleWatchNode::new(&self.rules, new_id);
323            node.watch2_on_highest(&self.rules, &self.decisions);
324            self.watch_graph.insert(&self.rules, node);
325            self.decisions.decide(learn_literal, level, new_id)?;
326        }
327        Ok(level)
328    }
329
330    /// `selectAndInstall`.
331    fn select_and_install(
332        &mut self,
333        level: i64,
334        decision_queue: &[i64],
335        rule: usize,
336        policy: &mut DefaultPolicy,
337    ) -> Result<i64, SolveError> {
338        let required = self.rules.rules[rule]
339            .required_package(self.arena)
340            .map(str::to_owned);
341        let mut literals = policy.select_preferred_packages(
342            self.pool,
343            self.arena,
344            decision_queue,
345            required.as_deref(),
346        );
347        let selected = literals.remove(0);
348        if !literals.is_empty() {
349            self.branches.push((literals, level));
350        }
351        self.set_propagate_learn(level, selected, rule)
352    }
353
354    /// `analyze`: (learned literal, level, new rule, why).
355    fn analyze(&mut self, level: i64, rule: usize) -> Result<(i64, i64, Rule, usize), SolveError> {
356        let analyzed_rule = rule;
357        let mut rule = rule;
358        let mut rule_level: i64 = 1;
359        let mut num = 0;
360        let mut l1num = 0;
361        let mut seen: HashSet<i64> = HashSet::new();
362        let mut learned_literal: Option<i64> = None;
363        let mut other_learned_literals: Vec<i64> = Vec::new();
364        let mut decision_id = self.decisions.len() as i64;
365        self.learned_pool.push(Vec::new());
366        let why = self.learned_pool.len() - 1;
367        'outer: loop {
368            self.learned_pool[why].push(rule);
369            let is_multi = self.rules.rules[rule].kind == RuleKind::MultiConflict;
370            for &literal in &self.rules.rules[rule].literals {
371                if is_multi && !self.decisions.decided(literal) {
372                    continue;
373                }
374                if self.decisions.satisfy(literal) {
375                    continue;
376                }
377                if seen.contains(&literal.abs()) {
378                    continue;
379                }
380                seen.insert(literal.abs());
381                let l = self.decisions.decision_level(literal);
382                if l == 1 {
383                    l1num += 1;
384                } else if level == l {
385                    num += 1;
386                } else {
387                    other_learned_literals.push(literal);
388                    if l > rule_level {
389                        rule_level = l;
390                    }
391                }
392            }
393            let mut l1retry = true;
394            let mut literal: i64;
395            while l1retry {
396                l1retry = false;
397                if num == 0 {
398                    l1num -= 1;
399                    if l1num == 0 {
400                        break 'outer;
401                    }
402                }
403                loop {
404                    if decision_id <= 0 {
405                        return Err(SolveError::Bug(format!(
406                            "Reached invalid decision id {decision_id} while looking through rule {rule} for a literal present in the analyzed rule {analyzed_rule}."
407                        )));
408                    }
409                    decision_id -= 1;
410                    let decision = self.decisions.at_offset(decision_id as usize);
411                    literal = decision.literal;
412                    if seen.contains(&literal.abs()) {
413                        break;
414                    }
415                }
416                seen.remove(&literal.abs());
417                if num != 0 {
418                    num -= 1;
419                    if num == 0 {
420                        learned_literal = Some(-literal);
421                        if l1num == 0 {
422                            break 'outer;
423                        }
424                        for other in &other_learned_literals {
425                            seen.remove(&other.abs());
426                        }
427                        l1num += 1;
428                        l1retry = true;
429                        continue;
430                    }
431                }
432                // `else` of `if (0 !== $num && 0 === --$num)`: reached when
433                // num was 0, or when the decrement did not bring it to 0.
434                let decision = self.decisions.at_offset(decision_id as usize);
435                rule = decision.reason;
436                if self.rules.rules[rule].kind == RuleKind::MultiConflict {
437                    let literals = self.rules.rules[rule].literals.clone();
438                    for rule_literal in literals {
439                        if !seen.contains(&rule_literal.abs())
440                            && self.decisions.satisfy(-rule_literal)
441                        {
442                            self.learned_pool[why].push(rule);
443                            let l = self.decisions.decision_level(rule_literal);
444                            if l == 1 {
445                                l1num += 1;
446                            } else if level == l {
447                                num += 1;
448                            } else {
449                                other_learned_literals.push(rule_literal);
450                                if l > rule_level {
451                                    rule_level = l;
452                                }
453                            }
454                            seen.insert(rule_literal.abs());
455                            break;
456                        }
457                    }
458                    l1retry = true;
459                }
460            }
461            let decision = self.decisions.at_offset(decision_id as usize);
462            rule = decision.reason;
463        }
464        let Some(learned_literal) = learned_literal else {
465            return Err(SolveError::Bug(format!(
466                "Did not find a learnable literal in analyzed rule {analyzed_rule}."
467            )));
468        };
469        other_learned_literals.insert(0, learned_literal);
470        let new_rule = Rule::generic(other_learned_literals, Reason::Learned(why));
471        Ok((learned_literal, rule_level, new_rule, why))
472    }
473
474    /// `analyzeUnsolvableRule`.
475    fn analyze_unsolvable_rule(
476        &self,
477        problem: &mut Problem,
478        conflict_rule: usize,
479        rule_seen: &mut HashSet<usize>,
480    ) {
481        rule_seen.insert(conflict_rule);
482        let rule = &self.rules.rules[conflict_rule];
483        if rule.rule_type == Some(RuleType::Learned) {
484            let learned_why = self.learned_why[&conflict_rule];
485            let problem_rules = self.learned_pool[learned_why].clone();
486            for problem_rule in problem_rules {
487                if !rule_seen.contains(&problem_rule) {
488                    self.analyze_unsolvable_rule(problem, problem_rule, rule_seen);
489                }
490            }
491            return;
492        }
493        if rule.rule_type == Some(RuleType::Package) {
494            return;
495        }
496        problem.next_section();
497        problem.add_rule(conflict_rule);
498    }
499
500    /// `analyzeUnsolvable`.
501    fn analyze_unsolvable(&mut self, conflict_rule: usize) {
502        let mut problem = Problem::new();
503        problem.add_rule(conflict_rule);
504        let mut rule_seen: HashSet<usize> = HashSet::new();
505        self.analyze_unsolvable_rule(&mut problem, conflict_rule, &mut rule_seen);
506        let mut seen: HashSet<i64> = HashSet::new();
507        for &literal in &self.rules.rules[conflict_rule].literals {
508            if self.decisions.satisfy(literal) {
509                continue;
510            }
511            seen.insert(literal.abs());
512        }
513        // `foreach ($this->decisions ...)`: from last to first.
514        for i in (0..self.decisions.len()).rev() {
515            let decision = self.decisions.at_offset(i);
516            if !seen.contains(&decision.literal.abs()) {
517                continue;
518            }
519            let why = decision.reason;
520            problem.add_rule(why);
521            self.analyze_unsolvable_rule(&mut problem, why, &mut rule_seen);
522            for &literal in &self.rules.rules[why].literals {
523                if self.decisions.satisfy(literal) {
524                    continue;
525                }
526                seen.insert(literal.abs());
527            }
528        }
529        self.problems.push(problem);
530    }
531
532    /// `runSat`.
533    fn run_sat(&mut self, policy: &mut DefaultPolicy) -> Result<(), SolveError> {
534        self.propagate_index = 0;
535        let mut level: i64 = 1;
536        let mut system_level = level + 1;
537        loop {
538            if level == 1 {
539                if let Some(conflict) = self.propagate(level)? {
540                    self.analyze_unsolvable(conflict);
541                    return Ok(());
542                }
543            }
544            if level < system_level {
545                // `foreach ($iterator as $rule)` over the REQUEST rules, then
546                // `$iterator->next(); if valid -> continue`: after a `break`
547                // (backtrack) anywhere but at the last rule, we start over
548                // from the beginning.
549                let request_rules: Vec<usize> = self.rules.ids_of_type(RuleType::Request).to_vec();
550                let mut position = 0;
551                let mut broke = false;
552                while position < request_rules.len() {
553                    let rule_id = request_rules[position];
554                    if self.rules.rules[rule_id].is_enabled() {
555                        let mut decision_queue: Vec<i64> = Vec::new();
556                        let mut none_satisfied = true;
557                        for &literal in &self.rules.rules[rule_id].literals {
558                            if self.decisions.satisfy(literal) {
559                                none_satisfied = false;
560                                break;
561                            }
562                            if literal > 0 && self.decisions.undecided(literal) {
563                                decision_queue.push(literal);
564                            }
565                        }
566                        if none_satisfied && !decision_queue.is_empty() {
567                            let pruned: Vec<i64> = decision_queue
568                                .iter()
569                                .copied()
570                                .filter(|l| self.fixed_map.contains(&(l.unsigned_abs() as usize)))
571                                .collect();
572                            if !pruned.is_empty() {
573                                decision_queue = pruned;
574                            }
575                        }
576                        if none_satisfied && !decision_queue.is_empty() {
577                            let o_level = level;
578                            level =
579                                self.select_and_install(level, &decision_queue, rule_id, policy)?;
580                            if level == 0 {
581                                return Ok(());
582                            }
583                            if level <= o_level {
584                                broke = true;
585                                break;
586                            }
587                        }
588                    }
589                    position += 1;
590                }
591                system_level = level + 1;
592                if broke {
593                    position += 1;
594                    if position < request_rules.len() {
595                        continue;
596                    }
597                }
598            }
599            if level < system_level {
600                system_level = level;
601            }
602            let mut rules_count = self.rules.len();
603            let mut i = 0;
604            let mut n = 0;
605            while n < rules_count {
606                if i == rules_count {
607                    i = 0;
608                }
609                let rule_id = self.rules.rule_by_id[i];
610                i += 1;
611                n += 1;
612                let rule = &self.rules.rules[rule_id];
613                if rule.disabled {
614                    continue;
615                }
616                let mut decision_queue: Vec<i64> = Vec::new();
617                let mut skip = false;
618                for &literal in &rule.literals {
619                    if literal <= 0 {
620                        if !self.decisions.decided_install(literal) {
621                            skip = true;
622                            break;
623                        }
624                    } else {
625                        if self.decisions.decided_install(literal) {
626                            skip = true;
627                            break;
628                        }
629                        if self.decisions.undecided(literal) {
630                            decision_queue.push(literal);
631                        }
632                    }
633                }
634                if skip || decision_queue.len() < 2 {
635                    continue;
636                }
637                level = self.select_and_install(level, &decision_queue, rule_id, policy)?;
638                if level == 0 {
639                    return Ok(());
640                }
641                rules_count = self.rules.len();
642                n = 0;
643            }
644            if level < system_level {
645                continue;
646            }
647            if !self.branches.is_empty() {
648                let mut last_literal: Option<i64> = None;
649                let mut last_level: i64 = 0;
650                let mut last_branch_index = 0;
651                let mut last_branch_offset = 0;
652                for bi in (0..self.branches.len()).rev() {
653                    let (literals, l) = &self.branches[bi];
654                    for (offset, &literal) in literals.iter().enumerate() {
655                        if literal > 0 && self.decisions.decision_level(literal) > l + 1 {
656                            last_literal = Some(literal);
657                            last_branch_index = bi;
658                            last_branch_offset = offset;
659                            last_level = *l;
660                        }
661                    }
662                }
663                if let Some(last_literal) = last_literal {
664                    self.branches[last_branch_index]
665                        .0
666                        .remove(last_branch_offset);
667                    level = last_level;
668                    self.revert(level);
669                    let why = self.decisions.last_reason();
670                    level = self.set_propagate_learn(level, last_literal, why)?;
671                    if level == 0 {
672                        return Ok(());
673                    }
674                    continue;
675                }
676            }
677            break;
678        }
679        Ok(())
680    }
681}