ruwuma/ruma-api-macros/src/derive_outgoing.rs
2019-11-27 19:28:54 +01:00

196 lines
5.9 KiB
Rust

use std::mem;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
parse_quote, punctuated::Pair, spanned::Spanned, Attribute, Data, DeriveInput, Fields,
GenericArgument, Path, PathArguments, Type, TypePath,
};
mod wrap_incoming;
use wrap_incoming::Meta;
pub fn expand_derive_outgoing(input: DeriveInput) -> syn::Result<TokenStream> {
let derive_deserialize = if no_deserialize_in_attrs(&input.attrs) {
TokenStream::new()
} else {
quote!(#[derive(ruma_api::exports::serde::Deserialize)])
};
let mut fields: Vec<_> = match input.data {
Data::Enum(_) | Data::Union(_) => {
panic!("#[derive(Outgoing)] is only supported for structs")
}
Data::Struct(s) => match s.fields {
Fields::Named(fs) => fs.named.into_pairs().map(Pair::into_value).collect(),
Fields::Unnamed(fs) => fs.unnamed.into_pairs().map(Pair::into_value).collect(),
Fields::Unit => return Ok(impl_send_recv_incoming_self(input.ident)),
},
};
let mut any_attribute = false;
for field in &mut fields {
let mut field_meta = None;
let mut remaining_attrs = Vec::new();
for attr in mem::replace(&mut field.attrs, Vec::new()) {
if let Some(meta) = Meta::from_attribute(&attr)? {
if field_meta.is_some() {
return Err(syn::Error::new_spanned(
attr,
"duplicate #[wrap_incoming] attribute",
));
}
field_meta = Some(meta);
any_attribute = true;
} else {
remaining_attrs.push(attr);
}
}
field.attrs = remaining_attrs;
if let Some(attr) = field_meta {
if let Some(type_to_wrap) = attr.type_to_wrap {
wrap_generic_arg(&type_to_wrap, &mut field.ty, attr.wrapper_type.as_ref())?;
} else {
wrap_ty(&mut field.ty, attr.wrapper_type)?;
}
}
}
if !any_attribute {
return Ok(impl_send_recv_incoming_self(input.ident));
}
let vis = input.vis;
let doc = format!("'Incoming' variant of [{ty}](struct.{ty}.html).", ty = input.ident);
let original_ident = input.ident;
let incoming_ident = Ident::new(&format!("Incoming{}", original_ident), Span::call_site());
Ok(quote! {
#[doc = #doc]
#derive_deserialize
#vis struct #incoming_ident {
#(#fields,)*
}
impl ruma_api::Outgoing for #original_ident {
type Incoming = #incoming_ident;
}
})
}
fn no_deserialize_in_attrs(attrs: &[Attribute]) -> bool {
for attr in attrs {
match &attr.path {
Path { leading_colon: None, segments }
if segments.len() == 1 && segments[0].ident == "incoming_no_deserialize" =>
{
return true
}
_ => {}
}
}
false
}
fn impl_send_recv_incoming_self(ident: Ident) -> TokenStream {
quote! {
impl ruma_api::Outgoing for #ident {
type Incoming = Self;
}
}
}
fn wrap_ty(ty: &mut Type, path: Option<Path>) -> syn::Result<()> {
if let Some(wrap_ty) = path {
*ty = parse_quote!(#wrap_ty<#ty>);
} else {
match ty {
Type::Path(TypePath { path, .. }) => {
let ty_ident = &mut path.segments.last_mut().unwrap().ident;
let ident = Ident::new(&format!("Incoming{}", ty_ident), Span::call_site());
*ty_ident = parse_quote!(#ident);
}
_ => return Err(syn::Error::new_spanned(ty, "Can't wrap this type")),
}
}
Ok(())
}
fn wrap_generic_arg(type_to_wrap: &Type, of: &mut Type, with: Option<&Path>) -> syn::Result<()> {
let mut span = None;
wrap_generic_arg_impl(type_to_wrap, of, with, &mut span)?;
if span.is_some() {
Ok(())
} else {
Err(syn::Error::new_spanned(
of,
format!(
"Couldn't find generic argument `{}` in this type",
type_to_wrap.to_token_stream()
),
))
}
}
fn wrap_generic_arg_impl(
type_to_wrap: &Type,
of: &mut Type,
with: Option<&Path>,
span: &mut Option<Span>,
) -> syn::Result<()> {
// TODO: Support things like array types?
let ty_path = match of {
Type::Path(TypePath { path, .. }) => path,
_ => return Ok(()),
};
let args = match &mut ty_path.segments.last_mut().unwrap().arguments {
PathArguments::AngleBracketed(ab) => &mut ab.args,
_ => return Ok(()),
};
for arg in args.iter_mut() {
let ty = match arg {
GenericArgument::Type(ty) => ty,
_ => continue,
};
if ty == type_to_wrap {
if let Some(s) = span {
let mut error = syn::Error::new(
*s,
format!(
"`{}` found multiple times, this is not currently supported",
type_to_wrap.to_token_stream()
),
);
error.combine(syn::Error::new_spanned(ty, "second occurrence"));
return Err(error);
}
*span = Some(ty.span());
if let Some(wrapper_type) = with {
*ty = parse_quote!(#wrapper_type<#ty>);
} else if let Type::Path(TypePath { path, .. }) = ty {
let ty_ident = &mut path.segments.last_mut().unwrap().ident;
let ident = Ident::new(&format!("Incoming{}", ty_ident), Span::call_site());
*ty_ident = parse_quote!(#ident);
} else {
return Err(syn::Error::new_spanned(ty, "Can't wrap this type"));
}
} else {
wrap_generic_arg_impl(type_to_wrap, ty, with, span)?;
}
}
Ok(())
}