Skip to content

Commit 8eb0e9a

Browse files
committed
Auto merge of #9184 - ehuss:fix-next-edition, r=alexcrichton
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.
2 parents 4ae4aad + a82a23c commit 8eb0e9a

File tree

20 files changed

+848
-321
lines changed

20 files changed

+848
-321
lines changed

crates/cargo-test-support/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,6 +1524,7 @@ fn substitute_macros(input: &str) -> String {
15241524
("[REPLACING]", " Replacing"),
15251525
("[UNPACKING]", " Unpacking"),
15261526
("[SUMMARY]", " Summary"),
1527+
("[FIXED]", " Fixed"),
15271528
("[FIXING]", " Fixing"),
15281529
("[EXE]", env::consts::EXE_SUFFIX),
15291530
("[IGNORED]", " Ignored"),
@@ -1534,6 +1535,7 @@ fn substitute_macros(input: &str) -> String {
15341535
("[LOGOUT]", " Logout"),
15351536
("[YANK]", " Yank"),
15361537
("[OWNER]", " Owner"),
1538+
("[MIGRATING]", " Migrating"),
15371539
];
15381540
let mut result = input.to_owned();
15391541
for &(pat, subst) in &macros {

src/bin/cargo/commands/fix.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,6 @@ pub fn cli() -> App {
4141
.long("edition")
4242
.help("Fix in preparation for the next edition"),
4343
)
44-
.arg(
45-
// This is a deprecated argument, we'll want to phase it out
46-
// eventually.
47-
Arg::with_name("prepare-for")
48-
.long("prepare-for")
49-
.help("Fix warnings in preparation of an edition upgrade")
50-
.takes_value(true)
51-
.possible_values(&["2018"])
52-
.conflicts_with("edition")
53-
.hidden(true),
54-
)
5544
.arg(
5645
Arg::with_name("idioms")
5746
.long("edition-idioms")
@@ -111,7 +100,6 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
111100
&ws,
112101
&mut ops::FixOptions {
113102
edition: args.is_present("edition"),
114-
prepare_for: args.value_of("prepare-for"),
115103
idioms: args.is_present("idioms"),
116104
compile_opts: opts,
117105
allow_dirty: args.is_present("allow-dirty"),

src/cargo/core/compiler/compilation.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use semver::Version;
88

99
use super::BuildContext;
1010
use crate::core::compiler::{CompileKind, Metadata, Unit};
11-
use crate::core::{Edition, Package};
11+
use crate::core::Package;
1212
use crate::util::{self, config, join_paths, process, CargoResult, Config, ProcessBuilder};
1313

1414
/// Structure with enough information to run `rustdoc --test`.
@@ -187,9 +187,7 @@ impl<'cfg> Compilation<'cfg> {
187187
let rustdoc = process(&*self.config.rustdoc()?);
188188
let cmd = fill_rustc_tool_env(rustdoc, unit);
189189
let mut p = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, true)?;
190-
if unit.target.edition() != Edition::Edition2015 {
191-
p.arg(format!("--edition={}", unit.target.edition()));
192-
}
190+
unit.target.edition().cmd_edition_arg(&mut p);
193191

194192
for crate_type in unit.target.rustc_crate_types() {
195193
p.arg("--crate-type").arg(crate_type.as_str());

src/cargo/core/compiler/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub use crate::core::compiler::unit::{Unit, UnitInterner};
5252
use crate::core::features::nightly_features_allowed;
5353
use crate::core::manifest::TargetSourcePath;
5454
use crate::core::profiles::{PanicStrategy, Profile, Strip};
55-
use crate::core::{Edition, Feature, PackageId, Target};
55+
use crate::core::{Feature, PackageId, Target};
5656
use crate::util::errors::{self, CargoResult, CargoResultExt, ProcessError, VerboseError};
5757
use crate::util::interning::InternedString;
5858
use crate::util::machine_message::Message;
@@ -747,9 +747,7 @@ fn build_base_args(
747747
cmd.arg("--crate-name").arg(&unit.target.crate_name());
748748

749749
let edition = unit.target.edition();
750-
if edition != Edition::Edition2015 {
751-
cmd.arg(format!("--edition={}", edition));
752-
}
750+
edition.cmd_edition_arg(cmd);
753751

754752
add_path_args(bcx.ws, unit, cmd);
755753
add_error_format_and_color(cx, cmd, cx.rmeta_required(unit));

src/cargo/core/features.rs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ use anyhow::{bail, Error};
101101
use serde::{Deserialize, Serialize};
102102

103103
use crate::util::errors::CargoResult;
104-
use crate::util::indented_lines;
104+
use crate::util::{indented_lines, ProcessBuilder};
105105

106106
pub const SEE_CHANNELS: &str =
107107
"See https://doc.rust-lang.org/book/appendix-07-nightly-rust.html for more information \
@@ -118,15 +118,117 @@ pub enum Edition {
118118
Edition2021,
119119
}
120120

121+
// Adding a new edition:
122+
// - Add the next edition to the enum.
123+
// - Update every match expression that now fails to compile.
124+
// - Update the `FromStr` impl.
125+
// - Update CLI_VALUES to include the new edition.
126+
// - Set LATEST_UNSTABLE to Some with the new edition.
127+
// - Add an unstable feature to the features! macro below for the new edition.
128+
// - Gate on that new feature in TomlManifest::to_real_manifest.
129+
// - Update the shell completion files.
130+
// - Update any failing tests (hopefully there are very few).
131+
//
132+
// Stabilization instructions:
133+
// - Set LATEST_UNSTABLE to None.
134+
// - Set LATEST_STABLE to the new version.
135+
// - Update `is_stable` to `true`.
136+
// - Set the editionNNNN feature to stable in the features macro below.
137+
// - Update the man page for the --edition flag.
121138
impl Edition {
139+
/// The latest edition that is unstable.
140+
///
141+
/// This is `None` if there is no next unstable edition.
142+
pub const LATEST_UNSTABLE: Option<Edition> = Some(Edition::Edition2021);
143+
/// The latest stable edition.
144+
pub const LATEST_STABLE: Edition = Edition::Edition2018;
145+
/// Possible values allowed for the `--edition` CLI flag.
146+
///
147+
/// This requires a static value due to the way clap works, otherwise I
148+
/// would have built this dynamically.
149+
pub const CLI_VALUES: &'static [&'static str] = &["2015", "2018", "2021"];
150+
151+
/// Returns the first version that a particular edition was released on
152+
/// stable.
122153
pub(crate) fn first_version(&self) -> Option<semver::Version> {
123154
use Edition::*;
124155
match self {
125156
Edition2015 => None,
126157
Edition2018 => Some(semver::Version::new(1, 31, 0)),
158+
// FIXME: This will likely be 1.56, update when that seems more likely.
127159
Edition2021 => Some(semver::Version::new(1, 62, 0)),
128160
}
129161
}
162+
163+
/// Returns `true` if this edition is stable in this release.
164+
pub fn is_stable(&self) -> bool {
165+
use Edition::*;
166+
match self {
167+
Edition2015 => true,
168+
Edition2018 => true,
169+
Edition2021 => false,
170+
}
171+
}
172+
173+
/// Returns the previous edition from this edition.
174+
///
175+
/// Returns `None` for 2015.
176+
pub fn previous(&self) -> Option<Edition> {
177+
use Edition::*;
178+
match self {
179+
Edition2015 => None,
180+
Edition2018 => Some(Edition2015),
181+
Edition2021 => Some(Edition2018),
182+
}
183+
}
184+
185+
/// Returns the next edition from this edition, returning the last edition
186+
/// if this is already the last one.
187+
pub fn saturating_next(&self) -> Edition {
188+
use Edition::*;
189+
match self {
190+
Edition2015 => Edition2018,
191+
Edition2018 => Edition2021,
192+
Edition2021 => Edition2021,
193+
}
194+
}
195+
196+
/// Updates the given [`ProcessBuilder`] to include the appropriate flags
197+
/// for setting the edition.
198+
pub(crate) fn cmd_edition_arg(&self, cmd: &mut ProcessBuilder) {
199+
if *self != Edition::Edition2015 {
200+
cmd.arg(format!("--edition={}", self));
201+
}
202+
if !self.is_stable() {
203+
cmd.arg("-Z").arg("unstable-options");
204+
}
205+
}
206+
207+
/// Whether or not this edition supports the `rust_*_compatibility` lint.
208+
///
209+
/// Ideally this would not be necessary, but currently 2021 does not have
210+
/// any lints, and thus `rustc` doesn't recognize it. Perhaps `rustc`
211+
/// could create an empty group instead?
212+
pub(crate) fn supports_compat_lint(&self) -> bool {
213+
use Edition::*;
214+
match self {
215+
Edition2015 => false,
216+
Edition2018 => true,
217+
Edition2021 => false,
218+
}
219+
}
220+
221+
/// Whether or not this edition supports the `rust_*_idioms` lint.
222+
///
223+
/// Ideally this would not be necessary...
224+
pub(crate) fn supports_idiom_lint(&self) -> bool {
225+
use Edition::*;
226+
match self {
227+
Edition2015 => false,
228+
Edition2018 => true,
229+
Edition2021 => false,
230+
}
231+
}
130232
}
131233

132234
impl fmt::Display for Edition {
@@ -282,6 +384,9 @@ features! {
282384

283385
// Specifying a minimal 'rust-version' attribute for crates
284386
(unstable, rust_version, "", "reference/unstable.html#rust-version"),
387+
388+
// Support for 2021 edition.
389+
(unstable, edition2021, "", "reference/unstable.html#edition-2021"),
285390
}
286391

287392
const PUBLISH_LOCKFILE_REMOVED: &str = "The publish-lockfile key in Cargo.toml \

src/cargo/core/workspace.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::core::features::Features;
1313
use crate::core::registry::PackageRegistry;
1414
use crate::core::resolver::features::RequestedFeatures;
1515
use crate::core::resolver::ResolveBehavior;
16-
use crate::core::{Dependency, PackageId, PackageIdSpec};
16+
use crate::core::{Dependency, Edition, PackageId, PackageIdSpec};
1717
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
1818
use crate::ops;
1919
use crate::sources::PathSource;
@@ -88,7 +88,7 @@ pub struct Workspace<'cfg> {
8888
ignore_lock: bool,
8989

9090
/// The resolver behavior specified with the `resolver` field.
91-
resolve_behavior: Option<ResolveBehavior>,
91+
resolve_behavior: ResolveBehavior,
9292

9393
/// Workspace-level custom metadata
9494
custom_metadata: Option<toml::Value>,
@@ -164,10 +164,7 @@ impl<'cfg> Workspace<'cfg> {
164164
.load_workspace_config()?
165165
.and_then(|cfg| cfg.custom_metadata);
166166
ws.find_members()?;
167-
ws.resolve_behavior = match ws.root_maybe() {
168-
MaybePackage::Package(p) => p.manifest().resolve_behavior(),
169-
MaybePackage::Virtual(vm) => vm.resolve_behavior(),
170-
};
167+
ws.set_resolve_behavior();
171168
ws.validate()?;
172169
Ok(ws)
173170
}
@@ -189,7 +186,7 @@ impl<'cfg> Workspace<'cfg> {
189186
require_optional_deps: true,
190187
loaded_packages: RefCell::new(HashMap::new()),
191188
ignore_lock: false,
192-
resolve_behavior: None,
189+
resolve_behavior: ResolveBehavior::V1,
193190
custom_metadata: None,
194191
}
195192
}
@@ -203,11 +200,11 @@ impl<'cfg> Workspace<'cfg> {
203200
let mut ws = Workspace::new_default(current_manifest, config);
204201
ws.root_manifest = Some(root_path.join("Cargo.toml"));
205202
ws.target_dir = config.target_dir()?;
206-
ws.resolve_behavior = manifest.resolve_behavior();
207203
ws.packages
208204
.packages
209205
.insert(root_path, MaybePackage::Virtual(manifest));
210206
ws.find_members()?;
207+
ws.set_resolve_behavior();
211208
// TODO: validation does not work because it walks up the directory
212209
// tree looking for the root which is a fake file that doesn't exist.
213210
Ok(ws)
@@ -231,7 +228,6 @@ impl<'cfg> Workspace<'cfg> {
231228
let mut ws = Workspace::new_default(package.manifest_path().to_path_buf(), config);
232229
ws.is_ephemeral = true;
233230
ws.require_optional_deps = require_optional_deps;
234-
ws.resolve_behavior = package.manifest().resolve_behavior();
235231
let key = ws.current_manifest.parent().unwrap();
236232
let id = package.package_id();
237233
let package = MaybePackage::Package(package);
@@ -244,9 +240,28 @@ impl<'cfg> Workspace<'cfg> {
244240
ws.members.push(ws.current_manifest.clone());
245241
ws.member_ids.insert(id);
246242
ws.default_members.push(ws.current_manifest.clone());
243+
ws.set_resolve_behavior();
247244
Ok(ws)
248245
}
249246

247+
fn set_resolve_behavior(&mut self) {
248+
// - If resolver is specified in the workspace definition, use that.
249+
// - If the root package specifies the resolver, use that.
250+
// - If the root package specifies edition 2021, use v2.
251+
// - Otherwise, use the default v1.
252+
self.resolve_behavior = match self.root_maybe() {
253+
MaybePackage::Package(p) => p.manifest().resolve_behavior().or_else(|| {
254+
if p.manifest().edition() >= Edition::Edition2021 {
255+
Some(ResolveBehavior::V2)
256+
} else {
257+
None
258+
}
259+
}),
260+
MaybePackage::Virtual(vm) => vm.resolve_behavior(),
261+
}
262+
.unwrap_or(ResolveBehavior::V1);
263+
}
264+
250265
/// Returns the current package of this workspace.
251266
///
252267
/// Note that this can return an error if it the current manifest is
@@ -634,7 +649,7 @@ impl<'cfg> Workspace<'cfg> {
634649
}
635650

636651
pub fn resolve_behavior(&self) -> ResolveBehavior {
637-
self.resolve_behavior.unwrap_or(ResolveBehavior::V1)
652+
self.resolve_behavior
638653
}
639654

640655
/// Returns `true` if this workspace uses the new CLI features behavior.
@@ -843,11 +858,11 @@ impl<'cfg> Workspace<'cfg> {
843858
if !manifest.patch().is_empty() {
844859
emit_warning("patch")?;
845860
}
846-
if manifest.resolve_behavior().is_some()
847-
&& manifest.resolve_behavior() != self.resolve_behavior
848-
{
849-
// Only warn if they don't match.
850-
emit_warning("resolver")?;
861+
if let Some(behavior) = manifest.resolve_behavior() {
862+
if behavior != self.resolve_behavior {
863+
// Only warn if they don't match.
864+
emit_warning("resolver")?;
865+
}
851866
}
852867
}
853868
}

src/cargo/ops/cargo_new.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::core::{Shell, Workspace};
1+
use crate::core::{Edition, Shell, Workspace};
22
use crate::util::errors::{CargoResult, CargoResultExt};
33
use crate::util::{existing_vcs_repo, FossilRepo, GitRepo, HgRepo, PijulRepo};
44
use crate::util::{paths, restricted_names, Config};
@@ -743,7 +743,7 @@ edition = {}
743743
},
744744
match opts.edition {
745745
Some(edition) => toml::Value::String(edition.to_string()),
746-
None => toml::Value::String("2018".to_string()),
746+
None => toml::Value::String(Edition::LATEST_STABLE.to_string()),
747747
},
748748
match opts.registry {
749749
Some(registry) => format!(

0 commit comments

Comments
 (0)