Skip to content

Commit bdf19e0

Browse files
committed
feat: add upper bound semantic for Less Op
1 parent c4d7d74 commit bdf19e0

File tree

2 files changed

+88
-22
lines changed

2 files changed

+88
-22
lines changed

src/cargo/util/semver_eval_ext.rs

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,24 @@
77
use semver::{Comparator, Op, Prerelease, Version, VersionReq};
88

99
pub(crate) fn matches_prerelease(req: &VersionReq, ver: &Version) -> bool {
10+
// Whether there are pre release version can be as lower bound
11+
let lower_bound_prerelease = &req.comparators.iter().any(|cmp| {
12+
if matches!(cmp.op, Op::Greater | Op::GreaterEq) && !cmp.pre.is_empty() {
13+
true
14+
} else {
15+
false
16+
}
17+
});
1018
for cmp in &req.comparators {
11-
if !matches_prerelease_impl(cmp, ver) {
19+
if !matches_prerelease_impl(cmp, ver, lower_bound_prerelease) {
1220
return false;
1321
}
1422
}
1523

1624
true
1725
}
1826

19-
fn matches_prerelease_impl(cmp: &Comparator, ver: &Version) -> bool {
27+
fn matches_prerelease_impl(cmp: &Comparator, ver: &Version, lower_bound_prerelease: &bool) -> bool {
2028
match cmp.op {
2129
Op::Exact | Op::Wildcard => matches_exact_prerelease(cmp, ver),
2230
Op::Greater => matches_greater(cmp, ver),
@@ -26,7 +34,13 @@ fn matches_prerelease_impl(cmp: &Comparator, ver: &Version) -> bool {
2634
}
2735
matches_greater(cmp, ver)
2836
}
29-
Op::Less => matches_less(&fill_partial_req(cmp), ver),
37+
Op::Less => {
38+
if *lower_bound_prerelease {
39+
matches_less(&fill_partial_req(cmp), ver)
40+
} else {
41+
matches_less(&fill_partial_req_include_pre(cmp), ver)
42+
}
43+
}
3044
Op::LessEq => {
3145
if matches_exact_prerelease(cmp, ver) {
3246
return true;
@@ -125,6 +139,21 @@ fn fill_partial_req(cmp: &Comparator) -> Comparator {
125139
cmp
126140
}
127141

142+
fn fill_partial_req_include_pre(cmp: &Comparator) -> Comparator {
143+
let mut cmp = cmp.clone();
144+
if cmp.minor.is_none() {
145+
cmp.minor = Some(0);
146+
cmp.patch = Some(0);
147+
cmp.pre = Prerelease::new("0").unwrap();
148+
} else if cmp.patch.is_none() {
149+
cmp.patch = Some(0);
150+
}
151+
if cmp.pre.is_empty() {
152+
cmp.pre = Prerelease::new("0").unwrap();
153+
}
154+
cmp
155+
}
156+
128157
fn matches_exact_prerelease(cmp: &Comparator, ver: &Version) -> bool {
129158
if matches_exact(cmp, ver) {
130159
return true;
@@ -334,29 +363,66 @@ mod matches_prerelease_semantic {
334363

335364
#[test]
336365
fn test_less() {
337-
// <I.J.K-0
338-
let ref r = req("<4.2.1-0");
339-
assert_match_all(r, &["0.0.0", "4.0.0"]);
340-
assert_match_none(r, &["4.2.1-0", "4.2.2", "5.0.0-0", "5.0.0"]);
341-
342-
// <I.J.K
343-
let ref r = req("<4.2.1");
344-
assert_match_all(r, &["0.0.0", "4.0.0", "4.2.1-0"]);
345-
assert_match_none(r, &["4.2.2", "5.0.0-0", "5.0.0"]);
366+
// <I.J.K equivalent to <I.J.K-0
367+
for r in &[req("<4.2.1"), req("<4.2.1-0")] {
368+
assert_match_all(r, &["0.0.0", "4.0.0"]);
369+
assert_match_none(r, &["4.2.1-0", "4.2.2", "5.0.0-0", "5.0.0"]);
370+
}
346371

347-
// <I.J equivalent to <I.J.0
348-
for r in &[req("<4.2"), req("<4.2.0")] {
349-
assert_match_all(r, &["0.0.0", "4.1.0", "4.2.0-0"]);
350-
assert_match_none(r, &["4.2.0", "4.3.0-0", "4.3.0"]);
372+
// <I.J equivalent to <I.J.0-0
373+
for r in &[req("<4.2"), req("<4.2.0-0")] {
374+
assert_match_all(r, &["0.0.0", "4.1.0"]);
375+
assert_match_none(r, &["4.2.0-0", "4.2.0", "4.3.0-0", "4.3.0"]);
351376
}
352377

353-
// <I equivalent to <I.0.0
354-
for r in &[req("<4"), req("<4.0.0")] {
355-
assert_match_all(r, &["0.0.0", "4.0.0-0"]);
356-
assert_match_none(r, &["4.0.0", "5.0.0-1", "5.0.0"]);
378+
// <I equivalent to <I.0.0-0
379+
for r in &[req("<4"), req("<4.0.0-0")] {
380+
assert_match_all(r, &["0.0.0", "3.9.0"]);
381+
assert_match_none(r, &["4.0.0-0", "4.0.0", "5.0.0-1", "5.0.0"]);
357382
}
358383
}
359384

385+
#[test]
386+
fn test_less_upper_bound() {
387+
// Lower bound without prerelase tag, so upper bound equivalent to <I.J.K-0
388+
for r in &[
389+
req(">1.2.3, <2"),
390+
req(">1.2.3, <2.0"),
391+
req(">1.2.3, <2.0.0"),
392+
req(">=1.2.3, <2.0.0"),
393+
req(">1.2.3, <2.0.0-0"),
394+
] {
395+
assert_match_all(r, &["1.2.4", "1.9.9"]);
396+
assert_match_none(r, &["2.0.0-0", "2.0.0", "2.1.2"]);
397+
}
398+
399+
// Lower bound has prerelase tag, so upper bound doesn't change.
400+
for r in &[
401+
req(">1.2.3-0, <2"),
402+
req(">1.2.3-0, <2.0"),
403+
req(">1.2.3-0, <2.0.0"),
404+
req(">=1.2.3-0, <2.0.0"),
405+
] {
406+
assert_match_all(r, &["1.2.4", "1.9.9", "2.0.0-0"]);
407+
assert_match_none(r, &["2.0.0", "2.1.2"]);
408+
}
409+
410+
for r in &[
411+
req(">=2.0.0-0, <2"),
412+
req(">=2.0.0-0, <2.0"),
413+
req(">=2.0.0-0, <2.0.0"),
414+
] {
415+
assert_match_all(r, &["2.0.0-0", "2.0.0-11"]);
416+
assert_match_none(r, &["0.0.9", "2.0.0"]);
417+
}
418+
419+
// There is no intersection between lower bound and upper bound, in this case nothing matches
420+
let ref r = req(">5.0.0, <2.0.0");
421+
assert_match_none(r, &["1.2.3", "3.0.0", "6.0.0"]);
422+
let ref r = req(">5.0.0-0, <2.0.0");
423+
assert_match_none(r, &["1.2.3", "3.0.0", "6.0.0"]);
424+
}
425+
360426
#[test]
361427
fn test_caret() {
362428
// ^I.J.K.0 (for I>0) — equivalent to >=I.J.K-0, <(I+1).0.0-0

src/cargo/util/semver_ext.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,11 @@ mod matches_prerelease {
227227
//
228228
(">1.2.3, <1.2.4", "1.2.3-0", false),
229229
(">1.2.3, <1.2.4", "1.2.3-1", false),
230-
(">1.2.3, <1.2.4", "1.2.4-0", true), // upper bound semantic
230+
(">1.2.3, <1.2.4", "1.2.4-0", false), // upper bound semantic
231231
//
232232
(">=1.2.3, <1.2.4", "1.2.3-0", false),
233233
(">=1.2.3, <1.2.4", "1.2.3-1", false),
234-
(">=1.2.3, <1.2.4", "1.2.4-0", true), // upper bound semantic
234+
(">=1.2.3, <1.2.4", "1.2.4-0", false), // upper bound semantic
235235
//
236236
(">1.2.3, <=1.2.4", "1.2.3-0", false),
237237
(">1.2.3, <=1.2.4", "1.2.3-1", false),

0 commit comments

Comments
 (0)