derive_wrapper 0.1.0

Rust custom derive macro for wrapping types
Documentation
#![recursion_limit = "256"]
#![cfg_attr(test, deny(warnings))]

#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

use syn::export::{ToTokens, TokenStream, TokenStream2};
use syn::punctuated::Pair;
use syn::{
    Attribute, Data, DeriveInput, Error, Field, Fields, Ident, Lit, Meta, NestedMeta, Path, Result,
    Type, TypeSlice,
};

struct Details<'a> {
    struct_name: &'a Ident,
    field_name: TokenStream2,
    field_type: &'a Type,
    std: Path,
}

impl<'a> Details<'a> {
    pub fn from_input(struct_name: &'a Ident, field: &'a Field) -> Self {
        let field_name = field
            .ident
            .as_ref()
            .map_or_else(|| quote!(0), ToTokens::into_token_stream);

        Details {
            struct_name,
            field_name,
            field_type: &field.ty,
            std: std(),
        }
    }
}

#[proc_macro_derive(AsRef, attributes(wrap))]
pub fn derive_asref(input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);
    aserf_inner(derive_input)
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

#[proc_macro_derive(Index, attributes(wrap))]
pub fn derive_index(input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);
    index_inner(derive_input)
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

#[proc_macro_derive(LowerHex, attributes(wrap))]
pub fn derive_lowerhex(input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);
    lowerhex_inner(derive_input)
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

#[proc_macro_derive(LowerHexIter, attributes(wrap))]
pub fn derive_lowerhex_iter(input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);
    lowerhexiter_inner(derive_input)
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

#[proc_macro_derive(Display, attributes(wrap, display_from))]
pub fn derive_display(input: TokenStream) -> TokenStream {
    let derive_input = parse_macro_input!(input as DeriveInput);
    display_inner(derive_input)
        .unwrap_or_else(|e| e.to_compile_error())
        .into()
}

fn display_inner(input: DeriveInput) -> Result<TokenStream2> {
    let field = get_field(&input)?;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let Details {
        struct_name, std, ..
    } = Details::from_input(&input.ident, field);

    let mut traits_found = Vec::with_capacity(input.attrs.len());
    for attr in &input.attrs {
        let mv = find_meta_value(attr, "display_from");
        if mv.multiple {
            return Err(Error::new_spanned(
                attr,
                "derive_wrapper: display_from doesn't nested attributes",
            ));
        }
        if mv.found {
            if let Some(trait_name) = mv.name {
                traits_found.push(trait_name);
            } else {
                return Err(Error::new_spanned(attr, "derive_wrapper: when using the display_from attribute on the struct you must specify the trait you want to use to implement Display"));
            }
        }
    }

    let display_from = match traits_found.len() {
        1 => traits_found.pop().unwrap(),
        0 => return Err(Error::new_spanned(&input, "Deriving Display requires specifying which trait to use using the `display_from` attribute. Try: `#[display_from(Debug)]`")),
        _ => return Err(Error::new_spanned(&input, "Deriving Display supports only a single display_from attribute")),
    };

    Ok(quote! {
        #[allow(unused_qualifications)]
        impl #impl_generics #std::fmt::Display for #struct_name #ty_generics #where_clause {
            #[inline]
            fn fmt(&self, f: &mut #std::fmt::Formatter) -> #std::fmt::Result {
                #std::fmt::#display_from::fmt(&self, f)
            }
        }
    })
}

fn lowerhexiter_inner(input: DeriveInput) -> Result<TokenStream2> {
    let field = get_field(&input)?;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let Details {
        struct_name,
        field_name,
        std,
        ..
    } = Details::from_input(&input.ident, field);

    Ok(quote! {
        #[allow(unused_qualifications)]
        impl #impl_generics #std::fmt::LowerHex<> for #struct_name #ty_generics #where_clause {
            #[inline]
            fn fmt(&self, f: &mut #std::fmt::Formatter) -> #std::fmt::Result {
                for ch in self.#field_name.iter() {
                    #std::fmt::LowerHex::fmt(&ch, f)?;
                }
                #std::result::Result::Ok(())
            }
        }
    })
}

