Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,22 +151,17 @@ pub type AllowFeatures = BTreeSet<String>;
/// - 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 invocation below for the new edition.
/// - Gate on that new feature in [`toml`].
/// - Update the shell completion files.
/// - Update any failing tests (hopefully there are very few).
/// - Update unstable.md to add a new section for this new edition (see [this example]).
///
/// ## Stabilization instructions
///
/// - Set [`LATEST_UNSTABLE`] to None.
/// - Set [`LATEST_STABLE`] to the new version.
/// - Update [`is_stable`] to `true`.
/// - Set [`first_version`] to the version it will be released.
/// - Set the editionNNNN feature to stable in the [`features!`] macro invocation below.
/// - Update any tests that are affected.
/// - Update the man page for the `--edition` flag.
/// - Update unstable.md to move the edition section to the bottom.
/// - Update the documentation:
/// - Update any features impacted by the edition.
/// - Update manifest.md#the-edition-field.
Expand All @@ -178,7 +173,6 @@ pub type AllowFeatures = BTreeSet<String>;
/// [`CLI_VALUES`]: Edition::CLI_VALUES
/// [`LATEST_UNSTABLE`]: Edition::LATEST_UNSTABLE
/// [`LATEST_STABLE`]: Edition::LATEST_STABLE
/// [this example]: https://github.com/rust-lang/cargo/blob/3ebb5f15a940810f250b68821149387af583a79e/src/doc/src/reference/unstable.md?plain=1#L1238-L1264
/// [`first_version`]: Edition::first_version
/// [`is_stable`]: Edition::is_stable
/// [`toml`]: crate::util::toml
Expand All @@ -196,12 +190,17 @@ pub enum Edition {
Edition2021,
/// The 2024 edition
Edition2024,
/// The future edition (permanently unstable)
EditionFuture,
}

impl Edition {
/// The latest edition that is unstable.
///
/// This is `None` if there is no next unstable edition.
///
/// Note that this does *not* include "future" since this is primarily
/// used for tests that need to step between stable and unstable.
pub const LATEST_UNSTABLE: Option<Edition> = None;
/// The latest stable edition.
pub const LATEST_STABLE: Edition = Edition::Edition2024;
Expand All @@ -210,11 +209,15 @@ impl Edition {
Self::Edition2018,
Self::Edition2021,
Self::Edition2024,
Self::EditionFuture,
];
/// 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.
///
/// This does not include `future` since we don't need to create new
/// packages with it.
pub const CLI_VALUES: [&'static str; 4] = ["2015", "2018", "2021", "2024"];

/// Returns the first version that a particular edition was released on
Expand All @@ -226,6 +229,7 @@ impl Edition {
Edition2018 => Some(semver::Version::new(1, 31, 0)),
Edition2021 => Some(semver::Version::new(1, 56, 0)),
Edition2024 => Some(semver::Version::new(1, 85, 0)),
EditionFuture => None,
}
}

Expand All @@ -237,6 +241,7 @@ impl Edition {
Edition2018 => true,
Edition2021 => true,
Edition2024 => true,
EditionFuture => false,
}
}

Expand All @@ -250,18 +255,21 @@ impl Edition {
Edition2018 => Some(Edition2015),
Edition2021 => Some(Edition2018),
Edition2024 => Some(Edition2021),
EditionFuture => panic!("future does not have a previous edition"),
}
}

/// 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::*;
// Nothing should treat "future" as being next.
match self {
Edition2015 => Edition2018,
Edition2018 => Edition2021,
Edition2021 => Edition2024,
Edition2024 => Edition2024,
EditionFuture => EditionFuture,
}
}

Expand All @@ -274,18 +282,23 @@ impl Edition {
}
}

/// Whether or not this edition supports the `rust_*_compatibility` lint.
///
/// Ideally this would not be necessary, but editions may 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 {
/// Adds the appropriate argument to generate warnings for this edition.
pub(crate) fn force_warn_arg(&self, cmd: &mut ProcessBuilder) {
use Edition::*;
match self {
Edition2015 => false,
Edition2018 => true,
Edition2021 => true,
Edition2024 => true,
Edition2015 => {}
EditionFuture => {
cmd.arg("--force-warn=edition_future_compatibility");
}
e => {
// Note that cargo always passes this even if the
// compatibility lint group does not exist. When a new edition
// is introduced, but there are no migration lints, rustc does
// not create the lint group. That's OK because rustc will
// just generate a warning about an unknown lint which will be
// suppressed due to cap-lints.
cmd.arg(format!("--force-warn=rust-{e}-compatibility"));
}
}
}

Expand All @@ -299,6 +312,7 @@ impl Edition {
Edition2018 => true,
Edition2021 => false,
Edition2024 => false,
EditionFuture => false,
}
}

