Skip to content

Commit

Permalink
Auto merge of #9184 - ehuss:fix-next-edition, r=alexcrichton
Browse files Browse the repository at this point in the history
Updates to edition handling.

This introduces some updates for edition handling (split into commits for review).  In short:

* `cargo-features = ["edition2021"]` can be used to opt-in to 2021 support without needing to pass around `-Z unstable-options`. I tried to emphasize in the docs that this is only for testing and experimentation.
* Make `"2"` the default resolver for 2021 edition.
* Make `cargo fix --edition` mean the "next" edition from the present one, and support 2021. Also, if already at the latest edition, generate a warning instead an error.
* I decided to allow `cargo fix --edition` from 2018 to 2021 on the nightly channel without an explicit opt-in. It's tricky to implement with an opt-in (see comment in diff).

Partial for #9048.
Fixes #9047.
  • Loading branch information
bors committed Feb 23, 2021
2 parents 4ae4aad + a82a23c commit 8eb0e9a
Show file tree
Hide file tree
Showing 20 changed files with 848 additions and 321 deletions.
2 changes: 2 additions & 0 deletions crates/cargo-test-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -1534,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 &macros {
Expand Down
12 changes: 0 additions & 12 deletions src/bin/cargo/commands/fix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,6 @@ pub fn cli() -> App {
.long("edition")
.help("Fix in preparation for the next edition"),
)
.arg(
// This is a deprecated argument, we'll want to phase it out
// eventually.
Arg::with_name("prepare-for")
.long("prepare-for")
.help("Fix warnings in preparation of an edition upgrade")
.takes_value(true)
.possible_values(&["2018"])
.conflicts_with("edition")
.hidden(true),
)
.arg(
Arg::with_name("idioms")
.long("edition-idioms")
Expand Down Expand Up @@ -111,7 +100,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
&ws,
&mut ops::FixOptions {
edition: args.is_present("edition"),
prepare_for: args.value_of("prepare-for"),
idioms: args.is_present("idioms"),
compile_opts: opts,
allow_dirty: args.is_present("allow-dirty"),
Expand Down
6 changes: 2 additions & 4 deletions src/cargo/core/compiler/compilation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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());
Expand Down
6 changes: 2 additions & 4 deletions src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -747,9 +747,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.ws, unit, cmd);
add_error_format_and_color(cx, cmd, cx.rmeta_required(unit));
Expand Down
107 changes: 106 additions & 1 deletion src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -118,15 +118,117 @@ 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 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.
pub(crate) fn first_version(&self) -> Option<semver::Version> {
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,
}
}

/// Returns the previous edition from this edition.
///
/// Returns `None` for 2015.
pub fn previous(&self) -> Option<Edition> {
use Edition::*;
match self {
Edition2015 => None,
Edition2018 => Some(Edition2015),
Edition2021 => Some(Edition2018),
}
}

/// Returns the next edition from this edition, returning the last edition
/// if this is already the last one.
pub fn saturating_next(&self) -> Edition {
use Edition::*;
match self {
Edition2015 => Edition2018,
Edition2018 => Edition2021,
Edition2021 => Edition2021,
}
}

/// 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");
}
}

