pub(crate) mod attrs;
use crate::rich::attrs::*;
use quote::{format_ident, quote};
use syn::parse::Parse;
#[derive(Debug)]
enum FieldType {
Option(syn::Type),
Normal(syn::Type),
}
#[derive(Debug)]
pub(crate) struct Field {
attrs: Vec<Attribute>,
name: syn::Ident,
ty: FieldType,
}
impl Field {
fn from_syn_field(field: &syn::Field) -> Result<Self, syn::Error> {
let name = field
.ident
.clone()
.ok_or_else(|| syn::Error::new_spanned(field, "expected field ident"))?;
let ty = field.ty.clone();
let ty = match ty {
syn::Type::Path(syn::TypePath {
path: syn::Path { ref segments, .. },
..
}) => {
let last_segment = segments.iter().last();
match last_segment {
Some(syn::PathSegment {
ident,
arguments:
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
args,
..
}),
}) => {
if ident != "Option" {
FieldType::Normal(ty)
} else {
let first_arg = args.into_iter().nth(0);
match first_arg {
Some(syn::GenericArgument::Type(ty)) => {
FieldType::Option(ty.clone())
}
_ => FieldType::Normal(ty),
}
}
}
_ => FieldType::Normal(ty),
}
}
_ => FieldType::Normal(ty),
};
let mut attrs = vec![];
for attr in field.attrs.iter() {
if attr.path.is_ident("rich") {
let result = attr
.parse_args_with(|parse_stream: syn::parse::ParseStream| {
parse_stream.parse_terminated::<_, syn::Token![,]>(Attribute::parse)
})?
.into_iter()
.collect::<Vec<Attribute>>();
attrs.extend(result);
}
}
Ok(Field { attrs, name, ty })
}
}
pub fn expand_derive_rich(
input: &syn::DeriveInput,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let result = match input.data {
syn::Data::Enum(_) => panic!("doesn't work with enums yet"),
syn::Data::Struct(ref s) => rich_for_struct(input, &s.fields),
syn::Data::Union(_) => panic!("doesn't work with unions yet"),
};
result.into()
}
fn rich_for_struct(
input: &syn::DeriveInput,
fields: &syn::Fields,
) -> Result<proc_macro2::TokenStream, syn::Error> {
match *fields {
syn::Fields::Named(ref fields) => rich_impl(&input, &fields.named),
syn::Fields::Unnamed(_) => panic!("doesn't work with tuple struct yet"),
syn::Fields::Unit => panic!("doesn't work with unit struct yet"),
}
}
fn rich_impl(
input: &syn::DeriveInput,
fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let name = &input.ident;
let fields = fields
.iter()
.map(|f| Field::from_syn_field(f))
.collect::<Vec<Result<Field, syn::Error>>>()
.into_iter()
.collect::<Result<Vec<Field>, syn::Error>>()?;
let fns = fields
.iter()
.map(|field| {
let field_name = &field.name;
let ty = &field.ty;
let mut fns = field
.attrs
.iter()
.map(|attr| match attr {
Attribute::WriteFn(write_fn) => write_fn.get_fn(field),
Attribute::ReadFn(read_fn) => read_fn.get_fn(field),
Attribute::ValueFns(value_fns) => value_fns.get_fns(field),
})
.collect::<Vec<proc_macro2::TokenStream>>();
let has_write_fn_or_value_fns = field.attrs.iter().any(|a| match a {
Attribute::WriteFn(_) | Attribute::ValueFns(_) => true,
_ => false,
});
let is_option = if let FieldType::Option(_) = ty {
true
} else {
false
};
if is_option && has_write_fn_or_value_fns {
let unset_fn_name = format_ident!("unset_{}", field_name);
let docs = format!("Set `None` to `self.{0}`", field_name);
let unset_fn = quote! {
#[doc = #docs]
pub fn #unset_fn_name(&mut self) -> &mut Self {
self.#field_name = None;
self
}
};
fns.push(unset_fn);
}
fns
})
.flatten()
.collect::<Vec<proc_macro2::TokenStream>>();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
impl #impl_generics #name #ty_generics #where_clause {
#( #fns )*
}
})
}