educe/trait_handlers/into/models/
type_attribute.rs

1use std::collections::HashMap;
2
3use syn::{punctuated::Punctuated, Attribute, Meta, Token};
4
5use crate::{
6    common::{bound::Bound, r#type::TypeWithPunctuatedMeta, tools::HashType},
7    panic, Trait,
8};
9
10pub(crate) struct TypeAttribute {
11    pub(crate) types: HashMap<HashType, Bound>,
12}
13
14#[derive(Debug)]
15pub(crate) struct TypeAttributeBuilder {
16    pub(crate) enable_types: bool,
17}
18
19impl TypeAttributeBuilder {
20    pub(crate) fn build_from_into_meta(&self, meta: &[Meta]) -> syn::Result<TypeAttribute> {
21        debug_assert!(!meta.is_empty());
22
23        let mut types = HashMap::new();
24
25        for meta in meta {
26            debug_assert!(meta.path().is_ident("Into"));
27
28            let correct_usage_for_into_attribute = {
29                let mut usage = vec![];
30
31                if self.enable_types {
32                    usage.push(stringify!(#[educe(Into(type))]));
33                    usage.push(stringify!(#[educe(Into(type, bound = false))]));
34                    usage.push(stringify!(#[educe(Into(type, bound(where_predicates)))]));
35                }
36
37                usage
38            };
39
40            match meta {
41                Meta::Path(_) | Meta::NameValue(_) => {
42                    return Err(panic::attribute_incorrect_format(
43                        meta.path().get_ident().unwrap(),
44                        &correct_usage_for_into_attribute,
45                    ));
46                },
47                Meta::List(list) => {
48                    if !self.enable_types {
49                        return Err(panic::attribute_incorrect_format(
50                            meta.path().get_ident().unwrap(),
51                            &correct_usage_for_into_attribute,
52                        ));
53                    }
54
55                    let TypeWithPunctuatedMeta {
56                        ty,
57                        list: result,
58                    } = list.parse_args()?;
59
60                    let ty = super::super::common::to_hash_type(&ty);
61
62                    let mut bound = Bound::Auto;
63                    let mut bound_is_set = false;
64
65                    let mut handler = |meta: Meta| -> syn::Result<bool> {
66                        if let Some(ident) = meta.path().get_ident() {
67                            if ident == "bound" {
68                                let v = Bound::from_meta(&meta)?;
69
70                                if bound_is_set {
71                                    return Err(panic::parameter_reset(ident));
72                                }
73
74                                bound_is_set = true;
75
76                                bound = v;
77
78                                return Ok(true);
79                            }
80                        }
81
82                        Ok(false)
83                    };
84
85                    for p in result {
86                        if !handler(p)? {
87                            return Err(panic::attribute_incorrect_format(
88                                meta.path().get_ident().unwrap(),
89                                &correct_usage_for_into_attribute,
90                            ));
91                        }
92                    }
93
94                    if types.contains_key(&ty) {
95                        return Err(super::super::panic::reset_a_type(&ty));
96                    }
97
98                    types.insert(ty, bound);
99                },
100            }
101        }
102
103        Ok(TypeAttribute {
104            types,
105        })
106    }
107
108    pub(crate) fn build_from_attributes(
109        &self,
110        attributes: &[Attribute],
111        traits: &[Trait],
112    ) -> syn::Result<TypeAttribute> {
113        let mut output: Option<TypeAttribute> = None;
114
115        let mut v_meta = Vec::new();
116
117        for attribute in attributes.iter() {
118            let path = attribute.path();
119
120            if path.is_ident("educe") {
121                if let Meta::List(list) = &attribute.meta {
122                    let result =
123                        list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
124
125                    for meta in result {
126                        let path = meta.path();
127
128                        let t = match Trait::from_path(path) {
129                            Some(t) => t,
130                            None => return Err(panic::unsupported_trait(meta.path())),
131                        };
132
133                        if !traits.contains(&t) {
134                            return Err(panic::trait_not_used(path.get_ident().unwrap()));
135                        }
136
137                        if t == Trait::Into {
138                            v_meta.push(meta);
139                        }
140                    }
141                }
142            }
143        }
144
145        if !v_meta.is_empty() {
146            output = Some(self.build_from_into_meta(&v_meta)?);
147        }
148
149        Ok(output.unwrap_or(TypeAttribute {
150            types: HashMap::new()
151        }))
152    }
153}