educe/trait_handlers/debug/models/
type_attribute.rs

1use proc_macro2::Ident;
2use syn::{punctuated::Punctuated, Attribute, Meta, Token};
3
4use crate::{
5    common::{
6        bound::Bound,
7        ident_bool::{meta_2_bool, meta_2_ident_and_bool, meta_name_value_2_ident, IdentOrBool},
8        unsafe_punctuated_meta::UnsafePunctuatedMeta,
9    },
10    panic, Trait,
11};
12
13#[derive(Debug, Clone)]
14pub(crate) enum TypeName {
15    Disable,
16    Default,
17    Custom(Ident),
18}
19
20impl TypeName {
21    #[inline]
22    pub(crate) fn to_ident_by_ident<'a, 'b: 'a>(&'a self, ident: &'b Ident) -> Option<&'a Ident> {
23        match self {
24            Self::Disable => None,
25            Self::Default => Some(ident),
26            Self::Custom(ident) => Some(ident),
27        }
28    }
29}
30
31pub(crate) struct TypeAttribute {
32    pub(crate) has_unsafe:  bool,
33    pub(crate) name:        TypeName,
34    pub(crate) named_field: bool,
35    pub(crate) bound:       Bound,
36}
37
38#[derive(Debug)]
39pub(crate) struct TypeAttributeBuilder {
40    pub(crate) enable_flag:        bool,
41    pub(crate) enable_unsafe:      bool,
42    pub(crate) enable_name:        bool,
43    pub(crate) enable_named_field: bool,
44    pub(crate) enable_bound:       bool,
45    pub(crate) name:               TypeName,
46    pub(crate) named_field:        bool,
47}
48
49impl TypeAttributeBuilder {
50    pub(crate) fn build_from_debug_meta(&self, meta: &Meta) -> syn::Result<TypeAttribute> {
51        debug_assert!(meta.path().is_ident("Debug"));
52
53        let mut has_unsafe = false;
54        let mut name = self.name.clone();
55        let mut named_field = self.named_field;
56        let mut bound = Bound::Auto;
57
58        let correct_usage_for_debug_attribute = {
59            let mut usage = vec![];
60
61            if self.enable_flag {
62                usage.push(stringify!(#[educe(Debug)]));
63            }
64
65            if self.enable_name {
66                if !self.enable_unsafe {
67                    usage.push(stringify!(#[educe(Debug = NewName)]));
68                }
69
70                usage.push(stringify!(#[educe(Debug(name(NewName)))]));
71
72                if let TypeName::Disable = &name {
73                    usage.push(stringify!(#[educe(Debug(name = true))]));
74                } else {
75                    usage.push(stringify!(#[educe(Debug(name = false))]));
76                }
77            }
78
79            if self.enable_named_field {
80                if !self.named_field {
81                    usage.push(stringify!(#[educe(Debug(named_field = true))]));
82                } else {
83                    usage.push(stringify!(#[educe(Debug(named_field = false))]));
84                }
85            }
86
87            if self.enable_bound {
88                usage.push(stringify!(#[educe(Debug(bound(where_predicates)))]));
89                usage.push(stringify!(#[educe(Debug(bound = false))]));
90            }
91
92            usage
93        };
94
95        match meta {
96            Meta::Path(_) => {
97                if !self.enable_flag {
98                    return Err(panic::attribute_incorrect_format(
99                        meta.path().get_ident().unwrap(),
100                        &correct_usage_for_debug_attribute,
101                    ));
102                }
103            },
104            Meta::NameValue(name_value) => {
105                if !self.enable_name {
106                    return Err(panic::attribute_incorrect_format(
107                        meta.path().get_ident().unwrap(),
108                        &correct_usage_for_debug_attribute,
109                    ));
110                }
111
112                name = TypeName::Custom(meta_name_value_2_ident(name_value)?);
113            },
114            Meta::List(list) => {
115                let result = if self.enable_unsafe {
116                    let result: UnsafePunctuatedMeta = list.parse_args()?;
117
118                    has_unsafe = result.has_unsafe;
119
120                    result.list
121                } else {
122                    list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?
123                };
124
125                let mut name_is_set = false;
126                let mut named_field_is_set = false;
127                let mut bound_is_set = false;
128
129                let mut handler = |meta: Meta| -> syn::Result<bool> {
130                    if let Some(ident) = meta.path().get_ident() {
131                        match ident.to_string().as_str() {
132                            "name" | "rename" => {
133                                if !self.enable_name {
134                                    return Ok(false);
135                                }
136
137                                let v = meta_2_ident_and_bool(&meta)?;
138
139                                if name_is_set {
140                                    return Err(panic::parameter_reset(ident));
141                                }
142
143                                name_is_set = true;
144
145                                name = match v {
146                                    IdentOrBool::Ident(ident) => TypeName::Custom(ident),
147                                    IdentOrBool::Bool(b) => {
148                                        if b {
149                                            TypeName::Default
150                                        } else {
151                                            TypeName::Disable
152                                        }
153                                    },
154                                };
155
156                                return Ok(true);
157                            },
158                            "named_field" => {
159                                if !self.enable_named_field {
160                                    return Ok(false);
161                                }
162
163                                let v = meta_2_bool(&meta)?;
164
165                                if named_field_is_set {
166                                    return Err(panic::parameter_reset(ident));
167                                }
168
169                                named_field_is_set = true;
170
171                                named_field = v;
172
173                                return Ok(true);
174                            },
175                            "bound" => {
176                                if !self.enable_bound {
177                                    return Ok(false);
178                                }
179
180                                let v = Bound::from_meta(&meta)?;
181
182                                if bound_is_set {
183                                    return Err(panic::parameter_reset(ident));
184                                }
185
186                                bound_is_set = true;
187
188                                bound = v;
189
190                                return Ok(true);
191                            },
192                            _ => (),
193                        }
194                    }
195
196                    Ok(false)
197                };
198
199                for p in result {
200                    if !handler(p)? {
201                        return Err(panic::attribute_incorrect_format(
202                            meta.path().get_ident().unwrap(),
203                            &correct_usage_for_debug_attribute,
204                        ));
205                    }
206                }
207            },
208        }
209
210        Ok(TypeAttribute {
211            has_unsafe,
212            name,
213            named_field,
214            bound,
215        })
216    }
217
218    pub(crate) fn build_from_attributes(
219        &self,
220        attributes: &[Attribute],
221        traits: &[Trait],
222    ) -> syn::Result<TypeAttribute> {
223        let mut output = None;
224
225        for attribute in attributes.iter() {
226            let path = attribute.path();
227
228            if path.is_ident("educe") {
229                if let Meta::List(list) = &attribute.meta {
230                    let result =
231                        list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
232
233                    for meta in result {
234                        let path = meta.path();
235
236                        let t = match Trait::from_path(path) {
237                            Some(t) => t,
238                            None => return Err(panic::unsupported_trait(meta.path())),
239                        };
240
241                        if !traits.contains(&t) {
242                            return Err(panic::trait_not_used(path.get_ident().unwrap()));
243                        }
244
245                        if t == Trait::Debug {
246                            if output.is_some() {
247                                return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
248                            }
249
250                            output = Some(self.build_from_debug_meta(&meta)?);
251                        }
252                    }
253                }
254            }
255        }
256
257        Ok(output.unwrap_or(TypeAttribute {
258            has_unsafe:  false,
259            name:        self.name.clone(),
260            named_field: self.named_field,
261            bound:       Bound::Auto,
262        }))
263    }
264}