Skip to content

Commit fd27ee7

Browse files
committed
A dep is equivalent to one of the things it can resolve to.
Thus, if all the things it can resolve to have already ben determined to be conflicting, then we can just say that we conflict with the parent.
1 parent 70c59ef commit fd27ee7

File tree

3 files changed

+100
-1
lines changed

3 files changed

+100
-1
lines changed

src/cargo/core/resolver/conflict_cache.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,28 @@ impl ConflictStoreTrie {
102102
*self = ConflictStoreTrie::Leaf(con)
103103
}
104104
}
105+
106+
fn contains(&self, mut iter: impl Iterator<Item = PackageId>, con: &ConflictMap) -> bool {
107+
match (self, iter.next()) {
108+
(ConflictStoreTrie::Leaf(c), None) => {
109+
if cfg!(debug_assertions) {
110+
let a: Vec<_> = con.keys().collect();
111+
let b: Vec<_> = c.keys().collect();
112+
assert_eq!(a, b);
113+
}
114+
true
115+
}
116+
(ConflictStoreTrie::Leaf(_), Some(_)) => false,
117+
(ConflictStoreTrie::Node(_), None) => false,
118+
(ConflictStoreTrie::Node(m), Some(n)) => {
119+
if let Some(next) = m.get(&n) {
120+
next.contains(iter, con)
121+
} else {
122+
false
123+
}
124+
}
125+
}
126+
}
105127
}
106128

107129
pub(super) struct ConflictCache {
@@ -206,6 +228,18 @@ impl ConflictCache {
206228
.insert(dep.clone());
207229
}
208230
}
231+
232+
/// Check if a conflict was previously added of the form:
233+
/// `dep` is known to be unresolvable if
234+
/// all the `PackageId` entries are activated.
235+
pub fn contains(&self, dep: &Dependency, con: &ConflictMap) -> bool {
236+
if let Some(cst) = self.con_from_dep.get(dep) {
237+
cst.contains(con.keys().cloned(), &con)
238+
} else {
239+
false
240+
}
241+
}
242+
209243
pub fn dependencies_conflicting_with(&self, pid: PackageId) -> Option<&HashSet<Dependency>> {
210244
self.dep_from_pid.get(&pid)
211245
}

src/cargo/core/resolver/mod.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,15 @@ fn activate_deps_loop(
299299
// not be right, so we can't push into our global cache.
300300
if !just_here_for_the_error_messages && !backtracked {
301301
past_conflicting_activations.insert(&dep, &conflicting_activations);
302+
if let Some(c) = generalize_conflicting(
303+
&cx,
304+
registry,
305+
&mut past_conflicting_activations,
306+
&dep,
307+
&conflicting_activations,
308+
) {
309+
conflicting_activations = c;
310+
}
302311
}
303312

304313
match find_candidate(
@@ -842,6 +851,62 @@ impl RemainingCandidates {
842851
}
843852
}
844853

854+
/// Attempts to find a new conflict that allows a bigger backjump then the input one.
855+
/// It will add the new conflict to the cache if one is found.
856+
///
857+
/// Panics if the input conflict is not all active in `cx`.
858+
fn generalize_conflicting(
859+
cx: &Context,
860+
registry: &mut RegistryQueryer<'_>,
861+
past_conflicting_activations: &mut conflict_cache::ConflictCache,
862+
dep: &Dependency,
863+
conflicting_activations: &ConflictMap,
864+
) -> Option<ConflictMap> {
865+
if conflicting_activations.is_empty() {
866+
return None;
867+
}
868+
// We need to determine the "age" that this `conflicting_activations` will jump to, and why.
869+
let (jumpback_critical_age, jumpback_critical_id) = conflicting_activations
870+
.keys()
871+
.map(|&c| (cx.is_active(c).expect("not currently active!?"), c))
872+
.max()
873+
.unwrap();
874+
let jumpback_critical_reason: ConflictReason =
875+
conflicting_activations[&jumpback_critical_id].clone();
876+
// What parents dose that critical activation have
877+
for (critical_parent, critical_parents_deps) in
878+
cx.parents.edges(&jumpback_critical_id).filter(|(p, _)| {
879+
// it will only help backjump further if it is older then the critical_age
880+
cx.is_active(*p).expect("parent not currently active!?") < jumpback_critical_age
881+
})
882+
{
883+
for critical_parents_dep in critical_parents_deps.iter() {
884+
// A dep is equivalent to one of the things it can resolve to.
885+
// Thus, if all the things it can resolve to have already ben determined
886+
// to be conflicting, then we can just say that we conflict with the parent.
887+
if registry
888+
.query(&critical_parents_dep)
889+
.expect("an already used dep now error!?")
890+
.iter()
891+
.rev() // the last one to be tried is the least likely to be in the cache, so start with that.
892+
.all(|other| {
893+
let mut con = conflicting_activations.clone();
894+
con.remove(&jumpback_critical_id);
895+
con.insert(other.summary.package_id(), jumpback_critical_reason.clone());
896+
past_conflicting_activations.contains(&dep, &con)
897+
})
898+
{
899+
let mut con = conflicting_activations.clone();
900+
con.remove(&jumpback_critical_id);
901+
con.insert(*critical_parent, jumpback_critical_reason);
902+
past_conflicting_activations.insert(&dep, &con);
903+
return Some(con);
904+
}
905+
}
906+
}
907+
None
908+
}
909+
845910
/// Looks through the states in `backtrack_stack` for dependencies with
846911
/// remaining candidates. For each one, also checks if rolling back
847912
/// could change the outcome of the failed resolution that caused backtracking

tests/testsuite/resolve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,7 @@ fn large_conflict_cache() {
13451345
pkg!(("last", "0.0.0") => [dep("bad")]), // just to make sure last is less constrained
13461346
];
13471347
let mut root_deps = vec![dep("last")];
1348-
const NUM_VERSIONS: u8 = 3;
1348+
const NUM_VERSIONS: u8 = 20;
13491349
for name in 0..=NUM_VERSIONS {
13501350
// a large number of conflicts can easily be generated by a sys crate.
13511351
let sys_name = format!("{}-sys", (b'a' + name) as char);

0 commit comments

Comments
 (0)