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