const_format_proc_macros/derive_debug/
attribute_parsing.rs
1use crate::{
2 datastructure::{DataStructure, Field, FieldMap},
3 utils::LinearResult,
4};
5
6use super::{syntax::ImplHeader, type_detection};
7
8use quote::ToTokens;
9
10use syn::{Attribute, Meta, MetaList, NestedMeta};
11
12use std::marker::PhantomData;
13
14pub(crate) struct ConstDebugConfig<'a> {
15 pub(crate) debug_print: bool,
16 pub(crate) crate_path: Option<syn::Path>,
17 pub(crate) impls: Vec<ImplHeader>,
18 pub(crate) field_map: FieldMap<FieldConfig<'a>>,
19 _marker: PhantomData<&'a ()>,
20}
21
22impl<'a> ConstDebugConfig<'a> {
23 fn new(roa: ConstDebugAttrs<'a>) -> Result<Self, crate::Error> {
24 let ConstDebugAttrs {
25 debug_print,
26 crate_path,
27 impls,
28 field_map,
29 errors: _,
30 _marker: PhantomData,
31 } = roa;
32
33 Ok(Self {
34 debug_print,
35 crate_path,
36 impls,
37 field_map,
38 _marker: PhantomData,
39 })
40 }
41}
42
43struct ConstDebugAttrs<'a> {
44 debug_print: bool,
45 crate_path: Option<syn::Path>,
46 impls: Vec<ImplHeader>,
47 field_map: FieldMap<FieldConfig<'a>>,
48 errors: LinearResult,
49 _marker: PhantomData<&'a ()>,
50}
51
52pub(crate) struct FieldConfig<'a> {
55 pub(crate) how_to_fmt: HowToFmt<'a>,
56}
57
58pub(crate) enum HowToFmt<'a> {
59 Regular,
61 Ignore,
63 Slice,
65 Option_,
67 Newtype(&'a syn::Ident),
70 With(syn::Path),
72 WithMacro(syn::Path),
75 WithWrapper(syn::Path),
78}
79
80#[derive(Copy, Clone)]
83enum ParseContext<'a> {
84 TypeAttr,
85 Field { field: &'a Field<'a> },
86}
87
88pub(crate) fn parse_attrs_for_derive<'a>(
89 ds: &'a DataStructure<'a>,
90) -> Result<ConstDebugConfig<'a>, crate::Error> {
91 let mut this = ConstDebugAttrs {
92 debug_print: false,
93 crate_path: None,
94 impls: Vec::new(),
95 field_map: FieldMap::with(ds, |f| FieldConfig {
96 how_to_fmt: type_detection::detect_type_formatting(f.ty),
97 }),
98 errors: LinearResult::ok(),
99 _marker: PhantomData,
100 };
101
102 let ty_ctx = ParseContext::TypeAttr;
103 parse_inner(&mut this, ds.attrs, ty_ctx)?;
104
105 for variant in &ds.variants {
106 for field in variant.fields.iter() {
107 parse_inner(&mut this, field.attrs, ParseContext::Field { field })?;
108 }
109 }
110
111 this.errors.take()?;
112
113 ConstDebugConfig::new(this)
114}
115
116fn parse_inner<'a, I>(
118 this: &mut ConstDebugAttrs<'a>,
119 attrs: I,
120 pctx: ParseContext<'a>,
121) -> Result<(), crate::Error>
122where
123 I: IntoIterator<Item = &'a Attribute>,
124{
125 for attr in attrs {
126 match attr.parse_meta() {
127 Ok(Meta::List(list)) => {
128 let x = parse_attr_list(this, pctx, list);
129 this.errors.combine_err(x);
130 }
131 Err(e) => {
132 this.errors.push_err(e);
133 }
134 _ => {}
135 }
136 }
137 Ok(())
138}
139
140fn parse_attr_list<'a>(
142 this: &mut ConstDebugAttrs<'a>,
143 pctx: ParseContext<'a>,
144 list: MetaList,
145) -> Result<(), crate::Error> {
146 if list.path.is_ident("cdeb") {
147 with_nested_meta("cdeb", list.nested, |attr| {
148 let x = parse_sabi_attr(this, pctx, attr);
149 this.errors.combine_err(x);
150 Ok(())
151 })?;
152 }
153
154 Ok(())
155}
156
157fn make_err(tokens: &dyn ToTokens) -> crate::Error {
158 spanned_err!(tokens, "unrecognized attribute")
159}
160
161fn parse_sabi_attr<'a>(
163 this: &mut ConstDebugAttrs<'a>,
164 pctx: ParseContext<'a>,
165 attr: Meta,
166) -> Result<(), crate::Error> {
167 match (pctx, attr) {
168 (ParseContext::Field { field, .. }, Meta::Path(path)) => {
169 let f_config = &mut this.field_map[field.index];
170
171 if path.is_ident("ignore") {
172 f_config.how_to_fmt = HowToFmt::Ignore;
173 } else {
174 return Err(make_err(&path));
175 }
176 }
177 (ParseContext::Field { field, .. }, Meta::NameValue(nv)) => {
178 let f_config = &mut this.field_map[field.index];
179
180 if nv.path.is_ident("with") {
181 f_config.how_to_fmt = HowToFmt::With(parse_lit(&nv.lit)?);
182 } else if nv.path.is_ident("with_macro") {
183 f_config.how_to_fmt = HowToFmt::WithMacro(parse_lit(&nv.lit)?);
184 } else if nv.path.is_ident("with_wrapper") {
185 f_config.how_to_fmt = HowToFmt::WithWrapper(parse_lit(&nv.lit)?);
186 } else {
187 return Err(make_err(&nv));
188 }
189 }
190 (ParseContext::Field { field, .. }, Meta::List(list)) => {
191 let f_config = &mut this.field_map[field.index];
192
193 if list.path.is_ident("is_a") {
194 match list.nested.len() {
195 0 => return Err(make_err(&list)),
196 1 => (),
197 _ => return_spanned_err!(
198 list,
199 "The `#[cdeb(is_a())` attribute must only specify one kind of type."
200 ),
201 }
202 with_nested_meta("is_a", list.nested, |attr| {
203 f_config.how_to_fmt = parse_the_is_a_attribute(attr, field)?;
204 Ok(())
205 })?;
206 } else {
207 return Err(make_err(&list));
208 }
209 }
210 (ParseContext::TypeAttr { .. }, Meta::Path(path)) => {
211 if path.is_ident("debug_print") {
212 this.debug_print = true;
213 } else {
214 return Err(make_err(&path));
215 }
216 }
217 (ParseContext::TypeAttr { .. }, Meta::NameValue(nv)) => {
218 if nv.path.is_ident("crate") {
219 this.crate_path = Some(parse_lit(&nv.lit)?);
220 } else {
221 return Err(make_err(&nv));
222 }
223 }
224 (ParseContext::TypeAttr { .. }, Meta::List(list)) => {
225 if list.path.is_ident("impls") {
226 for x in list.nested {
227 let lit = match x {
228 NestedMeta::Meta(attr) => return Err(make_err(&attr)),
229 NestedMeta::Lit(lit) => lit,
230 };
231 this.impls.push(parse_lit::<ImplHeader>(&lit)?);
232 }
233 } else {
234 return Err(make_err(&list));
235 }
236 }
237 #[allow(unreachable_patterns)]
238 (_, x) => return Err(make_err(&x)),
239 }
240 Ok(())
241}
242
243fn parse_the_is_a_attribute<'a>(
246 attr: syn::Meta,
247 f: &Field<'a>,
248) -> Result<HowToFmt<'a>, crate::Error> {
249 match attr {
250 Meta::Path(path) => {
251 if path.is_ident("array") || path.is_ident("slice") {
252 Ok(HowToFmt::Slice)
253 } else if path.is_ident("Option") || path.is_ident("option") {
254 Ok(HowToFmt::Option_)
255 } else if path.is_ident("newtype") {
256 let newtype = type_detection::parse_type_as_ident(f.ty)?;
257 Ok(HowToFmt::Newtype(newtype))
258 } else if path.is_ident("non_std") || path.is_ident("not_std") {
259 Ok(HowToFmt::Regular)
260 } else {
261 Err(make_err(&path))
262 }
263 }
264 _ => Err(make_err(&attr)),
265 }
266}
267
268fn parse_lit<T>(lit: &syn::Lit) -> Result<T, crate::Error>
271where
272 T: syn::parse::Parse,
273{
274 match lit {
275 syn::Lit::Str(x) => x.parse().map_err(crate::Error::from),
276 _ => Err(spanned_err!(
277 lit,
278 "Expected string literal containing identifier"
279 )),
280 }
281}
282
283#[allow(dead_code)]
284fn parse_expr(lit: syn::Lit) -> Result<syn::Expr, crate::Error> {
285 match lit {
286 syn::Lit::Str(x) => x.parse(),
287 syn::Lit::Int(x) => syn::parse_str(x.base10_digits()),
288 _ => return_spanned_err!(lit, "Expected string or integer literal"),
289 }
290 .map_err(crate::Error::from)
291}
292
293pub fn with_nested_meta<F>(
296 attr_name: &str,
297 iter: syn::punctuated::Punctuated<NestedMeta, syn::Token!(,)>,
298 mut f: F,
299) -> Result<(), crate::Error>
300where
301 F: FnMut(Meta) -> Result<(), crate::Error>,
302{
303 for repr in iter {
304 match repr {
305 NestedMeta::Meta(attr) => {
306 f(attr)?;
307 }
308 NestedMeta::Lit(lit) => {
309 return_spanned_err!(
310 lit,
311 "the #[{}(...)] attribute does not allow literals in the attribute list",
312 attr_name,
313 );
314 }
315 }
316 }
317 Ok(())
318}