1- use std:: collections:: { HashMap , HashSet } ;
1+ use std:: collections:: { BTreeMap , HashMap , HashSet } ;
22
33use super :: types:: ConflictReason ;
44use core:: resolver:: Context ;
55use core:: { Dependency , PackageId } ;
66
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+
789pub ( super ) struct ConflictCache {
890 // `con_from_dep` is a cache of the reasons for each time we
991 // backtrack. For example after several backtracks we may have:
1092 //
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+ // } ;
1597 //
1698 // This can be read as "we cannot find a candidate for dep `foo = "^1.0.2"`
1799 // if either `foo=1.0.1` OR `foo=1.0.0` are activated".
18100 //
19101 // Another example after several backtracks we may have:
20102 //
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+ // };
24108 //
25109 // This can be read as "we cannot find a candidate for dep `foo = ">=0.8.2,
26110 // <=0.9.3"` if both `foo=0.8.1` AND `foo=0.9.4` are activated".
27111 //
28112 // This is used to make sure we don't queue work we know will fail. See the
29113 // 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.
32117 //
33118 // Also, as a final note, this map is *not* ever removed from. This remains
34119 // as a global cache which we never delete from. Any entry in this map is
35120 // unconditionally true regardless of our resolution history of how we got
36121 // here.
37- con_from_dep : HashMap < Dependency , Vec < HashMap < PackageId , ConflictReason > > > ,
122+ con_from_dep : HashMap < Dependency , ConflictStoreTrie > ,
38123 // `dep_from_pid` is an inverse-index of `con_from_dep`.
39124 // For every `PackageId` this lists the `Dependency`s that mention it in `dep_from_pid`.
40125 dep_from_pid : HashMap < PackageId , HashSet < Dependency > > ,
@@ -54,47 +139,41 @@ impl ConflictCache {
54139 cx : & Context ,
55140 dep : & Dependency ,
56141 filter : F ,
57- ) -> Option < & HashMap < PackageId , ConflictReason > >
142+ ) -> Option < & BTreeMap < PackageId , ConflictReason > >
58143 where
59- for < ' r > F : FnMut ( & ' r & HashMap < PackageId , ConflictReason > ) -> bool ,
144+ for < ' r > F : Fn ( & ' r & BTreeMap < PackageId , ConflictReason > ) -> bool ,
60145 {
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)
67147 }
68148 pub fn conflicting (
69149 & self ,
70150 cx : & Context ,
71151 dep : & Dependency ,
72- ) -> Option < & HashMap < PackageId , ConflictReason > > {
152+ ) -> Option < & BTreeMap < PackageId , ConflictReason > > {
73153 self . find_conflicting ( cx, dep, |_| true )
74154 }
75155
76156 /// Add to the cache a conflict of the form:
77157 /// `dep` is known to be unresolvable if
78158 /// 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
82161 . 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 ( ) ) ;
98177 }
99178 }
100179 pub fn dependencies_conflicting_with ( & self , pid : & PackageId ) -> Option < & HashSet < Dependency > > {
0 commit comments