Skip to content

Commit ecd68bb

Browse files
committed
Implement major wild card coercion
1 parent 3ce4a5c commit ecd68bb

File tree

3 files changed

+69
-73
lines changed

3 files changed

+69
-73
lines changed

crates/resolver-tests/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ fn meta_test_multiple_versions_strategy() {
835835
.unwrap()
836836
.current();
837837
let reg = registry(input.clone());
838-
for this in input.iter().rev().take(10) {
838+
for this in input.iter().rev().take(11) {
839839
let res = resolve(
840840
vec![dep_req(&this.name(), &format!("={}", this.version()))],
841841
&reg,

crates/resolver-tests/tests/resolve.rs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -464,12 +464,7 @@ fn test_wildcard_major() {
464464

465465
assert_same(
466466
&res,
467-
&names(&[
468-
("root", "1.0.0"),
469-
("foo", "1.0.0"),
470-
("util", "0.1.0"),
471-
("util", "0.2.0"),
472-
]),
467+
&names(&[("root", "1.0.0"), ("foo", "1.0.0"), ("util", "0.1.0")]),
473468
);
474469
}
475470

@@ -490,12 +485,7 @@ fn test_range_major() {
490485

491486
assert_same(
492487
&res,
493-
&names(&[
494-
("root", "1.0.0"),
495-
("foo", "1.0.0"),
496-
("util", "0.1.0"),
497-
("util", "0.2.0"),
498-
]),
488+
&names(&[("root", "1.0.0"), ("foo", "1.0.0"), ("util", "0.1.0")]),
499489
);
500490
}
501491

@@ -556,12 +546,7 @@ fn test_wildcard_major_coerced_by_subdependency() {
556546
// by the subdependency of foo.
557547
assert_same(
558548
&res,
559-
&names(&[
560-
("root", "1.0.0"),
561-
("foo", "1.0.0"),
562-
("util", "0.1.0"),
563-
("util", "0.2.0"),
564-
]),
549+
&names(&[("root", "1.0.0"), ("foo", "1.0.0"), ("util", "0.1.0")]),
565550
);
566551
}
567552

@@ -591,7 +576,7 @@ fn test_wildcard_major_coerced_by_indirect_subdependency() {
591576
// version that exists in the dependency tree.
592577
assert_eq!(
593578
res.deps(pkg_id("root")).skip(2).next().unwrap().0,
594-
("util", "0.3.0").to_pkgid()
579+
("util", "0.2.0").to_pkgid()
595580
);
596581

597582
let res = res.sort();
@@ -605,7 +590,6 @@ fn test_wildcard_major_coerced_by_indirect_subdependency() {
605590
("car", "1.0.0"),
606591
("util", "0.1.0"),
607592
("util", "0.2.0"),
608-
("util", "0.3.0"),
609593
]),
610594
);
611595
}
@@ -843,7 +827,6 @@ fn resolving_with_sys_crates() {
843827
("r", "1.0.0"),
844828
("l-sys", "0.9.1"),
845829
("l", "0.9.1"),
846-
("l", "0.10.0"),
847830
]),
848831
);
849832
}

src/cargo/core/resolver/mod.rs

