1
- use std:: collections:: { HashMap , HashSet } ;
1
+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
2
2
3
3
use super :: types:: ConflictReason ;
4
4
use core:: resolver:: Context ;
5
5
use core:: { Dependency , PackageId } ;
6
6
7
+ /// This is a Trie for storing a large number of Sets designed to
8
+ /// efficiently see if any of the stored Sets are a subset of a search Set.
9
+ enum ConflictStoreTrie {
10
+ /// a Leaf is one of the stored Sets.
11
+ Leaf ( BTreeMap < PackageId , ConflictReason > ) ,
12
+ /// a Node is a map from an element to a subTrie where
13
+ /// all the Sets in the subTrie contains that element.
14
+ Node ( HashMap < PackageId , ConflictStoreTrie > ) ,
15
+ }
16
+
17
+ impl ConflictStoreTrie {
18
+ /// Finds any known set of conflicts, if any,
19
+ /// which are activated in `cx` and pass the `filter` specified?
20
+ fn find_conflicting < F > (
21
+ & self ,
22
+ cx : & Context ,
23
+ filter : & F ,
24
+ ) -> Option < & BTreeMap < PackageId , ConflictReason > >
25
+ where
26
+ for < ' r > F : Fn ( & ' r & BTreeMap < PackageId , ConflictReason > ) -> bool ,
27
+ {
28
+ match self {
29
+ ConflictStoreTrie :: Leaf ( c) => {
30
+ if filter ( & c) {
31
+ // is_conflicting checks that all the elements are active,
32
+ // but we have checked each one by the recursion of this function.
33
+ debug_assert ! ( cx. is_conflicting( None , c) ) ;
34
+ Some ( c)
35
+ } else {
36
+ None
37
+ }
38
+ }
39
+ ConflictStoreTrie :: Node ( m) => {
40
+ for ( pid, store) in m {
41
+ // if the key is active then we need to check all of the corresponding subTrie.
42
+ if cx. is_active ( pid) {
43
+ if let Some ( o) = store. find_conflicting ( cx, filter) {
44
+ return Some ( o) ;
45
+ }
46
+ } // else, if it is not active then there is no way any of the corresponding
47
+ // subTrie will be conflicting.
48
+ }
49
+ None
50
+ }
51
+ }
52
+ }
53
+
54
+ fn insert < ' a > (
55
+ & mut self ,
56
+ mut iter : impl Iterator < Item = & ' a PackageId > ,
57
+ con : BTreeMap < PackageId , ConflictReason > ,
58
+ ) {
59
+ if let Some ( pid) = iter. next ( ) {
60
+ if let ConflictStoreTrie :: Node ( p) = self {
61
+ p. entry ( pid. clone ( ) )
62
+ . or_insert_with ( || ConflictStoreTrie :: Node ( HashMap :: new ( ) ) )
63
+ . insert ( iter, con) ;
64
+ } // else, We already have a subset of this in the ConflictStore
65
+ } else {
66
+ // we are at the end of the set we are adding, there are 3 cases for what to do next:
67
+ // 1. self is a empty dummy Node inserted by `or_insert_with`
68
+ // in witch case we should replace it with `Leaf(con)`.
69
+ // 2. self is a Node because we previously inserted a superset of
70
+ // the thing we are working on (I don't know if this happens in practice)
71
+ // but the subset that we are working on will
72
+ // always match any time the larger set would have
73
+ // in witch case we can replace it with `Leaf(con)`.
74
+ // 3. self is a Leaf that is in the same spot in the structure as
75
+ // the thing we are working on. So it is equivalent.
76
+ // We can replace it with `Leaf(con)`.
77
+ if cfg ! ( debug_assertions) {
78
+ if let ConflictStoreTrie :: Leaf ( c) = self {
79
+ let a: Vec < _ > = con. keys ( ) . collect ( ) ;
80
+ let b: Vec < _ > = c. keys ( ) . collect ( ) ;
81
+ assert_eq ! ( a, b) ;
82
+ }
83
+ }
84
+ * self = ConflictStoreTrie :: Leaf ( con)
85
+ }
86
+ }
87
+ }
88
+
7
89
pub ( super ) struct ConflictCache {
8
90
// `con_from_dep` is a cache of the reasons for each time we
9
91
// backtrack. For example after several backtracks we may have:
10
92
//
11
- // con_from_dep[`foo = "^1.0.2"`] = vec![
12
- // map!{`foo=1.0.1`: Semver},
13
- // map!{`foo=1.0.0`: Semver},
14
- // ] ;
93
+ // con_from_dep[`foo = "^1.0.2"`] = map!{
94
+ // `foo=1.0.1`: map!{`foo=1.0.1`: Semver},
95
+ // `foo=1.0.0`: map!{`foo=1.0.0`: Semver},
96
+ // } ;
15
97
//
16
98
// This can be read as "we cannot find a candidate for dep `foo = "^1.0.2"`
17
99
// if either `foo=1.0.1` OR `foo=1.0.0` are activated".
18
100
//
19
101
// Another example after several backtracks we may have:
20
102
//
21
- // con_from_dep[`foo = ">=0.8.2, <=0.9.3"`] = vec![
22
- // map!{`foo=0.8.1`: Semver, `foo=0.9.4`: Semver},
23
- // ];
103
+ // con_from_dep[`foo = ">=0.8.2, <=0.9.3"`] = map!{
104
+ // `foo=0.8.1`: map!{
105
+ // `foo=0.9.4`: map!{`foo=0.8.1`: Semver, `foo=0.9.4`: Semver},
106
+ // }
107
+ // };
24
108
//
25
109
// This can be read as "we cannot find a candidate for dep `foo = ">=0.8.2,
26
110
// <=0.9.3"` if both `foo=0.8.1` AND `foo=0.9.4` are activated".
27
111
//
28
112
// This is used to make sure we don't queue work we know will fail. See the
29
113
// discussion in https://github.com/rust-lang/cargo/pull/5168 for why this
30
- // is so important, and there can probably be a better data structure here
31
- // but for now this works well enough!
114
+ // is so important. The nested HashMaps act as a kind of btree, that lets us
115
+ // look up which entries are still active without
116
+ // linearly scanning through the full list.
32
117
//
33
118
// Also, as a final note, this map is *not* ever removed from. This remains
34
119
// as a global cache which we never delete from. Any entry in this map is
35
120
// unconditionally true regardless of our resolution history of how we got
36
121
// here.
37
- con_from_dep : HashMap < Dependency , Vec < HashMap < PackageId , ConflictReason > > > ,
122
+ con_from_dep : HashMap < Dependency , ConflictStoreTrie > ,
38
123
// `dep_from_pid` is an inverse-index of `con_from_dep`.
39
124
// For every `PackageId` this lists the `Dependency`s that mention it in `dep_from_pid`.
40
125
dep_from_pid : HashMap < PackageId , HashSet < Dependency > > ,
@@ -54,47 +139,41 @@ impl ConflictCache {
54
139
cx : & Context ,
55
140
dep : & Dependency ,
56
141
filter : F ,
57
- ) -> Option < & HashMap < PackageId , ConflictReason > >
142
+ ) -> Option < & BTreeMap < PackageId , ConflictReason > >
58
143
where
59
- for < ' r > F : FnMut ( & ' r & HashMap < PackageId , ConflictReason > ) -> bool ,
144
+ for < ' r > F : Fn ( & ' r & BTreeMap < PackageId , ConflictReason > ) -> bool ,
60
145
{
61
- self . con_from_dep
62
- . get ( dep) ?
63
- . iter ( )
64
- . rev ( ) // more general cases are normally found letter. So start the search there.
65
- . filter ( filter)
66
- . find ( |conflicting| cx. is_conflicting ( None , conflicting) )
146
+ self . con_from_dep . get ( dep) ?. find_conflicting ( cx, & filter)
67
147
}
68
148
pub fn conflicting (
69
149
& self ,
70
150
cx : & Context ,
71
151
dep : & Dependency ,
72
- ) -> Option < & HashMap < PackageId , ConflictReason > > {
152
+ ) -> Option < & BTreeMap < PackageId , ConflictReason > > {
73
153
self . find_conflicting ( cx, dep, |_| true )
74
154
}
75
155
76
156
/// Add to the cache a conflict of the form:
77
157
/// `dep` is known to be unresolvable if
78
158
/// all the `PackageId` entries are activated
79
- pub fn insert ( & mut self , dep : & Dependency , con : & HashMap < PackageId , ConflictReason > ) {
80
- let past = self
81
- . con_from_dep
159
+ pub fn insert ( & mut self , dep : & Dependency , con : & BTreeMap < PackageId , ConflictReason > ) {
160
+ self . con_from_dep
82
161
. entry ( dep. clone ( ) )
83
- . or_insert_with ( Vec :: new) ;
84
- if !past . contains ( con) {
85
- trace ! (
86
- "{} = \" {} \" adding a skip {:?}" ,
87
- dep . package_name ( ) ,
88
- dep. version_req ( ) ,
89
- con
90
- ) ;
91
- past . push ( con . clone ( ) ) ;
92
- for c in con . keys ( ) {
93
- self . dep_from_pid
94
- . entry ( c . clone ( ) )
95
- . or_insert_with ( HashSet :: new )
96
- . insert ( dep . clone ( ) ) ;
97
- }
162
+ . or_insert_with ( || ConflictStoreTrie :: Node ( HashMap :: new ( ) ) )
163
+ . insert ( con. keys ( ) , con . clone ( ) ) ;
164
+
165
+ trace ! (
166
+ "{} = \" {} \" adding a skip {:?}" ,
167
+ dep. package_name ( ) ,
168
+ dep . version_req ( ) ,
169
+ con
170
+ ) ;
171
+
172
+ for c in con . keys ( ) {
173
+ self . dep_from_pid
174
+ . entry ( c . clone ( ) )
175
+ . or_insert_with ( HashSet :: new )
176
+ . insert ( dep . clone ( ) ) ;
98
177
}
99
178
}
100
179
pub fn dependencies_conflicting_with ( & self , pid : & PackageId ) -> Option < & HashSet < Dependency > > {
0 commit comments