cargo/util/toml_mut/
upgrade.rs

1use std::fmt::Display;
2
3use crate::CargoResult;
4
5/// Upgrade an existing requirement to a new version.
6/// Copied from cargo-edit.
7pub(crate) fn upgrade_requirement(
8    req: &str,
9    version: &semver::Version,
10) -> CargoResult<Option<(String, semver::VersionReq)>> {
11    let req_text = req.to_string();
12    let raw_req = semver::VersionReq::parse(&req_text)
13        .expect("semver to generate valid version requirements");
14    if raw_req.comparators.is_empty() {
15        // Empty matches everything, no-change.
16        Ok(None)
17    } else {
18        let comparators: Vec<_> = raw_req
19            .comparators
20            .into_iter()
21            // Don't downgrade if pre-release was used, see https://github.com/rust-lang/cargo/issues/14178 and https://github.com/rust-lang/cargo/issues/13290.
22            .filter(|p| p.pre.is_empty() || matches_greater(p, version))
23            .map(|p| set_comparator(p, version))
24            .collect::<CargoResult<_>>()?;
25        if comparators.is_empty() {
26            return Ok(None);
27        }
28        let new_req = semver::VersionReq { comparators };
29        let mut new_req_text = new_req.to_string();
30        if new_req_text.starts_with('^') && !req.starts_with('^') {
31            new_req_text.remove(0);
32        }
33        // Validate contract
34        #[cfg(debug_assertions)]
35        {
36            assert!(
37                new_req.matches(version),
38                "New req {} is invalid, because {} does not match {}",
39                new_req_text,
40                new_req,
41                version
42            )
43        }
44        if new_req_text == req_text {
45            Ok(None)
46        } else {
47            Ok(Some((new_req_text, new_req)))
48        }
49    }
50}
51
52fn set_comparator(
53    mut pred: semver::Comparator,
54    version: &semver::Version,
55) -> CargoResult<semver::Comparator> {
56    match pred.op {
57        semver::Op::Wildcard => {
58            pred.major = version.major;
59            if pred.minor.is_some() {
60                pred.minor = Some(version.minor);
61            }
62            if pred.patch.is_some() {
63                pred.patch = Some(version.patch);
64            }
65            Ok(pred)
66        }
67        semver::Op::Exact => Ok(assign_partial_req(version, pred)),
68        semver::Op::Greater | semver::Op::GreaterEq | semver::Op::Less | semver::Op::LessEq => {
69            let user_pred = pred.to_string();
70            Err(unsupported_version_req(user_pred))
71        }
72        semver::Op::Tilde => Ok(assign_partial_req(version, pred)),
73        semver::Op::Caret => Ok(assign_partial_req(version, pred)),
74        _ => {
75            let user_pred = pred.to_string();
76            Err(unsupported_version_req(user_pred))
77        }
78    }
79}
80
81// See https://github.com/dtolnay/semver/blob/69efd3cc770ead273a06ad1788477b3092996d29/src/eval.rs#L64-L88
82fn matches_greater(cmp: &semver::Comparator, ver: &semver::Version) -> bool {
83    if ver.major != cmp.major {
84        return ver.major > cmp.major;
85    }
86
87    match cmp.minor {
88        None => return false,
89        Some(minor) => {
90            if ver.minor != minor {
91                return ver.minor > minor;
92            }
93        }
94    }
95
96    match cmp.patch {
97        None => return false,
98        Some(patch) => {
99            if ver.patch != patch {
100                return ver.patch > patch;
101            }
102        }
103    }
104
105    ver.pre > cmp.pre
106}
107
108fn assign_partial_req(
109    version: &semver::Version,
110    mut pred: semver::Comparator,
111) -> semver::Comparator {
112    pred.major = version.major;
113    if pred.minor.is_some() {
114        pred.minor = Some(version.minor);
115    }
116    if pred.patch.is_some() {
117        pred.patch = Some(version.patch);
118    }
119    pred.pre = version.pre.clone();
120    pred
121}
122
123fn unsupported_version_req(req: impl Display) -> anyhow::Error {
124    anyhow::format_err!("Support for modifying {} is currently unsupported", req)
125}
126
127#[cfg(test)]
128mod test {
129    use super::*;
130
131    mod upgrade_requirement {
132        use super::*;
133
134        #[track_caller]
135        fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
136            let version = semver::Version::parse(version).unwrap();
137            let actual = upgrade_requirement(req, &version)
138                .unwrap()
139                .map(|(actual, _req)| actual);
140            let expected = expected.into();
141            assert_eq!(actual.as_deref(), expected);
142        }
143
144        #[test]
145        fn wildcard_major() {
146            assert_req_bump("1.0.0", "*", None);
147        }
148
149        #[test]
150        fn wildcard_minor() {
151            assert_req_bump("1.0.0", "1.*", None);
152            assert_req_bump("1.1.0", "1.*", None);
153            assert_req_bump("2.0.0", "1.*", "2.*");
154        }
155
156        #[test]
157        fn wildcard_patch() {
158            assert_req_bump("1.0.0", "1.0.*", None);
159            assert_req_bump("1.1.0", "1.0.*", "1.1.*");
160            assert_req_bump("1.1.1", "1.0.*", "1.1.*");
161            assert_req_bump("2.0.0", "1.0.*", "2.0.*");
162        }
163
164        #[test]
165        fn caret_major() {
166            assert_req_bump("1.0.0", "1", None);
167            assert_req_bump("1.0.0", "^1", None);
168
169            assert_req_bump("1.1.0", "1", None);
170            assert_req_bump("1.1.0", "^1", None);
171
172            assert_req_bump("2.0.0", "1", "2");
173            assert_req_bump("2.0.0", "^1", "^2");
174        }
175
176        #[test]
177        fn caret_minor() {
178            assert_req_bump("1.0.0", "1.0", None);
179            assert_req_bump("1.0.0", "^1.0", None);
180
181            assert_req_bump("1.1.0", "1.0", "1.1");
182            assert_req_bump("1.1.0", "^1.0", "^1.1");
183
184            assert_req_bump("1.1.1", "1.0", "1.1");
185            assert_req_bump("1.1.1", "^1.0", "^1.1");
186
187            assert_req_bump("2.0.0", "1.0", "2.0");
188            assert_req_bump("2.0.0", "^1.0", "^2.0");
189        }
190
191        #[test]
192        fn caret_patch() {
193            assert_req_bump("1.0.0", "1.0.0", None);
194            assert_req_bump("1.0.0", "^1.0.0", None);
195
196            assert_req_bump("1.1.0", "1.0.0", "1.1.0");
197            assert_req_bump("1.1.0", "^1.0.0", "^1.1.0");
198
199            assert_req_bump("1.1.1", "1.0.0", "1.1.1");
200            assert_req_bump("1.1.1", "^1.0.0", "^1.1.1");
201
202            assert_req_bump("2.0.0", "1.0.0", "2.0.0");
203            assert_req_bump("2.0.0", "^1.0.0", "^2.0.0");
204        }
205
206        #[test]
207        fn tilde_major() {
208            assert_req_bump("1.0.0", "~1", None);
209            assert_req_bump("1.1.0", "~1", None);
210            assert_req_bump("2.0.0", "~1", "~2");
211        }
212
213        #[test]
214        fn tilde_minor() {
215            assert_req_bump("1.0.0", "~1.0", None);
216            assert_req_bump("1.1.0", "~1.0", "~1.1");
217            assert_req_bump("1.1.1", "~1.0", "~1.1");
218            assert_req_bump("2.0.0", "~1.0", "~2.0");
219        }
220
221        #[test]
222        fn tilde_patch() {
223            assert_req_bump("1.0.0", "~1.0.0", None);
224            assert_req_bump("1.1.0", "~1.0.0", "~1.1.0");
225            assert_req_bump("1.1.1", "~1.0.0", "~1.1.1");
226            assert_req_bump("2.0.0", "~1.0.0", "~2.0.0");
227        }
228
229        #[test]
230        fn equal_major() {
231            assert_req_bump("1.0.0", "=1", None);
232            assert_req_bump("1.1.0", "=1", None);
233            assert_req_bump("2.0.0", "=1", "=2");
234        }
235
236        #[test]
237        fn equal_minor() {
238            assert_req_bump("1.0.0", "=1.0", None);
239            assert_req_bump("1.1.0", "=1.0", "=1.1");
240            assert_req_bump("1.1.1", "=1.0", "=1.1");
241            assert_req_bump("2.0.0", "=1.0", "=2.0");
242        }
243
244        #[test]
245        fn equal_patch() {
246            assert_req_bump("1.0.0", "=1.0.0", None);
247            assert_req_bump("1.1.0", "=1.0.0", "=1.1.0");
248            assert_req_bump("1.1.1", "=1.0.0", "=1.1.1");
249            assert_req_bump("2.0.0", "=1.0.0", "=2.0.0");
250        }
251
252        #[test]
253        fn greater_prerelease() {
254            assert_req_bump("1.7.0", "2.0.0-beta.21", None);
255            assert_req_bump("1.7.0", "=2.0.0-beta.21", None);
256            assert_req_bump("1.7.0", "~2.0.0-beta.21", None);
257            assert_req_bump("2.0.0-beta.20", "2.0.0-beta.21", None);
258            assert_req_bump("2.0.0-beta.21", "2.0.0-beta.21", None);
259            assert_req_bump("2.0.0-beta.22", "2.0.0-beta.21", "2.0.0-beta.22");
260            assert_req_bump("2.0.0", "2.0.0-beta.21", "2.0.0");
261            assert_req_bump("3.0.0", "2.0.0-beta.21", "3.0.0");
262        }
263    }
264}