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 16 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
7b57fb4
Move code that generates reading from a SeqAccess to a dedicated func…
Mingun May 23, 2023
35df5ee
Make `deserialize_seq` generic, parameterize it with data reading fun…
Mingun May 23, 2023
bdf3793
Inline `deserialize_newtype_struct` and `deserialize_newtype_struct_i…
Mingun May 23, 2023
918c1a2
Correctly process skipped fields in visit_newtype_struct
Mingun May 22, 2023
22f4221
Add tests for skipped fields in different kinds of structs
Mingun May 22, 2023
9598ba1
Change Tuple1as0(Skipped) representation: newtype -> tuple(0), simila…
Mingun May 28, 2023
228f9cc
Add tests for skipped fields in different kinds of enums
Mingun Jul 19, 2023
f7ecf33
Correctly process skipped fields in tuple variants of internally tagg…
Mingun Jul 16, 2023
d7cf4db
Correctly serialize tuple variants with permanently skipped fields
Mingun Jul 17, 2023
d6b1cc2
Correctly calculate serialized enum style for serialization
Mingun Jul 17, 2023
64357d3
Correctly construct tuples with permanently skipped fields
Mingun Jul 17, 2023
600ded8
Correctly construct newtypes with permanently skipped fields
Mingun Jul 17, 2023
5f7d386
Correctly calculate serialized enum style for deserialization
Mingun Jul 18, 2023
61121b9
Add tests for using visit_seq for deserialzation struct variants of a…
Mingun Jul 18, 2023
badba24
Inline wrap_deserialize_variant_with because it is very short and use…
Mingun Jul 22, 2023
20a391d
корректно обрабатываем skip в variant-with
Mingun Jul 22, 2023
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
428 changes: 270 additions & 158 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
25 changes: 3 additions & 22 deletions serde_derive/src/internals/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,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 @@ -680,12 +680,11 @@ impl Container {

pub fn non_exhaustive(&self) -> bool {
self.non_exhaustive
}
}
}

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 @@ -12,6 +12,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 @@ -294,6 +295,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
71 changes: 31 additions & 40 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 @@ -228,6 +228,11 @@ fn serialize_newtype_struct(
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(
Expand Down Expand Up @@ -526,7 +531,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 @@ -538,13 +543,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 @@ -603,7 +602,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 @@ -614,13 +613,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 @@ -668,7 +661,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 @@ -679,13 +672,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 @@ -778,20 +765,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 @@ -805,6 +786,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<'a> {
ExternallyTagged {
type_name: &'a str,
Expand Down Expand Up @@ -1295,13 +1293,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
Loading
Loading