Skip to content

Commit

Permalink
Do not generate Field structure for each variant
Browse files Browse the repository at this point in the history
  • Loading branch information
Mingun committed Sep 8, 2024
1 parent 71d23cc commit 2b5a5ef
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 29 deletions.
198 changes: 198 additions & 0 deletions serde/src/de/identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,201 @@ impl<'de, 'a, 'b> DeserializeSeed<'de> for &'b mut FieldFlattenSeed<'a> {
deserializer.deserialize_identifier(self)
}
}

////////////////////////////////////////////////////////////////////////////////

/// Creates a field deserialization seed that wrapped array with all possible
/// field names and their aliases.
///
/// # Example
///
/// ```
/// # use serde::__private::de::VariantSeed;
/// let seed = VariantSeed::new(
/// &[
/// // First field with two aliases
/// &["a", "alias 1", "alias 2"],
/// // Second field with one alias
/// &["b", "alias 3"],
/// // Third field without aliases
/// &["c"],
/// ],
/// &[
/// // First field with two aliases
/// "a", "alias 1", "alias 2",
/// // Second field with one alias
/// "b", "alias 3",
/// // Third field without aliases
/// "c",
/// ],
/// );
/// ```
#[derive(Debug)]
pub struct VariantSeed<'a> {
aliases: &'a [&'a [&'a str]],
variants: &'static [&'static str],
}

impl<'a> VariantSeed<'a> {
#[allow(missing_docs)]
pub const fn new(aliases: &'a [&'a [&'a str]], variants: &'static [&'static str]) -> Self {
Self { aliases, variants }
}

fn matches(&self, value: &[u8]) -> Option<usize> {
self.aliases
.iter()
.position(|variant| variant.iter().any(|v| v.as_bytes() == value))
}
}

impl<'de, 'a> Visitor<'de> for VariantSeed<'a> {
type Value = usize;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("variant identifier")
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: Error,
{
if value < self.aliases.len() as u64 {
Ok(value as usize)
} else {
// length of string "variant index 0 <= i < " and u64::MAX.to_string()
let mut buf = [0u8; 23 + 20];
let mut writer = format::Buf::new(&mut buf);
fmt::Write::write_fmt(
&mut writer,
format_args!("variant index 0 <= i < {}", value),
)
.unwrap();
Err(Error::invalid_value(
Unexpected::Unsigned(value),
&writer.as_str(),
))
}
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
match self.matches(value.as_bytes()) {
Some(index) => Ok(index),
None => Err(Error::unknown_variant(value, self.variants)),
}
}

fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
match self.matches(value) {
Some(index) => Ok(index),
None => Err(Error::unknown_variant(
&from_utf8_lossy(value),
self.variants,
)),
}
}
}

impl<'de, 'a> DeserializeSeed<'de> for VariantSeed<'a> {
type Value = usize;

#[inline]
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_identifier(self)
}
}

////////////////////////////////////////////////////////////////////////////////

/// Creates a field deserialization seed that wrapped array with all possible
/// field names and their aliases.
///
/// # Example
///
/// ```
/// # use serde::__private::de::VariantOtherSeed;
/// let seed = VariantOtherSeed::new(
/// &[
/// // First field with two aliases
/// &["a", "alias 1", "alias 2"],
/// // Second field with one alias
/// &["b", "alias 3"],
/// // Third field without aliases
/// &["c"],
/// ],
/// 2,
/// );
/// ```
#[derive(Debug)]
pub struct VariantOtherSeed<'a> {
aliases: &'a [&'a [&'a str]],
other: usize,
}

impl<'a> VariantOtherSeed<'a> {
#[allow(missing_docs)]
pub const fn new(aliases: &'a [&'a [&'a str]], other: usize) -> Self {
Self { aliases, other }
}

fn matches(&self, value: &[u8]) -> usize {
self.aliases
.iter()
.position(|variant| variant.iter().any(|v| v.as_bytes() == value))
.unwrap_or(self.other)
}
}

impl<'de, 'a> Visitor<'de> for VariantOtherSeed<'a> {
type Value = usize;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("variant identifier")
}

fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: Error,
{
if value < self.aliases.len() as u64 {
Ok(value as usize)
} else {
Ok(self.other)
}
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(self.matches(value.as_bytes()))
}

fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: Error,
{
Ok(self.matches(value))
}
}

