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,