enum_repr/lib.rs
1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7#![warn(clippy::pedantic)]
8
9#![recursion_limit = "128"]
10
11//! Generate enum repr conversions compatible with type aliases.
12//!
13//! Generate with `#[EnumRepr(type = "TYPE")]`.
14//!
15//! Functions generated are
16//! ```ignore
17//! fn repr(&self) -> EnumReprType
18//! fn from_repr(x: EnumReprType) -> Option<Self>
19//! ```
20//! The real enum discriminant is usually forced to be `#[repr(isize)]`.
21//! If `u*` or `i*` types are used for the discriminant, the actual enum
22//! representation is made to be `#[repr(that_type_specified)]`.
23//! The list of types recognized as `u*` and `i*` currently is as follows:
24//! `i8`, `i16`, `i32`, `i64`, `i128`, `u8`, `u16`, `u32`, `u64`, `u128`.
25//! If the type is specified through a type alias, `#[repr(isize)]` is used.
26//! Inability to specify type aliases as enum representations is this crate's
27//! reason to exist.
28//!
29//! The code generated does not require std.
30//!
31//! # Examples
32//! ```
33//! extern crate enum_repr;
34//! extern crate libc;
35//!
36//! use libc::*;
37//!
38//! use enum_repr::EnumRepr;
39//!
40//! #[EnumRepr(type = "c_int")]
41//! #[derive(Debug, PartialEq)]
42//! pub enum IpProto {
43//! IP = IPPROTO_IP,
44//! IPv6 = IPPROTO_IPV6,
45//! // …
46//! }
47//!
48//! assert_eq!(IpProto::IP.repr(), IPPROTO_IP);
49//! assert_eq!(IpProto::from_repr(IPPROTO_IPV6), Some(IpProto::IPv6));
50//! assert!(IpProto::from_repr(12345).is_none());
51//! ```
52//!
53//! ```
54//! # extern crate enum_repr;
55//! # extern crate libc;
56//! #
57//! # use libc::*;
58//! #
59//! # use enum_repr::EnumRepr;
60//! #
61//! #[EnumRepr(type = "c_int")]
62//! # #[derive(Debug, Eq, Hash, PartialEq)]
63//! pub enum InetDomain {
64//! Inet = 2,
65//! // …
66//! }
67//!
68//! #[EnumRepr(type = "c_int")]
69//! # #[derive(Debug, Eq, Hash, PartialEq)]
70//! pub enum SocketType {
71//! Stream = 1,
72//! // …
73//! }
74//!
75//! // …
76//!
77//! unsafe {
78//! assert!(
79//! socket(InetDomain::Inet.repr(), SocketType::Stream.repr(), 0) != -1
80//! );
81//! }
82//! ```
83//!
84//! ```no_run
85//! # extern crate enum_repr;
86//! # extern crate libc;
87//! #
88//! # use libc::*;
89//! #
90//! # use enum_repr::EnumRepr;
91//! #
92//! // compatible with documentation and other attributes
93//!
94//! /// Represents a layer 3 network protocol.
95//! #[EnumRepr(type = "c_int")]
96//! #[derive(Debug, PartialEq)]
97//! pub enum IpProto {
98//! IP = IPPROTO_IP,
99//! IPv6 = IPPROTO_IPV6,
100//! // …
101//! }
102//! ```
103//!
104//! Discriminants can be implicit if `implicit = true`:
105//! ```
106//! # extern crate enum_repr;
107//! # extern crate libc;
108//! #
109//! # use libc::*;
110//! #
111//! # use enum_repr::EnumRepr;
112//! #
113//!
114//! #[EnumRepr(type = "c_int", implicit = true)]
115//! #[derive(Debug, PartialEq)]
116//! pub enum Test {
117//! A,
118//! B,
119//! C = 5,
120//! D,
121//! }
122//!
123//! assert_eq!(Test::B.repr(), 1);
124//! assert_eq!(Test::from_repr(6), Some(Test::D));
125//! assert!(Test::from_repr(2).is_none());
126//! ```
127//!
128//! Using implicit discriminants without setting the flag is an error:
129//! ```compile_fail
130//! # extern crate enum_repr;
131//! # extern crate libc;
132//! #
133//! # use libc::*;
134//! #
135//! # use enum_repr::EnumRepr;
136//! #
137//!
138//! #[EnumRepr(type = "c_int")]
139//! pub enum Test {
140//! A,
141//! B = 3
142//! }
143//! ```
144//!
145//! Take extra care to avoid collisions when using implicit discriminants:
146//! ```compile_fail
147//! # #![deny(overflowing_literals)]
148//! # extern crate enum_repr;
149//! #
150//! # use enum_repr::EnumRepr;
151//! #
152//! #[EnumRepr(type = "u8", implicit = true)]
153//! enum Test {
154//! A = 1,
155//! B,
156//! C,
157//! D = 3,
158//! }
159//! ```
160//!
161//! Out of bound discriminants fail to compile:
162//! ```compile_fail
163//! # #![deny(overflowing_literals)]
164//! # extern crate enum_repr;
165//! #
166//! # use enum_repr::EnumRepr;
167//! #
168//! #[EnumRepr(type = "u8")]
169//! enum Test {
170//! A = 256
171//! }
172//! ```
173//!
174//! Even if they are implicit:
175//! ```compile_fail
176//! # #![deny(overflowing_literals)]
177//! # extern crate enum_repr;
178//! #
179//! # use enum_repr::EnumRepr;
180//! #
181//! #[EnumRepr(type = "u8", implicit = true)]
182//! enum Test {
183//! A = 255,
184//! B
185//! }
186//! ```
187//!
188//! Discriminants of a wrong type fail to compile as well:
189//! ```compile_fail
190//! # #![deny(overflowing_literals)]
191//! # extern crate enum_repr;
192//! #
193//! # use enum_repr::EnumRepr;
194//! #
195//! const C: u16 = 256;
196//!
197//! #[EnumRepr(type = "u8")]
198//! enum Test {
199//! A = C
200//! }
201//! ```
202//!
203//! Using the actual enum discriminant representation:
204//! ```
205//! # extern crate enum_repr;
206//! #
207//! # use std::mem::size_of;
208//! #
209//! # use enum_repr::EnumRepr;
210//! #
211//! #[EnumRepr(type = "u8")]
212//! #[derive(Debug, PartialEq)]
213//! enum Test {
214//! A = 1
215//! }
216//!
217//! assert_eq!(size_of::<u8>(), size_of::<Test>());
218//! ```
219
220extern crate proc_macro;
221extern crate proc_macro2;
222#[macro_use] extern crate quote;
223extern crate syn;
224
225use std::iter;
226
227use proc_macro2::Span;
228use proc_macro::TokenStream;
229use quote::ToTokens;
230use syn::*;
231
232type Args = punctuated::Punctuated<NestedMeta, token::Comma>;
233
234struct ArgsWrapper {
235 args: Args,
236}
237
238impl syn::parse::Parse for ArgsWrapper {
239 fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
240 Args::parse_terminated(input).map(|args| Self { args })
241 }
242}
243
244/// The code generator
245#[allow(non_snake_case)]
246#[proc_macro_attribute]
247pub fn EnumRepr(
248 args: TokenStream,
249 input: TokenStream
250) -> TokenStream {
251 let input = syn::parse::<ItemEnum>(input)
252 .expect("#[EnumRepr] must only be used on enums");
253 validate(&input.variants);
254
255 let (repr_ty, implicit) = get_repr_type(args);
256 let compiler_repr_ty = match repr_ty.to_string().as_str() {
257 "i8" | "i16" | "i32" | "i64" | "i128"
258 | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" => repr_ty.clone(),
259 _ => Ident::new(&"isize", Span::call_site())
260 };
261
262 let new_enum = convert_enum(&input, &compiler_repr_ty, implicit);
263 let mut ret: TokenStream = new_enum.into_token_stream().into();
264
265 let gen = generate_code(&input, &repr_ty);
266 ret.extend(gen);
267 ret
268}
269
270fn generate_code(input: &ItemEnum, repr_ty: &Ident) -> TokenStream {
271 let ty = input.ident.clone();
272 let vis = input.vis.clone();
273 let (names, discrs) = extract_variants(input);
274 let vars_len = input.variants.len();
275
276 let (names2, discrs2, discrs3) =
277 (names.clone(), discrs.clone(), discrs.clone());
278 let repr_ty2 = repr_ty.clone();
279 let repr_ty3 = repr_ty.clone();
280 let ty_repeat = iter::repeat(ty.clone()).take(vars_len);
281 let ty_repeat2 = ty_repeat.clone();
282 let repr_ty_repeat = iter::repeat(repr_ty.clone()).take(vars_len);
283 let repr_ty_repeat2 = repr_ty_repeat.clone();
284 let repr_ty_repeat3 = repr_ty_repeat.clone();
285
286 let (impl_generics, ty_generics, where_clause) =
287 input.generics.split_for_impl();
288
289 let ret: TokenStream = quote! {
290 impl #impl_generics #ty #ty_generics #where_clause {
291 #vis fn repr(&self) -> #repr_ty2 {
292 match self {
293 #( #ty_repeat2::#names2 => #discrs2 as #repr_ty_repeat ),*
294 }
295 }
296
297 #vis fn from_repr(x: #repr_ty3) -> Option<#ty> {
298 match x {
299 #( x if x == #discrs as #repr_ty_repeat2 => Some(#ty_repeat::#names), )*
300 _ => None
301 }
302 }
303
304 #[doc(hidden)]
305 #[allow(dead_code)]
306 fn _enum_repr_typecheck() {
307 #( let _x: #repr_ty_repeat3 = #discrs3; )*
308 panic!("don't call me!")
309 }
310 }
311 }.into();
312 ret
313}
314
315fn extract_variants(input: &ItemEnum) -> (Vec<Ident>, Vec<Expr>) {
316 let mut prev_expr: Option<Expr> = None;
317 let (names, discrs): (Vec<_>, Vec<_>) = input.variants.iter()
318 .map(|x| {
319 let expr = match x.discriminant.as_ref() {
320 Some(discr) => discr.1.clone(),
321 None => match prev_expr {
322 Some(ref old_expr) => parse_quote!( 1 + #old_expr ),
323 None => parse_quote!( 0 ),
324 }
325 };
326 prev_expr = Some(expr.clone());
327 ( x.ident.clone(), expr )
328 }).unzip();
329 (names, discrs)
330}
331
332fn get_repr_type(args: TokenStream) -> (Ident, bool) {
333 let mut repr_type = None;
334 let mut implicit = false;
335 let args = syn::parse::<ArgsWrapper>(args)
336 .expect("specify repr type in format \"#[EnumRepr]\"").args;
337 args.iter().for_each(|arg| {
338 match arg {
339 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
340 path, lit, ..
341 })) => {
342 let param = path.get_ident().unwrap().to_string();
343 if param == "type" {
344 repr_type = match lit {
345 Lit::Str(repr_ty) => Some(Ident::new(
346 &repr_ty.value(),
347 Span::call_site()
348 )),
349 _ => panic!("\"type\" parameter must be a string")
350 }
351 } else if param == "implicit" {
352 implicit = match lit {
353 Lit::Bool(imp) => imp.value,
354 _ => panic!("\"implicit\" parameter must be bool")
355 }
356 } else {
357 eprintln!("{}", param);
358 panic!("#[EnumRepr] accepts arguments named \
359 \"type\" and \"implicit\"")
360 }
361 },
362 _ => panic!("specify repr type in format \
363 \"#[EnumRepr(type = \"TYPE\")]\"")
364 }
365 });
366 match repr_type {
367 Some(repr_ty) => (repr_ty, implicit),
368 None => panic!("\"type \" parameter is required")
369 }
370}
371
372fn validate(vars: &punctuated::Punctuated<Variant, token::Comma>) {
373 for i in vars {
374 match i.fields {
375 Fields::Named(_) | Fields::Unnamed(_) =>
376 panic!("the enum's fields must \
377 be in the \"ident = discriminant\" form"),
378 Fields::Unit => ()
379 }
380 }
381}
382
383fn convert_enum(
384 input: &ItemEnum,
385 compiler_repr_ty: &Ident,
386 implicit: bool
387) -> ItemEnum {
388 let mut variants = input.variants.clone();
389
390 let mut prev_expr: Option<Expr> = None;
391 variants.iter_mut().for_each(|ref mut var| {
392 let discr_opt = var.discriminant.clone();
393 let (eq, new_expr): (syn::token::Eq, Expr) = if let Some(discr) = discr_opt {
394 let old_expr = discr.1.into_token_stream();
395 (discr.0, parse_quote!( (#old_expr) as #compiler_repr_ty ))
396 } else {
397 if !implicit {
398 panic!("use implicit = true to enable implicit discriminants")
399 }
400
401 let expr = if let Some(old_expr) = prev_expr.clone() {
402 parse_quote!( (1 + (#old_expr)) as #compiler_repr_ty )
403 } else {
404 parse_quote!( 0 as #compiler_repr_ty )
405 };
406
407 (syn::token::Eq { spans: [Span::call_site(),] }, expr)
408 };
409 prev_expr = Some(new_expr.clone());
410 var.discriminant = Some((eq, new_expr));
411 });
412
413 let mut attrs = input.attrs.clone();
414 attrs.push(parse_quote!( #[repr(#compiler_repr_ty)] ));
415
416 let ret = input.clone();
417 ItemEnum {
418 variants,
419 attrs,
420 .. ret
421 }
422}