#[macro_use]
extern crate quote;
extern crate proc_macro;
extern crate regex;
extern crate syn;
use proc_macro::TokenStream;
use syn::{Body, MetaItem, NestedMetaItem, VariantData};
use quote::Tokens;
#[doc(hidden)]
#[proc_macro_derive(FromUnchecked, attributes(uncon))]
pub fn from_unchecked(input: TokenStream) -> TokenStream {
let ast = syn::parse_derive_input(&input.to_string()).unwrap();
impl_from_unchecked(&ast).parse().unwrap()
}
fn as_item(item: &NestedMetaItem) -> Option<&MetaItem> {
if let NestedMetaItem::MetaItem(ref item) = *item {
Some(item)
} else {
None
}
}
fn meta_items<'a, T: 'a>(items: T, ident: &str) -> Option<&'a [NestedMetaItem]>
where T: IntoIterator<Item=&'a MetaItem>
{
items.into_iter().filter_map(|item| {
if let MetaItem::List(ref id, ref items) = *item {
if id == ident { return Some(items.as_ref()); }
}
None
}).next()
}
fn impl_from_unchecked(ast: &syn::DeriveInput) -> quote::Tokens {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let attr_items = |ident: &str| {
meta_items(ast.attrs.iter().map(|a| &a.value), ident)
};
let core = if cfg!(feature = "std") { quote!(std) } else { quote!(core) };
let (ty, init) = match ast.body {
Body::Enum(ref variants) => {
for variant in variants {
match variant.data {
VariantData::Unit => continue,
_ => panic!("Found non-unit variant '{}'", variant.ident),
}
}
let items = attr_items("repr").expect("Could not find `#[repr]` attribute");
let int_ty = regex::Regex::new("^(i|u)(\\d+|size)$").unwrap();
let repr = items.iter().filter_map(|item| {
if let NestedMetaItem::MetaItem(ref item) = *item {
let name = item.name();
if int_ty.is_match(name) {
return Some(name);
}
}
None
}).next().expect("Could not find integer repr for conversion");
let init = quote! { ::#core::mem::transmute(inner) };
let mut ty = Tokens::new();
ty.append(repr);
(ty, init)
},
Body::Struct(ref data) => {
let fields = data.fields();
if fields.len() != 1 {
panic!("`FromUnchecked` can only be derived for types with a single field");
}
let field = &fields[0];
let init = if let Some(ref ident) = field.ident {
quote! { #name { #ident: inner } }
} else {
quote! { #name(inner) }
};
let ty = &field.ty;
(quote!(#ty), init)
},
};
let mut items = <&[NestedMetaItem]>::default();
if let Some(ai) = attr_items("uncon") {
if let Some(mi) = meta_items(ai.iter().filter_map(as_item), "other") {
items = mi;
}
}
let tys_impl = items.iter().filter_map(|item| {
if let NestedMetaItem::MetaItem(MetaItem::Word(ref item)) = *item {
Some(quote! {
impl #impl_generics ::uncon::FromUnchecked<#item> for #name #ty_generics #where_clause {
#[inline]
unsafe fn from_unchecked(inner: #item) -> Self {
Self::from_unchecked(inner as #ty)
}
}
})
} else {
None
}
});
quote! {
impl #impl_generics ::uncon::FromUnchecked<#ty> for #name #ty_generics #where_clause {
#[inline]
unsafe fn from_unchecked(inner: #ty) -> Self {
#init
}
}
#(#tys_impl)*
}
}