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