Skip to main content

vivacity_resolver/
rule.rs

1//! Port of `Rule`, `GenericRule`, `Rule2Literals`, `MultiConflictRule` and
2//! `RuleSet` (docs/reference/resolver/). Rules live in the `RuleSet` and
3//! are referred to by their `ruleById`; literals are signed pool ids.
4
5use crate::constraint::Constraint;
6use crate::package::Link;
7use std::collections::HashMap;
8
9/// `Rule::RULE_*`.
10#[derive(Debug, Clone)]
11pub enum Reason {
12    /// `RULE_ROOT_REQUIRE`: `['packageName' => ..., 'constraint' => ...]`.
13    RootRequire {
14        package_name: String,
15        constraint: Constraint,
16    },
17    /// `RULE_FIXED`: `['package' => ...]` (arena index).
18    Fixed { package: usize },
19    /// `RULE_PACKAGE_CONFLICT`: the link.
20    PackageConflict(Link),
21    /// `RULE_PACKAGE_REQUIRES`: the link.
22    PackageRequires(Link),
23    /// `RULE_PACKAGE_SAME_NAME`: the replaced name.
24    PackageSameName(String),
25    /// `RULE_LEARNED`: index into `learnedPool`.
26    Learned(usize),
27    /// `RULE_PACKAGE_ALIAS`: the alias (arena index).
28    PackageAlias { alias: usize },
29    /// `RULE_PACKAGE_INVERSE_ALIAS`: the aliased package (arena index).
30    PackageInverseAlias { package: usize },
31    /// `RULE_LOCKED_FILTER_LIST_REMOVED`.
32    LockedFilterListRemoved { package: usize },
33}
34
35impl Reason {
36    pub fn code(&self) -> u8 {
37        match self {
38            Reason::RootRequire { .. } => 2,
39            Reason::Fixed { .. } => 3,
40            Reason::PackageConflict(_) => 6,
41            Reason::PackageRequires(_) => 7,
42            Reason::PackageSameName(_) => 10,
43            Reason::Learned(_) => 12,
44            Reason::PackageAlias { .. } => 13,
45            Reason::PackageInverseAlias { .. } => 14,
46            Reason::LockedFilterListRemoved { .. } => 15,
47        }
48    }
49}
50
51/// PHP class of the rule: determines the hash (hence deduplication) and
52/// the shape of the watches.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum RuleKind {
55    Generic,
56    TwoLiterals,
57    MultiConflict,
58}
59
60/// `RuleSet::TYPE_*`.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
62pub enum RuleType {
63    Package = 0,
64    Request = 1,
65    Learned = 4,
66}
67
68#[derive(Debug, Clone)]
69pub struct Rule {
70    pub kind: RuleKind,
71    /// Sorted ascending (`sort($literals)`), except Rule2Literals: [min, max],
72    /// which is the same thing.
73    pub literals: Vec<i64>,
74    pub reason: Reason,
75    /// None = 255: never added to the RuleSet (duplicate learned rule, still
76    /// used as a reason and as a watch node).
77    pub rule_type: Option<RuleType>,
78    pub disabled: bool,
79}
80
81impl Rule {
82    /// `new GenericRule($literals, ...)`.
83    pub fn generic(mut literals: Vec<i64>, reason: Reason) -> Rule {
84        literals.sort_unstable();
85        Rule {
86            kind: RuleKind::Generic,
87            literals,
88            reason,
89            rule_type: None,
90            disabled: false,
91        }
92    }
93
94    /// `new Rule2Literals($l1, $l2, ...)`.
95    pub fn two_literals(l1: i64, l2: i64, reason: Reason) -> Rule {
96        Rule {
97            kind: RuleKind::TwoLiterals,
98            literals: if l1 < l2 { vec![l1, l2] } else { vec![l2, l1] },
99            reason,
100            rule_type: None,
101            disabled: false,
102        }
103    }
104
105    /// `new MultiConflictRule($literals, ...)` (at least 3 literals).
106    pub fn multi_conflict(mut literals: Vec<i64>, reason: Reason) -> Rule {
107        literals.sort_unstable();
108        Rule {
109            kind: RuleKind::MultiConflict,
110            literals,
111            reason,
112            rule_type: None,
113            disabled: false,
114        }
115    }
116
117    pub fn is_assertion(&self) -> bool {
118        self.kind == RuleKind::Generic && self.literals.len() == 1
119    }
120
121    pub fn is_enabled(&self) -> bool {
122        !self.disabled
123    }
124
125    /// `getRequiredPackage`.
126    pub fn required_package<'a>(&'a self, arena: &'a [crate::package::Package]) -> Option<&'a str> {
127        match &self.reason {
128            Reason::RootRequire { package_name, .. } => Some(package_name),
129            Reason::Fixed { package } | Reason::LockedFilterListRemoved { package } => {
130                Some(&arena[*package].name)
131            }
132            Reason::PackageRequires(link) => Some(&link.target),
133            _ => None,
134        }
135    }
136}
137
138/// Deduplication key: the PHP hash (xxh3 of the literals for GenericRule,
139/// `"l1,l2"` for Rule2Literals, xxh3 prefixed with `c:` for
140/// MultiConflictRule) followed by `equals`; equivalent to (class,
141/// literals), up to 32-bit collisions across classes.
142#[derive(Debug, Clone, PartialEq, Eq, Hash)]
143struct RuleKey {
144    kind: RuleKind,
145    literals: Vec<i64>,
146}
147
148/// `Composer\DependencyResolver\RuleSet`. `rules` is the storage of all
149/// rules (including those rejected by `add` as duplicates, which the solver
150/// keeps using); `rule_by_id` is `ruleById`.
151#[derive(Debug, Default)]
152pub struct RuleSet {
153    pub rules: Vec<Rule>,
154    /// `ruleById`: registered rules, in insertion order.
155    pub rule_by_id: Vec<usize>,
156    /// `rules[$type]`: rules by type, in insertion order.
157    by_type: [Vec<usize>; 3],
158    keys: HashMap<RuleKey, usize>,
159}
160
161impl RuleSet {
162    pub fn new() -> RuleSet {
163        RuleSet::default()
164    }
165
166    fn slot(rule_type: RuleType) -> usize {
167        match rule_type {
168            RuleType::Package => 0,
169            RuleType::Request => 1,
170            RuleType::Learned => 2,
171        }
172    }
173
174    /// `add`: the rule is stored; it is registered (type set, `ruleById`)
175    /// only if no identical rule exists. Returns (storage index,
176    /// registered?).
177    pub fn add(&mut self, mut rule: Rule, rule_type: RuleType) -> (usize, bool) {
178        let key = RuleKey {
179            kind: rule.kind,
180            literals: rule.literals.clone(),
181        };
182        let id = self.rules.len();
183        if self.keys.contains_key(&key) {
184            self.rules.push(rule);
185            return (id, false);
186        }
187        rule.rule_type = Some(rule_type);
188        self.rules.push(rule);
189        self.rule_by_id.push(id);
190        self.by_type[Self::slot(rule_type)].push(id);
191        self.keys.insert(key, id);
192        (id, true)
193    }
194
195    /// `count()`: registered rules.
196    pub fn len(&self) -> usize {
197        self.rule_by_id.len()
198    }
199
200    pub fn is_empty(&self) -> bool {
201        self.rule_by_id.is_empty()
202    }
203
204    /// `getIteratorFor($type)`: ids of one type, in order.
205    pub fn ids_of_type(&self, rule_type: RuleType) -> &[usize] {
206        &self.by_type[Self::slot(rule_type)]
207    }
208
209    /// `getIterator()`: PACKAGE then REQUEST then LEARNED (sorted types).
210    pub fn ids_in_iterator_order(&self) -> Vec<usize> {
211        let mut out = Vec::with_capacity(self.rules.len());
212        for slot in &self.by_type {
213            out.extend(slot.iter().copied());
214        }
215        out
216    }
217}