Skip to content

Commit b48616f

Browse files
committed
allow find_candidate to backtrack from a PublicDependency
1 parent 4a2dcf4 commit b48616f

File tree

4 files changed

+114
-24
lines changed

4 files changed

+114
-24
lines changed

src/cargo/core/resolver/conflict_cache.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
22

33
use log::trace;
44

5-
use super::types::{ConflictMap, ConflictReason};
5+
use super::types::ConflictMap;
66
use crate::core::resolver::Context;
77
use crate::core::{Dependency, PackageId};
88

@@ -194,7 +194,7 @@ impl ConflictCache {
194194
/// `dep` is known to be unresolvable if
195195
/// all the `PackageId` entries are activated.
196196
pub fn insert(&mut self, dep: &Dependency, con: &ConflictMap) {
197-
if con.values().any(|c| *c == ConflictReason::PublicDependency) {
197+
if con.values().any(|c| c.is_public_dependency()) {
198198
// TODO: needs more info for back jumping
199199
// for now refuse to cache it.
200200
return;

src/cargo/core/resolver/context.rs

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ pub type ContextAge = usize;
4949
/// By storing this in a hash map we ensure that there is only one
5050
/// semver compatible version of each crate.
5151
/// This all so stores the `ContextAge`.
52-
pub type Activations =
53-
im_rc::HashMap<(InternedString, SourceId, SemverCompatibility), (Summary, ContextAge)>;
52+
pub type ActivationsKey = (InternedString, SourceId, SemverCompatibility);
53+
pub type Activations = im_rc::HashMap<ActivationsKey, (Summary, ContextAge)>;
5454

5555
/// A type that represents when cargo treats two Versions as compatible.
5656
/// Versions `a` and `b` are compatible if their left-most nonzero digit is the
5757
/// same.
58-
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
58+
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
5959
pub enum SemverCompatibility {
6060
Major(NonZeroU64),
6161
Minor(NonZeroU64),
@@ -75,7 +75,7 @@ impl From<&semver::Version> for SemverCompatibility {
7575
}
7676

7777
impl PackageId {
78-
pub fn as_activations_key(self) -> (InternedString, SourceId, SemverCompatibility) {
78+
pub fn as_activations_key(self) -> ActivationsKey {
7979
(self.name(), self.source_id(), self.version().into())
8080
}
8181
}
@@ -189,6 +189,42 @@ impl Context {
189189
.and_then(|(s, l)| if s.package_id() == id { Some(*l) } else { None })
190190
}
191191

192+
/// If the conflict reason on the package still applies returns the `ContextAge` when it was added
193+
pub fn still_applies(&self, id: PackageId, reason: &ConflictReason) -> Option<ContextAge> {
194+
self.is_active(id).and_then(|mut max| {
195+
match reason {
196+
ConflictReason::PublicDependency(name) => {
197+
if &id == name {
198+
return Some(max);
199+
}
200+
max = std::cmp::max(max, self.is_active(*name)?);
201+
max = std::cmp::max(
202+
max,
203+
self.public_dependency
204+
.as_ref()
205+
.unwrap()
206+
.can_see_item(*name, id)?,
207+
);
208+
}
209+
ConflictReason::PubliclyExports(name) => {
210+
if &id == name {
211+
return Some(max);
212+
}
213+
max = std::cmp::max(max, self.is_active(*name)?);
214+
max = std::cmp::max(
215+
max,
216+
self.public_dependency
217+
.as_ref()
218+
.unwrap()
219+
.publicly_exports_item(*name, id)?,
220+
);
221+
}
222+
_ => {}
223+
}
224+
Some(max)
225+
})
226+
}
227+
192228
/// Checks whether all of `parent` and the keys of `conflicting activations`
193229
/// are still active.
194230
/// If so returns the `ContextAge` when the newest one was added.
@@ -198,12 +234,12 @@ impl Context {
198234
conflicting_activations: &ConflictMap,
199235
) -> Option<usize> {
200236
let mut max = 0;
201-
for &id in conflicting_activations.keys().chain(parent.as_ref()) {
202-
if let Some(age) = self.is_active(id) {
203-
max = std::cmp::max(max, age);
204-
} else {
205-
return None;
206-
}
237+
if let Some(parent) = parent {
238+
max = std::cmp::max(max, self.is_active(parent)?);
239+
}
240+
241+
for (id, reason) in conflicting_activations.iter() {
242+
max = std::cmp::max(max, self.still_applies(*id, reason)?);
207243
}
208244
Some(max)
209245
}
@@ -270,6 +306,31 @@ impl PublicDependency {
270306
.chain(Some(candidate_pid)) // but even if not we know that everything exports itself
271307
.collect()
272308
}
309+
fn publicly_exports_item(
310+
&self,
311+
candidate_pid: PackageId,
312+
target: PackageId,
313+
) -> Option<ContextAge> {
314+
debug_assert_ne!(candidate_pid, target);
315+
let out = self
316+
.inner
317+
.get(&candidate_pid)
318+
.and_then(|names| names.get(&target.name()))
319+
.filter(|(p, _, _)| *p == target)
320+
.and_then(|(_, _, age)| *age);
321+
debug_assert_eq!(
322+
out.is_some(),
323+
self.publicly_exports(candidate_pid).contains(&target)
324+
);
325+
out
326+
}
327+
pub fn can_see_item(&self, candidate_pid: PackageId, target: PackageId) -> Option<ContextAge> {
328+
self.inner
329+
.get(&candidate_pid)
330+
.and_then(|names| names.get(&target.name()))
331+
.filter(|(p, _, _)| *p == target)
332+
.map(|(_, age, _)| *age)
333+
}
273334
pub fn add_edge(
274335
&mut self,
275336
candidate_pid: PackageId,
@@ -322,7 +383,13 @@ impl PublicDependency {
322383
parent: PackageId,
323384
is_public: bool,
324385
parents: &Graph<PackageId, Rc<Vec<Dependency>>>,
325-
) -> Result<(), (PackageId, ConflictReason)> {
386+
) -> Result<
387+
(),
388+
(
389+
((PackageId, ConflictReason), (PackageId, ConflictReason)),
390+
Option<(PackageId, ConflictReason)>,
391+
),
392+
> {
326393
// one tricky part is that `candidate_pid` may already be active and
327394
// have public dependencies of its own. So we not only need to check
328395
// `b_id` as visible to its parents but also all of its existing
@@ -336,7 +403,17 @@ impl PublicDependency {
336403
if o.0 != t {
337404
// the (transitive) parent can already see a different version by `t`s name.
338405
// So, adding `b` will cause `p` to have a public dependency conflict on `t`.
339-
return Err((p, ConflictReason::PublicDependency));
406+
return Err((
407+
(o.0, ConflictReason::PublicDependency(p)), // p can see the other version and
408+
(parent, ConflictReason::PublicDependency(p)), // p can see us
409+
))
410+
.map_err(|e| {
411+
if t == b_id {
412+
(e, None)
413+
} else {
414+
(e, Some((t, ConflictReason::PubliclyExports(b_id))))
415+
}
416+
});
340417
}
341418
if o.2.is_some() {
342419
// The previous time the parent saw `t`, it was a public dependency.

src/cargo/core/resolver/mod.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -763,10 +763,14 @@ impl RemainingCandidates {
763763
// activated and have public dependants of its own,
764764
// all of witch also need to be checked the same way.
765765
if let Some(public_dependency) = cx.public_dependency.as_ref() {
766-
if let Err((p, c)) =
766+
if let Err(((c1, c2), c3)) =
767767
public_dependency.can_add_edge(b_id, parent, dep.is_public(), &cx.parents)
768768
{
769-
conflicting_prev_active.insert(p, c);
769+
conflicting_prev_active.insert(c1.0, c1.1);
770+
conflicting_prev_active.insert(c2.0, c2.1);
771+
if let Some(c3) = c3 {
772+
conflicting_prev_active.insert(c3.0, c3.1);
773+
}
770774
continue;
771775
}
772776
}
@@ -812,6 +816,10 @@ fn generalize_conflicting(
812816
let backtrack_critical_reason: ConflictReason =
813817
conflicting_activations[&backtrack_critical_id].clone();
814818

819+
if backtrack_critical_reason.is_public_dependency() {
820+
return None;
821+
}
822+
815823
if cx
816824
.parents
817825
.is_path_from_to(&parent.package_id(), &backtrack_critical_id)
@@ -919,13 +927,7 @@ fn find_candidate(
919927
// The abnormal situations are things that do not put all of the reasons in `conflicting_activations`:
920928
// If we backtracked we do not know how our `conflicting_activations` related to
921929
// the cause of that backtrack, so we do not update it.
922-
// If we had a PublicDependency conflict, then we do not yet have a compact way to
923-
// represent all the parts of the problem, so `conflicting_activations` is incomplete.
924-
let age = if !backtracked
925-
&& !conflicting_activations
926-
.values()
927-
.any(|c| *c == ConflictReason::PublicDependency)
928-
{
930+
let age = if !backtracked {
929931
// we dont have abnormal situations. So we can ask `cx` for how far back we need to go.
930932
let a = cx.is_conflicting(Some(parent.package_id()), conflicting_activations);
931933
// If the `conflicting_activations` does not apply to `cx`, then something went very wrong

src/cargo/core/resolver/types.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,8 @@ pub enum ConflictReason {
286286
// TODO: needs more info for `activation_error`
287287
// TODO: needs more info for `find_candidate`
288288
/// pub dep error
289-
PublicDependency,
289+
PublicDependency(PackageId),
290+
PubliclyExports(PackageId),
290291
}
291292

292293
impl ConflictReason {
@@ -310,6 +311,16 @@ impl ConflictReason {
310311
}
311312
false
312313
}
314+
315+
pub fn is_public_dependency(&self) -> bool {
316+
if let ConflictReason::PublicDependency(_) = *self {
317+
return true;
318+
}
319+
if let ConflictReason::PubliclyExports(_) = *self {
320+
return true;
321+
}
322+
false
323+
}
313324
}
314325

315326
/// A list of packages that have gotten in the way of resolving a dependency.

0 commit comments

Comments
 (0)