derive_more_impl/
try_from.rs

1//! Implementation of a [`TryFrom`] derive macro.
2
3use proc_macro2::{Literal, TokenStream};
4use quote::{format_ident, quote, ToTokens};
5use syn::spanned::Spanned as _;
6
7use crate::utils::{
8    attr::{self, ParseMultiple as _},
9    Spanning,
10};
11
12/// Expands a [`TryFrom`] derive macro.
13pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
14    match &input.data {
15        syn::Data::Struct(data) => Err(syn::Error::new(
16            data.struct_token.span(),
17            "`TryFrom` cannot be derived for structs",
18        )),
19        syn::Data::Enum(data) => Ok(Expansion {
20            repr: attr::ReprInt::parse_attrs(&input.attrs, &format_ident!("repr"))?
21                .map(Spanning::into_inner)
22                .unwrap_or_default(),
23            attr: ItemAttribute::parse_attrs(&input.attrs, &format_ident!("try_from"))?
24                .map(|attr| {
25                    if matches!(attr.item, ItemAttribute::Types(_)) {
26                        Err(syn::Error::new(
27                            attr.span,
28                            "`#[try_from(repr(...))]` attribute is not supported yet",
29                        ))
30                    } else {
31                        Ok(attr.item)
32                    }
33                })
34                .transpose()?,
35            ident: input.ident.clone(),
36            generics: input.generics.clone(),
37            variants: data.variants.clone().into_iter().collect(),
38        }
39        .into_token_stream()),
40        syn::Data::Union(data) => Err(syn::Error::new(
41            data.union_token.span(),
42            "`TryFrom` cannot be derived for unions",
43        )),
44    }
45}
46
47/// Representation of a [`TryFrom`] derive macro struct item attribute.
48///
49/// ```rust,ignore
50/// #[try_from(repr)]
51/// #[try_from(repr(<types>))]
52/// ```
53type ItemAttribute = attr::ReprConversion;
54
55/// Expansion of a macro for generating [`TryFrom`] implementation of an enum.
56struct Expansion {
57    /// `#[repr(u/i*)]` of the enum.
58    repr: attr::ReprInt,
59
60    /// [`ItemAttribute`] of the enum.
61    attr: Option<ItemAttribute>,
62
63    /// [`syn::Ident`] of the enum.
64    ///
65    /// [`syn::Ident`]: struct@syn::Ident
66    ident: syn::Ident,
67
68    /// [`syn::Generics`] of the enum.
69    generics: syn::Generics,
70
71    /// [`syn::Variant`]s of the enum.
72    variants: Vec<syn::Variant>,
73}
74
75impl ToTokens for Expansion {
76    /// Expands [`TryFrom`] implementations for a struct.
77    fn to_tokens(&self, tokens: &mut TokenStream) {
78        if self.attr.is_none() {
79            return;
80        }
81
82        let ident = &self.ident;
83        let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
84
85        let repr_ty = &self.repr.ty();
86
87        let mut last_discriminant = quote! { 0 };
88        let mut inc = 0usize;
89        let (consts, (discriminants, variants)): (
90            Vec<syn::Ident>,
91            (Vec<TokenStream>, Vec<TokenStream>),
92        ) = self
93            .variants
94            .iter()
95            .filter_map(
96                |syn::Variant {
97                     ident,
98                     fields,
99                     discriminant,
100                     ..
101                 }| {
102                    if let Some(d) = discriminant {
103                        last_discriminant = d.1.to_token_stream();
104                        inc = 0;
105                    }
106                    let ret = {
107                        let inc = Literal::usize_unsuffixed(inc);
108                        fields.is_empty().then_some((
109                            format_ident!("__DISCRIMINANT_{ident}"),
110                            (
111                                quote! { #last_discriminant + #inc },
112                                quote! { #ident #fields },
113                            ),
114                        ))
115                    };
116                    inc += 1;
117                    ret
118                },
119            )
120            .unzip();
121
122        let error = quote! { derive_more::TryFromReprError<#repr_ty> };
123
124        quote! {
125            #[automatically_derived]
126            impl #impl_generics derive_more::core::convert::TryFrom<#repr_ty #ty_generics>
127             for #ident #where_clause {
128                type Error = #error;
129
130                #[allow(non_upper_case_globals)]
131                #[inline]
132                fn try_from(val: #repr_ty) -> derive_more::core::result::Result<Self, #error> {
133                    #( const #consts: #repr_ty = #discriminants; )*
134                    match val {
135                        #(#consts => derive_more::core::result::Result::Ok(#ident::#variants),)*
136                        _ => derive_more::core::result::Result::Err(
137                            derive_more::TryFromReprError::new(val)
138                        ),
139                    }
140                }
141            }
142        }.to_tokens(tokens);
143    }
144}