fn lowerhex_inner(input: DeriveInput) -> Result<TokenStream2> {
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let field = get_field(&input)?;
    let Details {
        struct_name,
        field_name,
        std,
        ..
    } = Details::from_input(&input.ident, field);

    Ok(quote! {
        #[allow(unused_qualifications)]
        impl #impl_generics #std::fmt::LowerHex for #struct_name #ty_generics #where_clause {
            #[inline]
            fn fmt(&self, f: &mut #std::fmt::Formatter) -> #std::fmt::Result {
                #std::fmt::LowerHex::fmt(&self.#field_name, f)
            }
        }
    })
}

fn index_inner(input: DeriveInput) -> Result<TokenStream2> {
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let field = get_field(&input)?;
    let Details {
        struct_name,
        field_name,
        field_type,
        std,
    } = Details::from_input(&input.ident, field);
    let field_type = array_to_slice(field_type.clone());

    Ok(quote! {
        #[allow(unused_qualifications)]
        impl #impl_generics #std::ops::Index<usize> for #struct_name #ty_generics #where_clause {
            type Output = <#field_type as #std::ops::Index<usize>>::Output;
            #[inline]
            fn index(&self, index: usize) -> &Self::Output {
                &self.#field_name[index]
            }
        }

        #[allow(unused_qualifications)]
        impl #impl_generics #std::ops::Index<#std::ops::Range<usize>> for #struct_name #ty_generics #where_clause {
            type Output = <#field_type as #std::ops::Index<#std::ops::Range<usize>>>::Output;

            #[inline]
            fn index(&self, index: #std::ops::Range<usize>) -> &Self::Output {
                &self.#field_name[index]
            }
        }

        #[allow(unused_qualifications)]
        impl #impl_generics #std::ops::Index<#std::ops::RangeTo<usize>> for #struct_name #ty_generics #where_clause {
            type Output = <#field_type as #std::ops::Index<#std::ops::RangeTo<usize>>>::Output;

            #[inline]
            fn index(&self, index: #std::ops::RangeTo<usize>) -> &Self::Output {
                &self.#field_name[index]
            }
        }

        #[allow(unused_qualifications)]
        impl #impl_generics #std::ops::Index<#std::ops::RangeFrom<usize>> for #struct_name #ty_generics #where_clause {
            type Output = <#field_type as #std::ops::Index<#std::ops::RangeFrom<usize>>>::Output;

            #[inline]
            fn index(&self, index: #std::ops::RangeFrom<usize>) -> &Self::Output {
                &self.#field_name[index]
            }
        }

        #[allow(unused_qualifications)]
        impl #impl_generics #std::ops::Index<#std::ops::RangeFull> for #struct_name #ty_generics #where_clause {
            type Output = <#field_type as #std::ops::Index<#std::ops::RangeFull>>::Output;

            #[inline]
            fn index(&self, index: #std::ops::RangeFull) -> &Self::Output {
                &self.#field_name[index]
            }
        }
    })
}

fn array_to_slice(ty: Type) -> Type {
    if let Type::Array(arr) = ty {
        Type::Slice(TypeSlice {
            bracket_token: arr.bracket_token,
            elem: arr.elem,
        })
    } else {
        ty
    }
}

fn aserf_inner(input: DeriveInput) -> Result<TokenStream2> {
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let field = get_field(&input)?;
    let Details {
        struct_name,
        field_name,
        field_type,
        std,
    } = Details::from_input(&input.ident, field);

    Ok(quote! {
        #[allow(unused_qualifications)]
        impl #impl_generics #std::convert::AsRef<#field_type> for #struct_name #ty_generics #where_clause {
            #[inline]
            fn as_ref(&self) -> &#field_type {
                &self.#field_name
            }
        }
    })
}

