From b3d865e11c1c496e4803afcb8cadc35200b46e0d Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 17 Feb 2021 20:11:15 -0800 Subject: [PATCH 01/11] Some minor refactoring of `fix` code. There shouldn't be any functional changes here. * Some doc comments. * Construct `FixArgs` at once so it doesn't need to bother with unnecessary Options. * Remove IdiomEditionMismatch, it is not used. * Use a general deduping mechanism for fix messages. --- src/cargo/ops/fix.rs | 144 +++++++++++++++------------- src/cargo/util/diagnostic_server.rs | 48 ++-------- 2 files changed, 83 insertions(+), 109 deletions(-) diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 1ebcef67bbc..0ba8b41c6b4 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -45,7 +45,7 @@ use std::path::{Path, PathBuf}; use std::process::{self, Command, ExitStatus}; use std::str; -use anyhow::{Context, Error}; +use anyhow::{bail, Context, Error}; use log::{debug, trace, warn}; use rustfix::diagnostics::Diagnostic; use rustfix::{self, CodeFix}; @@ -130,7 +130,7 @@ fn check_version_control(config: &Config, opts: &FixOptions<'_>) -> CargoResult< return Ok(()); } if !existing_vcs_repo(config.cwd(), config.cwd()) { - anyhow::bail!( + bail!( "no VCS found for this package and `cargo fix` can potentially \ perform destructive changes; if you'd like to suppress this \ error pass `--allow-no-vcs`" @@ -185,7 +185,7 @@ fn check_version_control(config: &Config, opts: &FixOptions<'_>) -> CargoResult< files_list.push_str(" (staged)\n"); } - anyhow::bail!( + bail!( "the working directory of this package has uncommitted changes, and \ `cargo fix` can potentially perform destructive changes; if you'd \ like to suppress this error pass `--allow-dirty`, `--allow-staged`, \ @@ -197,6 +197,14 @@ fn check_version_control(config: &Config, opts: &FixOptions<'_>) -> CargoResult< ); } +/// Entry point for `cargo` running as a proxy for `rustc`. +/// +/// This is called every time `cargo` is run to check if it is in proxy mode. +/// +/// Returns `false` if `fix` is not being run (not in proxy mode). Returns +/// `true` if in `fix` proxy mode, and the fix was complete without any +/// warnings or errors. If there are warnings or errors, this does not return, +/// and the process exits with the corresponding `rustc` exit code. pub fn fix_maybe_exec_rustc() -> CargoResult { let lock_addr = match env::var(FIX_ENV) { Ok(s) => s, @@ -206,17 +214,13 @@ pub fn fix_maybe_exec_rustc() -> CargoResult { let args = FixArgs::get()?; trace!("cargo-fix as rustc got file {:?}", args.file); - let rustc = args.rustc.as_ref().expect("fix wrapper rustc was not set"); let workspace_rustc = std::env::var("RUSTC_WORKSPACE_WRAPPER") .map(PathBuf::from) .ok(); - let rustc = util::process(rustc).wrapped(workspace_rustc.as_ref()); + let rustc = util::process(&args.rustc).wrapped(workspace_rustc.as_ref()); - let mut fixes = FixedCrate::default(); - if let Some(path) = &args.file { - trace!("start rustfixing {:?}", path); - fixes = rustfix_crate(&lock_addr, &rustc, path, &args)?; - } + trace!("start rustfixing {:?}", args.file); + let fixes = rustfix_crate(&lock_addr, &rustc, &args.file, &args)?; // Ok now we have our final goal of testing out the changes that we applied. // If these changes went awry and actually started to cause the crate to @@ -287,6 +291,10 @@ struct FixedFile { original_code: String, } +/// Attempts to apply fixes to a single crate. +/// +/// This runs `rustc` (possibly multiple times) to gather suggestions from the +/// compiler and applies them to the files on disk. fn rustfix_crate( lock_addr: &str, rustc: &ProcessBuilder, @@ -578,70 +586,92 @@ fn log_failed_fix(stderr: &[u8]) -> Result<(), Error> { Ok(()) } -#[derive(Default)] +/// Various command-line options and settings used when `cargo` is running as +/// a proxy for `rustc` during the fix operation. struct FixArgs { - file: Option, - prepare_for_edition: PrepareFor, + /// This is the `.rs` file that is being fixed. + file: PathBuf, + /// If `--edition` is used to migrate to the next edition, this is the + /// edition we are migrating towards. + prepare_for_edition: Option, + /// `true` if `--edition-idioms` is enabled. idioms: bool, + /// The current edition. + /// + /// `None` if on 2015. enabled_edition: Option, + /// Other command-line arguments not reflected by other fields in + /// `FixArgs`. other: Vec, - rustc: Option, + /// Path to the `rustc` executable. + rustc: PathBuf, + /// Console output flags (`--error-format`, `--json`, etc.). + /// + /// The normal fix procedure always uses `--json`, so it overrides what + /// Cargo normally passes when applying fixes. When displaying warnings or + /// errors, it will use these flags. format_args: Vec, } -enum PrepareFor { - Next, - Edition(Edition), - None, -} - -impl Default for PrepareFor { - fn default() -> PrepareFor { - PrepareFor::None - } -} - impl FixArgs { fn get() -> Result { - let mut ret = FixArgs::default(); - - ret.rustc = env::args_os().nth(1).map(PathBuf::from); + let rustc = env::args_os() + .nth(1) + .map(PathBuf::from) + .ok_or_else(|| anyhow::anyhow!("expected rustc as first argument"))?; + let mut file = None; + let mut enabled_edition = None; + let mut other = Vec::new(); + let mut format_args = Vec::new(); for arg in env::args_os().skip(2) { let path = PathBuf::from(arg); if path.extension().and_then(|s| s.to_str()) == Some("rs") && path.exists() { - ret.file = Some(path); + file = Some(path); continue; } if let Some(s) = path.to_str() { if let Some(edition) = s.strip_prefix("--edition=") { - ret.enabled_edition = Some(edition.parse()?); + enabled_edition = Some(edition.parse()?); continue; } if s.starts_with("--error-format=") || s.starts_with("--json=") { // Cargo may add error-format in some cases, but `cargo // fix` wants to add its own. - ret.format_args.push(s.to_string()); + format_args.push(s.to_string()); continue; } } - ret.other.push(path.into()); + other.push(path.into()); } - if let Ok(s) = env::var(PREPARE_FOR_ENV) { - ret.prepare_for_edition = PrepareFor::Edition(s.parse()?); + let file = file.ok_or_else(|| anyhow::anyhow!("could not find .rs file in rustc args"))?; + let idioms = env::var(IDIOMS_ENV).is_ok(); + + let prepare_for_edition = if let Ok(s) = env::var(PREPARE_FOR_ENV) { + Some(s.parse()?) } else if env::var(EDITION_ENV).is_ok() { - ret.prepare_for_edition = PrepareFor::Next; - } + match enabled_edition { + None | Some(Edition::Edition2015) => Some(Edition::Edition2018), + Some(Edition::Edition2018) => Some(Edition::Edition2018), // TODO: Change to 2021 when rustc is ready for it. + Some(Edition::Edition2021) => Some(Edition::Edition2021), + } + } else { + None + }; - ret.idioms = env::var(IDIOMS_ENV).is_ok(); - Ok(ret) + Ok(FixArgs { + file, + prepare_for_edition, + idioms, + enabled_edition, + other, + rustc, + format_args, + }) } fn apply(&self, cmd: &mut Command) { - if let Some(path) = &self.file { - cmd.arg(path); - } - + cmd.arg(&self.file); cmd.args(&self.other).arg("--cap-lints=warn"); if let Some(edition) = self.enabled_edition { cmd.arg("--edition").arg(edition.to_string()); @@ -650,7 +680,7 @@ impl FixArgs { } } - if let Some(edition) = self.prepare_for_edition_resolve() { + if let Some(edition) = self.prepare_for_edition { cmd.arg("-W").arg(format!("rust-{}-compatibility", edition)); } } @@ -663,7 +693,7 @@ impl FixArgs { /// actually be able to fix anything! If it looks like this is happening /// then yield an error to the user, indicating that this is happening. fn verify_not_preparing_for_enabled_edition(&self) -> CargoResult<()> { - let edition = match self.prepare_for_edition_resolve() { + let edition = match self.prepare_for_edition { Some(s) => s, None => return Ok(()), }; @@ -674,33 +704,13 @@ impl FixArgs { if edition != enabled { return Ok(()); } - let path = match &self.file { - Some(s) => s, - None => return Ok(()), - }; Message::EditionAlreadyEnabled { - file: path.display().to_string(), - edition: edition.to_string(), + file: self.file.display().to_string(), + edition, } .post()?; process::exit(1); } - - fn prepare_for_edition_resolve(&self) -> Option { - match self.prepare_for_edition { - PrepareFor::Edition(s) => Some(s), - PrepareFor::Next => Some(self.next_edition()), - PrepareFor::None => None, - } - } - - fn next_edition(&self) -> Edition { - match self.enabled_edition { - None | Some(Edition::Edition2015) => Edition::Edition2018, - Some(Edition::Edition2018) => Edition::Edition2018, // TODO: Change to 2021 when rustc is ready for it. - Some(Edition::Edition2021) => Edition::Edition2021, - } - } } diff --git a/src/cargo/util/diagnostic_server.rs b/src/cargo/util/diagnostic_server.rs index 705c1c989ae..dd75e1c185f 100644 --- a/src/cargo/util/diagnostic_server.rs +++ b/src/cargo/util/diagnostic_server.rs @@ -13,6 +13,7 @@ use anyhow::{Context, Error}; use log::warn; use serde::{Deserialize, Serialize}; +use crate::core::Edition; use crate::util::errors::CargoResult; use crate::util::{Config, ProcessBuilder}; @@ -28,7 +29,7 @@ const PLEASE_REPORT_THIS_BUG: &str = fixing code with the `--broken-code` flag\n\n\ "; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Hash, Eq, PartialEq, Clone)] pub enum Message { Fixing { file: String, @@ -45,12 +46,7 @@ pub enum Message { }, EditionAlreadyEnabled { file: String, - edition: String, - }, - IdiomEditionMismatch { - file: String, - idioms: String, - edition: Option, + edition: Edition, }, } @@ -80,16 +76,14 @@ impl Message { pub struct DiagnosticPrinter<'a> { config: &'a Config, - edition_already_enabled: HashSet, - idiom_mismatch: HashSet, + dedupe: HashSet, } impl<'a> DiagnosticPrinter<'a> { pub fn new(config: &'a Config) -> DiagnosticPrinter<'a> { DiagnosticPrinter { config, - edition_already_enabled: HashSet::new(), - idiom_mismatch: HashSet::new(), + dedupe: HashSet::new(), } } @@ -158,8 +152,7 @@ impl<'a> DiagnosticPrinter<'a> { Ok(()) } Message::EditionAlreadyEnabled { file, edition } => { - // Like above, only warn once per file - if !self.edition_already_enabled.insert(file.clone()) { + if !self.dedupe.insert(msg.clone()) { return Ok(()); } @@ -181,35 +174,6 @@ information about transitioning to the {0} edition see: self.config.shell().error(&msg)?; Ok(()) } - Message::IdiomEditionMismatch { - file, - idioms, - edition, - } => { - // Same as above - if !self.idiom_mismatch.insert(file.clone()) { - return Ok(()); - } - self.config.shell().error(&format!( - "\ -cannot migrate to the idioms of the {} edition for `{}` -because it is compiled {}, which doesn't match {0} - -consider migrating to the {0} edition by adding `edition = '{0}'` to -`Cargo.toml` and then rerunning this command; a more detailed transition -guide can be found at - - https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html -", - idioms, - file, - match edition { - Some(s) => format!("with the {} edition", s), - None => "without an edition".to_string(), - }, - ))?; - Ok(()) - } } } } From 820537c70643557a6376863d52911318a9770111 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 17 Feb 2021 20:41:38 -0800 Subject: [PATCH 02/11] Change Fixing to Fixed, and add a verbose "Fixing". What was previously "Fixing" was a message for after the fixes had been applied. I think it would be clearer if it said "Fixed", to indicate that the fixes had actually finished. The new "Fixing" is posted just before it starts. This is verbose-only since it is a little noisy. --- crates/cargo-test-support/src/lib.rs | 1 + src/cargo/ops/fix.rs | 9 +++- src/cargo/util/diagnostic_server.rs | 11 ++++- tests/testsuite/fix.rs | 72 ++++++++++++++-------------- tests/testsuite/glob_targets.rs | 4 ++ 5 files changed, 58 insertions(+), 39 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index fbaa9dd0d9b..b23561ad72d 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -1524,6 +1524,7 @@ fn substitute_macros(input: &str) -> String { ("[REPLACING]", " Replacing"), ("[UNPACKING]", " Unpacking"), ("[SUMMARY]", " Summary"), + ("[FIXED]", " Fixed"), ("[FIXING]", " Fixing"), ("[EXE]", env::consts::EXE_SUFFIX), ("[IGNORED]", " Ignored"), diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 0ba8b41c6b4..78e29ab8126 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -238,7 +238,7 @@ pub fn fix_maybe_exec_rustc() -> CargoResult { if output.status.success() { for (path, file) in fixes.files.iter() { - Message::Fixing { + Message::Fixed { file: path.clone(), fixes: file.fixes_applied, } @@ -695,7 +695,12 @@ impl FixArgs { fn verify_not_preparing_for_enabled_edition(&self) -> CargoResult<()> { let edition = match self.prepare_for_edition { Some(s) => s, - None => return Ok(()), + None => { + return Message::Fixing { + file: self.file.display().to_string(), + } + .post(); + } }; let enabled = match self.enabled_edition { Some(s) => s, diff --git a/src/cargo/util/diagnostic_server.rs b/src/cargo/util/diagnostic_server.rs index dd75e1c185f..dba68c08ad9 100644 --- a/src/cargo/util/diagnostic_server.rs +++ b/src/cargo/util/diagnostic_server.rs @@ -33,6 +33,9 @@ const PLEASE_REPORT_THIS_BUG: &str = pub enum Message { Fixing { file: String, + }, + Fixed { + file: String, fixes: u32, }, FixFailed { @@ -89,10 +92,14 @@ impl<'a> DiagnosticPrinter<'a> { pub fn print(&mut self, msg: &Message) -> CargoResult<()> { match msg { - Message::Fixing { file, fixes } => { + Message::Fixing { file } => self + .config + .shell() + .verbose(|shell| shell.status("Fixing", file)), + Message::Fixed { file, fixes } => { let msg = if *fixes == 1 { "fix" } else { "fixes" }; let msg = format!("{} ({} {})", file, fixes, msg); - self.config.shell().status("Fixing", msg) + self.config.shell().status("Fixed", msg) } Message::ReplaceFailed { file, message } => { let msg = format!("error applying suggestions to `{}`\n", file); diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs index cbb70ad2260..c87f957e128 100644 --- a/tests/testsuite/fix.rs +++ b/tests/testsuite/fix.rs @@ -1,8 +1,9 @@ //! Tests for the `cargo fix` command. +use cargo::core::Edition; use cargo_test_support::git; use cargo_test_support::paths; -use cargo_test_support::{basic_manifest, project}; +use cargo_test_support::{basic_manifest, is_nightly, project}; #[cargo_test] fn do_not_fix_broken_builds() { @@ -161,7 +162,7 @@ fn broken_fixes_backed_out() { ) .with_stderr_contains("Original diagnostics will follow.") .with_stderr_contains("[WARNING] variable does not need to be mutable") - .with_stderr_does_not_contain("[..][FIXING][..]") + .with_stderr_does_not_contain("[..][FIXED][..]") .run(); // Make sure the fix which should have been applied was backed out @@ -213,9 +214,9 @@ fn fix_path_deps() { .with_stderr_unordered( "\ [CHECKING] bar v0.1.0 ([..]) -[FIXING] bar/src/lib.rs (1 fix) +[FIXED] bar/src/lib.rs (1 fix) [CHECKING] foo v0.1.0 ([..]) -[FIXING] src/lib.rs (1 fix) +[FIXED] src/lib.rs (1 fix) [FINISHED] [..] ", ) @@ -285,7 +286,7 @@ fn prepare_for_2018() { let stderr = "\ [CHECKING] foo v0.0.1 ([..]) -[FIXING] src/lib.rs (2 fixes) +[FIXED] src/lib.rs (2 fixes) [FINISHED] [..] "; p.cargo("fix --edition --allow-no-vcs") @@ -319,14 +320,14 @@ fn local_paths() { ) .build(); - let stderr = "\ + p.cargo("fix --edition --allow-no-vcs") + .with_stderr( + "\ [CHECKING] foo v0.0.1 ([..]) -[FIXING] src/lib.rs (1 fix) +[FIXED] src/lib.rs (1 fix) [FINISHED] [..] -"; - - p.cargo("fix --edition --allow-no-vcs") - .with_stderr(stderr) +", + ) .with_stdout("") .run(); @@ -372,7 +373,7 @@ fn upgrade_extern_crate() { let stderr = "\ [CHECKING] bar v0.1.0 ([..]) [CHECKING] foo v0.1.0 ([..]) -[FIXING] src/lib.rs (1 fix) +[FIXED] src/lib.rs (1 fix) [FINISHED] [..] "; p.cargo("fix --allow-no-vcs") @@ -403,14 +404,15 @@ fn specify_rustflags() { ) .build(); - let stderr = "\ -[CHECKING] foo v0.0.1 ([..]) -[FIXING] src/lib.rs (1 fix) -[FINISHED] [..] -"; p.cargo("fix --edition --allow-no-vcs") .env("RUSTFLAGS", "-C linker=cc") - .with_stderr(stderr) + .with_stderr( + "\ +[CHECKING] foo v0.0.1 ([..]) +[FIXED] src/lib.rs (1 fix) +[FINISHED] [..] +", + ) .with_stdout("") .run(); } @@ -445,7 +447,7 @@ fn fixes_extra_mut() { let stderr = "\ [CHECKING] foo v0.0.1 ([..]) -[FIXING] src/lib.rs (1 fix) +[FIXED] src/lib.rs (1 fix) [FINISHED] [..] "; p.cargo("fix --allow-no-vcs") @@ -472,7 +474,7 @@ fn fixes_two_missing_ampersands() { let stderr = "\ [CHECKING] foo v0.0.1 ([..]) -[FIXING] src/lib.rs (2 fixes) +[FIXED] src/lib.rs (2 fixes) [FINISHED] [..] "; p.cargo("fix --allow-no-vcs") @@ -498,7 +500,7 @@ fn tricky() { let stderr = "\ [CHECKING] foo v0.0.1 ([..]) -[FIXING] src/lib.rs (2 fixes) +[FIXED] src/lib.rs (2 fixes) [FINISHED] [..] "; p.cargo("fix --allow-no-vcs") @@ -594,8 +596,8 @@ fn fix_two_files() { p.cargo("fix --allow-no-vcs") .env("__CARGO_FIX_YOLO", "1") - .with_stderr_contains("[FIXING] src/bar.rs (1 fix)") - .with_stderr_contains("[FIXING] src/lib.rs (1 fix)") + .with_stderr_contains("[FIXED] src/bar.rs (1 fix)") + .with_stderr_contains("[FIXED] src/lib.rs (1 fix)") .run(); assert!(!p.read_file("src/lib.rs").contains("let mut x = 3;")); assert!(!p.read_file("src/bar.rs").contains("let mut x = 3;")); @@ -629,16 +631,16 @@ fn fixes_missing_ampersand() { .env("__CARGO_FIX_YOLO", "1") .with_stdout("") .with_stderr_contains("[COMPILING] foo v0.0.1 ([..])") - .with_stderr_contains("[FIXING] build.rs (1 fix)") + .with_stderr_contains("[FIXED] build.rs (1 fix)") // Don't assert number of fixes for this one, as we don't know if we're // fixing it once or twice! We run this all concurrently, and if we // compile (and fix) in `--test` mode first, we get two fixes. Otherwise // we'll fix one non-test thing, and then fix another one later in // test mode. - .with_stderr_contains("[FIXING] src/lib.rs[..]") - .with_stderr_contains("[FIXING] src/main.rs (1 fix)") - .with_stderr_contains("[FIXING] examples/foo.rs (1 fix)") - .with_stderr_contains("[FIXING] tests/a.rs (1 fix)") + .with_stderr_contains("[FIXED] src/lib.rs[..]") + .with_stderr_contains("[FIXED] src/main.rs (1 fix)") + .with_stderr_contains("[FIXED] examples/foo.rs (1 fix)") + .with_stderr_contains("[FIXED] tests/a.rs (1 fix)") .with_stderr_contains("[FINISHED] [..]") .run(); p.cargo("build").run(); @@ -836,14 +838,14 @@ fn fix_overlapping() { ) .build(); - let stderr = "\ + p.cargo("fix --allow-no-vcs --prepare-for 2018 --lib") + .with_stderr( + "\ [CHECKING] foo [..] -[FIXING] src/lib.rs (2 fixes) +[FIXED] src/lib.rs (2 fixes) [FINISHED] dev [..] -"; - - p.cargo("fix --allow-no-vcs --prepare-for 2018 --lib") - .with_stderr(stderr) +", + ) .run(); let contents = p.read_file("src/lib.rs"); @@ -876,7 +878,7 @@ fn fix_idioms() { let stderr = "\ [CHECKING] foo [..] -[FIXING] src/lib.rs (1 fix) +[FIXED] src/lib.rs (1 fix) [FINISHED] [..] "; p.cargo("fix --edition-idioms --allow-no-vcs") diff --git a/tests/testsuite/glob_targets.rs b/tests/testsuite/glob_targets.rs index a2dc4fdff49..643572e42a9 100644 --- a/tests/testsuite/glob_targets.rs +++ b/tests/testsuite/glob_targets.rs @@ -150,6 +150,7 @@ fn fix_example() { "\ [CHECKING] foo v0.0.1 ([CWD]) [RUNNING] `[..] rustc --crate-name example1 [..]` +[FIXING] examples/example1.rs [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) @@ -164,6 +165,7 @@ fn fix_bin() { "\ [CHECKING] foo v0.0.1 ([CWD]) [RUNNING] `[..] rustc --crate-name bin1 [..]` +[FIXING] src/bin/bin1.rs [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) @@ -178,6 +180,7 @@ fn fix_bench() { "\ [CHECKING] foo v0.0.1 ([CWD]) [RUNNING] `[..] rustc --crate-name bench1 [..]` +[FIXING] benches/bench1.rs [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) @@ -192,6 +195,7 @@ fn fix_test() { "\ [CHECKING] foo v0.0.1 ([CWD]) [RUNNING] `[..] rustc --crate-name test1 [..]` +[FIXING] tests/test1.rs [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) From 3f2f7e30ff7aa2045010ac845d96f652f118ff3e Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 17 Feb 2021 21:36:28 -0800 Subject: [PATCH 03/11] Add a migrating message for `cargo fix --edition`. This helps indicate which edition you are moving from and to. --- crates/cargo-test-support/src/lib.rs | 1 + src/cargo/ops/fix.rs | 44 +++++++++++++--------------- src/cargo/util/diagnostic_server.rs | 18 ++++++++++++ tests/testsuite/fix.rs | 5 ++++ 4 files changed, 44 insertions(+), 24 deletions(-) diff --git a/crates/cargo-test-support/src/lib.rs b/crates/cargo-test-support/src/lib.rs index b23561ad72d..7b130f443af 100644 --- a/crates/cargo-test-support/src/lib.rs +++ b/crates/cargo-test-support/src/lib.rs @@ -1535,6 +1535,7 @@ fn substitute_macros(input: &str) -> String { ("[LOGOUT]", " Logout"), ("[YANK]", " Yank"), ("[OWNER]", " Owner"), + ("[MIGRATING]", " Migrating"), ]; let mut result = input.to_owned(); for &(pat, subst) in ¯os { diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 78e29ab8126..0b42f045b17 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -301,7 +301,7 @@ fn rustfix_crate( filename: &Path, args: &FixArgs, ) -> Result { - args.verify_not_preparing_for_enabled_edition()?; + args.check_edition_and_send_status()?; // First up, we want to make sure that each crate is only checked by one // process at a time. If two invocations concurrently check a crate then @@ -685,15 +685,10 @@ impl FixArgs { } } - /// Verifies that we're not both preparing for an enabled edition and enabling - /// the edition. - /// - /// This indicates that `cargo fix --prepare-for` is being executed out of - /// order with enabling the edition itself, meaning that we wouldn't - /// actually be able to fix anything! If it looks like this is happening - /// then yield an error to the user, indicating that this is happening. - fn verify_not_preparing_for_enabled_edition(&self) -> CargoResult<()> { - let edition = match self.prepare_for_edition { + /// Validates the edition, and sends a message indicating what is being + /// done. + fn check_edition_and_send_status(&self) -> CargoResult<()> { + let to_edition = match self.prepare_for_edition { Some(s) => s, None => { return Message::Fixing { @@ -702,20 +697,21 @@ impl FixArgs { .post(); } }; - let enabled = match self.enabled_edition { - Some(s) => s, - None => return Ok(()), - }; - if edition != enabled { - return Ok(()); - } - - Message::EditionAlreadyEnabled { - file: self.file.display().to_string(), - edition, + let from_edition = self.enabled_edition.unwrap_or(Edition::Edition2015); + if from_edition == to_edition { + Message::EditionAlreadyEnabled { + file: self.file.display().to_string(), + edition: to_edition, + } + .post()?; + process::exit(1); + } else { + Message::Migrating { + file: self.file.display().to_string(), + from_edition, + to_edition, + } + .post() } - .post()?; - - process::exit(1); } } diff --git a/src/cargo/util/diagnostic_server.rs b/src/cargo/util/diagnostic_server.rs index dba68c08ad9..a28b5bbf227 100644 --- a/src/cargo/util/diagnostic_server.rs +++ b/src/cargo/util/diagnostic_server.rs @@ -31,6 +31,11 @@ const PLEASE_REPORT_THIS_BUG: &str = #[derive(Deserialize, Serialize, Hash, Eq, PartialEq, Clone)] pub enum Message { + Migrating { + file: String, + from_edition: Edition, + to_edition: Edition, + }, Fixing { file: String, }, @@ -92,6 +97,19 @@ impl<'a> DiagnosticPrinter<'a> { pub fn print(&mut self, msg: &Message) -> CargoResult<()> { match msg { + Message::Migrating { + file, + from_edition, + to_edition, + } => { + if !self.dedupe.insert(msg.clone()) { + return Ok(()); + } + self.config.shell().status( + "Migrating", + &format!("{} from {} edition to {}", file, from_edition, to_edition), + ) + } Message::Fixing { file } => self .config .shell() diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs index c87f957e128..e598f051c09 100644 --- a/tests/testsuite/fix.rs +++ b/tests/testsuite/fix.rs @@ -286,6 +286,7 @@ fn prepare_for_2018() { let stderr = "\ [CHECKING] foo v0.0.1 ([..]) +[MIGRATING] src/lib.rs from 2015 edition to 2018 [FIXED] src/lib.rs (2 fixes) [FINISHED] [..] "; @@ -324,6 +325,7 @@ fn local_paths() { .with_stderr( "\ [CHECKING] foo v0.0.1 ([..]) +[MIGRATING] src/lib.rs from 2015 edition to 2018 [FIXED] src/lib.rs (1 fix) [FINISHED] [..] ", @@ -409,6 +411,7 @@ fn specify_rustflags() { .with_stderr( "\ [CHECKING] foo v0.0.1 ([..]) +[MIGRATING] src/lib.rs from 2015 edition to 2018 [FIXED] src/lib.rs (1 fix) [FINISHED] [..] ", @@ -842,6 +845,7 @@ fn fix_overlapping() { .with_stderr( "\ [CHECKING] foo [..] +[MIGRATING] src/lib.rs from 2015 edition to 2018 [FIXED] src/lib.rs (2 fixes) [FINISHED] dev [..] ", @@ -1164,6 +1168,7 @@ fn only_warn_for_relevant_crates() { "\ [CHECKING] a v0.1.0 ([..]) [CHECKING] foo v0.1.0 ([..]) +[MIGRATING] src/lib.rs from 2015 edition to 2018 [FINISHED] dev [unoptimized + debuginfo] target(s) in [..] ", ) From 2ae72ff747285b02a68a4ed4f983ff9425d624f0 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 17 Feb 2021 22:01:42 -0800 Subject: [PATCH 04/11] Add edition2021 cargo feature. This is intended to help make it easier to test the 2021 edition. --- src/cargo/core/compiler/compilation.rs | 6 +- src/cargo/core/compiler/mod.rs | 6 +- src/cargo/core/features.rs | 32 ++++++++- src/cargo/util/toml/mod.rs | 9 +++ src/doc/src/reference/unstable.md | 20 ++++++ tests/testsuite/edition.rs | 90 +++++++++++++++++++++++++- 6 files changed, 153 insertions(+), 10 deletions(-) diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index ba4f0f8f576..fee774da4d2 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -8,7 +8,7 @@ use semver::Version; use super::BuildContext; use crate::core::compiler::{CompileKind, Metadata, Unit}; -use crate::core::{Edition, Package}; +use crate::core::Package; use crate::util::{self, config, join_paths, process, CargoResult, Config, ProcessBuilder}; /// Structure with enough information to run `rustdoc --test`. @@ -187,9 +187,7 @@ impl<'cfg> Compilation<'cfg> { let rustdoc = process(&*self.config.rustdoc()?); let cmd = fill_rustc_tool_env(rustdoc, unit); let mut p = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, true)?; - if unit.target.edition() != Edition::Edition2015 { - p.arg(format!("--edition={}", unit.target.edition())); - } + unit.target.edition().cmd_edition_arg(&mut p); for crate_type in unit.target.rustc_crate_types() { p.arg("--crate-type").arg(crate_type.as_str()); diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index b9c3a15c581..7830bc1e6e0 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -52,7 +52,7 @@ pub use crate::core::compiler::unit::{Unit, UnitInterner}; use crate::core::features::nightly_features_allowed; use crate::core::manifest::TargetSourcePath; use crate::core::profiles::{PanicStrategy, Profile, Strip}; -use crate::core::{Edition, Feature, PackageId, Target}; +use crate::core::{Feature, PackageId, Target}; use crate::util::errors::{self, CargoResult, CargoResultExt, ProcessError, VerboseError}; use crate::util::interning::InternedString; use crate::util::machine_message::Message; @@ -782,9 +782,7 @@ fn build_base_args( cmd.arg("--crate-name").arg(&unit.target.crate_name()); let edition = unit.target.edition(); - if edition != Edition::Edition2015 { - cmd.arg(format!("--edition={}", edition)); - } + edition.cmd_edition_arg(cmd); add_path_args(bcx, unit, cmd); add_error_format_and_color(cx, cmd, cx.rmeta_required(unit)); diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 494e3399300..3dcd48ceab8 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -101,7 +101,7 @@ use anyhow::{bail, Error}; use serde::{Deserialize, Serialize}; use crate::util::errors::CargoResult; -use crate::util::indented_lines; +use crate::util::{indented_lines, ProcessBuilder}; pub const SEE_CHANNELS: &str = "See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \ @@ -119,14 +119,41 @@ pub enum Edition { } impl Edition { + /// The latest edition (may or may not be stable). + pub const LATEST: Edition = Edition::Edition2021; + + /// Returns the first version that a particular edition was released on + /// stable. pub(crate) fn first_version(&self) -> Option { use Edition::*; match self { Edition2015 => None, Edition2018 => Some(semver::Version::new(1, 31, 0)), + // FIXME: This will likely be 1.56, update when that seems more likely. Edition2021 => Some(semver::Version::new(1, 62, 0)), } } + + /// Returns `true` if this edition is stable in this release. + pub fn is_stable(&self) -> bool { + use Edition::*; + match self { + Edition2015 => true, + Edition2018 => true, + Edition2021 => false, + } + } + + /// Updates the given [`ProcessBuilder`] to include the appropriate flags + /// for setting the edition. + pub(crate) fn cmd_edition_arg(&self, cmd: &mut ProcessBuilder) { + if *self != Edition::Edition2015 { + cmd.arg(format!("--edition={}", self)); + } + if !self.is_stable() { + cmd.arg("-Z").arg("unstable-options"); + } + } } impl fmt::Display for Edition { @@ -282,6 +309,9 @@ features! { // Specifying a minimal 'rust-version' attribute for crates (unstable, rust_version, "", "reference/unstable.html#rust-version"), + + // Support for 2021 edition. + (unstable, edition2021, "", "reference/unstable.html#edition-2021"), } const PUBLISH_LOCKFILE_REMOVED: &str = "The publish-lockfile key in Cargo.toml \ diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 6d6b9f1e76a..683f5e7c825 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -1061,6 +1061,15 @@ impl TomlManifest { } else { Edition::Edition2015 }; + if edition == Edition::Edition2021 { + features.require(Feature::edition2021())?; + } else if !edition.is_stable() { + // Guard in case someone forgets to add .require() + return Err(util::errors::internal(format!( + "edition {} should be gated", + edition + ))); + } let rust_version = if let Some(rust_version) = &project.rust_version { if features.require(Feature::rust_version()).is_err() { diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 49ec4187350..c48fed762c4 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -1064,6 +1064,26 @@ version = "0.0.1" rust-version = "1.42" ``` +### edition 2021 + +Support for the 2021 [edition] can be enabled by adding the `edition2021` +unstable feature to the top of `Cargo.toml`: + +```toml +cargo-features = ["edition2021"] + +[package] +name = "my-package" +version = "0.1.0" +edition = "2021" +``` + +This feature is very unstable, and is only intended for early testing and +experimentation. Future nightly releases may introduce changes for the 2021 +edition that may break your build. + +[edition]: ../../edition-guide/index.html +