uncon_derive/
lib.rs

1//! Support for deriving traits found in [`uncon`].
2//!
3//! # Usage
4//!
5//! This crate is available [on crates.io][crate] and can be used by adding the
6//! following to your project's `Cargo.toml`:
7//!
8//! ```toml
9//! [dependencies]
10//! uncon_derive = "1.1.1"
11//! uncon = "1.1.0"
12//! ```
13//!
14//! and this to your crate root:
15//!
16//! ```
17//! #[macro_use]
18//! extern crate uncon_derive;
19//! extern crate uncon;
20//! # fn main() {}
21//! ```
22//!
23//! # Examples
24//!
25//! The [`FromUnchecked`] trait can be derived for:
26//!
27//! - Structs with a single field
28//! - C-like enums with `#[repr]` attribute
29//!
30//! ```
31//! # extern crate core;
32//! # #[macro_use] extern crate static_assertions;
33//! # #[macro_use] extern crate uncon_derive;
34//! # extern crate uncon;
35//! # use uncon::*;
36//! # macro_rules! assert_impl_from {
37//! #    ($t:ty, $($u:ty),+) => {
38//! #        assert_impl!($t, $(FromUnchecked<$u>, From<$u>),+)
39//! #    }
40//! # }
41//! #[derive(FromUnchecked)]
42//! struct U4 {
43//!     bits: u8
44//! }
45//!
46//! #[derive(FromUnchecked, PartialEq, Debug)]
47//! #[uncon(impl_from, other(u16, u32, u64, usize))]
48//! # #[uncon(other(i8, i16, i32, i64, isize))]
49//! #[repr(u8)]
50//! enum Flag {
51//!     A, B, C, D
52//! }
53//!
54//! // `usize` and `isize` also supported:
55//! #[derive(FromUnchecked)]
56//! #[repr(usize)]
57//! enum Value {
58//!     X, Y, Z
59//! }
60//!
61//! # fn main() {
62//! # assert_impl_from!(Flag, u8, u16, u32, u64, usize);
63//! # assert_impl_from!(Flag, i8, i16, i32, i64, isize);
64//! unsafe {
65//!     let b = 0b1010;
66//!     let x = U4::from_unchecked(b);
67//!     assert_eq!(x.bits, b);
68//!
69//!     let n = 2u8;
70//!     let f = Flag::from_unchecked(n);
71//!     assert_eq!(f, Flag::C);
72//!
73//!     // Done via `#[uncon(other(u32, ...))]`
74//!     assert_eq!(Flag::from_unchecked(n as u32), f);
75//!
76//!     // Done via `#[uncon(impl_from)]`
77//!     assert_eq!(Flag::from(5usize), Flag::B);
78//! }
79//! # }
80//! ```
81//!
82//! # Options
83//!
84//! - Derive [`FromUnchecked`] for other types:
85//!   - Done via `#[uncon(other(...))]`.
86//!   - Derives `FromUnchecked` with each type listed via an `as` cast to the
87//!     inner or representative type.
88//!
89//! - Derive [`From`]:
90//!   - Done via `#[uncon(from_impl)]`.
91//!   - Only for C-like enums such that no variant is assigned a discriminant.
92//!
93//! [crate]: https://crates.io/crates/uncon_derive
94//! [`uncon`]: https://docs.rs/uncon
95//! [`From`]: https://doc.rust-lang.org/std/convert/trait.From.html
96//! [`FromUnchecked`]: https://docs.rs/uncon/1.0.0/uncon/trait.FromUnchecked.html
97
98#[macro_use]
99extern crate quote;
100extern crate proc_macro;
101extern crate syn;
102
103use proc_macro::TokenStream;
104use syn::{Body, MetaItem, NestedMetaItem, VariantData};
105use quote::Tokens;
106
107#[doc(hidden)]
108#[proc_macro_derive(FromUnchecked, attributes(uncon))]
109pub fn from_unchecked(input: TokenStream) -> TokenStream {
110    let ast = syn::parse_derive_input(&input.to_string()).unwrap();
111    impl_from_unchecked(&ast).parse().unwrap()
112}
113
114fn as_item(item: &NestedMetaItem) -> Option<&MetaItem> {
115    if let NestedMetaItem::MetaItem(ref item) = *item {
116        Some(item)
117    } else {
118        None
119    }
120}
121
122fn meta_items<'a, T: 'a>(items: T, ident: &str) -> Vec<&'a [NestedMetaItem]>
123    where T: IntoIterator<Item=&'a MetaItem>
124{
125    items.into_iter().filter_map(|item| {
126        if let MetaItem::List(ref id, ref items) = *item {
127            if id == ident { return Some(items.as_ref()); }
128        }
129        None
130    }).collect()
131}
132
133fn is_int_ty(s: &str) -> bool {
134    let mut bytes = s.as_bytes();
135    match bytes.get(0) {
136        Some(&b'u') | Some(&b'i') => (),
137        _ => return false,
138    }
139    bytes = &bytes[1..];
140    match bytes.len() {
141        0 => false,
142        4 if bytes == b"size" => true,
143        _ => {
144            for &byte in bytes {
145                if byte < b'0' || byte > b'9' {
146                    return false;
147                }
148            }
149            true
150        },
151    }
152}
153
154fn impl_from_unchecked(ast: &syn::DeriveInput) -> quote::Tokens {
155    let name = &ast.ident;
156    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
157
158    let attr_items = |ident: &str| {
159        meta_items(ast.attrs.iter().map(|a| &a.value), ident)
160    };
161    let uncon_items = attr_items("uncon");
162
163    let core = if cfg!(feature = "std") { quote!(std) } else { quote!(core) };
164
165    let impl_from = uncon_items.iter().flat_map(|i| i.iter()).filter_map(|item| {
166        if let NestedMetaItem::MetaItem(MetaItem::Word(ref ident)) = *item {
167            if ident == "impl_from" { return Some(true); }
168        }
169        None
170    }).next().unwrap_or(false);
171
172    let (ty, init, from_impl) = match ast.body {
173        Body::Enum(ref variants) => {
174            for variant in variants {
175                assert!(!impl_from || variant.discriminant.is_none(),
176                        "Cannot derive From due to {}::{} discriminant",
177                        name, variant.ident);
178                match variant.data {
179                    VariantData::Unit => continue,
180                    _ => panic!("Found non-unit variant '{}'", variant.ident),
181                }
182            }
183
184            let items = *attr_items("repr").first().expect("Could not find `#[repr]` attribute");
185
186            let repr = items.iter().filter_map(|ref item| {
187                if let NestedMetaItem::MetaItem(ref item) = **item {
188                    let name = item.name();
189                    if is_int_ty(name) {
190                        return Some(name);
191                    }
192                }
193                None
194            }).next().expect("Could not find integer repr for conversion");
195
196            let init = quote! { ::#core::mem::transmute(inner) };
197            let mut ty = Tokens::new();
198            ty.append(repr);
199
200            let from_impl = if impl_from {
201                let num = variants.len();
202                Some(quote! {
203                    use uncon::IntoUnchecked;
204                    unsafe { (inner % (#num as #ty)).into_unchecked() }
205                })
206            } else {
207                None
208            };
209
210            (ty, init, from_impl)
211        },
212        Body::Struct(ref data) => {
213            assert!(!impl_from, "Cannot derive From for non-enum types");
214
215            let fields = data.fields();
216            if fields.len() != 1 {
217                panic!("`FromUnchecked` can only be derived for types with a single field");
218            }
219            let field = &fields[0];
220
221            let init = if let Some(ref ident) = field.ident {
222                quote! { #name { #ident: inner } }
223            } else {
224                quote! { #name(inner) }
225            };
226
227            let ty = &field.ty;
228            (quote!(#ty), init, None)
229        },
230    };
231
232    let mut other_items = Vec::<&NestedMetaItem>::new();
233
234    for uncon_item in uncon_items.iter() {
235        for other_item in meta_items(uncon_item.iter().filter_map(as_item), "other") {
236            other_items.extend(other_item);
237        }
238    }
239
240    let tys_impl = other_items.iter().filter_map(|item| {
241        if let NestedMetaItem::MetaItem(MetaItem::Word(ref item)) = **item {
242            let from_impl = from_impl.as_ref().map(|_| quote! {
243                impl #impl_generics From<#item> for #name #ty_generics #where_clause {
244                    #[inline]
245                    fn from(inner: #item) -> Self { (inner as #ty).into() }
246                }
247            });
248            Some(quote! {
249                impl #impl_generics ::uncon::FromUnchecked<#item> for #name #ty_generics #where_clause {
250                    #[inline]
251                    unsafe fn from_unchecked(inner: #item) -> Self {
252                        Self::from_unchecked(inner as #ty)
253                    }
254                }
255                #from_impl
256            })
257        } else {
258            None
259        }
260    });
261
262    let from_impl = from_impl.as_ref().map(|fi| quote! {
263        impl #impl_generics From<#ty> for #name #ty_generics #where_clause {
264            #[inline]
265            fn from(inner: #ty) -> Self { #fi }
266        }
267    });
268
269    quote! {
270        impl #impl_generics ::uncon::FromUnchecked<#ty> for #name #ty_generics #where_clause {
271            #[inline]
272            unsafe fn from_unchecked(inner: #ty) -> Self {
273                #init
274            }
275        }
276        #from_impl
277        #(#tys_impl)*
278    }
279}