amplify_derive/
from.rs

1// Rust language amplification derive library providing multiple generic trait
2// implementations, type wrappers, derive macros and other language enhancements
3//
4// Written in 2019-2020 by
5//     Dr. Maxim Orlovsky <orlovsky@pandoracore.com>
6//     Elichai Turkel <elichai.turkel@gmail.com>
7//
8// To the extent possible under law, the author(s) have dedicated all
9// copyright and related and neighboring rights to this software to
10// the public domain worldwide. This software is distributed without
11// any warranty.
12//
13// You should have received a copy of the MIT License
14// along with this software.
15// If not, see <https://opensource.org/licenses/MIT>.
16
17use proc_macro2::{Span, TokenStream as TokenStream2};
18use syn::punctuated::Punctuated;
19use syn::spanned::Spanned;
20use syn::{
21    Attribute, Data, DataEnum, DataStruct, DataUnion, DeriveInput, Error, Field, Fields,
22    FieldsNamed, FieldsUnnamed, Ident, Result, Type,
23};
24
25const NAME: &str = "from";
26const EXAMPLE: &str = r#"#[from(::std::fmt::Error)]"#;
27
28#[derive(Clone, PartialEq, Eq, Debug)]
29enum InstructionEntity {
30    Default,
31    DefaultEnumFields {
32        variant: Ident,
33        fields: Vec<Ident>,
34    },
35    Unit {
36        variant: Option<Ident>,
37    },
38    Named {
39        variant: Option<Ident>,
40        field: Ident,
41        other: Vec<Ident>,
42    },
43    Unnamed {
44        variant: Option<Ident>,
45        index: usize,
46        total: usize,
47    },
48}
49
50impl InstructionEntity {
51    pub fn with_fields(fields: &Fields, variant: Option<Ident>) -> Result<Self> {
52        let res = match (fields.len(), variant, fields.clone(), fields.iter().next().cloned()) {
53            (0, Some(v), ..) => InstructionEntity::Unit { variant: Some(v) },
54            (_, variant, Fields::Unit, ..) => InstructionEntity::Unit { variant },
55            (1, variant, Fields::Named(f), Some(Field { ident: Some(i), .. })) => {
56                InstructionEntity::Named {
57                    variant,
58                    field: i.clone(),
59                    other: f
60                        .named
61                        .iter()
62                        .filter_map(|f| f.ident.clone())
63                        .filter(|ident| ident != &i)
64                        .collect(),
65                }
66            }
67            (1, _, Fields::Named(_), ..) => {
68                unreachable!("If we have named field, it will match previous option")
69            }
70            (_, Some(variant), Fields::Named(f), ..) => InstructionEntity::DefaultEnumFields {
71                variant,
72                fields: f.named.iter().filter_map(|f| f.ident.clone()).collect(),
73            },
74            (len, variant, Fields::Unnamed(_), ..) => InstructionEntity::Unnamed {
75                variant,
76                index: 0,
77                total: len,
78            },
79            (_, None, ..) => InstructionEntity::Default,
80        };
81        Ok(res)
82    }
83
84    pub fn with_field(
85        index: usize,
86        total: usize,
87        field: &Field,
88        fields: &Fields,
89        variant: Option<Ident>,
90    ) -> Self {
91        if let Some(ref ident) = field.ident {
92            InstructionEntity::Named {
93                variant,
94                field: ident.clone(),
95                other: fields
96                    .iter()
97                    .filter_map(|f| f.ident.clone())
98                    .filter(|i| ident != i)
99                    .collect(),
100            }
101        } else {
102            InstructionEntity::Unnamed {
103                variant,
104                index,
105                total,
106            }
107        }
108    }
109
110    pub fn into_token_stream2(self) -> TokenStream2 {
111        match self {
112            InstructionEntity::Default => quote! {
113                Self::default()
114            },
115            InstructionEntity::Unit { variant } => {
116                let var = variant.map_or(quote! {}, |v| quote! {:: #v});
117                quote! { Self #var }
118            }
119            InstructionEntity::Named {
120                variant: None,
121                field,
122                ..
123            } => {
124                quote! {
125                    Self { #field: v.into(), ..Default::default() }
126                }
127            }
128            InstructionEntity::Named {
129                variant: Some(var),
130                field,
131                other,
132            } => {
133                quote! {
134                    Self :: #var { #field: v.into(), #( #other: Default::default(), )* }
135                }
136            }
137            InstructionEntity::Unnamed {
138                variant,
139                index,
140                total,
141            } => {
142                let var = variant.map_or(quote! {}, |v| quote! {:: #v});
143                let prefix = (0..index).fold(TokenStream2::new(), |mut stream, _| {
144                    stream.extend(quote! {Default::default(),});
145                    stream
146                });
147                let suffix = ((index + 1)..total).fold(TokenStream2::new(), |mut stream, _| {
148                    stream.extend(quote! {Default::default(),});
149                    stream
150                });
151                quote! {
152                    Self #var ( #prefix v.into(), #suffix )
153                }
154            }
155            InstructionEntity::DefaultEnumFields { variant, fields } => {
156                quote! {
157                    Self #variant { #( #fields: Default::default() )* }
158                }
159            }
160        }
161    }
162}
163
164#[derive(Clone)]
165struct InstructionEntry(pub Type, pub InstructionEntity);
166
167impl PartialEq for InstructionEntry {
168    // Ugly way, but with current `syn` version no other way is possible
169    fn eq(&self, other: &Self) -> bool {
170        let l = &self.0;
171        let r = &other.0;
172        let a = quote! { #l };
173        let b = quote! { #r };
174        format!("{}", a) == format!("{}", b)
175    }
176}
177
178impl InstructionEntry {
179    pub fn with_type(ty: &Type, entity: &InstructionEntity) -> Self {
180        Self(ty.clone(), entity.clone())
181    }
182
183    pub fn parse(
184        fields: &Fields,
185        attrs: &[Attribute],
186        entity: InstructionEntity,
187    ) -> Result<Vec<InstructionEntry>> {
188        let mut list = Vec::<InstructionEntry>::new();
189        for attr in attrs.iter().filter(|attr| attr.path.is_ident(NAME)) {
190            // #[from]
191            if attr.tokens.is_empty() {
192                match (fields.len(), fields.iter().next()) {
193                    (1, Some(field)) => list.push(InstructionEntry::with_type(&field.ty, &entity)),
194                    _ => {
195                        return Err(attr_err!(
196                            attr,
197                            "empty attribute is allowed only for entities with a single field; \
198                             for multi-field entities specify the attribute right ahead of the \
199                             target field"
200                        ));
201                    }
202                }
203            } else {
204                list.push(InstructionEntry::with_type(&attr.parse_args()?, &entity));
205            }
206        }
207        Ok(list)
208    }
209}
210
211#[derive(Default)]
212struct InstructionTable(Vec<InstructionEntry>);
213
214impl InstructionTable {
215    pub fn new() -> Self { Default::default() }
216
217    pub fn parse(
218        &mut self,
219        fields: &Fields,
220        attrs: &[Attribute],
221        variant: Option<Ident>,
222    ) -> Result<&Self> {
223        let entity = InstructionEntity::with_fields(fields, variant.clone())?;
224        self.extend(InstructionEntry::parse(fields, attrs, entity.clone())?)?;
225        for (index, field) in fields.iter().enumerate() {
226            let mut punctuated = Punctuated::new();
227            punctuated.push_value(field.clone());
228            self.extend(InstructionEntry::parse(
229                &field.ident.as_ref().map_or(
230                    Fields::Unnamed(FieldsUnnamed {
231                        paren_token: Default::default(),
232                        unnamed: punctuated.clone(),
233                    }),
234                    |_| {
235                        Fields::Named(FieldsNamed {
236                            brace_token: Default::default(),
237                            named: punctuated,
238                        })
239                    },
240                ),
241                &field.attrs,
242                InstructionEntity::with_field(index, fields.len(), field, fields, variant.clone()),
243            )?)?;
244        }
245        if variant.is_none() && fields.len() == 1 && self.0.is_empty() {
246            let field = fields
247                .into_iter()
248                .next()
249                .expect("we know we have at least one item");
250            self.push(InstructionEntry::with_type(&field.ty, &entity));
251        }
252        Ok(self)
253    }
254
255    fn push(&mut self, item: InstructionEntry) { self.0.push(item) }
256
257    fn extend<T>(&mut self, list: T) -> Result<usize>
258    where T: IntoIterator<Item = InstructionEntry> {
259        let mut count = 0;
260        for entry in list {
261            self.0.iter().find(|e| *e == &entry).map_or(Ok(()), |_| {
262                Err(Error::new(
263                    Span::call_site(),
264                    format!("Attribute `#[{}]`: repeated use of type `{}`", NAME, quote! {ty}),
265                ))
266            })?;
267            self.0.push(entry);
268            count += 1;
269        }
270        Ok(count)
271    }
272
273    pub fn into_token_stream2(self, input: &DeriveInput) -> TokenStream2 {
274        let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
275        let ident_name = &input.ident;
276
277        self.0.into_iter().fold(TokenStream2::new(), |mut stream, InstructionEntry(from, entity)| {
278            let convert = entity.into_token_stream2();
279            stream.extend(quote! {
280                #[automatically_derived]
281                impl #impl_generics ::core::convert::From<#from> for #ident_name #ty_generics #where_clause {
282                    fn from(v: #from) -> Self {
283                        #convert
284                    }
285                }
286            });
287            stream
288        })
289    }
290}
291
292pub(crate) fn inner(input: DeriveInput) -> Result<TokenStream2> {
293    match input.data {
294        Data::Struct(ref data) => inner_struct(&input, data),
295        Data::Enum(ref data) => inner_enum(&input, data),
296        Data::Union(ref data) => inner_union(&input, data),
297    }
298}
299
300fn inner_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream2> {
301    let mut instructions = InstructionTable::new();
302    instructions.parse(&data.fields, &input.attrs, None)?;
303    Ok(instructions.into_token_stream2(input))
304}
305
306fn inner_enum(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream2> {
307    // Do not let top-level `from` on enums
308    input
309        .attrs
310        .iter()
311        .find(|attr| attr.path.is_ident(NAME))
312        .map_or(Ok(()), |a| {
313            Err(attr_err!(
314                a,
315                "top-level attribute is not allowed, use it for specific fields or variants"
316            ))
317        })?;
318
319    let mut instructions = InstructionTable::new();
320    for v in &data.variants {
321        instructions.parse(&v.fields, &v.attrs, Some(v.ident.clone()))?;
322    }
323    Ok(instructions.into_token_stream2(input))
324}
325
326fn inner_union(input: &DeriveInput, data: &DataUnion) -> Result<TokenStream2> {
327    let mut instructions = InstructionTable::new();
328    instructions.parse(&Fields::Named(data.fields.clone()), &input.attrs, None)?;
329    Ok(instructions.into_token_stream2(input))
330}