1 //! High-level overview of how `fix` works:
3 //! The main goal is to run `cargo check` to get rustc to emit JSON
4 //! diagnostics with suggested fixes that can be applied to the files on the
5 //! filesystem, and validate that those changes didn't break anything.
7 //! Cargo begins by launching a `LockServer` thread in the background to
8 //! listen for network connections to coordinate locking when multiple targets
9 //! are built simultaneously. It ensures each package has only one fix running
12 //! The `RustfixDiagnosticServer` is launched in a background thread (in
13 //! `JobQueue`) to listen for network connections to coordinate displaying
14 //! messages to the user on the console (so that multiple processes don't try
15 //! to print at the same time).
17 //! Cargo begins a normal `cargo check` operation with itself set as a proxy
18 //! for rustc by setting `primary_unit_rustc` in the build config. When
19 //! cargo launches rustc to check a crate, it is actually launching itself.
20 //! The `FIX_ENV` environment variable is set so that cargo knows it is in
23 //! Each proxied cargo-as-rustc detects it is in fix-proxy-mode (via `FIX_ENV`
24 //! environment variable in `main`) and does the following:
26 //! - Acquire a lock from the `LockServer` from the master cargo process.
27 //! - Launches the real rustc (`rustfix_and_fix`), looking at the JSON output
28 //! for suggested fixes.
29 //! - Uses the `rustfix` crate to apply the suggestions to the files on the
31 //! - If rustfix fails to apply any suggestions (for example, they are
32 //! overlapping), but at least some suggestions succeeded, it will try the
33 //! previous two steps up to 4 times as long as some suggestions succeed.
34 //! - Assuming there's at least one suggestion applied, and the suggestions
35 //! applied cleanly, rustc is run again to verify the suggestions didn't
36 //! break anything. The change will be backed out if it fails (unless
37 //! `--broken-code` is used).
38 //! - If there are any warnings or errors, rustc will be run one last time to
39 //! show them to the user.
41 use std
::collections
::{BTreeSet, HashMap, HashSet}
;
43 use std
::ffi
::OsString
;
44 use std
::path
::{Path, PathBuf}
;
45 use std
::process
::{self, Command, ExitStatus}
;
48 use anyhow
::{bail, Context, Error}
;
49 use cargo_util
::{paths, ProcessBuilder}
;
50 use log
::{debug, trace, warn}
;
51 use rustfix
::diagnostics
::Diagnostic
;
52 use rustfix
::{self, CodeFix}
;
54 use crate::core
::compiler
::RustcTargetData
;
55 use crate::core
::resolver
::features
::{FeatureOpts, FeatureResolver}
;
56 use crate::core
::resolver
::{HasDevUnits, Resolve, ResolveBehavior}
;
57 use crate::core
::{Edition, MaybePackage, Workspace}
;
58 use crate::ops
::{self, CompileOptions}
;
59 use crate::util
::diagnostic_server
::{Message, RustfixDiagnosticServer}
;
60 use crate::util
::errors
::CargoResult
;
61 use crate::util
::Config
;
62 use crate::util
::{existing_vcs_repo, LockServer, LockServerClient}
;
63 use crate::{drop_eprint, drop_eprintln}
;
65 const FIX_ENV
: &str = "__CARGO_FIX_PLZ";
66 const BROKEN_CODE_ENV
: &str = "__CARGO_FIX_BROKEN_CODE";
67 const EDITION_ENV
: &str = "__CARGO_FIX_EDITION";
68 const IDIOMS_ENV
: &str = "__CARGO_FIX_IDIOMS";
70 pub struct FixOptions
{
73 pub compile_opts
: CompileOptions
,
74 pub allow_dirty
: bool
,
75 pub allow_no_vcs
: bool
,
76 pub allow_staged
: bool
,
77 pub broken_code
: bool
,
80 pub fn fix(ws
: &Workspace
<'_
>, opts
: &mut FixOptions
) -> CargoResult
<()> {
81 check_version_control(ws
.config(), opts
)?
;
83 check_resolver_change(ws
, opts
)?
;
86 // Spin up our lock server, which our subprocesses will use to synchronize fixes.
87 let lock_server
= LockServer
::new()?
;
88 let mut wrapper
= ProcessBuilder
::new(env
::current_exe()?
);
89 wrapper
.env(FIX_ENV
, lock_server
.addr().to_string());
90 let _started
= lock_server
.start()?
;
92 opts
.compile_opts
.build_config
.force_rebuild
= true;
95 wrapper
.env(BROKEN_CODE_ENV
, "1");
99 wrapper
.env(EDITION_ENV
, "1");
102 wrapper
.env(IDIOMS_ENV
, "1");
108 .rustfix_diagnostic_server
109 .borrow_mut() = Some(RustfixDiagnosticServer
::new()?
);
111 if let Some(server
) = opts
114 .rustfix_diagnostic_server
118 server
.configure(&mut wrapper
);
121 let rustc
= ws
.config().load_global_rustc(Some(ws
))?
;
122 wrapper
.arg(&rustc
.path
);
124 // primary crates are compiled using a cargo subprocess to do extra work of applying fixes and
125 // repeating build until there are no more changes to be applied
126 opts
.compile_opts
.build_config
.primary_unit_rustc
= Some(wrapper
);
128 ops
::compile(ws
, &opts
.compile_opts
)?
;
132 fn check_version_control(config
: &Config
, opts
: &FixOptions
) -> CargoResult
<()> {
133 if opts
.allow_no_vcs
{
136 if !existing_vcs_repo(config
.cwd(), config
.cwd()) {
138 "no VCS found for this package and `cargo fix` can potentially \
139 perform destructive changes; if you'd like to suppress this \
140 error pass `--allow-no-vcs`"
144 if opts
.allow_dirty
&& opts
.allow_staged
{
148 let mut dirty_files
= Vec
::new();
149 let mut staged_files
= Vec
::new();
150 if let Ok(repo
) = git2
::Repository
::discover(config
.cwd()) {
151 let mut repo_opts
= git2
::StatusOptions
::new();
152 repo_opts
.include_ignored(false);
153 for status
in repo
.statuses(Some(&mut repo_opts
))?
.iter() {
154 if let Some(path
) = status
.path() {
155 match status
.status() {
156 git2
::Status
::CURRENT
=> (),
157 git2
::Status
::INDEX_NEW
158 | git2
::Status
::INDEX_MODIFIED
159 | git2
::Status
::INDEX_DELETED
160 | git2
::Status
::INDEX_RENAMED
161 | git2
::Status
::INDEX_TYPECHANGE
=> {
162 if !opts
.allow_staged
{
163 staged_files
.push(path
.to_string())
167 if !opts
.allow_dirty
{
168 dirty_files
.push(path
.to_string())
176 if dirty_files
.is_empty() && staged_files
.is_empty() {
180 let mut files_list
= String
::new();
181 for file
in dirty_files
{
182 files_list
.push_str(" * ");
183 files_list
.push_str(&file
);
184 files_list
.push_str(" (dirty)\n");
186 for file
in staged_files
{
187 files_list
.push_str(" * ");
188 files_list
.push_str(&file
);
189 files_list
.push_str(" (staged)\n");
193 "the working directory of this package has uncommitted changes, and \
194 `cargo fix` can potentially perform destructive changes; if you'd \
195 like to suppress this error pass `--allow-dirty`, `--allow-staged`, \
196 or commit the changes to these files:\n\
204 fn check_resolver_change(ws
: &Workspace
<'_
>, opts
: &FixOptions
) -> CargoResult
<()> {
205 let root
= ws
.root_maybe();
207 MaybePackage
::Package(root_pkg
) => {
208 if root_pkg
.manifest().resolve_behavior().is_some() {
209 // If explicitly specified by the user, no need to check.
212 // Only trigger if updating the root package from 2018.
213 let pkgs
= opts
.compile_opts
.spec
.get_packages(ws
)?
;
214 if !pkgs
.iter().any(|&pkg
| pkg
== root_pkg
) {
215 // The root is not being migrated.
218 if root_pkg
.manifest().edition() != Edition
::Edition2018
{
219 // V1 to V2 only happens on 2018 to 2021.
223 MaybePackage
::Virtual(_vm
) => {
224 // Virtual workspaces don't have a global edition to set (yet).
228 // 2018 without `resolver` set must be V1
229 assert_eq
!(ws
.resolve_behavior(), ResolveBehavior
::V1
);
230 let specs
= opts
.compile_opts
.spec
.to_package_id_specs(ws
)?
;
231 let target_data
= RustcTargetData
::new(ws
, &opts
.compile_opts
.build_config
.requested_kinds
)?
;
232 // HasDevUnits::No because that may uncover more differences.
233 // This is not the same as what `cargo fix` is doing, since it is doing
234 // `--all-targets` which includes dev dependencies.
235 let ws_resolve
= ops
::resolve_ws_with_opts(
238 &opts
.compile_opts
.build_config
.requested_kinds
,
239 &opts
.compile_opts
.cli_features
,
242 crate::core
::resolver
::features
::ForceAllTargets
::No
,
245 let feature_opts
= FeatureOpts
::new_behavior(ResolveBehavior
::V2
, HasDevUnits
::No
);
246 let v2_features
= FeatureResolver
::resolve(
249 &ws_resolve
.targeted_resolve
,
251 &opts
.compile_opts
.cli_features
,
253 &opts
.compile_opts
.build_config
.requested_kinds
,
257 let differences
= v2_features
.compare_legacy(&ws_resolve
.resolved_features
);
258 if differences
.is_empty() {
259 // Nothing is different, nothing to report.
262 let config
= ws
.config();
264 "Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo.",
268 "This may cause some dependencies to be built with fewer features enabled than previously."
272 "More information about the resolver changes may be found \
273 at https://doc.rust-lang.org/nightly/edition-guide/rust-2021/default-cargo-resolver.html"
277 "When building the following dependencies, \
278 the given features will no longer be used:\n"
280 for ((pkg_id
, for_host
), removed
) in differences
{
281 drop_eprint
!(config
, " {}", pkg_id
);
283 drop_eprint
!(config
, " (as host dependency)");
285 drop_eprint
!(config
, ": ");
286 let joined
: Vec
<_
> = removed
.iter().map(|s
| s
.as_str()).collect();
287 drop_eprintln
!(config
, "{}", joined
.join(", "));
289 drop_eprint
!(config
, "\n");
290 report_maybe_diesel(config
, &ws_resolve
.targeted_resolve
)?
;
294 fn report_maybe_diesel(config
: &Config
, resolve
: &Resolve
) -> CargoResult
<()> {
297 .any(|pid
| pid
.name() == "diesel" && pid
.version().major
== 1)
298 && resolve
.iter().any(|pid
| pid
.name() == "diesel_migrations")
302 This project appears to use both diesel and diesel_migrations. These packages have
303 a known issue where the build may fail due to the version 2 resolver preventing
304 feature unification between those two packages. See
305 <https://github.com/rust-lang/cargo/issues/9450> for some potential workarounds.
312 /// Entry point for `cargo` running as a proxy for `rustc`.
314 /// This is called every time `cargo` is run to check if it is in proxy mode.
316 /// Returns `false` if `fix` is not being run (not in proxy mode). Returns
317 /// `true` if in `fix` proxy mode, and the fix was complete without any
318 /// warnings or errors. If there are warnings or errors, this does not return,
319 /// and the process exits with the corresponding `rustc` exit code.
320 pub fn fix_maybe_exec_rustc(config
: &Config
) -> CargoResult
<bool
> {
321 let lock_addr
= match env
::var(FIX_ENV
) {
323 Err(_
) => return Ok(false),
326 let args
= FixArgs
::get()?
;
327 trace
!("cargo-fix as rustc got file {:?}", args
.file
);
329 let workspace_rustc
= std
::env
::var("RUSTC_WORKSPACE_WRAPPER")
332 let rustc
= ProcessBuilder
::new(&args
.rustc
).wrapped(workspace_rustc
.as_ref());
334 trace
!("start rustfixing {:?}", args
.file
);
335 let fixes
= rustfix_crate(&lock_addr
, &rustc
, &args
.file
, &args
, config
)?
;
337 // Ok now we have our final goal of testing out the changes that we applied.
338 // If these changes went awry and actually started to cause the crate to
339 // *stop* compiling then we want to back them out and continue to print
340 // warnings to the user.
342 // If we didn't actually make any changes then we can immediately execute the
343 // new rustc, and otherwise we capture the output to hide it in the scenario
344 // that we have to back it all out.
345 if !fixes
.files
.is_empty() {
346 let mut cmd
= rustc
.build_command();
347 args
.apply(&mut cmd
, config
);
348 cmd
.arg("--error-format=json");
349 let output
= cmd
.output().context("failed to spawn rustc")?
;
351 if output
.status
.success() {
352 for (path
, file
) in fixes
.files
.iter() {
355 fixes
: file
.fixes_applied
,
361 // If we succeeded then we'll want to commit to the changes we made, if
362 // any. If stderr is empty then there's no need for the final exec at
363 // the end, we just bail out here.
364 if output
.status
.success() && output
.stderr
.is_empty() {
368 // Otherwise, if our rustc just failed, then that means that we broke the
369 // user's code with our changes. Back out everything and fall through
370 // below to recompile again.
371 if !output
.status
.success() {
372 if env
::var_os(BROKEN_CODE_ENV
).is_none() {
373 for (path
, file
) in fixes
.files
.iter() {
374 paths
::write(path
, &file
.original_code
)?
;
377 log_failed_fix(&output
.stderr
)?
;
381 // This final fall-through handles multiple cases;
382 // - If the fix failed, show the original warnings and suggestions.
383 // - If `--broken-code`, show the error messages.
384 // - If the fix succeeded, show any remaining warnings.
385 let mut cmd
= rustc
.build_command();
386 args
.apply(&mut cmd
, config
);
387 for arg
in args
.format_args
{
388 // Add any json/error format arguments that Cargo wants. This allows
389 // things like colored output to work correctly.
392 exit_with(cmd
.status().context("failed to spawn rustc")?
);
397 files
: HashMap
<String
, FixedFile
>,
401 errors_applying_fixes
: Vec
<String
>,
403 original_code
: String
,
406 /// Attempts to apply fixes to a single crate.
408 /// This runs `rustc` (possibly multiple times) to gather suggestions from the
409 /// compiler and applies them to the files on disk.
412 rustc
: &ProcessBuilder
,
416 ) -> Result
<FixedCrate
, Error
> {
417 args
.check_edition_and_send_status(config
)?
;
419 // First up, we want to make sure that each crate is only checked by one
420 // process at a time. If two invocations concurrently check a crate then
421 // it's likely to corrupt it.
423 // Historically this used per-source-file locking, then per-package
424 // locking. It now uses a single, global lock as some users do things like
425 // #[path] or include!() of shared files between packages. Serializing
426 // makes it slower, but is the only safe way to prevent concurrent
428 let _lock
= LockServerClient
::lock(&lock_addr
.parse()?
, "global")?
;
430 // Next up, this is a bit suspicious, but we *iteratively* execute rustc and
431 // collect suggestions to feed to rustfix. Once we hit our limit of times to
432 // execute rustc or we appear to be reaching a fixed point we stop running
435 // This is currently done to handle code like:
439 // where there are two fixes to happen here: `crate::foo::<crate::Bar>()`.
440 // The spans for these two suggestions are overlapping and its difficult in
441 // the compiler to **not** have overlapping spans here. As a result, a naive
442 // implementation would feed the two compiler suggestions for the above fix
443 // into `rustfix`, but one would be rejected because it overlaps with the
446 // In this case though, both suggestions are valid and can be automatically
447 // applied! To handle this case we execute rustc multiple times, collecting
448 // fixes each time we do so. Along the way we discard any suggestions that
449 // failed to apply, assuming that they can be fixed the next time we run
452 // Naturally, we want a few protections in place here though to avoid looping
453 // forever or otherwise losing data. To that end we have a few termination
456 // * Do this whole process a fixed number of times. In theory we probably
457 // need an infinite number of times to apply fixes, but we're not gonna
458 // sit around waiting for that.
459 // * If it looks like a fix genuinely can't be applied we need to bail out.
460 // Detect this when a fix fails to get applied *and* no suggestions
461 // successfully applied to the same file. In that case looks like we
462 // definitely can't make progress, so bail out.
463 let mut fixes
= FixedCrate
::default();
464 let mut last_fix_counts
= HashMap
::new();
465 let iterations
= env
::var("CARGO_FIX_MAX_RETRIES")
467 .and_then(|n
| n
.parse().ok())
469 for _
in 0..iterations
{
470 last_fix_counts
.clear();
471 for (path
, file
) in fixes
.files
.iter_mut() {
472 last_fix_counts
.insert(path
.clone(), file
.fixes_applied
);
473 // We'll generate new errors below.
474 file
.errors_applying_fixes
.clear();
476 rustfix_and_fix(&mut fixes
, rustc
, filename
, args
, config
)?
;
477 let mut progress_yet_to_be_made
= false;
478 for (path
, file
) in fixes
.files
.iter_mut() {
479 if file
.errors_applying_fixes
.is_empty() {
482 // If anything was successfully fixed *and* there's at least one
483 // error, then assume the error was spurious and we'll try again on
484 // the next iteration.
485 if file
.fixes_applied
!= *last_fix_counts
.get(path
).unwrap_or(&0) {
486 progress_yet_to_be_made
= true;
489 if !progress_yet_to_be_made
{
494 // Any errors still remaining at this point need to be reported as probably
495 // bugs in Cargo and/or rustfix.
496 for (path
, file
) in fixes
.files
.iter_mut() {
497 for error
in file
.errors_applying_fixes
.drain(..) {
498 Message
::ReplaceFailed
{
509 /// Executes `rustc` to apply one round of suggestions to the crate in question.
511 /// This will fill in the `fixes` map with original code, suggestions applied,
512 /// and any errors encountered while fixing files.
514 fixes
: &mut FixedCrate
,
515 rustc
: &ProcessBuilder
,
519 ) -> Result
<(), Error
> {
520 // If not empty, filter by these lints.
521 // TODO: implement a way to specify this.
522 let only
= HashSet
::new();
524 let mut cmd
= rustc
.build_command();
525 cmd
.arg("--error-format=json");
526 args
.apply(&mut cmd
, config
);
527 let output
= cmd
.output().with_context(|| {
529 "failed to execute `{}`",
530 rustc
.get_program().to_string_lossy()
534 // If rustc didn't succeed for whatever reasons then we're very likely to be
535 // looking at otherwise broken code. Let's not make things accidentally
536 // worse by applying fixes where a bug could cause *more* broken code.
537 // Instead, punt upwards which will reexec rustc over the original code,
538 // displaying pretty versions of the diagnostics we just read out.
539 if !output
.status
.success() && env
::var_os(BROKEN_CODE_ENV
).is_none() {
541 "rustfixing `{:?}` failed, rustc exited with {:?}",
548 let fix_mode
= env
::var_os("__CARGO_FIX_YOLO")
549 .map(|_
| rustfix
::Filter
::Everything
)
550 .unwrap_or(rustfix
::Filter
::MachineApplicableOnly
);
552 // Sift through the output of the compiler to look for JSON messages.
553 // indicating fixes that we can apply.
554 let stderr
= str::from_utf8(&output
.stderr
).context("failed to parse rustc stderr as UTF-8")?
;
556 let suggestions
= stderr
558 .filter(|x
| !x
.is_empty())
559 .inspect(|y
| trace
!("line: {}", y
))
560 // Parse each line of stderr, ignoring errors, as they may not all be JSON.
561 .filter_map(|line
| serde_json
::from_str
::<Diagnostic
>(line
).ok())
562 // From each diagnostic, try to extract suggestions from rustc.
563 .filter_map(|diag
| rustfix
::collect_suggestions(&diag
, &only
, fix_mode
));
565 // Collect suggestions by file so we can apply them one at a time later.
566 let mut file_map
= HashMap
::new();
567 let mut num_suggestion
= 0;
568 for suggestion
in suggestions
{
569 trace
!("suggestion");
570 // Make sure we've got a file associated with this suggestion and all
571 // snippets point to the same file. Right now it's not clear what
572 // we would do with multiple files.
573 let file_names
= suggestion
576 .flat_map(|s
| s
.replacements
.iter())
577 .map(|r
| &r
.snippet
.file_name
);
579 let file_name
= if let Some(file_name
) = file_names
.clone().next() {
582 trace
!("rejecting as it has no solutions {:?}", suggestion
);
586 if !file_names
.clone().all(|f
| f
== &file_name
) {
587 trace
!("rejecting as it changes multiple files: {:?}", suggestion
);
593 .or_insert_with(Vec
::new
)
599 "collected {} suggestions for `{}`",
604 for (file
, suggestions
) in file_map
{
605 // Attempt to read the source code for this file. If this fails then
606 // that'd be pretty surprising, so log a message and otherwise keep
608 let code
= match paths
::read(file
.as_ref()) {
611 warn
!("failed to read `{}`: {}", file
, e
);
615 let num_suggestions
= suggestions
.len();
616 debug
!("applying {} fixes to {}", num_suggestions
, file
);
618 // If this file doesn't already exist then we just read the original
619 // code, so save it. If the file already exists then the original code
620 // doesn't need to be updated as we've just read an interim state with
621 // some fixes but perhaps not all.
622 let fixed_file
= fixes
625 .or_insert_with(|| FixedFile
{
626 errors_applying_fixes
: Vec
::new(),
628 original_code
: code
.clone(),
630 let mut fixed
= CodeFix
::new(&code
);
632 // As mentioned above in `rustfix_crate`, we don't immediately warn
633 // about suggestions that fail to apply here, and instead we save them
634 // off for later processing.
635 for suggestion
in suggestions
.iter().rev() {
636 match fixed
.apply(suggestion
) {
637 Ok(()) => fixed_file
.fixes_applied
+= 1,
638 Err(e
) => fixed_file
.errors_applying_fixes
.push(e
.to_string()),
641 let new_code
= fixed
.finish()?
;
642 paths
::write(&file
, new_code
)?
;
648 fn exit_with(status
: ExitStatus
) -> ! {
652 use std
::os
::unix
::prelude
::*;
653 if let Some(signal
) = status
.signal() {
655 std
::io
::stderr().lock(),
656 "child failed with signal `{}`",
662 process
::exit(status
.code().unwrap_or(3));
665 fn log_failed_fix(stderr
: &[u8]) -> Result
<(), Error
> {
666 let stderr
= str::from_utf8(stderr
).context("failed to parse rustc stderr as utf-8")?
;
668 let diagnostics
= stderr
670 .filter(|x
| !x
.is_empty())
671 .filter_map(|line
| serde_json
::from_str
::<Diagnostic
>(line
).ok());
672 let mut files
= BTreeSet
::new();
673 let mut errors
= Vec
::new();
674 for diagnostic
in diagnostics
{
675 errors
.push(diagnostic
.rendered
.unwrap_or(diagnostic
.message
));
676 for span
in diagnostic
.spans
.into_iter() {
677 files
.insert(span
.file_name
);
680 let mut krate
= None
;
681 let mut prev_dash_dash_krate_name
= false;
682 for arg
in env
::args() {
683 if prev_dash_dash_krate_name
{
684 krate
= Some(arg
.clone());
687 if arg
== "--crate-name" {
688 prev_dash_dash_krate_name
= true;
690 prev_dash_dash_krate_name
= false;
694 let files
= files
.into_iter().collect();
705 /// Various command-line options and settings used when `cargo` is running as
706 /// a proxy for `rustc` during the fix operation.
708 /// This is the `.rs` file that is being fixed.
710 /// If `--edition` is used to migrate to the next edition, this is the
711 /// edition we are migrating towards.
712 prepare_for_edition
: Option
<Edition
>,
713 /// `true` if `--edition-idioms` is enabled.
715 /// The current edition.
717 /// `None` if on 2015.
718 enabled_edition
: Option
<Edition
>,
719 /// Other command-line arguments not reflected by other fields in
721 other
: Vec
<OsString
>,
722 /// Path to the `rustc` executable.
724 /// Console output flags (`--error-format`, `--json`, etc.).
726 /// The normal fix procedure always uses `--json`, so it overrides what
727 /// Cargo normally passes when applying fixes. When displaying warnings or
728 /// errors, it will use these flags.
729 format_args
: Vec
<String
>,
733 fn get() -> Result
<FixArgs
, Error
> {
734 let rustc
= env
::args_os()
737 .ok_or_else(|| anyhow
::anyhow
!("expected rustc as first argument"))?
;
739 let mut enabled_edition
= None
;
740 let mut other
= Vec
::new();
741 let mut format_args
= Vec
::new();
743 for arg
in env
::args_os().skip(2) {
744 let path
= PathBuf
::from(arg
);
745 if path
.extension().and_then(|s
| s
.to_str()) == Some("rs") && path
.exists() {
749 if let Some(s
) = path
.to_str() {
750 if let Some(edition
) = s
.strip_prefix("--edition=") {
751 enabled_edition
= Some(edition
.parse()?
);
754 if s
.starts_with("--error-format=") || s
.starts_with("--json=") {
755 // Cargo may add error-format in some cases, but `cargo
756 // fix` wants to add its own.
757 format_args
.push(s
.to_string());
761 other
.push(path
.into());
763 let file
= file
.ok_or_else(|| anyhow
::anyhow
!("could not find .rs file in rustc args"))?
;
764 let idioms
= env
::var(IDIOMS_ENV
).is_ok();
766 let prepare_for_edition
= env
::var(EDITION_ENV
).ok().map(|_
| {
768 .unwrap_or(Edition
::Edition2015
)
783 fn apply(&self, cmd
: &mut Command
, config
: &Config
) {
785 cmd
.args(&self.other
).arg("--cap-lints=warn");
786 if let Some(edition
) = self.enabled_edition
{
787 cmd
.arg("--edition").arg(edition
.to_string());
788 if self.idioms
&& edition
.supports_idiom_lint() {
789 cmd
.arg(format
!("-Wrust-{}-idioms", edition
));
793 if let Some(edition
) = self.prepare_for_edition
{
794 if edition
.supports_compat_lint() {
795 if config
.nightly_features_allowed
{
796 cmd
.arg("--force-warns")
797 .arg(format
!("rust-{}-compatibility", edition
))
798 .arg("-Zunstable-options");
800 cmd
.arg("-W").arg(format
!("rust-{}-compatibility", edition
));
806 /// Validates the edition, and sends a message indicating what is being
808 fn check_edition_and_send_status(&self, config
: &Config
) -> CargoResult
<()> {
809 let to_edition
= match self.prepare_for_edition
{
812 return Message
::Fixing
{
813 file
: self.file
.display().to_string(),
818 // Unfortunately determining which cargo targets are being built
819 // isn't easy, and each target can be a different edition. The
820 // cargo-as-rustc fix wrapper doesn't know anything about the
821 // workspace, so it can't check for the `cargo-features` unstable
822 // opt-in. As a compromise, this just restricts to the nightly
825 // Unfortunately this results in a pretty poor error message when
826 // multiple jobs run in parallel (the error appears multiple
827 // times). Hopefully this doesn't happen often in practice.
828 if !to_edition
.is_stable() && !config
.nightly_features_allowed
{
830 "cannot migrate {} to edition {to_edition}\n\
831 Edition {to_edition} is unstable and not allowed in this release, \
832 consider trying the nightly release channel.",
834 to_edition
= to_edition
837 let from_edition
= self.enabled_edition
.unwrap_or(Edition
::Edition2015
);
838 if from_edition
== to_edition
{
839 Message
::EditionAlreadyEnabled
{
840 file
: self.file
.display().to_string(),
846 file
: self.file
.display().to_string(),