From 5f5ad05d80c18db5be4c6f9a12c6646983506aff Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Sat, 24 May 2025 13:48:47 -0700 Subject: [PATCH] Add the future edition This adds support for the "future" edition which was added to rustc in https://github.com/rust-lang/rust/pull/137606. To enable support for unstable editions, this introduces a new `unstable-editions` cargo feature. The intent is that instead of having a new feature for each edition that we reuse this feature for all new editions. I don't see a particular reason we should have a separate one for each edition, and this helps a bit with scalability and simplifies some of the edition process. This also includes a change to rework `supports_compat_lint` explained in the comment. --- src/cargo/core/features.rs | 51 ++++++++++++++------- src/cargo/ops/fix.rs | 5 +-- src/cargo/util/toml/mod.rs | 12 +---- src/doc/src/reference/unstable.md | 17 +++++++ tests/testsuite/edition.rs | 73 +++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+), 31 deletions(-) diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index c37183a3310..cdf5c94eb0d 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -151,11 +151,8 @@ pub type AllowFeatures = BTreeSet; /// - 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 /// @@ -163,10 +160,8 @@ pub type AllowFeatures = BTreeSet; /// - 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. @@ -178,7 +173,6 @@ pub type AllowFeatures = BTreeSet; /// [`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 @@ -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 = None; /// The latest stable edition. pub const LATEST_STABLE: Edition = Edition::Edition2024; @@ -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 @@ -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, } } @@ -237,6 +241,7 @@ impl Edition { Edition2018 => true, Edition2021 => true, Edition2024 => true, + EditionFuture => false, } } @@ -250,6 +255,7 @@ impl Edition { Edition2018 => Some(Edition2015), Edition2021 => Some(Edition2018), Edition2024 => Some(Edition2021), + EditionFuture => panic!("future does not have a previous edition"), } } @@ -257,11 +263,13 @@ impl 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, } } @@ -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")); + } } } @@ -299,6 +312,7 @@ impl Edition { Edition2018 => true, Edition2021 => false, Edition2024 => false, + EditionFuture => false, } } @@ -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"), } } } @@ -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.", @@ -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. diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 727201bab84..bd810defccd 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -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); } } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 4af8e15df5e..d3b5e524559 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -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() { diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 2a45f54f6a9..f16ecf25a33 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -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. @@ -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 diff --git a/tests/testsuite/edition.rs b/tests/testsuite/edition.rs index 2c6ea53b08b..9b4bf48164f 100644 --- a/tests/testsuite/edition.rs +++ b/tests/testsuite/edition.rs @@ -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(); +}