extern crate proc_macro;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Parse, ParseStream};
use syn::{
parse_macro_input, parse_quote, Attribute, Expr, ExprLit, Field, ItemEnum, ItemStruct, Lit,
Meta, NestedMeta, Type,
};
fn render_serialize_fn(fn_ident: &Ident, ty: &Type, len: usize) -> TokenStream {
quote! {
fn #fn_ident<S>(array: &#ty, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeTuple;
let mut seq = serializer.serialize_tuple(#len)?;
for e in array {
seq.serialize_element(e)?;
}
seq.end()
}
}
}
fn render_deserialize_fn(fn_ident: &Ident, ty: &Type, len: usize) -> TokenStream {
let count = 0..len;
quote! {
fn #fn_ident<'de, D>(deserializer: D) -> core::result::Result<#ty, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ArrayVisitor;
impl<'de> serde::de::Visitor<'de> for ArrayVisitor {
type Value = #ty;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(std::concat!("an array of length ", #len))
}
#[inline]
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
Ok([
#(
match seq.next_element()? {
Some(val) => val,
None => return Err(serde::de::Error::invalid_length(#count, &self)),
}
),*
])
}
}
deserializer.deserialize_tuple(#len, ArrayVisitor)
}
}
}
fn parse_big_array(field: &mut Field) -> Option<(&mut Field, usize)> {
if let Type::Array(array_type) = &field.ty {
if let Expr::Lit(ExprLit {
lit: Lit::Int(len), ..
}) = &array_type.len
{
let len: usize = len.base10_parse().unwrap();
if len > 32 {
return Some((field, len));
}
}
}
None
}
fn check_if_serializing_deserializing<'a>(
attrs: impl Iterator<Item = &'a Attribute>,
) -> (bool, bool) {
let mut serialize = false;
let mut deserialize = false;
for derive_attr in attrs.filter(|attr| attr.path.is_ident("derive")) {
if let Meta::List(derive_attr) = derive_attr.parse_meta().unwrap() {
for derive in derive_attr.nested {
if let NestedMeta::Meta(Meta::Path(path)) = derive {
if let Some(last_segment) = path.segments.iter().last() {
if last_segment.ident == "Serialize" {
serialize = true;
} else if last_segment.ident == "Deserialize" {
deserialize = true;
}
}
};
}
}
}
(serialize, deserialize)
}
enum Item {
Struct(ItemStruct),
Enum(ItemEnum),
}
impl Item {
fn fields(&mut self) -> impl Iterator<Item = &mut Field> {
let result: Box<dyn Iterator<Item = &mut Field>> = match self {
Item::Struct(s) => Box::new(s.fields.iter_mut()),
Item::Enum(e) => {
let outer_iter = e.variants.iter_mut();
let result = outer_iter.map(|v| v.fields.iter_mut()).flatten();
Box::new(result)
}
};
result
}
fn attrs(&self) -> impl Iterator<Item = &Attribute> {
match self {
Item::Struct(s) => s.attrs.iter(),
Item::Enum(e) => e.attrs.iter(),
}
}
fn ident(&self) -> &Ident {
match self {
Item::Struct(s) => &s.ident,
Item::Enum(e) => &e.ident,
}
}
}
impl Parse for Item {
fn parse(input: ParseStream) -> syn::Result<Self> {
let item: syn::Item = input.parse()?;
match item {
syn::Item::Struct(s) => Ok(Self::Struct(s)),
syn::Item::Enum(e) => Ok(Self::Enum(e)),
_ => Err(syn::Error::new(
input.span(),
"serbia accepts only enums or structs",
)),
}
}
}
impl ToTokens for Item {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Item::Struct(s) => s.to_tokens(tokens),
Item::Enum(e) => e.to_tokens(tokens),
}
}
}
#[proc_macro_attribute]
pub fn serbia(
_attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut input = parse_macro_input!(input as Item);
let struct_name = input.ident().to_string();
let external_attrs = input.attrs();
let (serialize, deserialize) = check_if_serializing_deserializing(external_attrs);
let mut fn_defs = vec![];
for (i, (field, len)) in input.fields().filter_map(parse_big_array).enumerate() {
let ty = &field.ty;
if serialize {
let fn_ident = format_ident!("serbia_serialize_{}_arr_{}", struct_name, i);
let fn_name = fn_ident.to_string();
field.attrs.push(parse_quote! {
#[serde(serialize_with = #fn_name)]
});
fn_defs.push(render_serialize_fn(&fn_ident, &ty, len));
}
if deserialize {
let fn_ident = format_ident!("serbia_deserialize_{}_arr_{}", struct_name, i);
let fn_name = fn_ident.to_string();
field.attrs.push(parse_quote! {
#[serde(deserialize_with = #fn_name)]
});
fn_defs.push(render_deserialize_fn(&fn_ident, &ty, len));
}
}
let expanded = quote! {
#input
#(#fn_defs)*
};
proc_macro::TokenStream::from(expanded)
}
#[test]
fn test_parse_big_array() {
let s: ItemStruct = parse_quote! {
struct S {
a: String,
b: [u32; 32],
c: [u32; 33],
}
};
let mut fields: Vec<_> = s.fields.into_iter().collect();
assert!(parse_big_array(&mut fields[0]).is_none());
assert!(parse_big_array(&mut fields[1]).is_none());
assert!(parse_big_array(&mut fields[2]).unwrap().1 == 33);
}
#[test]
fn test_no_serde_derive() {
let attrs: Vec<Attribute> = vec![
parse_quote! {
#[derive(Serializer, Debug, Asd)]
},
parse_quote! {
#[asd]
},
parse_quote! {
#[serde(serialize_with = "asd")]
},
];
assert_eq!(
check_if_serializing_deserializing(attrs.iter()),
(false, false)
);
}
#[test]
fn test_detect_serialize() {
let attrs: Vec<Attribute> = vec![
parse_quote! {
#[asd]
},
parse_quote! {
#[derive(Deserializer, Debug, Asd, Serialize)]
},
parse_quote! {
#[asd]
},
parse_quote! {
#[serde(serialize_with = "asd")]
},
];
assert_eq!(
check_if_serializing_deserializing(attrs.iter()),
(true, false)
);
}
#[test]
fn test_detect_deserialize() {
let attrs: Vec<Attribute> = vec![
parse_quote! {
#[asd]
},
parse_quote! {
#[derive(Deserializer, Debug, Deserialize, Asd)]
},
parse_quote! {
#[asd]
},
parse_quote! {
#[serde(serialize_with = "asd")]
},
];
assert_eq!(
check_if_serializing_deserializing(attrs.iter()),
(false, true)
);
}
#[test]
fn test_detect_serialize_deserialize() {
let attrs: Vec<Attribute> = vec![
parse_quote! {
#[asd]
},
parse_quote! {
#[derive(Serialize, Deserializer, Debug, Deserialize, Asd)]
},
parse_quote! {
#[asd]
},
parse_quote! {
#[serde(serialize_with = "asd")]
},
];
assert_eq!(
check_if_serializing_deserializing(attrs.iter()),
(true, true)
);
}
#[test]
fn test_detect_serialize_deserialize_qualified() {
let attrs: Vec<Attribute> = vec![
parse_quote! {
#[asd]
},
parse_quote! {
#[derive(serde::Serialize, Deserializer, Debug, serde::Deserialize, Asd)]
},
parse_quote! {
#[asd]
},
parse_quote! {
#[serde(serialize_with = "asd")]
},
];
assert_eq!(
check_if_serializing_deserializing(attrs.iter()),
(true, true)
);
}