diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 4632c5ed..b8f665ca 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -15,6 +15,7 @@ use syn::{parse_quote, Ident, Index, Member}; mod enum_adjacently; mod enum_externally; mod enum_internally; +mod enum_untagged; pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result { replace_receiver(input); @@ -1249,7 +1250,7 @@ fn deserialize_enum( return _serde::#private::Ok(__ok); } }; - deserialize_untagged_enum(params, untagged, cattrs, Some(tagged_frag)) + enum_untagged::deserialize_untagged_enum(params, untagged, cattrs, Some(tagged_frag)) } None => deserialize_homogeneous_enum(params, variants, cattrs), } @@ -1268,7 +1269,7 @@ fn deserialize_homogeneous_enum( attr::TagType::Adjacent { tag, content } => { enum_adjacently::deserialize_adjacently_tagged_enum(params, variants, cattrs, tag, content) } - attr::TagType::None => deserialize_untagged_enum(params, variants, cattrs, None), + attr::TagType::None => enum_untagged::deserialize_untagged_enum(params, variants, cattrs, None), } } @@ -1314,121 +1315,6 @@ fn prepare_enum_variant_enum(variants: &[Variant]) -> (TokenStream, Stmts) { (variants_stmt, variant_visitor) } -/// Generates `Deserialize::deserialize` body for an `enum Enum {...}` with `#[serde(untagged)]` attribute -fn deserialize_untagged_enum( - params: &Parameters, - variants: &[Variant], - cattrs: &attr::Container, - first_attempt: Option, -) -> Fragment { - let attempts = variants - .iter() - .filter(|variant| !variant.attrs.skip_deserializing()) - .map(|variant| Expr(deserialize_untagged_variant(params, variant, cattrs))); - // TODO this message could be better by saving the errors from the failed - // attempts. The heuristic used by TOML was to count the number of fields - // processed before an error, and use the error that happened after the - // largest number of fields. I'm not sure I like that. Maybe it would be - // better to save all the errors and combine them into one message that - // explains why none of the variants matched. - let fallthrough_msg = format!( - "data did not match any variant of untagged enum {}", - params.type_name() - ); - let fallthrough_msg = cattrs.expecting().unwrap_or(&fallthrough_msg); - - let private2 = private; - quote_block! { - let __content = _serde::de::DeserializeSeed::deserialize(_serde::#private::de::ContentVisitor::new(), __deserializer)?; - let __deserializer = _serde::#private::de::ContentRefDeserializer::<__D::Error>::new(&__content); - - #first_attempt - - #( - if let _serde::#private2::Ok(__ok) = #attempts { - return _serde::#private2::Ok(__ok); - } - )* - - _serde::#private::Err(_serde::de::Error::custom(#fallthrough_msg)) - } -} - -fn deserialize_untagged_variant( - params: &Parameters, - variant: &Variant, - cattrs: &attr::Container, -) -> Fragment { - if let Some(path) = variant.attrs.deserialize_with() { - let unwrap_fn = unwrap_to_variant_closure(params, variant, false); - return quote_block! { - _serde::#private::Result::map(#path(__deserializer), #unwrap_fn) - }; - } - - let variant_ident = &variant.ident; - - match effective_style(variant) { - Style::Unit => { - let this_value = ¶ms.this_value; - let type_name = params.type_name(); - let variant_name = variant.ident.to_string(); - let default = variant.fields.first().map(|field| { - let default = Expr(expr_is_missing(field, cattrs)); - quote!((#default)) - }); - quote_expr! { - match _serde::Deserializer::deserialize_any( - __deserializer, - _serde::#private::de::UntaggedUnitVisitor::new(#type_name, #variant_name) - ) { - _serde::#private::Ok(()) => _serde::#private::Ok(#this_value::#variant_ident #default), - _serde::#private::Err(__err) => _serde::#private::Err(__err), - } - } - } - Style::Newtype => { - deserialize_untagged_newtype_variant(variant_ident, params, &variant.fields[0]) - } - Style::Tuple => deserialize_tuple( - params, - &variant.fields, - cattrs, - TupleForm::Untagged(variant_ident), - ), - Style::Struct => deserialize_struct( - params, - &variant.fields, - cattrs, - StructForm::Untagged(variant_ident), - ), - } -} - -fn deserialize_untagged_newtype_variant( - variant_ident: &syn::Ident, - params: &Parameters, - field: &Field, -) -> Fragment { - let this_value = ¶ms.this_value; - let field_ty = field.ty; - match field.attrs.deserialize_with() { - None => { - let span = field.original.span(); - let func = quote_spanned!(span=> <#field_ty as _serde::Deserialize>::deserialize); - quote_expr! { - _serde::#private::Result::map(#func(__deserializer), #this_value::#variant_ident) - } - } - Some(path) => { - quote_block! { - let __value: _serde::#private::Result<#field_ty, _> = #path(__deserializer); - _serde::#private::Result::map(__value, #this_value::#variant_ident) - } - } - } -} - struct FieldWithAliases<'a> { ident: Ident, aliases: &'a BTreeSet, diff --git a/serde_derive/src/de/enum_adjacently.rs b/serde_derive/src/de/enum_adjacently.rs index c5e64a23..ac013e37 100644 --- a/serde_derive/src/de/enum_adjacently.rs +++ b/serde_derive/src/de/enum_adjacently.rs @@ -5,7 +5,8 @@ //! enum Enum {} //! ``` -use crate::de::{deserialize_untagged_variant, field_i, prepare_enum_variant_enum, Parameters}; +use crate::de::enum_untagged; +use crate::de::{field_i, prepare_enum_variant_enum, Parameters}; use crate::fragment::{Fragment, Match}; use crate::internals::ast::{Style, Variant}; use crate::internals::attr; @@ -35,7 +36,7 @@ pub fn deserialize_adjacently_tagged_enum( .map(|(i, variant)| { let variant_index = field_i(i); - let block = Match(deserialize_untagged_variant(params, variant, cattrs)); + let block = Match(enum_untagged::deserialize_untagged_variant(params, variant, cattrs)); quote! { __Field::#variant_index => #block diff --git a/serde_derive/src/de/enum_internally.rs b/serde_derive/src/de/enum_internally.rs index 8c848e22..4dbb6cfa 100644 --- a/serde_derive/src/de/enum_internally.rs +++ b/serde_derive/src/de/enum_internally.rs @@ -5,9 +5,10 @@ //! enum Enum {} //! ``` +use crate::de::enum_untagged; use crate::de::{ - deserialize_struct, deserialize_untagged_newtype_variant, effective_style, expr_is_missing, - field_i, prepare_enum_variant_enum, unwrap_to_variant_closure, Parameters, StructForm, + deserialize_struct, effective_style, expr_is_missing, field_i, prepare_enum_variant_enum, + unwrap_to_variant_closure, Parameters, StructForm, }; use crate::fragment::{Expr, Fragment, Match}; use crate::internals::ast::{Style, Variant}; @@ -91,7 +92,7 @@ fn deserialize_internally_tagged_variant( } } Style::Newtype => { - deserialize_untagged_newtype_variant(variant_ident, params, &variant.fields[0]) + enum_untagged::deserialize_untagged_newtype_variant(variant_ident, params, &variant.fields[0]) } Style::Struct => deserialize_struct( params, diff --git a/serde_derive/src/de/enum_untagged.rs b/serde_derive/src/de/enum_untagged.rs new file mode 100644 index 00000000..78546870 --- /dev/null +++ b/serde_derive/src/de/enum_untagged.rs @@ -0,0 +1,133 @@ +//! Generator of the deserialization code for the untagged enums: +//! +//! ```ignore +//! #[serde(untagged)] +//! enum Enum {} +//! ``` + +use crate::de::{ + deserialize_struct, deserialize_tuple, effective_style, expr_is_missing, + unwrap_to_variant_closure, Parameters, StructForm, TupleForm, +}; +use crate::fragment::{Expr, Fragment}; +use crate::internals::ast::{Field, Style, Variant}; +use crate::internals::attr; +use crate::private; +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::spanned::Spanned; + +/// Generates `Deserialize::deserialize` body for an `enum Enum {...}` with `#[serde(untagged)]` attribute +pub fn deserialize_untagged_enum( + params: &Parameters, + variants: &[Variant], + cattrs: &attr::Container, + first_attempt: Option, +) -> Fragment { + let attempts = variants + .iter() + .filter(|variant| !variant.attrs.skip_deserializing()) + .map(|variant| Expr(deserialize_untagged_variant(params, variant, cattrs))); + // TODO this message could be better by saving the errors from the failed + // attempts. The heuristic used by TOML was to count the number of fields + // processed before an error, and use the error that happened after the + // largest number of fields. I'm not sure I like that. Maybe it would be + // better to save all the errors and combine them into one message that + // explains why none of the variants matched. + let fallthrough_msg = format!( + "data did not match any variant of untagged enum {}", + params.type_name() + ); + let fallthrough_msg = cattrs.expecting().unwrap_or(&fallthrough_msg); + + let private2 = private; + quote_block! { + let __content = _serde::de::DeserializeSeed::deserialize(_serde::#private::de::ContentVisitor::new(), __deserializer)?; + let __deserializer = _serde::#private::de::ContentRefDeserializer::<__D::Error>::new(&__content); + + #first_attempt + + #( + if let _serde::#private2::Ok(__ok) = #attempts { + return _serde::#private2::Ok(__ok); + } + )* + + _serde::#private::Err(_serde::de::Error::custom(#fallthrough_msg)) + } +} + +pub fn deserialize_untagged_variant( + params: &Parameters, + variant: &Variant, + cattrs: &attr::Container, +) -> Fragment { + if let Some(path) = variant.attrs.deserialize_with() { + let unwrap_fn = unwrap_to_variant_closure(params, variant, false); + return quote_block! { + _serde::#private::Result::map(#path(__deserializer), #unwrap_fn) + }; + } + + let variant_ident = &variant.ident; + + match effective_style(variant) { + Style::Unit => { + let this_value = ¶ms.this_value; + let type_name = params.type_name(); + let variant_name = variant.ident.to_string(); + let default = variant.fields.first().map(|field| { + let default = Expr(expr_is_missing(field, cattrs)); + quote!((#default)) + }); + quote_expr! { + match _serde::Deserializer::deserialize_any( + __deserializer, + _serde::#private::de::UntaggedUnitVisitor::new(#type_name, #variant_name) + ) { + _serde::#private::Ok(()) => _serde::#private::Ok(#this_value::#variant_ident #default), + _serde::#private::Err(__err) => _serde::#private::Err(__err), + } + } + } + Style::Newtype => { + deserialize_untagged_newtype_variant(variant_ident, params, &variant.fields[0]) + } + Style::Tuple => deserialize_tuple( + params, + &variant.fields, + cattrs, + TupleForm::Untagged(variant_ident), + ), + Style::Struct => deserialize_struct( + params, + &variant.fields, + cattrs, + StructForm::Untagged(variant_ident), + ), + } +} + +pub fn deserialize_untagged_newtype_variant( + variant_ident: &syn::Ident, + params: &Parameters, + field: &Field, +) -> Fragment { + let this_value = ¶ms.this_value; + let field_ty = field.ty; + match field.attrs.deserialize_with() { + None => { + let span = field.original.span(); + let func = quote_spanned!(span=> <#field_ty as _serde::Deserialize>::deserialize); + quote_expr! { + _serde::#private::Result::map(#func(__deserializer), #this_value::#variant_ident) + } + } + Some(path) => { + quote_block! { + let __value: _serde::#private::Result<#field_ty, _> = #path(__deserializer); + _serde::#private::Result::map(__value, #this_value::#variant_ident) + } + } + } +}