Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect representation of tuple variants with skipped fields #2549

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
bbc2bf8
Move code that generates reading from a SeqAccess to a dedicated func…
Mingun May 23, 2023
f7b6944
Make `deserialize_seq` generic, parameterize it with data reading fun…
Mingun May 23, 2023
2a975fd
Inline `deserialize_newtype_struct` and `deserialize_newtype_struct_i…
Mingun May 23, 2023
b812de8
Correctly process skipped fields in visit_newtype_struct
Mingun May 22, 2023
a6a2c82
Add tests for skipped fields in different kinds of structs
Mingun May 22, 2023
654ea6d
Change Tuple1as0(Skipped) representation: newtype -> tuple(0), simila…
Mingun May 28, 2023
1eac41c
Change Tuple2as1(Skipped, x) representation: tuple(1) -> newtype
Mingun May 29, 2023
b3e8da3
Add tests for skipped fields in different kinds of enums
Mingun Jul 19, 2023
f92623c
Correctly process skipped fields in tuple variants of internally tagg…
Mingun Jul 16, 2023
47e031c
Correctly serialize tuples with permanently skipped fields
Mingun Jul 17, 2023
ff1e64b
Correctly calculate serialized enum style for serialization
Mingun Jul 17, 2023
09366b3
Correctly construct tuples with permanently skipped fields
Mingun Jul 17, 2023
93fb53a
Correctly construct newtypes with permanently skipped fields
Mingun Jul 17, 2023
32f2c49
Correctly calculate serialized enum style for deserialization
Mingun Jul 18, 2023
4647c19
Add tests for using visit_seq for deserialzation struct variants of a…
Mingun Jul 18, 2023
0c8fa03
Inline wrap_deserialize_variant_with because it is very short and use…
Mingun Jul 22, 2023
77b3293
корректно обрабатываем skip в variant-with
Mingun Jul 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
470 changes: 285 additions & 185 deletions serde_derive/src/de.rs

Large diffs are not rendered by default.

45 changes: 44 additions & 1 deletion serde_derive/src/internals/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,54 @@ pub enum Data<'a> {
/// A variant of an enum.
pub struct Variant<'a> {
pub ident: syn::Ident,
/// #[serde(...)] attributes of this variant
pub attrs: attr::Variant,
/// Style used by Rust code to represent enum variant. Can differ from the
/// style of the serialized form due to fields skipping
pub style: Style,
pub fields: Vec<Field<'a>>,
pub original: &'a syn::Variant,
}
impl<'a> Variant<'a> {
/// Effective style of serialized representation used for deserialization,
/// calculated by taking into account fields skipped for deserialization.
pub fn de_style(&self) -> Style {
effective_style(
self.style,
self.fields
.iter()
.filter(|f| !f.attrs.skip_deserializing())
.count(),
)
}

/// Effective style of serialized representation used for serialization,
/// calculated by taking into account fields skipped for serialization.
pub fn ser_style(&self) -> Style {
effective_style(
self.style,
self.fields
.iter()
.filter(|f| !f.attrs.skip_serializing())
.count(),
)
}
}

fn effective_style(style: Style, fields: usize) -> Style {
match (style, fields) {
(Style::Unit, _) => Style::Unit,

(Style::Newtype, 0) => Style::Unit,
(Style::Newtype, _) => Style::Newtype,

(Style::Tuple, 0) => Style::Unit,
(Style::Tuple, 1) => Style::Newtype,
(Style::Tuple, _) => Style::Tuple,

(Style::Struct, _) => Style::Struct,
}
}

/// A field of a struct.
pub struct Field<'a> {
Expand All @@ -44,7 +87,7 @@ pub struct Field<'a> {
pub original: &'a syn::Field,
}

#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Style {
/// Named fields.
Struct,
Expand Down
23 changes: 2 additions & 21 deletions serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ impl Container {
},
ser_bound: ser_bound.get(),
de_bound: de_bound.get(),
tag: decide_tag(cx, item, untagged, internal_tag, content),
tag: decide_tag(cx, untagged, internal_tag, content),
type_from: type_from.get(),
type_try_from: type_try_from.get(),
type_into: type_into.get(),
Expand Down Expand Up @@ -685,7 +685,6 @@ impl Container {

fn decide_tag(
cx: &Ctxt,
item: &syn::DeriveInput,
untagged: BoolAttr,
internal_tag: Attr<String>,
content: Attr<String>,
Expand All @@ -697,25 +696,7 @@ fn decide_tag(
) {
(None, None, None) => TagType::External,
(Some(_), None, None) => TagType::None,
(None, Some((_, tag)), None) => {
// Check that there are no tuple variants.
if let syn::Data::Enum(data) = &item.data {
for variant in &data.variants {
match &variant.fields {
syn::Fields::Named(_) | syn::Fields::Unit => {}
syn::Fields::Unnamed(fields) => {
if fields.unnamed.len() != 1 {
let msg =
"#[serde(tag = \"...\")] cannot be used with tuple variants";
cx.error_spanned_by(variant, msg);
break;
}
}
}
}
}
TagType::Internal { tag }
}
(None, Some((_, tag)), None) => TagType::Internal { tag },
(Some((untagged_tokens, _)), Some((tag_tokens, _)), None) => {
let msg = "enum cannot be both untagged and internally tagged";
cx.error_spanned_by(untagged_tokens, msg);
Expand Down
24 changes: 24 additions & 0 deletions serde_derive/src/internals/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub fn check(cx: &Ctxt, cont: &mut Container, derive: Derive) {
check_flatten(cx, cont);
check_identifier(cx, cont);
check_variant_skip_attrs(cx, cont);
check_internal_no_tuples(cx, cont, derive);
check_internal_tag_field_name_conflict(cx, cont);
check_adjacent_tag_conflict(cx, cont);
check_transparent(cx, cont, derive);
Expand Down Expand Up @@ -260,6 +261,29 @@ fn check_variant_skip_attrs(cx: &Ctxt, cont: &Container) {
}
}

// The tag of an internally-tagged struct variant must not be the same as either
// one of its fields, as this would result in duplicate keys in the serialized
// output and/or ambiguity in the to-be-deserialized input.
fn check_internal_no_tuples(cx: &Ctxt, cont: &Container, derive: Derive) {
let variants = match &cont.data {
Data::Enum(variants) => variants,
Data::Struct(_, _) => return,
};

if let TagType::Internal { .. } = cont.attrs.tag() {
for variant in variants {
if derive == Derive::Serialize && variant.ser_style() == Style::Tuple
|| derive == Derive::Deserialize && variant.de_style() == Style::Tuple
{
cx.error_spanned_by(
variant.original,
"#[serde(tag = \"...\")] cannot be used with tuple variants",
);
}
}
}
}

// The tag of an internally-tagged struct variant must not be the same as either
// one of its fields, as this would result in duplicate keys in the serialized
// output and/or ambiguity in the to-be-deserialized input.
Expand Down
2 changes: 1 addition & 1 deletion serde_derive/src/internals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use syn::Type;
pub use self::ctxt::Ctxt;
pub use self::receiver::replace_receiver;

#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Derive {
Serialize,
Deserialize,
Expand Down
91 changes: 42 additions & 49 deletions serde_derive/src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::internals::ast::{Container, Data, Field, Style, Variant};
use crate::internals::{attr, replace_receiver, Ctxt, Derive};
use crate::{bound, dummy, pretend, this};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
use syn::{parse_quote, Ident, Index, Member};

Expand Down Expand Up @@ -176,7 +176,7 @@ fn serialize_body(cont: &Container, params: &Parameters) -> Fragment {
serialize_tuple_struct(params, fields, &cont.attrs)
}
Data::Struct(Style::Newtype, fields) => {
serialize_newtype_struct(params, &fields[0], &cont.attrs)
serialize_newtype_struct(params, 0, &fields[0], &cont.attrs)
}
Data::Struct(Style::Unit, _) => serialize_unit_struct(&cont.attrs),
}
Expand Down Expand Up @@ -225,19 +225,18 @@ fn serialize_unit_struct(cattrs: &attr::Container) -> Fragment {

fn serialize_newtype_struct(
params: &Parameters,
index: usize,
field: &Field,
cattrs: &attr::Container,
) -> Fragment {
// For `struct Tuple1as0(#[serde(skip)] u8);` cases
if field.attrs.skip_serializing() {
return serialize_tuple_struct(params, &[], cattrs);
}

let type_name = cattrs.name().serialize_name();

let mut field_expr = get_member(
params,
field,
&Member::Unnamed(Index {
index: 0,
span: Span::call_site(),
}),
);
let mut field_expr = get_member(params, field, &Member::Unnamed(Index::from(index)));
if let Some(path) = field.attrs.serialize_with() {
field_expr = wrap_serialize_field_with(params, field.ty, path, &field_expr);
}
Expand Down Expand Up @@ -265,6 +264,14 @@ fn serialize_tuple_struct(
.filter(|(_, field)| !field.attrs.skip_serializing())
.peekable();

match serialized_fields.clone().count() {
1 => {
let (index, field) = serialized_fields.next().unwrap();
return serialize_newtype_struct(params, index, field, cattrs);
}
_ => {}
}

let let_mut = mut_if(serialized_fields.peek().is_some());

let len = serialized_fields
Expand Down Expand Up @@ -513,7 +520,7 @@ fn serialize_externally_tagged_variant(
};
}

match effective_style(variant) {
match variant.ser_style() {
Style::Unit => {
quote_expr! {
_serde::Serializer::serialize_unit_variant(
Expand All @@ -525,13 +532,7 @@ fn serialize_externally_tagged_variant(
}
}
Style::Newtype => {
let field = &variant.fields[0];
let mut field_expr = quote!(__field0);
if let Some(path) = field.attrs.serialize_with() {
field_expr = wrap_serialize_field_with(params, field.ty, path, &field_expr);
}

let span = field.original.span();
let (field_expr, span) = newtype_field(params, &variant);
let func = quote_spanned!(span=> _serde::Serializer::serialize_newtype_variant);
quote_expr! {
#func(
Expand Down Expand Up @@ -590,7 +591,7 @@ fn serialize_internally_tagged_variant(
};
}

match effective_style(variant) {
match variant.ser_style() {
Style::Unit => {
quote_block! {
let mut __struct = _serde::Serializer::serialize_struct(
Expand All @@ -601,13 +602,7 @@ fn serialize_internally_tagged_variant(
}
}
Style::Newtype => {
let field = &variant.fields[0];
let mut field_expr = quote!(__field0);
if let Some(path) = field.attrs.serialize_with() {
field_expr = wrap_serialize_field_with(params, field.ty, path, &field_expr);
}

let span = field.original.span();
let (field_expr, span) = newtype_field(params, &variant);
let func = quote_spanned!(span=> _serde::__private::ser::serialize_tagged_newtype);
quote_expr! {
#func(
Expand Down Expand Up @@ -647,7 +642,7 @@ fn serialize_adjacently_tagged_variant(
_serde::Serialize::serialize(#ser, __serializer)
}
} else {
match effective_style(variant) {
match variant.ser_style() {
Style::Unit => {
return quote_block! {
let mut __struct = _serde::Serializer::serialize_struct(
Expand All @@ -658,13 +653,7 @@ fn serialize_adjacently_tagged_variant(
};
}
Style::Newtype => {
let field = &variant.fields[0];
let mut field_expr = quote!(__field0);
if let Some(path) = field.attrs.serialize_with() {
field_expr = wrap_serialize_field_with(params, field.ty, path, &field_expr);
}

let span = field.original.span();
let (field_expr, span) = newtype_field(params, &variant);
let func = quote_spanned!(span=> _serde::ser::SerializeStruct::serialize_field);
return quote_block! {
let mut __struct = _serde::Serializer::serialize_struct(
Expand Down Expand Up @@ -757,20 +746,14 @@ fn serialize_untagged_variant(
};
}

match effective_style(variant) {
match variant.ser_style() {
Style::Unit => {
quote_expr! {
_serde::Serializer::serialize_unit(__serializer)
}
}
Style::Newtype => {
let field = &variant.fields[0];
let mut field_expr = quote!(__field0);
if let Some(path) = field.attrs.serialize_with() {
field_expr = wrap_serialize_field_with(params, field.ty, path, &field_expr);
}

let span = field.original.span();
let (field_expr, span) = newtype_field(params, &variant);
let func = quote_spanned!(span=> _serde::Serialize::serialize);
quote_expr! {
#func(#field_expr, __serializer)
Expand All @@ -784,6 +767,23 @@ fn serialize_untagged_variant(
}
}

fn newtype_field(params: &Parameters, variant: &Variant) -> (TokenStream, Span) {
let (i, field) = variant
.fields
.iter()
.enumerate()
.find(|(_, field)| !field.attrs.skip_serializing())
.expect("checked in Variant::ser_style");

let mut field_expr =
Ident::new(&format!("__field{}", i), Span::call_site()).into_token_stream();
if let Some(path) = field.attrs.serialize_with() {
field_expr = wrap_serialize_field_with(params, field.ty, path, &field_expr);
}

(field_expr, field.original.span())
}

enum TupleVariant {
ExternallyTagged {
type_name: String,
Expand Down Expand Up @@ -1274,13 +1274,6 @@ fn get_member(params: &Parameters, field: &Field, member: &Member) -> TokenStrea
}
}

fn effective_style(variant: &Variant) -> Style {
match variant.style {
Style::Newtype if variant.fields[0].attrs.skip_serializing() => Style::Unit,
other => other,
}
}

enum StructTrait {
SerializeMap,
SerializeStruct,
Expand Down