From c48a4f08d48d77efe8fa95a475195cfc526a7553 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 +++++++++++++++++++++++++++++++++++--- tests/testsuite/edition.rs | 65 +++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 5 deletions(-) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 2202f6b3b0cc..9c17759f8a4a 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/tests/testsuite/edition.rs b/tests/testsuite/edition.rs index 377a86ec06f9..4c7393966e25 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,