Lines changed: 64 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,8 @@ fn activate_deps_loop(
260260
.conflicting(&resolver_ctx, &dep)
261261
.is_some();
262262

263-
let mut remaining_candidates = RemainingCandidates::new(&candidates);
264-
265-
// `conflicting_activations` stores all the reasons we were unable to
266-
// activate candidates. One of these reasons will have to go away for
267-
// backtracking to find a place to restart. It is also the list of
268-
// things to explain in the error message if we fail to resolve.
269-
//
270-
// This is a map of package ID to a reason why that packaged caused a
271-
// conflict for us.
272-
let mut conflicting_activations = ConflictMap::new();
263+
let (mut remaining_candidates, mut conflicting_activations) =
264+
RemainingCandidates::new(&candidates, &resolver_ctx);
273265

274266
// When backtracking we don't fully update `conflicting_activations`
275267
// especially for the cases that we didn't make a backtrack frame in the
@@ -279,7 +271,7 @@ fn activate_deps_loop(
279271
let mut backtracked = false;
280272

281273
loop {
282-
let next = remaining_candidates.next(&mut conflicting_activations, &resolver_ctx);
274+
let next = remaining_candidates.next();
283275

284276
let (candidate, has_another) = next.ok_or(()).or_else(|_| {
285277
// If we get here then our `remaining_candidates` was just
@@ -712,47 +704,38 @@ struct BacktrackFrame {
712704
/// more inputs) but in general acts like one. Each `RemainingCandidates` is
713705
/// created with a list of candidates to choose from. When attempting to iterate
714706
/// over the list of candidates only *valid* candidates are returned. Validity
715-
/// is defined within a `Context`.
707+
/// is defined within a `Context`. In addition, the iterator will return
708+
/// candidates that already have been activated first.
716709
///
717710
/// Candidates passed to `new` may not be returned from `next` as they could be
718-
/// filtered out, and as they are filtered the causes will be added to `conflicting_prev_active`.
711+
/// filtered out. The filtered candidates and the causes will be returned by `new`.
719712
#[derive(Clone)]
720713
struct RemainingCandidates {
721-
remaining: RcVecIter<Summary>,
714+
prioritized_candidates: RcVecIter<Summary>,
722715
// This is an inlined peekable generator
723716
has_another: Option<Summary>,
724717
}
725718

726719
impl RemainingCandidates {
727-
fn new(candidates: &Rc<Vec<Summary>>) -> RemainingCandidates {
728-
RemainingCandidates {
729-
remaining: RcVecIter::new(Rc::clone(candidates)),
730-
has_another: None,
731-
}
732-
}
733-
734-
/// Attempts to find another candidate to check from this list.
735-
///
736-
/// This method will attempt to move this iterator forward, returning a
737-
/// candidate that's possible to activate. The `cx` argument is the current
738-
/// context which determines validity for candidates returned, and the `dep`
739-
/// is the dependency listing that we're activating for.
740-
///
741-
/// If successful a `(Candidate, bool)` pair will be returned. The
742-
/// `Candidate` is the candidate to attempt to activate, and the `bool` is
743-
/// an indicator of whether there are remaining candidates to try of if
744-
/// we've reached the end of iteration.
745-
///
746-
/// If we've reached the end of the iterator here then `Err` will be
747-
/// returned. The error will contain a map of package ID to conflict reason,
748-
/// where each package ID caused a candidate to be filtered out from the
749-
/// original list for the reason listed.
750-
fn next(
751-
&mut self,
752-
conflicting_prev_active: &mut ConflictMap,
720+
/// Prefilters and sorts the list of candidates to determine which ones are
721+
/// valid to activate, and which ones should be prioritized.
722+
fn new(
723+
candidates: &Rc<Vec<Summary>>,
753724
cx: &ResolverContext,
754-
) -> Option<(Summary, bool)> {
755-
for b in self.remaining.by_ref() {
725+
) -> (RemainingCandidates, ConflictMap) {
726+
// `conflicting_activations` stores all the reasons we were unable to
727+
// activate candidates. One of these reasons will have to go away for
728+
// backtracking to find a place to restart. It is also the list of
729+
// things to explain in the error message if we fail to resolve.
730+
//
731+
// This is a map of package ID to a reason why that packaged caused a
732+
// conflict for us.
733+
let mut conflicting_activations = ConflictMap::new();
734+
735+
let mut activated_candidates: Vec<Summary> = Vec::with_capacity(candidates.len());
736+
let mut non_activated_candidates: Vec<Summary> = Vec::with_capacity(candidates.len());
737+
738+
for b in candidates.as_ref() {
756739
let b_id = b.package_id();
757740
// The `links` key in the manifest dictates that there's only one
758741
// package in a dependency graph, globally, with that particular
@@ -761,7 +744,7 @@ impl RemainingCandidates {
761744
if let Some(link) = b.links() {
762745
if let Some(&a) = cx.links.get(&link) {
763746
if a != b_id {
764-
conflicting_prev_active
747+
conflicting_activations
765748
.entry(a)
766749
.or_insert_with(|| ConflictReason::Links(link));
767750
continue;
@@ -774,18 +757,50 @@ impl RemainingCandidates {
774757
// semver-compatible versions of a crate. For example we can't
775758
// simultaneously activate `foo 1.0.2` and `foo 1.2.0`. We can,
776759
// however, activate `1.0.2` and `2.0.0`.
777-
//
778-
// Here we throw out our candidate if it's *compatible*, yet not
779-
// equal, to all previously activated versions.
780760
if let Some((a, _)) = cx.activations.get(&b_id.as_activations_key()) {
781-
if *a != b {
782-
conflicting_prev_active
761+
// If this candidate is already activated, then we want to put
762+
// it in our prioritized list to try first.
763+
if a == b {
764+
activated_candidates.push(b.clone());
765+
continue;
766+
}
767+
// Here we throw out our candidate if it's *compatible*, yet not
768+
// equal, to all previously activated versions.
769+
else {
770+
conflicting_activations
783771
.entry(a.package_id())
784772
.or_insert(ConflictReason::Semver);
785773
continue;
786774
}
775+
} else {
776+
non_activated_candidates.push(b.clone());
787777
}
778+
}
788779

780+
// Combine the prioritized and non-prioritized candidates into one list
781+
// such that the prioritized candidates are tried first.
782+
activated_candidates.append(&mut non_activated_candidates);
783+
784+
(
785+
RemainingCandidates {
786+
prioritized_candidates: RcVecIter::new(Rc::new(activated_candidates)),
787+
has_another: None,
788+
},
789+
conflicting_activations,
790+
)
791+
}
792+
793+
/// Attempts to find another candidate to check from this list.
794+
///
795+
/// This method will attempt to move this iterator forward, returning a
796+
/// candidate that's possible to activate.
797+
///
798+
/// If successful a `(Candidate, bool)` pair will be returned. The
799+
/// `Candidate` is the candidate to attempt to activate, and the `bool` is
800+
/// an indicator of whether there are remaining candidates to try of if
801+
/// we've reached the end of iteration.
802+
fn next(&mut self) -> Option<(Summary, bool)> {
803+
for b in self.prioritized_candidates.by_ref() {
789804
// Well if we made it this far then we've got a valid dependency. We
790805
// want this iterator to be inherently "peekable" so we don't
791806
// necessarily return the item just yet. Instead we stash it away to
@@ -961,9 +976,7 @@ fn find_candidate(
961976
};
962977

963978
while let Some(mut frame) = backtrack_stack.pop() {
964-
let next = frame
965-
.remaining_candidates
966-
.next(&mut frame.conflicting_activations, &frame.context);
979+
let next = frame.remaining_candidates.next();
967980
let Some((candidate, has_another)) = next else {
968981
continue;
969982
};

0 commit comments

Comments
 (0)