Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions serde/src/private/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -816,17 +816,18 @@ mod content {
pub struct TaggedContentVisitor<T> {
tag_name: &'static str,
expecting: &'static str,
value: PhantomData<T>,
/// If set, this tag will be used if tag will not be found in data
default: Option<T>,
}

impl<T> TaggedContentVisitor<T> {
/// Visitor for the content of an internally tagged enum with the given
/// tag name.
pub fn new(name: &'static str, expecting: &'static str) -> Self {
pub fn new(name: &'static str, expecting: &'static str, default: Option<T>) -> Self {
TaggedContentVisitor {
tag_name: name,
expecting,
value: PhantomData,
default,
}
}
}
Expand All @@ -846,6 +847,8 @@ mod content {
where
S: SeqAccess<'de>,
{
// We do not support sequence representation without tags, because that may
// create ambiguity during deserialization
let tag = match tri!(seq.next_element()) {
Some(tag) => tag,
None => {
Expand Down Expand Up @@ -879,7 +882,7 @@ mod content {
}
}
}
match tag {
match tag.or(self.default) {
None => Err(de::Error::missing_field(self.tag_name)),
Some(tag) => Ok((tag, Content::Map(vec))),
}
Expand Down
33 changes: 21 additions & 12 deletions serde_derive/src/de/enum_internally.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,42 @@ pub(super) fn deserialize(
let (variants_stmt, variant_visitor) = enum_::prepare_enum_variant_enum(variants);

// Match arms to extract a variant from a string
let variant_arms = variants
let mut variants = variants
.iter()
.enumerate()
.filter(|&(_, variant)| !variant.attrs.skip_deserializing())
.map(|(i, variant)| {
let variant_name = field_i(i);
.filter(|&(_, variant)| !variant.attrs.skip_deserializing());
let variant_arms = variants.clone().map(|(i, variant)| {
let variant_name = field_i(i);

let block = Match(deserialize_internally_tagged_variant(
params, variant, cattrs,
));
let block = Match(deserialize_internally_tagged_variant(
params, variant, cattrs,
));

quote! {
__Field::#variant_name => #block
}
});
quote! {
__Field::#variant_name => #block
}
});

let expecting = format!("internally tagged enum {}", params.type_name());
let expecting = cattrs.expecting().unwrap_or(&expecting);

// We checked that only one variant is marked with #[serde(default)]
let default = match variants.find(|(_, variant)| variant.attrs.default()) {
Some((i, _)) => {
let default = field_i(i);
quote! { _serde::#private::Some(__Field::#default) }
}
None => quote! { _serde::#private::None },
};

quote_block! {
#variant_visitor

#variants_stmt

let (__tag, __content) = _serde::Deserializer::deserialize_any(
__deserializer,
_serde::#private::de::TaggedContentVisitor::<__Field>::new(#tag, #expecting))?;
_serde::#private::de::TaggedContentVisitor::<__Field>::new(#tag, #expecting, #default))?;
let __deserializer = _serde::#private::de::ContentDeserializer::<__D::Error>::new(__content);

match __tag {
Expand Down
13 changes: 13 additions & 0 deletions serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,7 @@ pub struct Variant {
deserialize_with: Option<syn::ExprPath>,
borrow: Option<BorrowAttribute>,
untagged: bool,
default: bool,
}

struct BorrowAttribute {
Expand All @@ -760,6 +761,7 @@ impl Variant {
let mut deserialize_with = Attr::none(cx, DESERIALIZE_WITH);
let mut borrow = Attr::none(cx, BORROW);
let mut untagged = BoolAttr::none(cx, UNTAGGED);
let mut default = BoolAttr::none(cx, DEFAULT);

for attr in &variant.attrs {
if attr.path() != SERDE {
Expand Down Expand Up @@ -878,7 +880,11 @@ impl Variant {
}
}
} else if meta.path == UNTAGGED {
// #[serde(untagged)]
untagged.set_true(&meta.path);
} else if meta.path == DEFAULT {
// #[serde(default)]
default.set_true(&meta.path);
} else {
let path = meta.path.to_token_stream().to_string().replace(' ', "");
return Err(
Expand Down Expand Up @@ -911,6 +917,7 @@ impl Variant {
deserialize_with: deserialize_with.get(),
borrow: borrow.get(),
untagged: untagged.get(),
default: default.get(),
}
}

Expand Down Expand Up @@ -972,6 +979,12 @@ impl Variant {
pub fn untagged(&self) -> bool {
self.untagged
}

/// This variant marked as default for internally tagged enums. If tag field
/// will not be found, that variant will be assumed
pub fn default(&self) -> bool {
self.default
}
}

/// Represents field attribute information
Expand Down
45 changes: 44 additions & 1 deletion serde_derive/src/internals/check.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::internals::ast::{Container, Data, Field, Style};
use crate::internals::ast::{Container, Data, Field, Style, Variant};
use crate::internals::attr::{Default, Identifier, TagType};
use crate::internals::{ungroup, Ctxt, Derive};
use syn::{Member, Type};
Expand All @@ -13,6 +13,7 @@ pub fn check(cx: &Ctxt, cont: &mut Container, derive: Derive) {
check_identifier(cx, cont);
check_variant_skip_attrs(cx, cont);
check_internal_tag_field_name_conflict(cx, cont);
check_internal_default_variant(cx, cont);
check_adjacent_tag_conflict(cx, cont);
check_transparent(cx, cont, derive);
check_from_and_try_from(cx, cont);
Expand Down Expand Up @@ -347,6 +348,48 @@ fn check_internal_tag_field_name_conflict(cx: &Ctxt, cont: &Container) {
}
}

// Variants may be marked with #[serde(default)] attribute, but only in internally-tagged
// enums and only when not skipped. Also, there must be only one such variant
fn check_internal_default_variant(cx: &Ctxt, cont: &Container) {
let variants = match &cont.data {
Data::Enum(variants) => variants,
Data::Struct(_, _) => return,
};

let mut default: Option<&Variant<'_>> = None;
for variant in variants {
if variant.attrs.default() {
if let TagType::Internal { .. } = cont.attrs.tag() {
if let Some(default) = default {
cx.error_spanned_by(
variant.original,
format!(
"#[serde(default)] already defined at variant {}",
default.ident
),
);
continue;
}
default = Some(variant);

if variant.attrs.skip_deserializing() {
cx.error_spanned_by(
variant.original,
format!("variant `{}` cannot have both #[serde(default)] and #[serde(skip)] or #[serde(skip_deserializing)]", variant.ident),
);
}
} else {
cx.error_spanned_by(
variant.original,
"#[serde(default)] can only be used on variants of internally tagged enums",
);
// We do not want to repeat this error for each variant with #[serde(default)]
return;
}
}
}
}

// In the case of adjacently-tagged enums, the type and the contents tag must
// differ, for the same reason.
fn check_adjacent_tag_conflict(cx: &Ctxt, cont: &Container) {
Expand Down
120 changes: 120 additions & 0 deletions test_suite/tests/test_enum_internally_tagged.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,126 @@ mod struct_enum {
}
}

#[test]
fn default_variant() {
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "tag")]
enum InternallyTaggedWithDefault {
Unit,
NewtypeUnit(()),
NewtypeUnitStruct(Unit),
NewtypeNewtype(Newtype),
NewtypeMap(BTreeMap<String, String>),
NewtypeStruct(Struct),
NewtypeEnum(Enum),
#[serde(default)]
Struct {
a: u8,
},
StructEnum {
enum_: Enum,
},
}

let value = InternallyTaggedWithDefault::Struct { a: 1 };

// Special case: no tag field, use enum tokens
assert_de_tokens(
&value,
&[
Token::Struct {
name: "InternallyTagged",
len: 1,
},
Token::Str("a"),
Token::U8(1),
Token::StructEnd,
],
);
assert_de_tokens(
&value,
&[
Token::Struct {
name: "InternallyTagged",
len: 1,
},
Token::BorrowedStr("a"),
Token::U8(1),
Token::StructEnd,
],
);

// Special case: no tag field, Map representation
assert_de_tokens(
&value,
&[
Token::Map { len: Some(1) },
Token::Str("a"),
Token::U8(1),
Token::MapEnd,
],
);
assert_de_tokens(
&value,
&[
Token::Map { len: Some(1) },
Token::BorrowedStr("a"),
Token::U8(1),
Token::MapEnd,
],
);

// Special case: Map representation, unknown tag
assert_de_tokens_error::<InternallyTaggedWithDefault>(
&[
Token::Map { len: Some(1) },
Token::Str("tag"),
Token::Str("Z"),
Token::MapEnd,
],
"unknown variant `Z`, expected one of \
`Unit`, \
`NewtypeUnit`, \
`NewtypeUnitStruct`, \
`NewtypeNewtype`, \
`NewtypeMap`, \
`NewtypeStruct`, \
`NewtypeEnum`, \
`Struct`, \
`StructEnum`",
);

// Special case: Seq representation, unknown tag
assert_de_tokens_error::<InternallyTaggedWithDefault>(
&[
Token::Seq { len: Some(1) },
Token::Str("Z"), // tag
Token::SeqEnd,
],
"unknown variant `Z`, expected one of \
`Unit`, \
`NewtypeUnit`, \
`NewtypeUnitStruct`, \
`NewtypeNewtype`, \
`NewtypeMap`, \
`NewtypeStruct`, \
`NewtypeEnum`, \
`Struct`, \
`StructEnum`",
);

// Special case: Seq representation cannot be used without a tag due to ambiguity
assert_de_tokens_error::<InternallyTaggedWithDefault>(
&[
Token::Seq { len: Some(1) },
Token::U8(1), // tag (== NewtypeUnit)
Token::SeqEnd,
],
// The error is not very clear, because actually we got end of sequence instead of a Unit
"invalid type: sequence, expected unit",
);
}

#[test]
fn wrong_tag() {
assert_de_tokens_error::<InternallyTagged>(
Expand Down
11 changes: 11 additions & 0 deletions test_suite/tests/ui/enum-representation/adjacently-default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use serde_derive::Deserialize;

#[derive(Deserialize)]
#[serde(tag = "tag", content = "content")]
enum E {
V1 { f: u8 },
#[serde(default)]
V2 { f: u8 },
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
error: #[serde(default)] can only be used on variants of internally tagged enums
--> tests/ui/enum-representation/adjacently-default.rs:7:5
|
7 | / #[serde(default)]
8 | | V2 { f: u8 },
| |________________^
10 changes: 10 additions & 0 deletions test_suite/tests/ui/enum-representation/external-default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde_derive::Deserialize;

#[derive(Deserialize)]
enum E {
V1 { f: u8 },
#[serde(default)]
V2 { f: u8 },
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
error: #[serde(default)] can only be used on variants of internally tagged enums
--> tests/ui/enum-representation/external-default.rs:6:5
|
6 | / #[serde(default)]
7 | | V2 { f: u8 },
| |________________^
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use serde_derive::Deserialize;

#[derive(Deserialize)]
#[serde(tag = "tag")]
enum E1 {
V1 { f: u8 },
#[serde(default, skip)]
V2 { f: u8 },
}

#[derive(Deserialize)]
#[serde(tag = "tag")]
enum E2 {
V1 { f: u8 },
#[serde(default, skip_deserializing)]
V2 { f: u8 },
}

fn main() {}
Loading
Loading