From 756e2ac49ef8f8f4a3de8cc8098bfca40f5702ad Mon Sep 17 00:00:00 2001 From: Mingun Date: Sat, 31 May 2025 16:31:02 +0500 Subject: [PATCH] Allow only one unit variant in untagged enums, because we cannot distinguish between them when deserialize If you really need several unit variants, mark all of them except one with `#[serde(skip_deserializing)]` --- serde_derive/src/internals/check.rs | 34 +++++++++++++++++++ .../untagged-with-muliple-units.rs | 29 ++++++++++++++++ .../untagged-with-muliple-units.stderr | 12 +++++++ 3 files changed, 75 insertions(+) create mode 100644 test_suite/tests/ui/enum-representation/untagged-with-muliple-units.rs create mode 100644 test_suite/tests/ui/enum-representation/untagged-with-muliple-units.stderr diff --git a/serde_derive/src/internals/check.rs b/serde_derive/src/internals/check.rs index da2a0fbdc..5932f136d 100644 --- a/serde_derive/src/internals/check.rs +++ b/serde_derive/src/internals/check.rs @@ -16,6 +16,7 @@ pub fn check(cx: &Ctxt, cont: &mut Container, derive: Derive) { check_adjacent_tag_conflict(cx, cont); check_transparent(cx, cont, derive); check_from_and_try_from(cx, cont); + check_enum_untagged(cx, cont, derive); } // If some field of a tuple struct is marked #[serde(default)] then all fields @@ -475,3 +476,36 @@ fn check_from_and_try_from(cx: &Ctxt, cont: &mut Container) { ); } } + +/// Checks that untagged enum has only one unit variant, because we cannot distinguish between +/// different variants when deserializing +fn check_enum_untagged(cx: &Ctxt, cont: &mut Container, derive: Derive) { + // We allow serialization of enums with multiple units, because new units could + // be added to maintain backward compatibility + if let Derive::Serialize = derive { + return; + } + let variants = match &cont.data { + Data::Enum(variants) => variants, + Data::Struct(_, _) => return, + }; + if !matches!(cont.attrs.tag(), TagType::None) { + return; + } + + let mut unit = None; + for variant in variants { + if variant.attrs.skip_deserializing() { + continue; + } + if let Style::Unit = variant.style { + if unit.is_some() { + cx.error_spanned_by( + variant.original, + "untagged enums can contain only one unit variant. Use #[serde(skip_deserializing)] if you really want several unit variants", + ); + } + unit = Some(variant); + } + } +} diff --git a/test_suite/tests/ui/enum-representation/untagged-with-muliple-units.rs b/test_suite/tests/ui/enum-representation/untagged-with-muliple-units.rs new file mode 100644 index 000000000..00bf1f7e6 --- /dev/null +++ b/test_suite/tests/ui/enum-representation/untagged-with-muliple-units.rs @@ -0,0 +1,29 @@ +use serde_derive::{Deserialize, Serialize}; + +#[derive(Deserialize)] +#[serde(untagged)] +enum E1 { + Unit1, + Tuple1(usize), + Unit2, + Struct {}, + #[serde(skip_serializing)] + Unit3, + #[serde(skip_deserializing)] + Unit4, +} + +#[derive(Serialize)] +#[serde(untagged)] +enum E2 { + Unit1, + Tuple1(usize), + Unit2, + Struct {}, + #[serde(skip_serializing)] + Unit3, + #[serde(skip_deserializing)] + Unit4, +} + +fn main() {} diff --git a/test_suite/tests/ui/enum-representation/untagged-with-muliple-units.stderr b/test_suite/tests/ui/enum-representation/untagged-with-muliple-units.stderr new file mode 100644 index 000000000..6e8fe136d --- /dev/null +++ b/test_suite/tests/ui/enum-representation/untagged-with-muliple-units.stderr @@ -0,0 +1,12 @@ +error: untagged enums can contain only one unit variant. Use #[serde(skip_deserializing)] if you really want several unit variants + --> tests/ui/enum-representation/untagged-with-muliple-units.rs:8:5 + | +8 | Unit2, + | ^^^^^ + +error: untagged enums can contain only one unit variant. Use #[serde(skip_deserializing)] if you really want several unit variants + --> tests/ui/enum-representation/untagged-with-muliple-units.rs:10:5 + | +10 | / #[serde(skip_serializing)] +11 | | Unit3, + | |_________^