Skip to content

Commit a525ffb

Browse files
committed
chore: use negative assertions instead of fixed assignments
In previous commits we replaced some usages of fixed assignments. The only remaining usage was for excluded packages, but since excluded packages are basically negative assertions, we can reuse the (IMO simpler) negative assertion system. As a bonus, we get a reduction in LOC.
1 parent 6ff4fcf commit a525ffb

File tree

2 files changed

+13
-63
lines changed

2 files changed

+13
-63
lines changed

src/solver/decision_tracker.rs

+4-49
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@ pub(crate) struct DecisionTracker {
1010
map: DecisionMap,
1111
stack: Vec<Decision>,
1212
propagate_index: usize,
13-
14-
// Fixed assignments are decisions that are true regardless of previous decisions. These
15-
// assignments are not cleared after backtracked.
16-
fixed_assignments: Vec<Decision>,
17-
fixed_assignment_index: usize,
1813
}
1914

2015
impl DecisionTracker {
@@ -23,24 +18,13 @@ impl DecisionTracker {
2318
map: DecisionMap::new(),
2419
stack: Vec::new(),
2520
propagate_index: 0,
26-
fixed_assignment_index: 0,
27-
fixed_assignments: Vec::new(),
2821
}
2922
}
3023

3124
pub(crate) fn clear(&mut self) {
3225
self.map = DecisionMap::new();
3326
self.stack = Vec::new();
3427
self.propagate_index = 0;
35-
36-
// The fixed assignment decisions are kept but the propagation index is. This assures that
37-
// during the next propagation all fixed assignment decisions are repropagated.
38-
self.fixed_assignment_index = 0;
39-
40-
// Re-apply all the fixed decisions
41-
for decision in self.fixed_assignments.iter() {
42-
self.map.set(decision.solvable_id, decision.value, 1);
43-
}
4428
}
4529

4630
pub(crate) fn is_empty(&self) -> bool {
@@ -56,10 +40,7 @@ impl DecisionTracker {
5640
}
5741

5842
pub(crate) fn stack(&self) -> impl Iterator<Item = Decision> + DoubleEndedIterator + '_ {
59-
self.fixed_assignments
60-
.iter()
61-
.copied()
62-
.chain(self.stack.iter().copied())
43+
self.stack.iter().copied()
6344
}
6445

6546
pub(crate) fn level(&self, solvable_id: SolvableId) -> u32 {
@@ -92,26 +73,6 @@ impl DecisionTracker {
9273
}
9374
}
9475

95-
/// Attempts to add a fixed assignment decision. A fixed assignment is different from a regular
96-
/// decision in that its value is persistent and cannot be reverted by backtracking. This is
97-
/// useful for assertion clauses.
98-
///
99-
/// Returns true if the solvable was undecided, false if it was already decided to the same
100-
/// value.
101-
///
102-
/// Returns an error if the solvable was decided to a different value (which means there is a conflict)
103-
pub(crate) fn try_add_fixed_assignment(&mut self, decision: Decision) -> Result<bool, ()> {
104-
match self.map.value(decision.solvable_id) {
105-
None => {
106-
self.map.set(decision.solvable_id, decision.value, 1);
107-
self.fixed_assignments.push(decision);
108-
Ok(true)
109-
}
110-
Some(value) if value == decision.value => Ok(false),
111-
_ => Err(()),
112-
}
113-
}
114-
11576
pub(crate) fn undo_until(&mut self, level: u32) {
11677
while let Some(decision) = self.stack.last() {
11778
if self.level(decision.solvable_id) <= level {
@@ -136,14 +97,8 @@ impl DecisionTracker {
13697
///
13798
/// Side-effect: the decision will be marked as propagated
13899
pub(crate) fn next_unpropagated(&mut self) -> Option<Decision> {
139-
if self.fixed_assignment_index < self.fixed_assignments.len() {
140-
let &decision = &self.fixed_assignments[self.fixed_assignment_index];
141-
self.fixed_assignment_index += 1;
142-
Some(decision)
143-
} else {
144-
let &decision = self.stack[self.propagate_index..].iter().next()?;
145-
self.propagate_index += 1;
146-
Some(decision)
147-
}
100+
let &decision = self.stack[self.propagate_index..].iter().next()?;
101+
self.propagate_index += 1;
102+
Some(decision)
148103
}
149104
}

src/solver/mod.rs

+9-14
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,8 @@ impl<VS: VersionSet, N: PackageName + Display, D: DependencyProvider<VS, N>> Sol
293293
.alloc(ClauseState::forbid_multiple(candidate, other_candidate));
294294

295295
let clause = &mut self.clauses[clause_id];
296-
if clause.has_watches() {
297-
self.watches.start_watching(clause, clause_id);
298-
}
296+
debug_assert!(clause.has_watches());
297+
self.watches.start_watching(clause, clause_id);
299298
}
300299
}
301300

@@ -308,9 +307,9 @@ impl<VS: VersionSet, N: PackageName + Display, D: DependencyProvider<VS, N>> Sol
308307
.alloc(ClauseState::lock(locked_solvable_id, other_candidate));
309308

310309
let clause = &mut self.clauses[clause_id];
311-
if clause.has_watches() {
312-
self.watches.start_watching(clause, clause_id);
313-
}
310+
311+
debug_assert!(clause.has_watches());
312+
self.watches.start_watching(clause, clause_id);
314313
}
315314
}
316315
}
@@ -319,15 +318,11 @@ impl<VS: VersionSet, N: PackageName + Display, D: DependencyProvider<VS, N>> Sol
319318
for (solvable, reason) in package_candidates.excluded.iter().copied() {
320319
let clause_id = self.clauses.alloc(ClauseState::exclude(solvable, reason));
321320

322-
// Immediately add the decision to unselect this solvable. This should be fine because
323-
// no other decision should have been made about this solvable in the first place.
324-
let exclude_descision = Decision::new(solvable, false, clause_id);
325-
self.decision_tracker
326-
.try_add_fixed_assignment(exclude_descision)
327-
.expect("already decided about a solvable that wasn't properly added yet.");
321+
// Exclusions are negative assertions, tracked outside of the watcher system
322+
self.negative_assertions.push((solvable, clause_id));
328323

329-
// No need to add watches for this clause because the clause is an assertion on which
330-
// we already decided.
324+
// Conflicts should be impossible here
325+
debug_assert!(self.decision_tracker.assigned_value(solvable) != Some(true));
331326
}
332327

333328
self.clauses_added_for_package.insert(package_name);

0 commit comments

Comments
 (0)