From 27f6c4d596cdc949aeb1ff5120e919bc8bfad164 Mon Sep 17 00:00:00 2001 From: est31 Date: Thu, 22 Jun 2023 07:44:59 +0200 Subject: [PATCH] Allow the edition specifier to be an integer This helps save the number of characters needed to specify an edition. So far, each edition was fully described by its year number, 2015, 2018, 2021. Also for the future, the intent is that editions will always just be year numbers. Therefore, we don't have to use a toml string for them in Cargo.toml, an integer is enough. This saves two characters in each Cargo.toml. --- src/cargo/util/toml/mod.rs | 73 ++++++++++++++++++++++++++++--- src/doc/src/reference/manifest.md | 2 + tests/testsuite/edition.rs | 65 +++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 5 deletions(-) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 2202f6b3b0c..9c17759f8a4 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -979,6 +979,22 @@ impl StringOrVec { } } +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] +#[serde(untagged, expecting = "expected a string or an integer")] +pub enum StringOrI64 { + String(String), + I64(i64), +} + +impl Display for StringOrI64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + StringOrI64::String(s) => f.write_str(s), + StringOrI64::I64(v) => write!(f, "{v}"), + } + } +} + #[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(untagged, expecting = "expected a boolean or a string")] pub enum StringOrBool { @@ -1262,6 +1278,50 @@ impl TomlWorkspaceDependency { //. This already has a `Deserialize` impl from version_trim_whitespace type MaybeWorkspaceSemverVersion = MaybeWorkspace; +type MaybeWorkspaceStringOrI64 = MaybeWorkspace; +impl<'de> de::Deserialize<'de> for MaybeWorkspaceStringOrI64 { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = MaybeWorkspaceStringOrI64; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + f.write_str("a string, integer or workspace") + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + let int = de::value::I64Deserializer::new(value); + StringOrI64::deserialize(int).map(MaybeWorkspace::Defined) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + let string = de::value::StringDeserializer::new(value); + StringOrI64::deserialize(string).map(MaybeWorkspace::Defined) + } + + fn visit_map(self, map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mvd = de::value::MapAccessDeserializer::new(map); + TomlWorkspaceField::deserialize(mvd).map(MaybeWorkspace::Workspace) + } + } + + d.deserialize_any(Visitor) + } +} + type MaybeWorkspaceString = MaybeWorkspace; impl<'de> de::Deserialize<'de> for MaybeWorkspaceString { fn deserialize(d: D) -> Result @@ -1501,7 +1561,7 @@ impl WorkspaceInherit for TomlWorkspaceField { #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "kebab-case")] pub struct TomlPackage { - edition: Option, + edition: Option, rust_version: Option, name: InternedString, #[serde(deserialize_with = "version_trim_whitespace")] @@ -1583,7 +1643,7 @@ pub struct InheritableFields { license_file: Option, repository: Option, publish: Option, - edition: Option, + edition: Option, badges: Option>>, exclude: Option>, include: Option>, @@ -1620,7 +1680,7 @@ impl InheritableFields { ("package.categories", categories -> Vec), ("package.description", description -> String), ("package.documentation", documentation -> String), - ("package.edition", edition -> String), + ("package.edition", edition -> StringOrI64), ("package.exclude", exclude -> Vec), ("package.homepage", homepage -> String), ("package.include", include -> Vec), @@ -1728,7 +1788,7 @@ impl TomlManifest { .edition .as_ref() .and_then(|e| e.as_defined()) - .map(|e| Edition::from_str(e)) + .map(|e| Edition::from_str(&e.to_string())) .unwrap_or(Ok(Edition::Edition2015)) .map(|e| e.default_resolve_behavior()) })?; @@ -2040,9 +2100,12 @@ impl TomlManifest { let edition = if let Some(edition) = package.edition.clone() { let edition: Edition = edition .resolve("edition", || inherit()?.edition())? + .to_string() .parse() .with_context(|| "failed to parse the `edition` key")?; - package.edition = Some(MaybeWorkspace::Defined(edition.to_string())); + package.edition = Some(MaybeWorkspace::Defined(StringOrI64::String( + edition.to_string(), + ))); edition } else { Edition::Edition2015 diff --git a/src/doc/src/reference/manifest.md b/src/doc/src/reference/manifest.md index 5a3d60ccc35..3ef7694056a 100644 --- a/src/doc/src/reference/manifest.md +++ b/src/doc/src/reference/manifest.md @@ -149,6 +149,8 @@ examples, etc. edition = '2021' ``` +For the value of the `edition` key, both strings and integers are supported. + Most manifests have the `edition` field filled in automatically by [`cargo new`] with the latest stable edition. By default `cargo new` creates a manifest with the 2021 edition currently. diff --git a/tests/testsuite/edition.rs b/tests/testsuite/edition.rs index 377a86ec06f..4c7393966e2 100644 --- a/tests/testsuite/edition.rs +++ b/tests/testsuite/edition.rs @@ -34,6 +34,71 @@ fn edition_works_for_build_script() { p.cargo("check -v").run(); } +#[cargo_test] +fn edition_works_as_integer() { + const NEEDS_2021: &str = "pub fn foo() { let hi: u8 = 0i32.try_into().unwrap(); }"; + let p_2021 = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = 2021 + "#, + ) + .file("src/lib.rs", NEEDS_2021) + .build(); + p_2021.cargo("check").run(); + + let p_2018 = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = 2018 + "#, + ) + .file("src/lib.rs", NEEDS_2021) + .build(); + p_2018 + .cargo("check") + .with_status(101) + .with_stderr_contains("[..] is included in the prelude starting in Edition 2021") + .run(); +} + +#[cargo_test] +fn edition_breaks_as_float() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = 2021.0 + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check -v") + .with_status(101) + .with_stderr( + "\ +[ERROR] failed to parse manifest at `[..]/foo/Cargo.toml` + +Caused by: + invalid type: floating point `2021`, expected a string, integer or workspace + in `package.edition` +", + ) + .run(); +} + #[cargo_test] fn edition_unstable_gated() { // During the period where a new edition is coming up, but not yet stable,