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}