@@ -9,6 +9,7 @@ use crate::{
9
9
} ;
10
10
11
11
use crate :: internal:: id:: StringId ;
12
+ use crate :: solver:: decision_tracker:: DecisionTracker ;
12
13
use elsa:: FrozenMap ;
13
14
use std:: fmt:: { Debug , Display , Formatter } ;
14
15
use std:: hash:: Hash ;
@@ -83,31 +84,74 @@ pub(crate) enum Clause {
83
84
}
84
85
85
86
impl Clause {
86
- /// Returns the ids of the solvables that will be watched as well as the clause itself.
87
+ /// Returns the building blocks needed for a new [ClauseState] of the [Clause::Requires] kind.
88
+ ///
89
+ /// These building blocks are:
90
+ ///
91
+ /// - The [Clause] itself;
92
+ /// - The ids of the solvables that will be watched (unless there are no candidates, i.e. the
93
+ /// clause is actually an assertion);
94
+ /// - A boolean indicating whether the clause conflicts with existing decisions. This should
95
+ /// always be false when adding clauses before starting the solving process, but can be true
96
+ /// for clauses that are added dynamically.
87
97
fn requires (
88
- candidate : SolvableId ,
98
+ parent : SolvableId ,
89
99
requirement : VersionSetId ,
90
100
candidates : & [ SolvableId ] ,
91
- ) -> ( Self , Option < [ SolvableId ; 2 ] > ) {
92
- (
93
- Clause :: Requires ( candidate, requirement) ,
94
- if candidates. is_empty ( ) {
95
- None
96
- } else {
97
- Some ( [ candidate, candidates[ 0 ] ] )
98
- } ,
99
- )
101
+ decision_tracker : & DecisionTracker ,
102
+ ) -> ( Self , Option < [ SolvableId ; 2 ] > , bool ) {
103
+ // It only makes sense to introduce a requires clause when the parent solvable is undecided
104
+ // or going to be installed
105
+ assert_ne ! ( decision_tracker. assigned_value( parent) , Some ( false ) ) ;
106
+
107
+ let kind = Clause :: Requires ( parent, requirement) ;
108
+ if candidates. is_empty ( ) {
109
+ ( kind, None , false )
110
+ } else {
111
+ match candidates
112
+ . iter ( )
113
+ . find ( |& & c| decision_tracker. assigned_value ( c) != Some ( false ) )
114
+ {
115
+ // Watch any candidate that is not assigned to false
116
+ Some ( watched_candidate) => ( kind, Some ( [ parent, * watched_candidate] ) , false ) ,
117
+
118
+ // All candidates are assigned to false! Therefore the clause conflicts with the
119
+ // current decisions. There are no valid watches for it at the moment, but we will
120
+ // assign default ones nevertheless, because they will become valid after the solver
121
+ // restarts.
122
+ None => ( kind, Some ( [ parent, candidates[ 0 ] ] ) , true ) ,
123
+ }
124
+ }
100
125
}
101
126
102
- /// Returns the ids of the solvables that will be watched as well as the clause itself.
127
+ /// Returns the building blocks needed for a new [ClauseState] of the [Clause::Constrains] kind.
128
+ ///
129
+ /// These building blocks are:
130
+ ///
131
+ /// - The [Clause] itself;
132
+ /// - The ids of the solvables that will be watched;
133
+ /// - A boolean indicating whether the clause conflicts with existing decisions. This should
134
+ /// always be false when adding clauses before starting the solving process, but can be true
135
+ /// for clauses that are added dynamically.
103
136
fn constrains (
104
- candidate : SolvableId ,
105
- constrained_candidate : SolvableId ,
137
+ parent : SolvableId ,
138
+ forbidden_solvable : SolvableId ,
106
139
via : VersionSetId ,
107
- ) -> ( Self , Option < [ SolvableId ; 2 ] > ) {
140
+ decision_tracker : & DecisionTracker ,
141
+ ) -> ( Self , Option < [ SolvableId ; 2 ] > , bool ) {
142
+ // It only makes sense to introduce a constrains clause when the parent solvable is
143
+ // undecided or going to be installed
144
+ assert_ne ! ( decision_tracker. assigned_value( parent) , Some ( false ) ) ;
145
+
146
+ // If the forbidden solvable is already assigned to true, that means that there is a
147
+ // conflict with current decisions, because it implies the parent solvable would be false
148
+ // (and we just asserted that it is not)
149
+ let conflict = decision_tracker. assigned_value ( forbidden_solvable) == Some ( true ) ;
150
+
108
151
(
109
- Clause :: Constrains ( candidate, constrained_candidate, via) ,
110
- Some ( [ candidate, constrained_candidate] ) ,
152
+ Clause :: Constrains ( parent, forbidden_solvable, via) ,
153
+ Some ( [ parent, forbidden_solvable] ) ,
154
+ conflict,
111
155
)
112
156
}
113
157
@@ -243,25 +287,48 @@ impl ClauseState {
243
287
Self :: from_kind_and_initial_watches ( kind, watched_literals)
244
288
}
245
289
246
- /// Shorthand method to construct a Clause::Requires without requiring complicated arguments.
290
+ /// Shorthand method to construct a [Clause::Requires] without requiring complicated arguments.
291
+ ///
292
+ /// The returned boolean value is true when adding the clause resulted in a conflict.
247
293
pub fn requires (
248
294
candidate : SolvableId ,
249
295
requirement : VersionSetId ,
250
296
matching_candidates : & [ SolvableId ] ,
251
- ) -> Self {
252
- let ( kind, watched_literals) =
253
- Clause :: requires ( candidate, requirement, matching_candidates) ;
254
- Self :: from_kind_and_initial_watches ( kind, watched_literals)
297
+ decision_tracker : & DecisionTracker ,
298
+ ) -> ( Self , bool ) {
299
+ let ( kind, watched_literals, conflict) = Clause :: requires (
300
+ candidate,
301
+ requirement,
302
+ matching_candidates,
303
+ decision_tracker,
304
+ ) ;
305
+
306
+ (
307
+ Self :: from_kind_and_initial_watches ( kind, watched_literals) ,
308
+ conflict,
309
+ )
255
310
}
256
311
312
+ /// Shorthand method to construct a [Clause::Constrains] without requiring complicated arguments.
313
+ ///
314
+ /// The returned boolean value is true when adding the clause resulted in a conflict.
257
315
pub fn constrains (
258
316
candidate : SolvableId ,
259
317
constrained_package : SolvableId ,
260
318
requirement : VersionSetId ,
261
- ) -> Self {
262
- let ( kind, watched_literals) =
263
- Clause :: constrains ( candidate, constrained_package, requirement) ;
264
- Self :: from_kind_and_initial_watches ( kind, watched_literals)
319
+ decision_tracker : & DecisionTracker ,
320
+ ) -> ( Self , bool ) {
321
+ let ( kind, watched_literals, conflict) = Clause :: constrains (
322
+ candidate,
323
+ constrained_package,
324
+ requirement,
325
+ decision_tracker,
326
+ ) ;
327
+
328
+ (
329
+ Self :: from_kind_and_initial_watches ( kind, watched_literals) ,
330
+ conflict,
331
+ )
265
332
}
266
333
267
334
pub fn lock ( locked_candidate : SolvableId , other_candidate : SolvableId ) -> Self {
@@ -556,6 +623,7 @@ impl<VS: VersionSet, N: PackageName + Display> Debug for ClauseDebug<'_, VS, N>
556
623
mod test {
557
624
use super :: * ;
558
625
use crate :: internal:: arena:: ArenaId ;
626
+ use crate :: solver:: decision:: Decision ;
559
627
560
628
fn clause ( next_clauses : [ ClauseId ; 2 ] , watched_solvables : [ SolvableId ; 2 ] ) -> ClauseState {
561
629
ClauseState {
@@ -694,6 +762,104 @@ mod test {
694
762
}
695
763
}
696
764
765
+ #[ test]
766
+ fn test_requires_with_and_without_conflict ( ) {
767
+ let mut decisions = DecisionTracker :: new ( ) ;
768
+
769
+ let parent = SolvableId :: from_usize ( 1 ) ;
770
+ let candidate1 = SolvableId :: from_usize ( 2 ) ;
771
+ let candidate2 = SolvableId :: from_usize ( 3 ) ;
772
+
773
+ // No conflict, all candidates available
774
+ let ( clause, conflict) = ClauseState :: requires (
775
+ parent,
776
+ VersionSetId :: from_usize ( 0 ) ,
777
+ & [ candidate1, candidate2] ,
778
+ & decisions,
779
+ ) ;
780
+ assert ! ( !conflict) ;
781
+ assert_eq ! ( clause. watched_literals[ 0 ] , parent) ;
782
+ assert_eq ! ( clause. watched_literals[ 1 ] , candidate1) ;
783
+
784
+ // No conflict, still one candidate available
785
+ decisions
786
+ . try_add_decision ( Decision :: new ( candidate1, false , ClauseId :: null ( ) ) , 1 )
787
+ . unwrap ( ) ;
788
+ let ( clause, conflict) = ClauseState :: requires (
789
+ parent,
790
+ VersionSetId :: from_usize ( 0 ) ,
791
+ & [ candidate1, candidate2] ,
792
+ & decisions,
793
+ ) ;
794
+ assert ! ( !conflict) ;
795
+ assert_eq ! ( clause. watched_literals[ 0 ] , parent) ;
796
+ assert_eq ! ( clause. watched_literals[ 1 ] , candidate2) ;
797
+
798
+ // Conflict, no candidates available
799
+ decisions
800
+ . try_add_decision ( Decision :: new ( candidate2, false , ClauseId :: null ( ) ) , 1 )
801
+ . unwrap ( ) ;
802
+ let ( clause, conflict) = ClauseState :: requires (
803
+ parent,
804
+ VersionSetId :: from_usize ( 0 ) ,
805
+ & [ candidate1, candidate2] ,
806
+ & decisions,
807
+ ) ;
808
+ assert ! ( conflict) ;
809
+ assert_eq ! ( clause. watched_literals[ 0 ] , parent) ;
810
+ assert_eq ! ( clause. watched_literals[ 1 ] , candidate1) ;
811
+
812
+ // Panic
813
+ decisions
814
+ . try_add_decision ( Decision :: new ( parent, false , ClauseId :: null ( ) ) , 1 )
815
+ . unwrap ( ) ;
816
+ let panicked = std:: panic:: catch_unwind ( || {
817
+ ClauseState :: requires (
818
+ parent,
819
+ VersionSetId :: from_usize ( 0 ) ,
820
+ & [ candidate1, candidate2] ,
821
+ & decisions,
822
+ )
823
+ } )
824
+ . is_err ( ) ;
825
+ assert ! ( panicked) ;
826
+ }
827
+
828
+ #[ test]
829
+ fn test_constrains_with_and_without_conflict ( ) {
830
+ let mut decisions = DecisionTracker :: new ( ) ;
831
+
832
+ let parent = SolvableId :: from_usize ( 1 ) ;
833
+ let forbidden = SolvableId :: from_usize ( 2 ) ;
834
+
835
+ // No conflict, forbidden package not installed
836
+ let ( clause, conflict) =
837
+ ClauseState :: constrains ( parent, forbidden, VersionSetId :: from_usize ( 0 ) , & decisions) ;
838
+ assert ! ( !conflict) ;
839
+ assert_eq ! ( clause. watched_literals[ 0 ] , parent) ;
840
+ assert_eq ! ( clause. watched_literals[ 1 ] , forbidden) ;
841
+
842
+ // Conflict, forbidden package installed
843
+ decisions
844
+ . try_add_decision ( Decision :: new ( forbidden, true , ClauseId :: null ( ) ) , 1 )
845
+ . unwrap ( ) ;
846
+ let ( clause, conflict) =
847
+ ClauseState :: constrains ( parent, forbidden, VersionSetId :: from_usize ( 0 ) , & decisions) ;
848
+ assert ! ( conflict) ;
849
+ assert_eq ! ( clause. watched_literals[ 0 ] , parent) ;
850
+ assert_eq ! ( clause. watched_literals[ 1 ] , forbidden) ;
851
+
852
+ // Panic
853
+ decisions
854
+ . try_add_decision ( Decision :: new ( parent, false , ClauseId :: null ( ) ) , 1 )
855
+ . unwrap ( ) ;
856
+ let panicked = std:: panic:: catch_unwind ( || {
857
+ ClauseState :: constrains ( parent, forbidden, VersionSetId :: from_usize ( 0 ) , & decisions)
858
+ } )
859
+ . is_err ( ) ;
860
+ assert ! ( panicked) ;
861
+ }
862
+
697
863
#[ test]
698
864
fn test_clause_size ( ) {
699
865
// This test is here to ensure we don't increase the size of `ClauseState` by accident, as
0 commit comments