Add support for deserialize_with and borrow

This commit is contained in:
Dirkjan Ochtman 2022-11-29 13:07:20 +01:00
parent aab73952a1
commit 20f73b7e01
7 changed files with 302 additions and 75 deletions

View File

@ -1,4 +1,6 @@
use proc_macro2::{Ident, Span, TokenStream};
use std::collections::BTreeSet;
use proc_macro2::{Ident, Literal, Span, TokenStream};
use quote::quote;
use syn::spanned::Spanned;
@ -49,7 +51,7 @@ fn deserialize_scalar_enum(
variants.extend(quote!(Ok(#serialize_as) => #ident::#v_ident,));
}
let generics = meta.xml_generics();
let generics = meta.xml_generics(BTreeSet::new());
let (impl_generics, _, _) = generics.split_for_impl();
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
@ -90,6 +92,7 @@ fn deserialize_wrapped_enum(
let ident = &input.ident;
let mut variants = TokenStream::new();
let mut borrowed = BTreeSet::new();
for variant in data.variants.iter() {
let field = match &variant.fields {
syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
@ -113,7 +116,7 @@ fn deserialize_wrapped_enum(
}
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
discard_lifetimes(&mut no_lifetime_type, &mut borrowed, false, true);
if !variants.is_empty() {
variants.extend(quote!(else));
@ -132,7 +135,7 @@ fn deserialize_wrapped_enum(
let name = meta.tag();
let default_namespace = meta.default_namespace();
let generics = meta.xml_generics();
let generics = meta.xml_generics(borrowed);
let (xml_impl_generics, _, _) = generics.split_for_impl();
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
quote!(
@ -194,6 +197,7 @@ fn deserialize_struct(
let mut declare_values = TokenStream::new();
let mut return_val = TokenStream::new();
let mut borrowed = BTreeSet::new();
for (index, field) in fields.named.iter().enumerate() {
let field_meta = match FieldMeta::from_field(field, &container_meta) {
Ok(meta) => meta,
@ -205,15 +209,20 @@ fn deserialize_struct(
false => &mut elements_tokens,
};
named_field(
let result = named_field(
field,
index,
&mut declare_values,
&mut return_val,
tokens,
&mut borrowed,
field_meta,
&container_meta,
);
if let Err(err) = result {
return err.into_compile_error();
}
}
// Elements
@ -237,7 +246,7 @@ fn deserialize_struct(
let ident = &input.ident;
let name = container_meta.tag();
let default_namespace = container_meta.default_namespace();
let generics = container_meta.xml_generics();
let generics = container_meta.xml_generics(borrowed);
let (xml_impl_generics, _, _) = generics.split_for_impl();
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
@ -313,9 +322,10 @@ fn named_field(
declare_values: &mut TokenStream,
return_val: &mut TokenStream,
tokens: &mut Tokens,
field_meta: FieldMeta,
borrowed: &mut BTreeSet<syn::Lifetime>,
mut field_meta: FieldMeta,
container_meta: &ContainerMeta,
) {
) -> Result<(), syn::Error> {
let field_name = field.ident.as_ref().unwrap();
let field_tag = field_meta.tag;
let default_ns = match &field_meta.ns.uri {
@ -329,8 +339,18 @@ fn named_field(
None => quote!(""),
};
if field_meta.borrow && field_meta.deserialize_with.is_none() {
if is_cow(&field.ty, is_str) {
field_meta.deserialize_with =
Some(Literal::string("::instant_xml::de::borrow_cow_str"));
} else if is_cow(&field.ty, is_slice_u8) {
field_meta.deserialize_with =
Some(Literal::string("::instant_xml::de::borrow_cow_slice_u8"));
}
}
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
discard_lifetimes(&mut no_lifetime_type, borrowed, field_meta.borrow, true);
let enum_name = Ident::new(&format!("__Value{index}"), Span::call_site());
tokens.r#enum.extend(quote!(#enum_name,));
@ -351,7 +371,28 @@ fn named_field(
let mut #enum_name: Option<#no_lifetime_type> = None;
));
let deserialize_with = field_meta
.deserialize_with
.map(|with| {
let path = with.to_string();
syn::parse_str::<syn::Path>(path.trim_matches('"')).map_err(|err| {
syn::Error::new(
with.span(),
format!("failed to parse deserialize_with as path: {err}"),
)
})
})
.transpose()?;
if !field_meta.attribute {
if let Some(with) = deserialize_with {
tokens.r#match.extend(quote!(
__Elements::#enum_name => {
let mut nested = deserializer.nested(data);
#with(&mut nested, &mut #enum_name)?;
},
));
} else {
tokens.r#match.extend(quote!(
__Elements::#enum_name => match <#no_lifetime_type as FromXml>::KIND {
Kind::Element(_) => {
@ -365,6 +406,15 @@ fn named_field(
}
},
));
}
} else {
if let Some(with) = deserialize_with {
tokens.r#match.extend(quote!(
__Attributes::#enum_name => {
let mut nested = deserializer.nested(data);
#with(&mut nested, &mut #enum_name)?;
},
));
} else {
tokens.r#match.extend(quote!(
__Attributes::#enum_name => {
@ -373,6 +423,7 @@ fn named_field(
},
));
}
}
return_val.extend(quote!(
#field_name: match #enum_name {
@ -380,6 +431,8 @@ fn named_field(
None => <#no_lifetime_type>::missing_value()?,
},
));
Ok(())
}
fn deserialize_tuple_struct(
@ -397,7 +450,7 @@ fn deserialize_tuple_struct(
// Varying values
let mut declare_values = TokenStream::new();
let mut return_val = TokenStream::new();
let mut borrowed = BTreeSet::new();
for (index, field) in fields.unnamed.iter().enumerate() {
if !field.attrs.is_empty() {
return syn::Error::new(
@ -407,13 +460,19 @@ fn deserialize_tuple_struct(
.to_compile_error();
}
unnamed_field(field, index, &mut declare_values, &mut return_val);
unnamed_field(
field,
index,
&mut declare_values,
&mut return_val,
&mut borrowed,
);
}
let ident = &input.ident;
let name = container_meta.tag();
let default_namespace = container_meta.default_namespace();
let generics = container_meta.xml_generics();
let generics = container_meta.xml_generics(borrowed);
let (xml_impl_generics, _, _) = generics.split_for_impl();
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
@ -448,9 +507,10 @@ fn unnamed_field(
index: usize,
declare_values: &mut TokenStream,
return_val: &mut TokenStream,
borrowed: &mut BTreeSet<syn::Lifetime>,
) {
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
discard_lifetimes(&mut no_lifetime_type, borrowed, false, true);
let name = Ident::new(&format!("v{index}"), Span::call_site());
declare_values.extend(quote!(
@ -487,7 +547,7 @@ fn deserialize_unit_struct(input: &syn::DeriveInput, meta: &ContainerMeta) -> To
let ident = &input.ident;
let name = meta.tag();
let default_namespace = meta.default_namespace();
let generics = meta.xml_generics();
let generics = meta.xml_generics(BTreeSet::new());
let (xml_impl_generics, _, _) = generics.split_for_impl();
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
@ -511,6 +571,68 @@ fn deserialize_unit_struct(input: &syn::DeriveInput, meta: &ContainerMeta) -> To
)
}
fn is_cow(ty: &syn::Type, elem: fn(&syn::Type) -> bool) -> bool {
let path = match ungroup(ty) {
syn::Type::Path(ty) => &ty.path,
_ => {
return false;
}
};
let seg = match path.segments.last() {
Some(seg) => seg,
None => {
return false;
}
};
let args = match &seg.arguments {
syn::PathArguments::AngleBracketed(bracketed) => &bracketed.args,
_ => {
return false;
}
};
seg.ident == "Cow"
&& args.len() == 2
&& match (&args[0], &args[1]) {
(syn::GenericArgument::Lifetime(_), syn::GenericArgument::Type(arg)) => elem(arg),
_ => false,
}
}
fn is_str(ty: &syn::Type) -> bool {
is_primitive_type(ty, "str")
}
fn is_slice_u8(ty: &syn::Type) -> bool {
match ungroup(ty) {
syn::Type::Slice(ty) => is_primitive_type(&ty.elem, "u8"),
_ => false,
}
}
fn is_primitive_type(ty: &syn::Type, primitive: &str) -> bool {
match ungroup(ty) {
syn::Type::Path(ty) => ty.qself.is_none() && is_primitive_path(&ty.path, primitive),
_ => false,
}
}
fn is_primitive_path(path: &syn::Path, primitive: &str) -> bool {
path.leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments[0].ident == primitive
&& path.segments[0].arguments.is_empty()
}
pub fn ungroup(mut ty: &syn::Type) -> &syn::Type {
while let syn::Type::Group(group) = ty {
ty = &group.elem;
}
ty
}
#[derive(Default)]
struct Tokens {
r#enum: TokenStream,

View File

@ -1,5 +1,8 @@
extern crate proc_macro;
use std::collections::BTreeSet;
use std::mem;
use proc_macro2::{Literal, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::spanned::Spanned;
@ -71,11 +74,10 @@ impl<'input> ContainerMeta<'input> {
})
}
fn xml_generics(&self) -> Generics {
fn xml_generics<'a>(&self, borrowed: BTreeSet<syn::Lifetime>) -> Generics {
let mut xml_generics = self.input.generics.clone();
let mut xml = syn::LifetimeDef::new(syn::Lifetime::new("'xml", Span::call_site()));
xml.bounds
.extend(xml_generics.lifetimes().map(|lt| lt.lifetime.clone()));
xml.bounds.extend(borrowed.into_iter());
xml_generics.params.push(xml.into());
for param in xml_generics.type_params_mut() {
@ -105,9 +107,11 @@ impl<'input> ContainerMeta<'input> {
#[derive(Debug, Default)]
struct FieldMeta {
attribute: bool,
borrow: bool,
ns: NamespaceMeta,
tag: TokenStream,
serialize_with: Option<Literal>,
deserialize_with: Option<Literal>,
}
impl FieldMeta {
@ -124,9 +128,11 @@ impl FieldMeta {
for (item, span) in meta_items(&input.attrs) {
match item {
MetaItem::Attribute => meta.attribute = true,
MetaItem::Borrow => meta.borrow = true,
MetaItem::Ns(ns) => meta.ns = ns,
MetaItem::Rename(lit) => meta.tag = quote!(#lit),
MetaItem::SerializeWith(lit) => meta.serialize_with = Some(lit),
MetaItem::DeserializeWith(lit) => meta.deserialize_with = Some(lit),
MetaItem::RenameAll(_) => {
return Err(syn::Error::new(
span,
@ -216,20 +222,51 @@ impl VariantMeta {
}
}
fn discard_lifetimes(ty: &mut syn::Type) {
fn discard_lifetimes(
ty: &mut syn::Type,
borrowed: &mut BTreeSet<syn::Lifetime>,
borrow: bool,
top: bool,
) {
match ty {
syn::Type::Path(ty) => discard_path_lifetimes(ty),
syn::Type::Path(ty) => discard_path_lifetimes(ty, borrowed, borrow),
syn::Type::Reference(ty) => {
if top {
// If at the top level, we'll want to borrow from `&'a str` and `&'a [u8]`.
match &*ty.elem {
syn::Type::Path(inner) if top && inner.path.is_ident("str") => {
if let Some(lt) = ty.lifetime.take() {
borrowed.insert(lt);
}
}
syn::Type::Slice(inner) if top => match &*inner.elem {
syn::Type::Path(inner) if inner.path.is_ident("u8") => {
borrowed.extend(ty.lifetime.take().into_iter());
}
_ => {}
},
_ => {}
}
} else if borrow {
// Otherwise, only borrow if the user has requested it.
borrowed.extend(ty.lifetime.take().into_iter());
} else {
ty.lifetime = None;
discard_lifetimes(&mut ty.elem);
}
discard_lifetimes(&mut ty.elem, borrowed, borrow, false);
}
_ => {}
}
}
fn discard_path_lifetimes(path: &mut syn::TypePath) {
fn discard_path_lifetimes(
path: &mut syn::TypePath,
borrowed: &mut BTreeSet<syn::Lifetime>,
borrow: bool,
) {
if let Some(q) = &mut path.qself {
discard_lifetimes(&mut q.ty);
discard_lifetimes(&mut q.ty, borrowed, borrow, false);
}
for segment in &mut path.path.segments {
@ -238,17 +275,23 @@ fn discard_path_lifetimes(path: &mut syn::TypePath) {
syn::PathArguments::AngleBracketed(args) => {
args.args.iter_mut().for_each(|arg| match arg {
syn::GenericArgument::Lifetime(lt) => {
*lt = syn::Lifetime::new("'_", Span::call_site())
let lt = mem::replace(lt, syn::Lifetime::new("'_", Span::call_site()));
if borrow {
borrowed.insert(lt);
}
}
syn::GenericArgument::Type(ty) => {
discard_lifetimes(ty, borrowed, borrow, false)
}
syn::GenericArgument::Type(ty) => discard_lifetimes(ty),
syn::GenericArgument::Binding(_)
| syn::GenericArgument::Constraint(_)
| syn::GenericArgument::Const(_) => {}
})
}
syn::PathArguments::Parenthesized(args) => {
args.inputs.iter_mut().for_each(discard_lifetimes)
}
syn::PathArguments::Parenthesized(args) => args
.inputs
.iter_mut()
.for_each(|ty| discard_lifetimes(ty, borrowed, borrow, false)),
}
}
}

View File

@ -292,6 +292,9 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
if id == "attribute" {
items.push((MetaItem::Attribute, span));
MetaState::Comma
} else if id == "borrow" {
items.push((MetaItem::Borrow, span));
MetaState::Comma
} else if id == "ns" {
MetaState::Ns
} else if id == "rename" {
@ -306,6 +309,8 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
MetaState::Comma
} else if id == "serialize_with" {
MetaState::SerializeWith
} else if id == "deserialize_with" {
MetaState::DeserializeWith
} else {
panic!("unexpected key in xml attribute");
}
@ -340,6 +345,13 @@ pub(crate) fn meta_items(attrs: &[syn::Attribute]) -> Vec<(MetaItem, Span)> {
items.push((MetaItem::SerializeWith(lit), span));
MetaState::Comma
}
(MetaState::DeserializeWith, TokenTree::Punct(punct)) if punct.as_char() == '=' => {
MetaState::DeserializeWithValue
}
(MetaState::DeserializeWithValue, TokenTree::Literal(lit)) => {
items.push((MetaItem::DeserializeWith(lit), span));
MetaState::Comma
}
(state, tree) => {
panic!(
"invalid state transition while parsing xml attribute ({}, {tree})",
@ -363,6 +375,8 @@ enum MetaState {
RenameAllValue,
SerializeWith,
SerializeWithValue,
DeserializeWith,
DeserializeWithValue,
}
impl MetaState {
@ -377,6 +391,8 @@ impl MetaState {
MetaState::RenameAllValue => "RenameAllValue",
MetaState::SerializeWith => "SerializeWith",
MetaState::SerializeWithValue => "SerializeWithValue",
MetaState::DeserializeWith => "DeserializeWith",
MetaState::DeserializeWithValue => "DeserializeWithValue",
}
}
}
@ -460,9 +476,11 @@ impl fmt::Debug for Namespace {
#[derive(Debug)]
pub(crate) enum MetaItem {
Attribute,
Borrow,
Ns(NamespaceMeta),
Rename(Literal),
Mode(Mode),
RenameAll(Literal),
SerializeWith(Literal),
DeserializeWith(Literal),
}

View File

@ -1,3 +1,5 @@
use std::collections::BTreeSet;
use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
@ -180,12 +182,14 @@ fn serialize_struct(
let tag = meta.tag();
let mut body = TokenStream::new();
let mut attributes = TokenStream::new();
let mut borrowed = BTreeSet::new();
match &data.fields {
syn::Fields::Named(fields) => {
body.extend(quote!(serializer.end_start()?;));
for field in &fields.named {
if let Err(err) = named_field(field, &mut body, &mut attributes, &meta) {
if let Err(err) =
named_field(field, &mut body, &mut attributes, &mut borrowed, &meta)
{
return err.to_compile_error();
}
}
@ -194,7 +198,7 @@ fn serialize_struct(
syn::Fields::Unnamed(fields) => {
body.extend(quote!(serializer.end_start()?;));
for (index, field) in fields.unnamed.iter().enumerate() {
if let Err(err) = unnamed_field(field, index, &mut body) {
if let Err(err) = unnamed_field(field, index, &mut body, &mut borrowed) {
return err.to_compile_error();
}
}
@ -261,6 +265,7 @@ fn named_field(
field: &syn::Field,
body: &mut TokenStream,
attributes: &mut TokenStream,
borrowed: &mut BTreeSet<syn::Lifetime>,
meta: &ContainerMeta,
) -> Result<(), syn::Error> {
let field_name = field.ident.as_ref().unwrap();
@ -338,7 +343,7 @@ fn named_field(
};
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
discard_lifetimes(&mut no_lifetime_type, borrowed, false, true);
body.extend(quote!(
self.#field_name.serialize(Some(::instant_xml::Id { ns: #ns, name: #tag }), serializer)?;
));
@ -350,6 +355,7 @@ fn unnamed_field(
field: &syn::Field,
index: usize,
body: &mut TokenStream,
borrowed: &mut BTreeSet<syn::Lifetime>,
) -> Result<(), syn::Error> {
if !field.attrs.is_empty() {
return Err(syn::Error::new(
@ -359,7 +365,7 @@ fn unnamed_field(
}
let mut no_lifetime_type = field.ty.clone();
discard_lifetimes(&mut no_lifetime_type);
discard_lifetimes(&mut no_lifetime_type, borrowed, false, true);
let index = syn::Index::from(index);
body.extend(quote!(
self.#index.serialize(None, serializer)?;

View File

@ -1,7 +1,9 @@
use std::borrow::Cow;
use std::collections::{BTreeMap, VecDeque};
use xmlparser::{ElementEnd, Token, Tokenizer};
use crate::impls::decode;
use crate::{Error, Id, Kind};
pub struct Deserializer<'cx, 'xml> {
@ -320,6 +322,38 @@ impl<'xml> Iterator for Context<'xml> {
}
}
pub fn borrow_cow_str<'xml>(
deserializer: &mut Deserializer<'_, 'xml>,
into: &mut Option<Cow<'xml, str>>,
) -> Result<(), Error> {
if into.is_some() {
return Err(Error::DuplicateValue);
}
let value = deserializer.take_str()?;
*into = Some(decode(value));
deserializer.ignore()?;
Ok(())
}
pub fn borrow_cow_slice_u8<'xml>(
deserializer: &mut Deserializer<'_, 'xml>,
into: &mut Option<Cow<'xml, [u8]>>,
) -> Result<(), Error> {
if into.is_some() {
return Err(Error::DuplicateValue);
}
let value = deserializer.take_str()?;
*into = Some(match decode(value) {
Cow::Borrowed(v) => Cow::Borrowed(v.as_bytes()),
Cow::Owned(v) => Cow::Owned(v.into_bytes()),
});
deserializer.ignore()?;
Ok(())
}
#[derive(Debug)]
pub enum Node<'xml> {
Attribute(Attribute<'xml>),

View File

@ -176,16 +176,10 @@ impl<'xml> FromXml<'xml> for String {
return Err(Error::DuplicateValue);
}
let mut value = None;
<Cow<'xml, str> as FromXml<'xml>>::deserialize(deserializer, &mut value)?;
match value {
Some(value) => {
*into = Some(value.into_owned());
let value = deserializer.take_str()?;
*into = Some(decode(value).into_owned());
Ok(())
}
None => Err(Error::MissingValue(&Kind::Scalar)),
}
}
const KIND: Kind<'static> = Kind::Scalar;
}
@ -199,24 +193,27 @@ impl<'xml> FromXml<'xml> for &'xml str {
return Err(Error::DuplicateValue);
}
let mut value = None;
<Cow<'xml, str> as FromXml<'xml>>::deserialize(deserializer, &mut value)?;
match value {
Some(Cow::Borrowed(s)) => {
*into = Some(s);
Ok(())
}
Some(Cow::Owned(_)) => Err(Error::UnexpectedValue(
let value = deserializer.take_str()?;
match decode(value) {
Cow::Borrowed(str) => *into = Some(str),
Cow::Owned(_) => {
return Err(Error::UnexpectedValue(
"string with escape characters cannot be deserialized as &str",
)),
None => Err(Error::MissingValue(&Kind::Scalar)),
))
}
}
Ok(())
}
const KIND: Kind<'static> = Kind::Scalar;
}
impl<'xml> FromXml<'xml> for Cow<'xml, str> {
impl<'xml, 'a, T: ?Sized> FromXml<'xml> for Cow<'a, T>
where
T: ToOwned,
T::Owned: FromXml<'xml>,
{
fn deserialize(
deserializer: &mut Deserializer<'_, 'xml>,
into: &mut Option<Self>,
@ -225,10 +222,16 @@ impl<'xml> FromXml<'xml> for Cow<'xml, str> {
return Err(Error::DuplicateValue);
}
let value = deserializer.take_str()?;
*into = Some(decode(value));
let mut value = None;
T::Owned::deserialize(deserializer, &mut value)?;
match value {
Some(value) => {
*into = Some(Cow::Owned(value));
Ok(())
}
None => Err(Error::MissingValue(&Kind::Scalar)),
}
}
const KIND: Kind<'static> = Kind::Scalar;
}
@ -386,7 +389,7 @@ fn encode(input: &str) -> Result<Cow<'_, str>, Error> {
Ok(Cow::Owned(result))
}
fn decode(input: &str) -> Cow<'_, str> {
pub(crate) fn decode(input: &str) -> Cow<'_, str> {
let mut result = String::with_capacity(input.len());
let input_len = input.len();

View File

@ -9,6 +9,7 @@ use instant_xml::{from_str, to_string, Error, FromXml, ToXml};
struct StructSpecialEntities<'a> {
string: String,
str: &'a str,
#[xml(borrow)]
cow: Cow<'a, str>,
}