1#[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}