fn get_field(input: &DeriveInput) -> Result<&Field> {
    let fields = match input.data {
        Data::Struct(ref data) => &data.fields,
        _ => {
            return Err(Error::new_spanned(
                &input,
                "Deriving AsRef is supported only in structs",
            ))
        }
    };

    if fields.iter().len() > 1 {
        let mut marked_fields = parse_outer_attributes(&input.attrs, &fields)?;
        marked_fields.extend(parse_field_attributes(&fields)?);
        match marked_fields.len() {
            1 => Ok(marked_fields.pop().unwrap()),
            0 => Err(Error::new_spanned(&input, "Deriving AsRef for a struct with multiple fields requires specifying a wrap attribute")),
            _ => Err(Error::new_spanned(&input, "Deriving AsRef supports only a single wrap attribute")),
        }
    } else {
        fields.iter().next().ok_or_else(|| {
            Error::new_spanned(&input, "Deriving AsRef for an empty struct isn't supported")
        })
    }
}

#[derive(Default)]
struct MetaValue {
    pub found: bool,
    pub name: Option<Ident>,
    pub multiple: bool,
}

fn find_meta_value(attr: &Attribute, name: &str) -> MetaValue {
    let mut res = MetaValue::default();
    if let Ok(meta) = attr.parse_meta() {
        if meta.name() == name {
            res.found = true;
            match meta {
                Meta::NameValue(nv) => res.name = lit_to_ident(nv.lit),
                Meta::List(mut list) => {
                    res.multiple = list.nested.len() > 1;
                    res.name = list
                        .nested
                        .pop()
                        .map(Pair::into_value)
                        .and_then(|nestedmeta| match nestedmeta {
                            NestedMeta::Literal(lit) => lit_to_ident(lit),
                            NestedMeta::Meta(meta) => {
                                if let Meta::Word(ident) = meta {
                                    Some(ident)
                                } else {
                                    None
                                }
                            }
                        });
                }
                Meta::Word(_) => (),
            }
        }
    }
    res
}

fn lit_to_ident(lit: Lit) -> Option<Ident> {
    if let Lit::Str(l) = lit {
        Some(Ident::new(&l.value(), l.span()))
    } else {
        None
    }
}

fn parse_outer_attributes<'a>(attrs: &[Attribute], fields: &'a Fields) -> Result<Vec<&'a Field>> {
    let mut res = Vec::with_capacity(attrs.len());
    for attr in attrs {
        let mv = find_meta_value(attr, "wrap");
        if mv.found {
            if let Some(lit_name) = mv.name {
                let mut found = false;
                for f in fields {
                    if let Some(ref field_name) = f.ident {
                        if lit_name == field_name.to_string() {
                            res.push(f);
                            found = true;
                            break;
                        }
                    }
                }
                if !found {
                    return Err(Error::new_spanned(
                        &fields,
                        format!("derive_wrapper: field {} doesn't exist", lit_name),
                    ));
                }
            } else {
                return Err(Error::new_spanned(&fields, "derive_wrapper: when using the wrap attribute on the struct you must specify the field name"));
            }
        }
    }
    Ok(res)
}

fn parse_field_attributes(fields: &Fields) -> Result<Vec<&Field>> {
    let mut res = Vec::with_capacity(fields.iter().len());
    for field in fields.iter() {
        for attr in &field.attrs {
            let mv = find_meta_value(attr, "wrap");
            if mv.found {
                if let Some(ref ident) = field.ident {
                    let ident = ident.to_string();
                    if let Some(lit) = mv.name {
                        if lit != ident {
                            return Err(Error::new_spanned(&field, format!("derive_wrapper: The provided field name doesn't match the field name it's above: `{} != {}`", lit, ident)));
                        }
                    }
                    res.push(field)
                } else {
                    return Err(Error::new_spanned(&field, "derive_wrapper doesn't yet support attributes on unnamed fields (Please file an issue)"));
                }
            }
        }
    }
    Ok(res)
}

#[inline(always)]
fn std() -> Path {
    #[cfg(feature = "std")]
    return parse_quote!(::std);
    #[cfg(not(feature = "std"))]
    return parse_quote!(::core);
}