educe/trait_handlers/hash/models/
type_attribute.rs

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