Make edition transitions easier.

This attempts to centralize all the edition stuff in one place (the
`Edition` enum) so that adding a new edition or stabilizing one should
be relatively little work (and more importantly, we don't miss things).

Importantly, this changes `cargo new` to default to the latest stable.
It also changes the `cargo fix --edition-idiom` behavior to only apply
idioms for the *current* edition.
This commit is contained in:
Eric Huss 2021-02-19 12:57:36 -08:00
parent 21cfac6c09
commit aa61976c5f
6 changed files with 78 additions and 37 deletions

View File

@ -118,9 +118,35 @@ pub enum Edition {
Edition2021,
}
// Adding a new edition:
// - Add the next edition to the enum.
// - Update every match expression that now fails to compile.
// - Update the `FromStr` impl.
// - Update CLI_VALUES to include the new edition.
// - Set LATEST_UNSTABLE to Some with the new edition.
// - Add an unstable feature to the features! macro below for the new edition.
// - Gate on that new feature in TomlManifest::to_real_manifest.
// - Update the shell completion files.
// - Update any failing tests (hopefully there are very few).
//
// Stabilization instructions:
// - Set LATEST_UNSTABLE to None.
// - Set LATEST_STABLE to the new version.
// - Update `is_stable` to `true`.
// - Set the editionNNNN feature to stable in the features macro below.
// - Update the man page for the --edition flag.
impl Edition {
/// The latest edition (may or may not be stable).
pub const LATEST: Edition = Edition::Edition2021;
/// The latest edition that is unstable.
///
/// This is `None` if there is no next unstable edition.
pub const LATEST_UNSTABLE: Option<Edition> = Some(Edition::Edition2021);
/// The latest stable edition.
pub const LATEST_STABLE: Edition = Edition::Edition2018;
/// Possible values allowed for the `--edition` CLI flag.
///
/// This requires a static value due to the way clap works, otherwise I
/// would have built this dynamically.
pub const CLI_VALUES: &'static [&'static str] = &["2015", "2018", "2021"];
/// Returns the first version that a particular edition was released on
/// stable.
@ -180,6 +206,18 @@ impl Edition {
Edition2021 => false,
}
}
/// Whether or not this edition supports the `rust_*_idioms` lint.
///
/// Ideally this would not be necessary...
pub(crate) fn supports_idiom_lint(&self) -> bool {
use Edition::*;
match self {
Edition2015 => false,
Edition2018 => true,
Edition2021 => false,
}
}
}
impl fmt::Display for Edition {

View File

@ -1,4 +1,4 @@
use crate::core::{Shell, Workspace};
use crate::core::{Edition, Shell, Workspace};
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
use crate::util::{paths, restricted_names, Config};
@ -743,7 +743,7 @@ edition = {}
},
match opts.edition {
Some(edition) => toml::Value::String(edition.to_string()),
None => toml::Value::String("2018".to_string()),
None => toml::Value::String(Edition::LATEST_STABLE.to_string()),
},
match opts.registry {
Some(registry) => format!(

View File

@ -675,8 +675,8 @@ impl FixArgs {
cmd.args(&self.other).arg("--cap-lints=warn");
if let Some(edition) = self.enabled_edition {
cmd.arg("--edition").arg(edition.to_string());
if self.idioms && edition >= Edition::Edition2018 {
cmd.arg("-Wrust-2018-idioms");
if self.idioms && edition.supports_idiom_lint() {
cmd.arg(format!("-Wrust-{}-idioms", edition));
}
}

View File

@ -1,5 +1,5 @@
use crate::core::compiler::{BuildConfig, MessageFormat};
use crate::core::Workspace;
use crate::core::{Edition, Workspace};
use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionControl};
use crate::sources::CRATES_IO_REGISTRY;
use crate::util::important_paths::find_root_manifest_for_wd;
@ -188,7 +188,7 @@ pub trait AppExt: Sized {
._arg(opt("lib", "Use a library template"))
._arg(
opt("edition", "Edition to set for the crate generated")
.possible_values(&["2015", "2018", "2021"])
.possible_values(Edition::CLI_VALUES)
.value_name("YEAR"),
)
._arg(

View File

@ -39,11 +39,13 @@ fn edition_unstable_gated() {
// During the period where a new edition is coming up, but not yet stable,
// this test will verify that it cannot be used on stable. If there is no
// next edition, it does nothing.
let next = Edition::LATEST;
if next.is_stable() {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
let next = match Edition::LATEST_UNSTABLE {
Some(next) => next,
None => {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
};
let p = project()
.file(
"Cargo.toml",
@ -87,11 +89,13 @@ fn edition_unstable() {
// This test is fundamentally always nightly.
return;
}
let next = Edition::LATEST;
if next.is_stable() {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
let next = match Edition::LATEST_UNSTABLE {
Some(next) => next,
None => {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
};
let p = project()
.file(
"Cargo.toml",

View File

@ -795,12 +795,14 @@ fn prepare_for_unstable() {
// During the period where a new edition is coming up, but not yet stable,
// this test will verify that it cannot be migrated to on stable. If there
// is no next edition, it does nothing.
let next = Edition::LATEST;
if next.is_stable() {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
let latest_stable = next.previous().unwrap();
let next = match Edition::LATEST_UNSTABLE {
Some(next) => next,
None => {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
};
let latest_stable = Edition::LATEST_STABLE;
let p = project()
.file(
"Cargo.toml",
@ -853,12 +855,7 @@ To learn more, run the command again with --verbose.
#[cargo_test]
fn prepare_for_latest_stable() {
// This is the stable counterpart of prepare_for_unstable.
let latest = Edition::LATEST;
let latest_stable = if latest.is_stable() {
latest
} else {
latest.previous().unwrap()
};
let latest_stable = Edition::LATEST_STABLE;
let previous = latest_stable.previous().unwrap();
let p = project()
.file(
@ -897,11 +894,13 @@ fn prepare_for_already_on_latest_unstable() {
// This test is fundamentally always nightly.
return;
}
let next_edition = Edition::LATEST;
if next_edition.is_stable() {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
let next_edition = match Edition::LATEST_UNSTABLE {
Some(next) => next,
None => {
eprintln!("Next edition is currently not available, skipping test.");
return;
}
};
let p = project()
.file(
"Cargo.toml",
@ -936,11 +935,11 @@ fn prepare_for_already_on_latest_unstable() {
#[cargo_test]
fn prepare_for_already_on_latest_stable() {
// Stable counterpart of prepare_for_already_on_latest_unstable.
if !Edition::LATEST.is_stable() {
if Edition::LATEST_UNSTABLE.is_some() {
eprintln!("This test cannot run while the latest edition is unstable, skipping.");
return;
}
let latest_stable = Edition::LATEST;
let latest_stable = Edition::LATEST_STABLE;
let p = project()
.file(
"Cargo.toml",