Expand All @@ -320,6 +334,7 @@ impl fmt::Display for Edition {
Edition::Edition2018 => f.write_str("2018"),
Edition::Edition2021 => f.write_str("2021"),
Edition::Edition2024 => f.write_str("2024"),
Edition::EditionFuture => f.write_str("future"),
}
}
}
Expand All @@ -332,6 +347,7 @@ impl FromStr for Edition {
"2018" => Ok(Edition::Edition2018),
"2021" => Ok(Edition::Edition2021),
"2024" => Ok(Edition::Edition2024),
"future" => Ok(Edition::EditionFuture),
s if s.parse().map_or(false, |y: u16| y > 2024 && y < 2050) => bail!(
"this version of Cargo is older than the `{}` edition, \
and only supports `2015`, `2018`, `2021`, and `2024` editions.",
Expand Down Expand Up @@ -519,6 +535,9 @@ features! {

/// Allow paths that resolve relatively to a base specified in the config.
(unstable, path_bases, "", "reference/unstable.html#path-bases"),

/// Allows use of editions that are not yet stable.
(unstable, unstable_editions, "", "reference/unstable.html#unstable-editions"),
}

/// Status and metadata for a single unstable feature.
Expand Down
5 changes: 1 addition & 4 deletions src/cargo/ops/fix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1238,10 +1238,7 @@ impl FixArgs {
}

if let Some(edition) = self.prepare_for_edition {
if edition.supports_compat_lint() {
cmd.arg("--force-warn")
.arg(format!("rust-{}-compatibility", edition));
}
edition.force_warn_arg(cmd);
}
}

Expand Down
12 changes: 1 addition & 11 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1342,18 +1342,8 @@ pub fn to_real_manifest(
}
default_edition
};
// Add these lines if start a new unstable edition.
// ```
// if edition == Edition::Edition20xx {
// features.require(Feature::edition20xx())?;
// }
// ```
if !edition.is_stable() {
// Guard in case someone forgets to add .require()
return Err(util::errors::internal(format!(
"edition {} should be gated",
edition
)));
features.require(Feature::unstable_editions())?;
}

if original_toml.project.is_some() {
Expand Down
17 changes: 17 additions & 0 deletions src/doc/src/reference/unstable.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Each new feature described below should explain how to use it.
* [Profile `trim-paths` option](#profile-trim-paths-option) --- Control the sanitization of file paths in build outputs.
* [`[lints.cargo]`](#lintscargo) --- Allows configuring lints for Cargo.
* [path bases](#path-bases) --- Named base directories for path dependencies.
* [`unstable-editions`](#unstable-editions) --- Allows use of editions that are not yet stable.
* Information and metadata
* [Build-plan](#build-plan) --- Emits JSON information on which commands will be run.
* [unit-graph](#unit-graph) --- Emits JSON for Cargo's internal graph structure.
Expand Down Expand Up @@ -1898,6 +1899,22 @@ be stored in `.rmeta` files.
cargo +nightly -Zno-embed-metadata build
```

## `unstable-editions`

The `unstable-editions` value in the `cargo-features` list allows a `Cargo.toml` manifest to specify an edition that is not yet stable.

```toml
cargo-features = ["unstable-editions"]

[package]
name = "my-package"
edition = "future"
```

When new editions are introduced, the `unstable-editions` feature is required until the edition is stabilized.

The special "future" edition is a home for new features that are under development, and is permanently unstable. The "future" edition also has no new behavior by itself. Each change in the future edition requires an opt-in such as a `#![feature(...)]` attribute.

# Stabilized and removed features

## Compile progress
Expand Down
73 changes: 73 additions & 0 deletions tests/testsuite/edition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,76 @@ fn unset_edition_works_on_old_msrv() {
"#]])
.run();
}

#[cargo_test]
fn future_edition_is_gated() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "future"
"#,
)
.file("src/lib.rs", "")
.build();

p.cargo("check")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`

Caused by:
feature `unstable-editions` is required

The package requires the Cargo feature called `unstable-editions`, but that feature is not stabilized in this version of Cargo ([..]).
Consider trying a newer version of Cargo (this may require the nightly release).
See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unstable-editions for more information about the status of this feature.

"#]])
.run();

// Repeat on nightly.
p.cargo("check")
.masquerade_as_nightly_cargo(&["unstable-editions"])
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`

Caused by:
feature `unstable-editions` is required

The package requires the Cargo feature called `unstable-editions`, but that feature is not stabilized in this version of Cargo ([..]).
Consider adding `cargo-features = ["unstable-editions"]` to the top of Cargo.toml (above the [package] table) to tell Cargo you are opting in to use this unstable feature.
See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unstable-editions for more information about the status of this feature.

"#]])
.run();
}

#[cargo_test(nightly, reason = "future edition is always unstable")]
fn future_edition_works() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["unstable-editions"]

[package]
name = "foo"
edition = "future"
"#,
)
.file("src/lib.rs", "")
.build();

p.cargo("check")
.masquerade_as_nightly_cargo(&["unstable-editions"])
.with_stderr_data(str![[r#"
[CHECKING] foo v0.0.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s

"#]])
.run();
}