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
52////////////////////////////////////////////////////////////////////////////////
53
54pub(crate) struct FieldConfig<'a> {
55    pub(crate) how_to_fmt: HowToFmt<'a>,
56}
57
58pub(crate) enum HowToFmt<'a> {
59    /// coerce_to_fmt!(&field).const_debug_fmt(f)`
60    Regular,
61    /// Doesn't print the field.
62    Ignore,
63    /// A slice or an array
64    Slice,
65    /// A single field tuple struct, èg: `struct Foo(u32);;`
66    Option_,
67    /// A single field tuple struct, èg: `struct Foo(u32);;`
68    /// The path of the field is parsed from the type, erroring if it's not a path.
69    Newtype(&'a syn::Ident),
70    /// The function used to format the field.
71    With(syn::Path),
72    //// The macro used to format the field,
73    //// it's expected to be callable as `themacro!(var, formatter);`.
74    WithMacro(syn::Path),
75    /// The newtype used to format the field, taking the field by reference.
76    /// eg: `struct Foo<'a>(&'a u32);`.
77    WithWrapper(syn::Path),
78}
79
80////////////////////////////////////////////////////////////////////////////////
81
82#[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
116/// Parses an individual attribute
117fn 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
140/// Parses an individual attribute list (A `#[attribute( .. )] attribute`).
141fn 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
161/// Parses the contents of a `#[sabi( .. )]` attribute.
162fn 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
243///////////////////////////////////////////////////////////////////////////////
244
245fn 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
268///////////////////////////////////////////////////////////////////////////////
269
270fn 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
293///////////////////////////////////////////////////////////////////////////////
294
295pub 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}