/// Whether or not this edition supports the `rust_*_compatibility` lint.
///
/// Ideally this would not be necessary, but currently 2021 does not have
/// any lints, and thus `rustc` doesn't recognize it. Perhaps `rustc`
/// could create an empty group instead?
pub(crate) fn supports_compat_lint(&self) -> bool {
use Edition::*;
match self {
Edition2015 => false,
Edition2018 => true,
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 {
Expand Down Expand Up @@ -282,6 +384,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 \
Expand Down
45 changes: 30 additions & 15 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::core::features::Features;
use crate::core::registry::PackageRegistry;
use crate::core::resolver::features::RequestedFeatures;
use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, PackageId, PackageIdSpec};
use crate::core::{Dependency, Edition, PackageId, PackageIdSpec};
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
use crate::ops;
use crate::sources::PathSource;
Expand Down Expand Up @@ -88,7 +88,7 @@ pub struct Workspace<'cfg> {
ignore_lock: bool,

/// The resolver behavior specified with the `resolver` field.
resolve_behavior: Option<ResolveBehavior>,
resolve_behavior: ResolveBehavior,

/// Workspace-level custom metadata
custom_metadata: Option<toml::Value>,
Expand Down Expand Up @@ -164,10 +164,7 @@ impl<'cfg> Workspace<'cfg> {
.load_workspace_config()?
.and_then(|cfg| cfg.custom_metadata);
ws.find_members()?;
ws.resolve_behavior = match ws.root_maybe() {
MaybePackage::Package(p) => p.manifest().resolve_behavior(),
MaybePackage::Virtual(vm) => vm.resolve_behavior(),
};
ws.set_resolve_behavior();
ws.validate()?;
Ok(ws)
}
Expand All @@ -189,7 +186,7 @@ impl<'cfg> Workspace<'cfg> {
require_optional_deps: true,
loaded_packages: RefCell::new(HashMap::new()),
ignore_lock: false,
resolve_behavior: None,
resolve_behavior: ResolveBehavior::V1,
custom_metadata: None,
}
}
Expand All @@ -203,11 +200,11 @@ impl<'cfg> Workspace<'cfg> {
let mut ws = Workspace::new_default(current_manifest, config);
ws.root_manifest = Some(root_path.join("Cargo.toml"));
ws.target_dir = config.target_dir()?;
ws.resolve_behavior = manifest.resolve_behavior();
ws.packages
.packages
.insert(root_path, MaybePackage::Virtual(manifest));
ws.find_members()?;
ws.set_resolve_behavior();
// TODO: validation does not work because it walks up the directory
// tree looking for the root which is a fake file that doesn't exist.
Ok(ws)
Expand All @@ -231,7 +228,6 @@ impl<'cfg> Workspace<'cfg> {
let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), config);
ws.is_ephemeral = true;
ws.require_optional_deps = require_optional_deps;
ws.resolve_behavior = package.manifest().resolve_behavior();
let key = ws.current_manifest.parent().unwrap();
let id = package.package_id();
let package = MaybePackage::Package(package);
Expand All @@ -244,9 +240,28 @@ impl<'cfg> Workspace<'cfg> {
ws.members.push(ws.current_manifest.clone());
ws.member_ids.insert(id);
ws.default_members.push(ws.current_manifest.clone());
ws.set_resolve_behavior();
Ok(ws)
}

fn set_resolve_behavior(&mut self) {
// - If resolver is specified in the workspace definition, use that.
// - If the root package specifies the resolver, use that.
// - If the root package specifies edition 2021, use v2.
// - Otherwise, use the default v1.
self.resolve_behavior = match self.root_maybe() {
MaybePackage::Package(p) => p.manifest().resolve_behavior().or_else(|| {
if p.manifest().edition() >= Edition::Edition2021 {
Some(ResolveBehavior::V2)
} else {
None
}
}),
MaybePackage::Virtual(vm) => vm.resolve_behavior(),
}
.unwrap_or(ResolveBehavior::V1);
}

/// Returns the current package of this workspace.
///
/// Note that this can return an error if it the current manifest is
Expand Down Expand Up @@ -634,7 +649,7 @@ impl<'cfg> Workspace<'cfg> {
}

pub fn resolve_behavior(&self) -> ResolveBehavior {
self.resolve_behavior.unwrap_or(ResolveBehavior::V1)
self.resolve_behavior
}

/// Returns `true` if this workspace uses the new CLI features behavior.
Expand Down Expand Up @@ -843,11 +858,11 @@ impl<'cfg> Workspace<'cfg> {
if !manifest.patch().is_empty() {
emit_warning("patch")?;
}
if manifest.resolve_behavior().is_some()
&& manifest.resolve_behavior() != self.resolve_behavior
{
// Only warn if they don't match.
emit_warning("resolver")?;
if let Some(behavior) = manifest.resolve_behavior() {
if behavior != self.resolve_behavior {
// Only warn if they don't match.
emit_warning("resolver")?;
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/cargo/ops/cargo_new.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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!(
Expand Down
Loading

0 comments on commit 8eb0e9a

Please sign in to comment.