educe/trait_handlers/partial_ord/models/
field_attribute.rs

1use proc_macro2::Span;
2use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Path, Token};
3
4use crate::{
5    common::{
6        ident_bool::{meta_2_bool_allow_path, meta_name_value_2_bool},
7        int::meta_2_isize,
8        path::meta_2_path,
9    },
10    panic,
11    supported_traits::Trait,
12};
13
14pub(crate) struct FieldAttribute {
15    pub(crate) ignore:    bool,
16    pub(crate) method:    Option<Path>,
17    pub(crate) rank:      isize,
18    pub(crate) rank_span: Option<Span>,
19}
20
21pub(crate) struct FieldAttributeBuilder {
22    pub(crate) enable_ignore: bool,
23    pub(crate) enable_method: bool,
24    pub(crate) enable_rank:   bool,
25    pub(crate) rank:          isize,
26}
27
28impl FieldAttributeBuilder {
29    pub(crate) fn build_from_partial_ord_meta(&self, meta: &Meta) -> syn::Result<FieldAttribute> {
30        debug_assert!(meta.path().is_ident("PartialOrd"));
31
32        let mut ignore = false;
33        let mut method = None;
34        let mut rank = self.rank;
35        let mut rank_span = None;
36
37        let correct_usage_for_partial_eq_attribute = {
38            let mut usage = vec![];
39
40            if self.enable_ignore {
41                usage.push(stringify!(#[educe(PartialOrd = false)]));
42                usage.push(stringify!(#[educe(PartialOrd(ignore))]));
43            }
44
45            if self.enable_method {
46                usage.push(stringify!(#[educe(PartialOrd(method(path_to_method)))]));
47            }
48
49            if self.enable_rank {
50                usage.push(stringify!(#[educe(PartialOrd(rank = integer))]));
51            }
52
53            usage
54        };
55
56        match meta {
57            Meta::Path(_) => {
58                return Err(panic::attribute_incorrect_format(
59                    meta.path().get_ident().unwrap(),
60                    &correct_usage_for_partial_eq_attribute,
61                ));
62            },
63            Meta::NameValue(name_value) => {
64                if self.enable_ignore {
65                    ignore = !meta_name_value_2_bool(name_value)?;
66                } else {
67                    return Err(panic::attribute_incorrect_format(
68                        meta.path().get_ident().unwrap(),
69                        &correct_usage_for_partial_eq_attribute,
70                    ));
71                }
72            },
73            Meta::List(list) => {
74                let result =
75                    list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
76
77                let mut ignore_is_set = false;
78                let mut method_is_set = false;
79                let mut rank_is_set = false;
80
81                let mut handler = |meta: Meta| -> syn::Result<bool> {
82                    if let Some(ident) = meta.path().get_ident() {
83                        match ident.to_string().as_str() {
84                            "ignore" => {
85                                if !self.enable_ignore {
86                                    return Ok(false);
87                                }
88
89                                let v = meta_2_bool_allow_path(&meta)?;
90
91                                if ignore_is_set {
92                                    return Err(panic::parameter_reset(ident));
93                                }
94
95                                ignore_is_set = true;
96
97                                ignore = v;
98
99                                return Ok(true);
100                            },
101                            "method" => {
102                                if !self.enable_method {
103                                    return Ok(false);
104                                }
105
106                                let v = meta_2_path(&meta)?;
107
108                                if method_is_set {
109                                    return Err(panic::parameter_reset(ident));
110                                }
111
112                                method_is_set = true;
113
114                                method = Some(v);
115
116                                return Ok(true);
117                            },
118                            "rank" => {
119                                if !self.enable_rank {
120                                    return Ok(false);
121                                }
122
123                                let v = meta_2_isize(&meta)?;
124
125                                if rank_is_set {
126                                    return Err(panic::parameter_reset(ident));
127                                }
128
129                                rank_is_set = true;
130
131                                rank = v;
132                                rank_span = Some(meta.span());
133
134                                return Ok(true);
135                            },
136                            _ => (),
137                        }
138                    }
139
140                    Ok(false)
141                };
142
143                for p in result {
144                    if !handler(p)? {
145                        return Err(panic::attribute_incorrect_format(
146                            meta.path().get_ident().unwrap(),
147                            &correct_usage_for_partial_eq_attribute,
148                        ));
149                    }
150                }
151            },
152        }
153
154        Ok(FieldAttribute {
155            ignore,
156            method,
157            rank,
158            rank_span,
159        })
160    }
161
162    pub(crate) fn build_from_attributes(
163        &self,
164        attributes: &[Attribute],
165        traits: &[Trait],
166    ) -> syn::Result<FieldAttribute> {
167        let mut output = None;
168
169        for attribute in attributes.iter() {
170            let path = attribute.path();
171
172            if path.is_ident("educe") {
173                if let Meta::List(list) = &attribute.meta {
174                    let result =
175                        list.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
176
177                    for meta in result {
178                        let path = meta.path();
179
180                        let t = match Trait::from_path(path) {
181                            Some(t) => t,
182                            None => return Err(panic::unsupported_trait(meta.path())),
183                        };
184
185                        if !traits.contains(&t) {
186                            return Err(panic::trait_not_used(path.get_ident().unwrap()));
187                        }
188
189                        if t == Trait::PartialOrd {
190                            if output.is_some() {
191                                return Err(panic::reuse_a_trait(path.get_ident().unwrap()));
192                            }
193
194                            output = Some(self.build_from_partial_ord_meta(&meta)?);
195                        }
196                    }
197                }
198            }
199        }
200
201        Ok(output.unwrap_or(FieldAttribute {
202            ignore:    false,
203            method:    None,
204            rank:      self.rank,
205            rank_span: None,
206        }))
207    }
208}