impl<'de, 'a> DeserializeSeed<'de> for VariantOtherSeed<'a> {
type Value = usize;

#[inline]
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_identifier(self)
}
}
4 changes: 3 additions & 1 deletion serde/src/private/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ pub use self::content::{
TagOrContentField, TagOrContentFieldVisitor, TaggedContentVisitor, UntaggedUnitVisitor,
};

pub use crate::de::identifier::{Field, FieldSeed, FieldStrong, FieldStrongSeed};
pub use crate::de::identifier::{
Field, FieldSeed, FieldStrong, FieldStrongSeed, VariantOtherSeed, VariantSeed,
};
#[cfg(any(feature = "std", feature = "alloc"))]
pub use crate::de::identifier::{FieldFlatten, FieldFlattenSeed};
pub use crate::seed::InPlaceSeed;
Expand Down
62 changes: 34 additions & 28 deletions serde_derive/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1267,50 +1267,50 @@ fn deserialize_externally_tagged_enum(
let expecting = format!("enum {}", params.type_name());
let expecting = cattrs.expecting().unwrap_or(&expecting);

let (variants_stmt, variant_visitor) = prepare_enum_variant_enum(variants);

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

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

quote! {
(__Field::#variant_name, __variant) => #block
(#i, __variant) => #block
}
});

let all_skipped = variants
let seed = match variants
.iter()
.all(|variant| variant.attrs.skip_deserializing());
let match_variant = if all_skipped {
// This is an empty enum like `enum Impossible {}` or an enum in which
// all variants have `#[serde(skip_deserializing)]`.
quote! {
// FIXME: Once feature(exhaustive_patterns) is stable:
// let _serde::__private::Err(__err) = _serde::de::EnumAccess::variant::<__Field>(__data);
// _serde::__private::Err(__err)
_serde::__private::Result::map(
_serde::de::EnumAccess::variant::<__Field>(__data),
|(__impossible, _)| match __impossible {})
}
} else {
quote! {
match _serde::de::EnumAccess::variant(__data)? {
#(#variant_arms)*
}
.filter(|variant| !variant.attrs.skip_deserializing())
.position(|variant| variant.attrs.other())
{
Some(other) => {
quote!(_serde::__private::de::VariantOtherSeed::new(VARIANT_ALIASES, #other))
}
None => quote!(_serde::__private::de::VariantSeed::new(
VARIANT_ALIASES,
VARIANTS
)),
};

quote_block! {
#variant_visitor
let variant_names = variants
.iter()
.filter(|variant| !variant.attrs.skip_deserializing())
.map(|variant| variant.attrs.name().deserialize_name());

let aliases = variants.iter().filter_map(|variant| {
if variant.attrs.skip_deserializing() {
None
} else {
let aliases = variant.attrs.aliases();
Some(quote!(&[ #(#aliases),* ]))
}
});

quote_block! {
#[doc(hidden)]
struct __Visitor #de_impl_generics #where_clause {
marker: _serde::__private::PhantomData<#this_type #ty_generics>,
Expand All @@ -1328,11 +1328,17 @@ fn deserialize_externally_tagged_enum(
where
__A: _serde::de::EnumAccess<#delife>,
{
#match_variant
match _serde::de::EnumAccess::variant_seed(__data, #seed)? {
#(#variant_arms)*
_ => unreachable!(),
}
}
}

#variants_stmt
#[doc(hidden)]
const VARIANTS: &'static [&'static str] = &[ #(#variant_names),* ];
#[doc(hidden)]
const VARIANT_ALIASES: &[&[&str]] = &[ #(#aliases),* ];

_serde::Deserializer::deserialize_enum(
__deserializer,
Expand Down

0 comments on commit 2b5a5ef

Please sign in to comment.