]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/fix.rs
Auto merge of #6431 - ehuss:panic-in-panic, r=nrc
[cargo.git] / src / cargo / ops / fix.rs
CommitLineData
92485f8c 1use std::collections::{BTreeSet, HashMap, HashSet};
b02ba377 2use std::env;
fa7a3877
AC
3use std::ffi::OsString;
4use std::fs;
5use std::path::{Path, PathBuf};
b02ba377
AC
6use std::process::{self, Command, ExitStatus};
7use std::str;
8
9use failure::{Error, ResultExt};
9ed82b57 10use log::{debug, trace, warn};
b02ba377 11use rustfix::diagnostics::Diagnostic;
876a5036 12use rustfix::{self, CodeFix};
b02ba377 13
04ddd4d0
DW
14use crate::core::Workspace;
15use crate::ops::{self, CompileOptions};
16use crate::util::diagnostic_server::{Message, RustfixDiagnosticServer};
17use crate::util::errors::CargoResult;
18use crate::util::paths;
19use crate::util::{existing_vcs_repo, LockServer, LockServerClient};
b02ba377
AC
20
21const FIX_ENV: &str = "__CARGO_FIX_PLZ";
22const BROKEN_CODE_ENV: &str = "__CARGO_FIX_BROKEN_CODE";
b2b120e9 23const PREPARE_FOR_ENV: &str = "__CARGO_FIX_PREPARE_FOR";
fa7a3877 24const EDITION_ENV: &str = "__CARGO_FIX_EDITION";
b02ba377 25
80f9d318
AC
26const IDIOMS_ENV: &str = "__CARGO_FIX_IDIOMS";
27
b02ba377 28pub struct FixOptions<'a> {
b2b120e9
AC
29 pub edition: bool,
30 pub prepare_for: Option<&'a str>,
80f9d318 31 pub idioms: bool,
b02ba377
AC
32 pub compile_opts: CompileOptions<'a>,
33 pub allow_dirty: bool,
34 pub allow_no_vcs: bool,
6a616cb7 35 pub allow_staged: bool,
b02ba377
AC
36 pub broken_code: bool,
37}
38
b8b7faee 39pub fn fix(ws: &Workspace<'_>, opts: &mut FixOptions<'_>) -> CargoResult<()> {
b02ba377
AC
40 check_version_control(opts)?;
41
42 // Spin up our lock server which our subprocesses will use to synchronize
43 // fixes.
44 let lock_server = LockServer::new()?;
92485f8c
E
45 opts.compile_opts
46 .build_config
47 .extra_rustc_env
48 .push((FIX_ENV.to_string(), lock_server.addr().to_string()));
b02ba377
AC
49 let _started = lock_server.start()?;
50
616e0ad3
PH
51 opts.compile_opts.build_config.force_rebuild = true;
52
b02ba377
AC
53 if opts.broken_code {
54 let key = BROKEN_CODE_ENV.to_string();
92485f8c
E
55 opts.compile_opts
56 .build_config
57 .extra_rustc_env
58 .push((key, "1".to_string()));
b02ba377
AC
59 }
60
b2b120e9
AC
61 if opts.edition {
62 let key = EDITION_ENV.to_string();
92485f8c
E
63 opts.compile_opts
64 .build_config
65 .extra_rustc_env
66 .push((key, "1".to_string()));
b2b120e9 67 } else if let Some(edition) = opts.prepare_for {
92485f8c
E
68 opts.compile_opts
69 .build_config
70 .extra_rustc_env
71 .push((PREPARE_FOR_ENV.to_string(), edition.to_string()));
b02ba377 72 }
80f9d318 73 if opts.idioms {
92485f8c
E
74 opts.compile_opts
75 .build_config
76 .extra_rustc_env
77 .push((IDIOMS_ENV.to_string(), "1".to_string()));
80f9d318 78 }
b02ba377 79 opts.compile_opts.build_config.cargo_as_rustc_wrapper = true;
92485f8c
E
80 *opts
81 .compile_opts
82 .build_config
83 .rustfix_diagnostic_server
84 .borrow_mut() = Some(RustfixDiagnosticServer::new()?);
b02ba377
AC
85
86 ops::compile(ws, &opts.compile_opts)?;
87 Ok(())
88}
89
b8b7faee 90fn check_version_control(opts: &FixOptions<'_>) -> CargoResult<()> {
b02ba377 91 if opts.allow_no_vcs {
92485f8c 92 return Ok(());
b02ba377
AC
93 }
94 let config = opts.compile_opts.config;
95 if !existing_vcs_repo(config.cwd(), config.cwd()) {
54c42142 96 failure::bail!(
92485f8c
E
97 "no VCS found for this package and `cargo fix` can potentially \
98 perform destructive changes; if you'd like to suppress this \
99 error pass `--allow-no-vcs`"
100 )
b02ba377
AC
101 }
102
6a616cb7 103 if opts.allow_dirty && opts.allow_staged {
92485f8c 104 return Ok(());
b02ba377
AC
105 }
106
107 let mut dirty_files = Vec::new();
6a616cb7 108 let mut staged_files = Vec::new();
b02ba377 109 if let Ok(repo) = git2::Repository::discover(config.cwd()) {
6a616cb7
JJ
110 let mut repo_opts = git2::StatusOptions::new();
111 repo_opts.include_ignored(false);
112 for status in repo.statuses(Some(&mut repo_opts))?.iter() {
113 if let Some(path) = status.path() {
114 match status.status() {
115 git2::Status::CURRENT => (),
92485f8c
E
116 git2::Status::INDEX_NEW
117 | git2::Status::INDEX_MODIFIED
118 | git2::Status::INDEX_DELETED
119 | git2::Status::INDEX_RENAMED
120 | git2::Status::INDEX_TYPECHANGE => {
6a616cb7
JJ
121 if !opts.allow_staged {
122 staged_files.push(path.to_string())
92485f8c
E
123 }
124 }
125 _ => {
6a616cb7
JJ
126 if !opts.allow_dirty {
127 dirty_files.push(path.to_string())
92485f8c
E
128 }
129 }
6a616cb7 130 };
b02ba377 131 }
b02ba377
AC
132 }
133 }
134
6a616cb7 135 if dirty_files.is_empty() && staged_files.is_empty() {
92485f8c 136 return Ok(());
b02ba377
AC
137 }
138
139 let mut files_list = String::new();
140 for file in dirty_files {
141 files_list.push_str(" * ");
142 files_list.push_str(&file);
4539ff21 143 files_list.push_str(" (dirty)\n");
b02ba377 144 }
6a616cb7
JJ
145 for file in staged_files {
146 files_list.push_str(" * ");
147 files_list.push_str(&file);
148 files_list.push_str(" (staged)\n");
149 }
b02ba377 150
54c42142 151 failure::bail!(
92485f8c
E
152 "the working directory of this package has uncommitted changes, and \
153 `cargo fix` can potentially perform destructive changes; if you'd \
154 like to suppress this error pass `--allow-dirty`, `--allow-staged`, \
155 or commit the changes to these files:\n\
156 \n\
157 {}\n\
158 ",
159 files_list
160 );
b02ba377
AC
161}
162
163pub fn fix_maybe_exec_rustc() -> CargoResult<bool> {
164 let lock_addr = match env::var(FIX_ENV) {
165 Ok(s) => s,
166 Err(_) => return Ok(false),
167 };
168
fa7a3877
AC
169 let args = FixArgs::get();
170 trace!("cargo-fix as rustc got file {:?}", args.file);
b02ba377
AC
171 let rustc = env::var_os("RUSTC").expect("failed to find RUSTC env var");
172
173 // Our goal is to fix only the crates that the end user is interested in.
174 // That's very likely to only mean the crates in the workspace the user is
175 // working on, not random crates.io crates.
176 //
177 // To that end we only actually try to fix things if it looks like we're
178 // compiling a Rust file and it *doesn't* have an absolute filename. That's
179 // not the best heuristic but matches what Cargo does today at least.
180 let mut fixes = FixedCrate::default();
fa7a3877 181 if let Some(path) = &args.file {
4f784a10 182 if args.primary_package {
b02ba377 183 trace!("start rustfixing {:?}", path);
fa7a3877 184 fixes = rustfix_crate(&lock_addr, rustc.as_ref(), path, &args)?;
b02ba377
AC
185 }
186 }
187
188 // Ok now we have our final goal of testing out the changes that we applied.
189 // If these changes went awry and actually started to cause the crate to
190 // *stop* compiling then we want to back them out and continue to print
191 // warnings to the user.
192 //
193 // If we didn't actually make any changes then we can immediately exec the
194 // new rustc, and otherwise we capture the output to hide it in the scenario
195 // that we have to back it all out.
876a5036 196 if !fixes.files.is_empty() {
fa7a3877
AC
197 let mut cmd = Command::new(&rustc);
198 args.apply(&mut cmd);
199 cmd.arg("--error-format=json");
b02ba377
AC
200 let output = cmd.output().context("failed to spawn rustc")?;
201
202 if output.status.success() {
876a5036
AC
203 for (path, file) in fixes.files.iter() {
204 Message::Fixing {
205 file: path.clone(),
206 fixes: file.fixes_applied,
92485f8c
E
207 }
208 .post()?;
b02ba377
AC
209 }
210 }
211
212 // If we succeeded then we'll want to commit to the changes we made, if
213 // any. If stderr is empty then there's no need for the final exec at
214 // the end, we just bail out here.
385b54b3 215 if output.status.success() && output.stderr.is_empty() {
b02ba377
AC
216 return Ok(true);
217 }
218
219 // Otherwise if our rustc just failed then that means that we broke the
220 // user's code with our changes. Back out everything and fall through
221 // below to recompile again.
222 if !output.status.success() {
08dc6da0
AC
223 if env::var_os(BROKEN_CODE_ENV).is_none() {
224 for (path, file) in fixes.files.iter() {
225 fs::write(path, &file.original_code)
226 .with_context(|_| format!("failed to write file `{}`", path))?;
227 }
b02ba377
AC
228 }
229 log_failed_fix(&output.stderr)?;
230 }
231 }
232
233 let mut cmd = Command::new(&rustc);
fa7a3877 234 args.apply(&mut cmd);
b02ba377
AC
235 exit_with(cmd.status().context("failed to spawn rustc")?);
236}
237
238#[derive(Default)]
239struct FixedCrate {
876a5036
AC
240 files: HashMap<String, FixedFile>,
241}
242
243struct FixedFile {
244 errors_applying_fixes: Vec<String>,
245 fixes_applied: u32,
246 original_code: String,
b02ba377
AC
247}
248
92485f8c
E
249fn rustfix_crate(
250 lock_addr: &str,
251 rustc: &Path,
252 filename: &Path,
253 args: &FixArgs,
254) -> Result<FixedCrate, Error> {
fa7a3877 255 args.verify_not_preparing_for_enabled_edition()?;
fa7a3877 256
b02ba377
AC
257 // First up we want to make sure that each crate is only checked by one
258 // process at a time. If two invocations concurrently check a crate then
259 // it's likely to corrupt it.
260 //
261 // Currently we do this by assigning the name on our lock to the first
262 // argument that looks like a Rust file.
263 let _lock = LockServerClient::lock(&lock_addr.parse()?, filename)?;
264
876a5036
AC
265 // Next up this is a bit suspicious, but we *iteratively* execute rustc and
266 // collect suggestions to feed to rustfix. Once we hit our limit of times to
267 // execute rustc or we appear to be reaching a fixed point we stop running
268 // rustc.
269 //
270 // This is currently done to handle code like:
271 //
272 // ::foo::<::Bar>();
273 //
274 // where there are two fixes to happen here: `crate::foo::<crate::Bar>()`.
275 // The spans for these two suggestions are overlapping and its difficult in
276 // the compiler to *not* have overlapping spans here. As a result, a naive
277 // implementation would feed the two compiler suggestions for the above fix
278 // into `rustfix`, but one would be rejected because it overlaps with the
279 // other.
280 //
281 // In this case though, both suggestions are valid and can be automatically
282 // applied! To handle this case we execute rustc multiple times, collecting
283 // fixes each time we do so. Along the way we discard any suggestions that
284 // failed to apply, assuming that they can be fixed the next time we run
285 // rustc.
286 //
287 // Naturally we want a few protections in place here though to avoid looping
288 // forever or otherwise losing data. To that end we have a few termination
289 // conditions:
290 //
291 // * Do this whole process a fixed number of times. In theory we probably
292 // need an infinite number of times to apply fixes, but we're not gonna
293 // sit around waiting for that.
294 // * If it looks like a fix genuinely can't be applied we need to bail out.
295 // Detect this when a fix fails to get applied *and* no suggestions
296 // successfully applied to the same file. In that case looks like we
297 // definitely can't make progress, so bail out.
298 let mut fixes = FixedCrate::default();
299 let mut last_fix_counts = HashMap::new();
300 let iterations = env::var("CARGO_FIX_MAX_RETRIES")
301 .ok()
302 .and_then(|n| n.parse().ok())
303 .unwrap_or(4);
304 for _ in 0..iterations {
305 last_fix_counts.clear();
306 for (path, file) in fixes.files.iter_mut() {
307 last_fix_counts.insert(path.clone(), file.fixes_applied);
308 file.errors_applying_fixes.clear(); // we'll generate new errors below
309 }
310 rustfix_and_fix(&mut fixes, rustc, filename, args)?;
311 let mut progress_yet_to_be_made = false;
312 for (path, file) in fixes.files.iter_mut() {
8798bf0d 313 if file.errors_applying_fixes.is_empty() {
92485f8c 314 continue;
876a5036
AC
315 }
316 // If anything was successfully fixed *and* there's at least one
317 // error, then assume the error was spurious and we'll try again on
318 // the next iteration.
319 if file.fixes_applied != *last_fix_counts.get(path).unwrap_or(&0) {
320 progress_yet_to_be_made = true;
321 }
322 }
323 if !progress_yet_to_be_made {
92485f8c 324 break;
876a5036
AC
325 }
326 }
327
328 // Any errors still remaining at this point need to be reported as probably
329 // bugs in Cargo and/or rustfix.
330 for (path, file) in fixes.files.iter_mut() {
331 for error in file.errors_applying_fixes.drain(..) {
332 Message::ReplaceFailed {
333 file: path.clone(),
334 message: error,
92485f8c
E
335 }
336 .post()?;
876a5036
AC
337 }
338 }
339
340 Ok(fixes)
341}
342
343/// Execute `rustc` to apply one round of suggestions to the crate in question.
344///
345/// This will fill in the `fixes` map with original code, suggestions applied,
346/// and any errors encountered while fixing files.
92485f8c
E
347fn rustfix_and_fix(
348 fixes: &mut FixedCrate,
349 rustc: &Path,
350 filename: &Path,
351 args: &FixArgs,
352) -> Result<(), Error> {
876a5036
AC
353 // If not empty, filter by these lints
354 //
355 // TODO: Implement a way to specify this
356 let only = HashSet::new();
357
358 let mut cmd = Command::new(rustc);
fa7a3877
AC
359 cmd.arg("--error-format=json");
360 args.apply(&mut cmd);
92485f8c
E
361 let output = cmd
362 .output()
b02ba377
AC
363 .with_context(|_| format!("failed to execute `{}`", rustc.display()))?;
364
365 // If rustc didn't succeed for whatever reasons then we're very likely to be
366 // looking at otherwise broken code. Let's not make things accidentally
367 // worse by applying fixes where a bug could cause *more* broken code.
368 // Instead, punt upwards which will reexec rustc over the original code,
369 // displaying pretty versions of the diagnostics we just read out.
370 if !output.status.success() && env::var_os(BROKEN_CODE_ENV).is_none() {
371 debug!(
372 "rustfixing `{:?}` failed, rustc exited with {:?}",
373 filename,
374 output.status.code()
375 );
6dd73398 376 return Ok(());
b02ba377
AC
377 }
378
379 let fix_mode = env::var_os("__CARGO_FIX_YOLO")
380 .map(|_| rustfix::Filter::Everything)
381 .unwrap_or(rustfix::Filter::MachineApplicableOnly);
382
383 // Sift through the output of the compiler to look for JSON messages
384 // indicating fixes that we can apply.
385 let stderr = str::from_utf8(&output.stderr).context("failed to parse rustc stderr as utf-8")?;
386
92485f8c
E
387 let suggestions = stderr
388 .lines()
b02ba377
AC
389 .filter(|x| !x.is_empty())
390 .inspect(|y| trace!("line: {}", y))
b02ba377
AC
391 // Parse each line of stderr ignoring errors as they may not all be json
392 .filter_map(|line| serde_json::from_str::<Diagnostic>(line).ok())
b02ba377
AC
393 // From each diagnostic try to extract suggestions from rustc
394 .filter_map(|diag| rustfix::collect_suggestions(&diag, &only, fix_mode));
395
396 // Collect suggestions by file so we can apply them one at a time later.
397 let mut file_map = HashMap::new();
398 let mut num_suggestion = 0;
399 for suggestion in suggestions {
400 trace!("suggestion");
401 // Make sure we've got a file associated with this suggestion and all
59b3b3ec
PA
402 // snippets point to the same file. Right now it's not clear what
403 // we would do with multiple files.
9ed82b57
AC
404 let file_names = suggestion
405 .solutions
406 .iter()
a9f64b20
PA
407 .flat_map(|s| s.replacements.iter())
408 .map(|r| &r.snippet.file_name);
409
410 let file_name = if let Some(file_name) = file_names.clone().next() {
411 file_name.clone()
412 } else {
413 trace!("rejecting as it has no solutions {:?}", suggestion);
414 continue;
b02ba377 415 };
a9f64b20
PA
416
417 if !file_names.clone().all(|f| f == &file_name) {
59b3b3ec 418 trace!("rejecting as it changes multiple files: {:?}", suggestion);
b02ba377
AC
419 continue;
420 }
421
422 file_map
423 .entry(file_name)
424 .or_insert_with(Vec::new)
425 .push(suggestion);
426 num_suggestion += 1;
427 }
428
429 debug!(
430 "collected {} suggestions for `{}`",
fa7a3877
AC
431 num_suggestion,
432 filename.display(),
b02ba377
AC
433 );
434
b02ba377
AC
435 for (file, suggestions) in file_map {
436 // Attempt to read the source code for this file. If this fails then
437 // that'd be pretty surprising, so log a message and otherwise keep
438 // going.
439 let code = match paths::read(file.as_ref()) {
440 Ok(s) => s,
441 Err(e) => {
442 warn!("failed to read `{}`: {}", file, e);
443 continue;
444 }
445 };
446 let num_suggestions = suggestions.len();
447 debug!("applying {} fixes to {}", num_suggestions, file);
448
876a5036
AC
449 // If this file doesn't already exist then we just read the original
450 // code, so save it. If the file already exists then the original code
451 // doesn't need to be updated as we've just read an interim state with
452 // some fixes but perhaps not all.
92485f8c
E
453 let fixed_file = fixes
454 .files
455 .entry(file.clone())
456 .or_insert_with(|| FixedFile {
457 errors_applying_fixes: Vec::new(),
458 fixes_applied: 0,
459 original_code: code.clone(),
876a5036
AC
460 });
461 let mut fixed = CodeFix::new(&code);
462
463 // As mentioned above in `rustfix_crate`, we don't immediately warn
464 // about suggestions that fail to apply here, and instead we save them
465 // off for later processing.
466 for suggestion in suggestions.iter().rev() {
467 match fixed.apply(suggestion) {
468 Ok(()) => fixed_file.fixes_applied += 1,
469 Err(e) => fixed_file.errors_applying_fixes.push(e.to_string()),
b02ba377
AC
470 }
471 }
876a5036 472 let new_code = fixed.finish()?;
92485f8c 473 fs::write(&file, new_code).with_context(|_| format!("failed to write file `{}`", file))?;
b02ba377
AC
474 }
475
876a5036 476 Ok(())
b02ba377
AC
477}
478
479fn exit_with(status: ExitStatus) -> ! {
480 #[cfg(unix)]
481 {
482 use std::os::unix::prelude::*;
483 if let Some(signal) = status.signal() {
484 eprintln!("child failed with signal `{}`", signal);
485 process::exit(2);
486 }
487 }
488 process::exit(status.code().unwrap_or(3));
489}
490
491fn log_failed_fix(stderr: &[u8]) -> Result<(), Error> {
492 let stderr = str::from_utf8(stderr).context("failed to parse rustc stderr as utf-8")?;
493
494 let diagnostics = stderr
495 .lines()
496 .filter(|x| !x.is_empty())
497 .filter_map(|line| serde_json::from_str::<Diagnostic>(line).ok());
498 let mut files = BTreeSet::new();
499 for diagnostic in diagnostics {
500 for span in diagnostic.spans.into_iter() {
501 files.insert(span.file_name);
502 }
503 }
504 let mut krate = None;
505 let mut prev_dash_dash_krate_name = false;
506 for arg in env::args() {
507 if prev_dash_dash_krate_name {
508 krate = Some(arg.clone());
509 }
510
511 if arg == "--crate-name" {
512 prev_dash_dash_krate_name = true;
513 } else {
514 prev_dash_dash_krate_name = false;
515 }
516 }
517
518 let files = files.into_iter().collect();
519 Message::FixFailed { files, krate }.post()?;
520
521 Ok(())
522}
fa7a3877
AC
523
524#[derive(Default)]
525struct FixArgs {
526 file: Option<PathBuf>,
b2b120e9 527 prepare_for_edition: PrepareFor,
80f9d318 528 idioms: bool,
fa7a3877
AC
529 enabled_edition: Option<String>,
530 other: Vec<OsString>,
4f784a10 531 primary_package: bool,
fa7a3877
AC
532}
533
b2b120e9
AC
534enum PrepareFor {
535 Next,
536 Edition(String),
537 None,
538}
539
540impl Default for PrepareFor {
541 fn default() -> PrepareFor {
542 PrepareFor::None
543 }
544}
545
fa7a3877
AC
546impl FixArgs {
547 fn get() -> FixArgs {
548 let mut ret = FixArgs::default();
549 for arg in env::args_os().skip(1) {
550 let path = PathBuf::from(arg);
92485f8c
E
551 if path.extension().and_then(|s| s.to_str()) == Some("rs") && path.exists() {
552 ret.file = Some(path);
553 continue;
fa7a3877
AC
554 }
555 if let Some(s) = path.to_str() {
556 let prefix = "--edition=";
557 if s.starts_with(prefix) {
558 ret.enabled_edition = Some(s[prefix.len()..].to_string());
92485f8c 559 continue;
fa7a3877
AC
560 }
561 }
562 ret.other.push(path.into());
563 }
b2b120e9
AC
564 if let Ok(s) = env::var(PREPARE_FOR_ENV) {
565 ret.prepare_for_edition = PrepareFor::Edition(s);
566 } else if env::var(EDITION_ENV).is_ok() {
567 ret.prepare_for_edition = PrepareFor::Next;
fa7a3877 568 }
80f9d318 569 ret.idioms = env::var(IDIOMS_ENV).is_ok();
4f784a10 570 ret.primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok();
8798bf0d 571 ret
fa7a3877
AC
572 }
573
574 fn apply(&self, cmd: &mut Command) {
575 if let Some(path) = &self.file {
576 cmd.arg(path);
577 }
92485f8c 578 cmd.args(&self.other).arg("--cap-lints=warn");
fa7a3877
AC
579 if let Some(edition) = &self.enabled_edition {
580 cmd.arg("--edition").arg(edition);
92485f8c
E
581 if self.idioms && self.primary_package && edition == "2018" {
582 cmd.arg("-Wrust-2018-idioms");
80f9d318 583 }
fa7a3877 584 }
4f784a10
AC
585 if self.primary_package {
586 if let Some(edition) = self.prepare_for_edition_resolve() {
587 cmd.arg("-W").arg(format!("rust-{}-compatibility", edition));
588 }
fa7a3877
AC
589 }
590 }
591
592 /// Verify that we're not both preparing for an enabled edition and enabling
593 /// the edition.
594 ///
595 /// This indicates that `cargo fix --prepare-for` is being executed out of
596 /// order with enabling the edition itself, meaning that we wouldn't
597 /// actually be able to fix anything! If it looks like this is happening
598 /// then yield an error to the user, indicating that this is happening.
599 fn verify_not_preparing_for_enabled_edition(&self) -> CargoResult<()> {
35f745ac
DW
600 let edition = match self.prepare_for_edition_resolve() {
601 Some(s) => s,
602 None => return Ok(()),
fa7a3877
AC
603 };
604 let enabled = match &self.enabled_edition {
605 Some(s) => s,
606 None => return Ok(()),
607 };
608 if edition != enabled {
92485f8c 609 return Ok(());
fa7a3877
AC
610 }
611 let path = match &self.file {
612 Some(s) => s,
613 None => return Ok(()),
614 };
615
616 Message::EditionAlreadyEnabled {
617 file: path.display().to_string(),
618 edition: edition.to_string(),
92485f8c
E
619 }
620 .post()?;
fa7a3877
AC
621
622 process::exit(1);
623 }
624
35f745ac
DW
625 fn prepare_for_edition_resolve(&self) -> Option<&str> {
626 match &self.prepare_for_edition {
627 PrepareFor::Edition(s) => Some(s),
628 PrepareFor::Next => Some(self.next_edition()),
629 PrepareFor::None => None,
630 }
631 }
632
b2b120e9
AC
633 fn next_edition(&self) -> &str {
634 match self.enabled_edition.as_ref().map(|s| &**s) {
635 // 2015 -> 2018,
636 None | Some("2015") => "2018",
637
638 // This'll probably be wrong in 2020, but that's future Cargo's
639 // problem. Eventually though we'll just add more editions here as
640 // necessary.
641 _ => "2018",
642 }
643 }
fa7a3877 644}