Compare commits

...

11 Commits

Author SHA1 Message Date
David Tolnay f9c6f0ab62 Release 1.0.52 2018-05-09 13:01:41 -07:00
David Tolnay b2b36e1764 Accept implicitly borrowed data inside of Option 2018-05-08 12:19:09 -07:00
David Tolnay 4ad140ea70 Improve error for struct deserialized from array that is too short 2018-05-08 12:03:35 -07:00
David Tolnay 67777eb585 Account for skip_serializing_if in tuple struct length 2018-05-08 11:49:37 -07:00
David Tolnay b4e51fcc77 Respect skip_serializing in tuple structs and variants 2018-05-08 11:37:52 -07:00
David Tolnay be7fe2a5eb Introduce bound attribute on enum variants 2018-05-08 11:16:10 -07:00
David Tolnay b4076f4577 Release 1.0.51 2018-05-08 10:07:45 -07:00
David Tolnay c4181f46be Respect variant skip attribute in inferred bounds 2018-05-07 21:30:00 -07:00
David Tolnay 8c0efc3d77 Add a variant skip attribute 2018-05-07 21:27:34 -07:00
David Tolnay 7e3efaf6c5 Improve error when a 'de lifetime parameter already exists 2018-05-07 21:15:44 -07:00
David Tolnay 12fe42ed45 Support empty adjacently tagged enum 2018-05-07 21:02:42 -07:00
13 changed files with 354 additions and 37 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "serde"
version = "1.0.50" # remember to update html_root_url
version = "1.0.52" # remember to update html_root_url
authors = ["Erick Tryzelaar <erick.tryzelaar@gmail.com>", "David Tolnay <dtolnay@gmail.com>"]
license = "MIT/Apache-2.0"
description = "A generic serialization/deserialization framework"
+1 -1
View File
@@ -79,7 +79,7 @@
////////////////////////////////////////////////////////////////////////////////
// Serde types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/serde/1.0.50")]
#![doc(html_root_url = "https://docs.rs/serde/1.0.52")]
// Support using Serde without the standard library!
#![cfg_attr(not(feature = "std"), no_std)]
// Unstable functionality only if the user asks for it. For tracking and
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "serde_derive"
version = "1.0.50" # remember to update html_root_url
version = "1.0.52" # remember to update html_root_url
authors = ["Erick Tryzelaar <erick.tryzelaar@gmail.com>", "David Tolnay <dtolnay@gmail.com>"]
license = "MIT/Apache-2.0"
description = "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]"
+22
View File
@@ -65,6 +65,28 @@ pub fn with_where_predicates_from_fields(
generics
}
pub fn with_where_predicates_from_variants(
cont: &Container,
generics: &syn::Generics,
from_variant: fn(&attr::Variant) -> Option<&[syn::WherePredicate]>,
) -> syn::Generics {
let variants = match cont.data {
Data::Enum(ref variants) => variants,
Data::Struct(_, _) => {
return generics.clone();
}
};
let predicates = variants
.iter()
.flat_map(|variant| from_variant(&variant.attrs))
.flat_map(|predicates| predicates.to_vec());
let mut generics = generics.clone();
generics.make_where_clause().predicates.extend(predicates);
generics
}
// Puts the given bound on any generic type parameters that are used in fields
// for which filter returns true.
//
+63 -18
View File
@@ -82,6 +82,11 @@ pub fn expand_derive_deserialize(input: &syn::DeriveInput) -> Result<Tokens, Str
}
fn precondition(cx: &Ctxt, cont: &Container) {
precondition_sized(cx, cont);
precondition_no_de_lifetime(cx, cont);
}
fn precondition_sized(cx: &Ctxt, cont: &Container) {
if let Data::Struct(_, ref fields) = cont.data {
if let Some(last) = fields.last() {
if let syn::Type::Slice(_) = *last.ty {
@@ -91,6 +96,17 @@ fn precondition(cx: &Ctxt, cont: &Container) {
}
}
fn precondition_no_de_lifetime(cx: &Ctxt, cont: &Container) {
if let BorrowedLifetimes::Borrowed(_) = borrowed_lifetimes(cont) {
for param in cont.generics.lifetimes() {
if param.lifetime.to_string() == "'de" {
cx.error("cannot deserialize when there is a lifetime parameter called 'de");
return;
}
}
}
}
struct Parameters {
/// Name of the type the `derive` is on.
local: syn::Ident,
@@ -147,6 +163,9 @@ fn build_generics(cont: &Container, borrowed: &BorrowedLifetimes) -> syn::Generi
let generics = bound::with_where_predicates_from_fields(cont, &generics, attr::Field::de_bound);
let generics =
bound::with_where_predicates_from_variants(cont, &generics, attr::Variant::de_bound);
match cont.attrs.de_bound() {
Some(predicates) => bound::with_where_predicates(&generics, predicates),
None => {
@@ -175,13 +194,18 @@ fn build_generics(cont: &Container, borrowed: &BorrowedLifetimes) -> syn::Generi
}
}
// Fields with a `skip_deserializing` or `deserialize_with` attribute are not
// deserialized by us so we do not generate a bound. Fields with a `bound`
// attribute specify their own bound so we do not generate one. All other fields
// may need a `T: Deserialize` bound where T is the type of the field.
// Fields with a `skip_deserializing` or `deserialize_with` attribute, or which
// belong to a variant with a `skip_deserializing` or `deserialize_with`
// attribute, are not deserialized by us so we do not generate a bound. Fields
// with a `bound` attribute specify their own bound so we do not generate one.
// All other fields may need a `T: Deserialize` bound where T is the type of the
// field.
fn needs_deserialize_bound(field: &attr::Field, variant: Option<&attr::Variant>) -> bool {
!field.skip_deserializing() && field.deserialize_with().is_none() && field.de_bound().is_none()
&& variant.map_or(true, |variant| variant.deserialize_with().is_none())
&& variant.map_or(true, |variant| {
!variant.skip_deserializing() && variant.deserialize_with().is_none()
&& variant.de_bound().is_none()
})
}
// Fields with a `default` attribute (not `default=...`), and fields with a
@@ -403,7 +427,9 @@ fn deserialize_tuple(
None
};
let visit_seq = Stmts(deserialize_seq(&type_path, params, fields, false, cattrs));
let visit_seq = Stmts(deserialize_seq(
&type_path, params, fields, false, cattrs, &expecting,
));
let visitor_expr = quote! {
__Visitor {
@@ -487,7 +513,7 @@ fn deserialize_tuple_in_place(
None
};
let visit_seq = Stmts(deserialize_seq_in_place(params, fields, cattrs));
let visit_seq = Stmts(deserialize_seq_in_place(params, fields, cattrs, &expecting));
let visitor_expr = quote! {
__Visitor {
@@ -553,6 +579,7 @@ fn deserialize_seq(
fields: &[Field],
is_struct: bool,
cattrs: &attr::Container,
expecting: &str,
) -> Fragment {
let vars = (0..fields.len()).map(field_i as fn(_) -> _);
@@ -562,7 +589,11 @@ fn deserialize_seq(
.iter()
.filter(|field| !field.attrs.skip_deserializing())
.count();
let expecting = format!("tuple of {} elements", deserialized_count);
let expecting = if deserialized_count == 1 {
format!("{} with 1 element", expecting)
} else {
format!("{} with {} elements", expecting, deserialized_count)
};
let mut index_in_seq = 0_usize;
let let_values = vars.clone().zip(fields).map(|(var, field)| {
@@ -647,6 +678,7 @@ fn deserialize_seq_in_place(
params: &Parameters,
fields: &[Field],
cattrs: &attr::Container,
expecting: &str,
) -> Fragment {
let vars = (0..fields.len()).map(field_i as fn(_) -> _);
@@ -656,7 +688,11 @@ fn deserialize_seq_in_place(
.iter()
.filter(|field| !field.attrs.skip_deserializing())
.count();
let expecting = format!("tuple of {} elements", deserialized_count);
let expecting = if deserialized_count == 1 {
format!("{} with 1 element", expecting)
} else {
format!("{} with {} elements", expecting, deserialized_count)
};
let mut index_in_seq = 0usize;
let write_values = vars.clone()
@@ -828,7 +864,7 @@ fn deserialize_struct(
None => format!("struct {}", params.type_name()),
};
let visit_seq = Stmts(deserialize_seq(&type_path, params, fields, true, cattrs));
let visit_seq = Stmts(deserialize_seq(&type_path, params, fields, true, cattrs, &expecting));
let (field_visitor, fields_stmt, visit_map) = if cattrs.has_flatten() {
deserialize_struct_as_map_visitor(&type_path, params, fields, cattrs)
@@ -967,7 +1003,7 @@ fn deserialize_struct_in_place(
None => format!("struct {}", params.type_name()),
};
let visit_seq = Stmts(deserialize_seq_in_place(params, fields, cattrs));
let visit_seq = Stmts(deserialize_seq_in_place(params, fields, cattrs, &expecting));
let (field_visitor, fields_stmt, visit_map) =
deserialize_struct_as_struct_in_place_visitor(params, fields, cattrs);
@@ -1393,6 +1429,21 @@ fn deserialize_adjacently_tagged_enum(
}
};
let finish_content_then_tag = if variant_arms.is_empty() {
quote! {
match try!(_serde::de::MapAccess::next_value::<__Field>(&mut __map)) {}
}
} else {
quote! {
let __ret = try!(match try!(_serde::de::MapAccess::next_value(&mut __map)) {
// Deserialize the buffered content now that we know the variant.
#(#variant_arms)*
});
// Visit remaining keys, looking for duplicates.
#visit_remaining_keys
}
};
quote_block! {
#variant_visitor
@@ -1469,13 +1520,7 @@ fn deserialize_adjacently_tagged_enum(
// Second key is the tag.
_serde::export::Some(_serde::private::de::TagOrContentField::Tag) => {
let __deserializer = _serde::private::de::ContentDeserializer::<__A::Error>::new(__content);
// Parse the tag.
let __ret = try!(match try!(_serde::de::MapAccess::next_value(&mut __map)) {
// Deserialize the buffered content now that we know the variant.
#(#variant_arms)*
});
// Visit remaining keys, looking for duplicates.
#visit_remaining_keys
#finish_content_then_tag
}
// Second key is a duplicate of the content.
_serde::export::Some(_serde::private::de::TagOrContentField::Content) => {
+73 -2
View File
@@ -527,6 +527,8 @@ pub struct Variant {
ser_renamed: bool,
de_renamed: bool,
rename_all: RenameRule,
ser_bound: Option<Vec<syn::WherePredicate>>,
de_bound: Option<Vec<syn::WherePredicate>>,
skip_deserializing: bool,
skip_serializing: bool,
other: bool,
@@ -542,6 +544,8 @@ impl Variant {
let mut skip_deserializing = BoolAttr::none(cx, "skip_deserializing");
let mut skip_serializing = BoolAttr::none(cx, "skip_serializing");
let mut rename_all = Attr::none(cx, "rename_all");
let mut ser_bound = Attr::none(cx, "bound");
let mut de_bound = Attr::none(cx, "bound");
let mut other = BoolAttr::none(cx, "other");
let mut serialize_with = Attr::none(cx, "serialize_with");
let mut deserialize_with = Attr::none(cx, "deserialize_with");
@@ -580,6 +584,12 @@ impl Variant {
}
}
// Parse `#[serde(skip)]`
Meta(Word(word)) if word == "skip" => {
skip_serializing.set_true();
skip_deserializing.set_true();
}
// Parse `#[serde(skip_deserializing)]`
Meta(Word(word)) if word == "skip_deserializing" => {
skip_deserializing.set_true();
@@ -595,6 +605,24 @@ impl Variant {
other.set_true();
}
// Parse `#[serde(bound = "D: Serialize")]`
Meta(NameValue(ref m)) if m.ident == "bound" => {
if let Ok(where_predicates) =
parse_lit_into_where(cx, m.ident.as_ref(), m.ident.as_ref(), &m.lit)
{
ser_bound.set(where_predicates.clone());
de_bound.set(where_predicates);
}
}
// Parse `#[serde(bound(serialize = "D: Serialize", deserialize = "D: Deserialize"))]`
Meta(List(ref m)) if m.ident == "bound" => {
if let Ok((ser, de)) = get_where_predicates(cx, &m.nested) {
ser_bound.set_opt(ser);
de_bound.set_opt(de);
}
}
// Parse `#[serde(with = "...")]`
Meta(NameValue(ref m)) if m.ident == "with" => {
if let Ok(path) = parse_lit_into_expr_path(cx, m.ident.as_ref(), &m.lit) {
@@ -663,6 +691,8 @@ impl Variant {
ser_renamed: ser_renamed,
de_renamed: de_renamed,
rename_all: rename_all.get().unwrap_or(RenameRule::None),
ser_bound: ser_bound.get(),
de_bound: de_bound.get(),
skip_deserializing: skip_deserializing.get(),
skip_serializing: skip_serializing.get(),
other: other.get(),
@@ -689,6 +719,14 @@ impl Variant {
&self.rename_all
}
pub fn ser_bound(&self) -> Option<&[syn::WherePredicate]> {
self.ser_bound.as_ref().map(|vec| &vec[..])
}
pub fn de_bound(&self) -> Option<&[syn::WherePredicate]> {
self.de_bound.as_ref().map(|vec| &vec[..])
}
pub fn skip_deserializing(&self) -> bool {
self.skip_deserializing
}
@@ -1000,7 +1038,7 @@ impl Field {
};
deserialize_with.set_if_none(expr);
}
} else if is_rptr(&field.ty, is_str) || is_rptr(&field.ty, is_slice_u8) {
} else if is_implicitly_borrowed(&field.ty) {
// Types &str and &[u8] are always implicitly borrowed. No need for
// a #[serde(borrow)].
collect_lifetimes(&field.ty, &mut borrowed_lifetimes);
@@ -1262,6 +1300,14 @@ fn parse_lit_into_lifetimes(
Err(())
}
fn is_implicitly_borrowed(ty: &syn::Type) -> bool {
is_implicitly_borrowed_reference(ty) || is_option(ty, is_implicitly_borrowed_reference)
}
fn is_implicitly_borrowed_reference(ty: &syn::Type) -> bool {
is_reference(ty, is_str) || is_reference(ty, is_slice_u8)
}
// Whether the type looks like it might be `std::borrow::Cow<T>` where elem="T".
// This can have false negatives and false positives.
//
@@ -1309,6 +1355,31 @@ fn is_cow(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
}
}
fn is_option(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
let path = match *ty {
syn::Type::Path(ref ty) => &ty.path,
_ => {
return false;
}
};
let seg = match path.segments.last() {
Some(seg) => seg.into_value(),
None => {
return false;
}
};
let args = match seg.arguments {
syn::PathArguments::AngleBracketed(ref bracketed) => &bracketed.args,
_ => {
return false;
}
};
seg.ident == "Option" && args.len() == 1 && match args[0] {
syn::GenericArgument::Type(ref arg) => elem(arg),
_ => false,
}
}
// Whether the type looks like it might be `&T` where elem="T". This can have
// false negatives and false positives.
//
@@ -1329,7 +1400,7 @@ fn is_cow(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
// struct S<'a> {
// r: &'a str,
// }
fn is_rptr(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
fn is_reference(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
match *ty {
syn::Type::Reference(ref ty) => ty.mutability.is_none() && elem(&ty.elem),
_ => false,
+1 -1
View File
@@ -22,7 +22,7 @@
//!
//! [https://serde.rs/derive.html]: https://serde.rs/derive.html
#![doc(html_root_url = "https://docs.rs/serde_derive/1.0.50")]
#![doc(html_root_url = "https://docs.rs/serde_derive/1.0.52")]
#![cfg_attr(feature = "cargo-clippy", deny(clippy, clippy_pedantic))]
// Whitelisted clippy lints
#![cfg_attr(
+48 -9
View File
@@ -138,6 +138,9 @@ fn build_generics(cont: &Container) -> syn::Generics {
let generics =
bound::with_where_predicates_from_fields(cont, &generics, attr::Field::ser_bound);
let generics =
bound::with_where_predicates_from_variants(cont, &generics, attr::Variant::ser_bound);
match cont.attrs.ser_bound() {
Some(predicates) => bound::with_where_predicates(&generics, predicates),
None => bound::with_bound(
@@ -150,13 +153,16 @@ fn build_generics(cont: &Container) -> syn::Generics {
}
// Fields with a `skip_serializing` or `serialize_with` attribute, or which
// belong to a variant with a `serialize_with` attribute, are not serialized by
// us so we do not generate a bound. Fields with a `bound` attribute specify
// their own bound so we do not generate one. All other fields may need a `T:
// Serialize` bound where T is the type of the field.
// belong to a variant with a 'skip_serializing` or `serialize_with` attribute,
// are not serialized by us so we do not generate a bound. Fields with a `bound`
// attribute specify their own bound so we do not generate one. All other fields
// may need a `T: Serialize` bound where T is the type of the field.
fn needs_serialize_bound(field: &attr::Field, variant: Option<&attr::Variant>) -> bool {
!field.skip_serializing() && field.serialize_with().is_none() && field.ser_bound().is_none()
&& variant.map_or(true, |variant| variant.serialize_with().is_none())
&& variant.map_or(true, |variant| {
!variant.skip_serializing() && variant.serialize_with().is_none()
&& variant.ser_bound().is_none()
})
}
fn serialize_body(cont: &Container, params: &Parameters) -> Fragment {
@@ -237,8 +243,25 @@ fn serialize_tuple_struct(
serialize_tuple_struct_visitor(fields, params, false, &TupleTrait::SerializeTupleStruct);
let type_name = cattrs.name().serialize_name();
let len = serialize_stmts.len();
let let_mut = mut_if(len > 0);
let mut serialized_fields = fields
.iter()
.enumerate()
.filter(|&(_, ref field)| !field.attrs.skip_serializing())
.peekable();
let let_mut = mut_if(serialized_fields.peek().is_some());
let len = serialized_fields
.map(|(i, field)| match field.attrs.skip_serializing_if() {
None => quote!(1),
Some(path) => {
let index = syn::Index { index: i as u32, span: Span::call_site() };
let field_expr = get_member(params, field, &Member::Unnamed(index));
quote!(if #path(#field_expr) { 0 } else { 1 })
}
})
.fold(quote!(0), |sum, expr| quote!(#sum + #expr));
quote_block! {
let #let_mut __serde_state = try!(_serde::Serializer::serialize_tuple_struct(__serializer, #type_name, #len));
@@ -737,8 +760,23 @@ fn serialize_tuple_variant(
let serialize_stmts = serialize_tuple_struct_visitor(fields, params, true, &tuple_trait);
let len = serialize_stmts.len();
let let_mut = mut_if(len > 0);
let mut serialized_fields = fields
.iter()
.enumerate()
.filter(|&(_, ref field)| !field.attrs.skip_serializing())
.peekable();
let let_mut = mut_if(serialized_fields.peek().is_some());
let len = serialized_fields
.map(|(i, field)| match field.attrs.skip_serializing_if() {
None => quote!(1),
Some(path) => {
let field_expr = Ident::new(&format!("__field{}", i), Span::call_site());
quote!(if #path(#field_expr) { 0 } else { 1 })
}
})
.fold(quote!(0), |sum, expr| quote!(#sum + #expr));
match context {
TupleVariant::ExternallyTagged {
@@ -960,6 +998,7 @@ fn serialize_tuple_struct_visitor(
fields
.iter()
.enumerate()
.filter(|&(_, ref field)| !field.attrs.skip_serializing())
.map(|(i, field)| {
let mut field_expr = if is_enum {
let id = Ident::new(&format!("__field{}", i), Span::call_site());
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "serde_test"
version = "1.0.50" # remember to update html_root_url
version = "1.0.52" # remember to update html_root_url
authors = ["Erick Tryzelaar <erick.tryzelaar@gmail.com>", "David Tolnay <dtolnay@gmail.com>"]
license = "MIT/Apache-2.0"
description = "Token De/Serializer for testing De/Serialize implementations"
+1 -1
View File
@@ -161,7 +161,7 @@
//! # }
//! ```
#![doc(html_root_url = "https://docs.rs/serde_test/1.0.50")]
#![doc(html_root_url = "https://docs.rs/serde_test/1.0.52")]
#![cfg_attr(feature = "cargo-clippy", deny(clippy, clippy_pedantic))]
// Whitelisted clippy lints
#![cfg_attr(feature = "cargo-clippy", allow(float_cmp))]
@@ -0,0 +1,15 @@
// Copyright 2018 Serde Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#[macro_use]
extern crate serde_derive;
#[derive(Deserialize)] //~ ERROR: proc-macro derive panicked
struct S<'de> {
s: &'de str, //~^^ HELP: cannot deserialize when there is a lifetime parameter called 'de
}
+72 -2
View File
@@ -657,6 +657,44 @@ fn test_skip_serializing_struct() {
);
}
#[derive(Debug, PartialEq, Serialize)]
struct SkipSerializingTupleStruct<'a, B, C>(
&'a i8,
#[serde(skip_serializing)] B,
#[serde(skip_serializing_if = "ShouldSkip::should_skip")] C,
)
where
C: ShouldSkip;
#[test]
fn test_skip_serializing_tuple_struct() {
let a = 1;
assert_ser_tokens(
&SkipSerializingTupleStruct(&a, 2, 3),
&[
Token::TupleStruct {
name: "SkipSerializingTupleStruct",
len: 2,
},
Token::I8(1),
Token::I32(3),
Token::TupleStructEnd,
],
);
assert_ser_tokens(
&SkipSerializingTupleStruct(&a, 2, 123),
&[
Token::TupleStruct {
name: "SkipSerializingTupleStruct",
len: 1,
},
Token::I8(1),
Token::TupleStructEnd,
],
);
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct SkipStruct<B> {
a: i8,
@@ -705,6 +743,11 @@ where
#[serde(skip_serializing_if = "ShouldSkip::should_skip")]
c: C,
},
Tuple(
&'a i8,
#[serde(skip_serializing)] B,
#[serde(skip_serializing_if = "ShouldSkip::should_skip")] C,
),
}
#[test]
@@ -743,6 +786,33 @@ fn test_skip_serializing_enum() {
Token::StructVariantEnd,
],
);
assert_ser_tokens(
&SkipSerializingEnum::Tuple(&a, 2, 3),
&[
Token::TupleVariant {
name: "SkipSerializingEnum",
variant: "Tuple",
len: 2,
},
Token::I8(1),
Token::I32(3),
Token::TupleVariantEnd,
],
);
assert_ser_tokens(
&SkipSerializingEnum::Tuple(&a, 2, 123),
&[
Token::TupleVariant {
name: "SkipSerializingEnum",
variant: "Tuple",
len: 1,
},
Token::I8(1),
Token::TupleVariantEnd,
],
);
}
#[derive(Debug, PartialEq)]
@@ -1230,7 +1300,7 @@ fn test_invalid_length_enum() {
Token::I32(1),
Token::TupleVariantEnd,
],
"invalid length 1, expected tuple of 3 elements",
"invalid length 1, expected tuple variant InvalidLengthEnum::A with 3 elements",
);
assert_de_tokens_error::<InvalidLengthEnum>(
&[
@@ -1242,7 +1312,7 @@ fn test_invalid_length_enum() {
Token::I32(1),
Token::TupleVariantEnd,
],
"invalid length 1, expected tuple of 2 elements",
"invalid length 1, expected tuple variant InvalidLengthEnum::B with 2 elements",
);
}
+55
View File
@@ -216,6 +216,42 @@ fn test_gen() {
}
assert::<WithTraits2<X, X>>();
#[derive(Serialize, Deserialize)]
#[serde(bound = "D: SerializeWith + DeserializeWith")]
enum VariantWithTraits1<D, E> {
#[serde(
serialize_with = "SerializeWith::serialize_with",
deserialize_with = "DeserializeWith::deserialize_with"
)]
D(D),
#[serde(
serialize_with = "SerializeWith::serialize_with",
deserialize_with = "DeserializeWith::deserialize_with",
bound = "E: SerializeWith + DeserializeWith"
)]
E(E),
}
assert::<VariantWithTraits1<X, X>>();
#[derive(Serialize, Deserialize)]
#[serde(bound(serialize = "D: SerializeWith", deserialize = "D: DeserializeWith"))]
enum VariantWithTraits2<D, E> {
#[serde(
serialize_with = "SerializeWith::serialize_with",
deserialize_with = "DeserializeWith::deserialize_with"
)]
D(D),
#[serde(
serialize_with = "SerializeWith::serialize_with", bound(serialize = "E: SerializeWith")
)]
#[serde(
deserialize_with = "DeserializeWith::deserialize_with",
bound(deserialize = "E: DeserializeWith")
)]
E(E),
}
assert::<VariantWithTraits2<X, X>>();
#[derive(Serialize, Deserialize)]
struct CowStr<'a>(Cow<'a, str>);
assert::<CowStr>();
@@ -588,6 +624,25 @@ fn test_gen() {
}
}
}
#[derive(Deserialize)]
#[serde(tag = "t", content = "c")]
enum AdjacentlyTaggedVoid {}
#[derive(Serialize, Deserialize)]
enum SkippedVariant<T> {
#[serde(skip)]
#[allow(dead_code)]
T(T),
Unit,
}
assert::<SkippedVariant<X>>();
#[derive(Deserialize)]
struct ImpliciltyBorrowedOption<'a> {
option: std::option::Option<&'a str>,
}
}
//////////////////////////////////////////////////////////////////////////