use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_quote, Ident};
use crate::transform::{Transformed, ValueExpr, ValueExprMatch};
pub(crate) fn produce(transformed: Transformed) -> TokenStream {
let Transformed {
repr_type,
variants,
mut item,
} = transformed;
item.attrs.push(parse_quote!(#[repr(#repr_type)]));
struct TryFromPattern<'a> {
repr_type: &'a Ident,
value_expr: ValueExpr,
}
impl<'a> ToTokens for TryFromPattern<'a> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let TryFromPattern {
repr_type,
value_expr,
} = self;
let ValueExpr { base, offset } = value_expr;
let pat = match (base, offset) {
(None, offset) => quote!(val if val == #offset as #repr_type),
(Some(expr), 0) => quote!(val if val == #expr),
(Some(expr), offset) => {
quote!(val if val == #repr_type::wrapping_add(#expr, #offset as #repr_type))
}
};
pat.to_tokens(tokens);
}
}
struct TryFromMatchArm<'a, 'b> {
pattern: TryFromPattern<'a>,
enum_ident: &'b Ident,
variant: Ident,
}
impl<'a, 'b> ToTokens for TryFromMatchArm<'a, 'b> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let TryFromMatchArm {
pattern,
enum_ident,
variant,
} = self;
let arm = quote!(#pattern => Ok(#enum_ident :: #variant));
arm.to_tokens(tokens);
}
}
let enum_ident = &item.ident;
let conversions = variants
.into_iter()
.map(|ValueExprMatch(ident, value_expr)| {
let pattern = TryFromPattern {
repr_type: &repr_type,
value_expr,
};
let arm = TryFromMatchArm {
pattern,
enum_ident,
variant: ident,
};
quote!(#arm)
})
.collect::<Vec<_>>();
quote!(
#item
impl ::std::convert::From<#enum_ident> for #repr_type {
fn from(e: #enum_ident) -> Self {
e as Self
}
}
impl ::std::convert::From<&#enum_ident> for #repr_type {
fn from(e: &#enum_ident) -> Self {
unsafe { *::std::mem::transmute::<&#enum_ident, &#repr_type>(e) }
}
}
impl ::std::convert::TryFrom<#repr_type> for #enum_ident {
type Error = ();
fn try_from(value: #repr_type) -> Result<Self, Self::Error> {
match value {
#(#conversions,)*
_ => Err(())
}
}
}
impl ::std::convert::TryFrom<&#repr_type> for #enum_ident {
type Error = ();
fn try_from(value: &#repr_type) -> Result<Self, Self::Error> {
#enum_ident::try_from(*value)
}
}
)
}
#[cfg(test)]
mod tests {
use super::*;
use syn::{parse::Parse, parse_quote, ItemEnum, ItemImpl};
struct EnumDefAndImplBlocks {
#[allow(dead_code)]
enum_def: ItemEnum,
#[allow(dead_code)]
impl_blocks: [ItemImpl; 4],
}
impl Parse for EnumDefAndImplBlocks {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let enum_def: ItemEnum = input.parse()?;
Ok(Self {
enum_def,
impl_blocks: [
input.parse()?,
input.parse()?,
input.parse()?,
input.parse()?,
],
})
}
}
#[test]
fn output_is_enum_def_and_impl_blocks() {
let repr_type: Ident = parse_quote!(u16);
let variants = vec![
ValueExprMatch(
parse_quote!(Zeroth),
ValueExpr {
base: None,
offset: 0,
},
),
ValueExprMatch(
parse_quote!(First),
ValueExpr {
base: Some(parse_quote! {2 * 7}),
offset: 0,
},
),
ValueExprMatch(
parse_quote!(Second),
ValueExpr {
base: Some(parse_quote! {2 * 7}),
offset: 1,
},
),
ValueExprMatch(
parse_quote!(Third),
ValueExpr {
base: Some(parse_quote! {1}),
offset: 0,
},
),
];
let item: ItemEnum = parse_quote!(
enum Fieldless {
Zeroth,
First = 2 * 7,
Second,
Third = 1,
}
);
let transformed = Transformed {
repr_type,
variants,
item,
};
let code = produce(transformed);
let _ = syn::parse2::<EnumDefAndImplBlocks>(code).unwrap();
}
}