fack_codegen/
lib.rs

1//! Code generation for the `fack` crate.
2//!
3//! This is not a `proc-macro` crate, therefore, this can be freely depended
4//! upon in both normal and procedural macro contexts.
5#![no_std]
6#![forbid(unsafe_code, missing_docs, rustdoc::all)]
7
8extern crate alloc;
9
10use core::any;
11
12use alloc::vec::Vec;
13
14use proc_macro2::Span;
15
16use syn::{Data, DataEnum, DataStruct, DeriveInput, Variant, spanned::Spanned};
17
18use common::{FieldRef, Format, ImportRoot, InlineOptions, Param, ParamKind, Transparent};
19
20use structure::{Structure, StructureOptions};
21
22pub mod enumerate;
23
24pub mod structure;
25
26pub mod common;
27
28pub mod expand;
29
30/// A list of [`syn::Error`]s that accumulates them into a single one.
31///
32/// This can be reduced to a single [`syn::Error`] in the end, but can still be
33/// devoid of errors and result in a success.
34#[derive(Debug, Clone)]
35struct ErrorList(Option<syn::Error>);
36
37impl ErrorList {
38    /// Construct a new, empty error list.
39    #[inline]
40    pub const fn empty() -> Self {
41        Self(None)
42    }
43
44    /// Append the target [`syn::Error`] to this list.
45    #[inline]
46    pub fn append(&mut self, error: syn::Error) {
47        let Self(target_container) = self;
48
49        match target_container.as_mut() {
50            Some(target_list) => target_list.combine(error),
51            None => *target_container = Some(error),
52        }
53    }
54
55    /// Compose this error list with some type `T`.
56    ///
57    /// Returns [`Err`] if this error list is not empty, [`Ok`] otherwise.
58    #[inline]
59    pub fn compose<T>(self, value: T) -> syn::Result<T> {
60        match self {
61            Self(Some(target_error)) => Err(target_error),
62            Self(None) => Ok(value),
63        }
64    }
65}
66
67/// An error-derivation target.
68///
69/// Is either an enumeration or a structure.
70#[derive(Clone, Debug, PartialEq, Eq, Hash)]
71#[allow(clippy::large_enum_variant)]
72pub enum Target {
73    /// An error type that is an enumeration.
74    Enum(enumerate::Enumeration),
75
76    /// An error type that is a structure.
77    Struct(structure::Structure),
78}
79
80impl Target {
81    /// Parse an error [`Target`] from a target [`DeriveInput`].
82    pub fn input(input: &DeriveInput) -> syn::Result<Self> {
83        struct ParamBucket<P> {
84            /// The span of the subject parameter receptor.
85            span_subject: Span,
86
87            /// The target parameter that was found.
88            first_found: Option<(Span, P)>,
89
90            /// The extraneous parameters that were found.
91            extra_found: Vec<(Span, P)>,
92        }
93
94        impl<P> ParamBucket<P> {
95            /// Construct a new, empty parameter bucket.
96            #[inline]
97            pub fn empty() -> Self {
98                Self {
99                    span_subject: Span::call_site(),
100                    first_found: None,
101                    extra_found: Vec::new(),
102                }
103            }
104
105            /// Construct a new, empty parameter bucket, but with an associated
106            /// span.
107            #[inline]
108            pub fn spanned<S>(span_subject: &S) -> Self
109            where
110                S: Spanned,
111            {
112                Self {
113                    first_found: None,
114                    extra_found: Vec::new(),
115                    span_subject: span_subject.span(),
116                }
117            }
118
119            /// Push the param `P` into this bucket.
120            #[inline]
121            pub fn push<T>(&mut self, (spanned, param): (T, P))
122            where
123                T: Spanned,
124            {
125                let &mut Self {
126                    ref mut first_found,
127                    ref mut extra_found,
128                    ..
129                } = self;
130
131                let span = spanned.span();
132
133                let value = (span, param);
134
135                match first_found {
136                    Some(_) => extra_found.push(value),
137                    None => *first_found = Some(value),
138                }
139            }
140
141            /// Expect either one or none of the parameters to be found.
142            #[inline]
143            pub fn one_or_nothing(self) -> syn::Result<Option<P>> {
144                let Self {
145                    first_found, extra_found, ..
146                } = self;
147
148                match first_found {
149                    Some((span, param)) => {
150                        if extra_found.is_empty() {
151                            Ok(Some(param))
152                        } else {
153                            let mut error = syn::Error::new(span, "multiple parameters found");
154
155                            for (span, _) in extra_found {
156                                error.combine(syn::Error::new(span, "extraneous parameter"));
157                            }
158
159                            Err(error)
160                        }
161                    }
162                    None => Ok(None),
163                }
164            }
165
166            /// Expect exactly one of the parameters to be found.
167            #[inline]
168            pub fn only_one(self) -> syn::Result<P> {
169                let Self {
170                    span_subject,
171                    first_found,
172                    extra_found,
173                } = self;
174
175                match first_found {
176                    Some((span, param)) => {
177                        if extra_found.is_empty() {
178                            Ok(param)
179                        } else {
180                            let mut error = syn::Error::new(span, "multiple parameters found");
181
182                            for (span, _) in extra_found {
183                                error.combine(syn::Error::new(span, "extraneous parameter"));
184                            }
185
186                            Err(error)
187                        }
188                    }
189                    None => Err(syn::Error::new(
190                        span_subject,
191                        format_args!("{}: no parameters found, but one was expected", any::type_name::<P>()),
192                    )),
193                }
194            }
195        }
196
197        let DeriveInput {
198            attrs,
199            ident: name_ident,
200            generics,
201            data,
202            ..
203        } = input;
204
205        let mut error_list = ErrorList::empty();
206
207        match data {
208            Data::Struct(DataStruct { fields, .. }) => {
209                let (param_list, ..) = common::Param::classify(attrs)?;
210
211                let mut source_bucket = ParamBucket::<FieldRef>::empty();
212                let mut format_bucket = ParamBucket::<Format>::empty();
213                let mut inline_bucket = ParamBucket::<InlineOptions>::empty();
214                let mut root_bucket = ParamBucket::<ImportRoot>::empty();
215
216                let mut transparent_bucket = ParamBucket::<Transparent>::empty();
217                let mut from_bucket = ParamBucket::<()>::empty();
218
219                for Param { name, kind } in param_list {
220                    match kind {
221                        ParamKind::Source(source) => source_bucket.push((name, source)),
222                        ParamKind::Format(format) => format_bucket.push((name, format)),
223                        ParamKind::Inline(inline) => inline_bucket.push((name, inline)),
224                        ParamKind::Import(root) => root_bucket.push((name, root)),
225                        ParamKind::Transparent(transparent) => transparent_bucket.push((name, transparent)),
226                        ParamKind::From => from_bucket.push((name, ())),
227                    }
228                }
229
230                let source_field = source_bucket.one_or_nothing()?;
231                let inline_opts = inline_bucket.one_or_nothing()?;
232                let root_import = root_bucket.one_or_nothing()?;
233                let transparent = transparent_bucket.one_or_nothing()?;
234
235                let from = from_bucket.one_or_nothing()?;
236
237                let options = match (transparent, from) {
238                    (Some(transparent), None) => StructureOptions::Transparent(transparent),
239                    (None, Some(..)) => {
240                        let format_args = format_bucket.only_one()?;
241
242                        let (field_ref, field_type) = structure::FieldList::exactly_one(fields)?;
243
244                        StructureOptions::Forward {
245                            source_field,
246                            field_ref,
247                            field_type,
248                            format_args,
249                        }
250                    }
251                    (None, None) => {
252                        let format_args = format_bucket.only_one()?;
253
254                        StructureOptions::Standalone { source_field, format_args }
255                    }
256                    (Some(_), Some(())) => {
257                        return Err(syn::Error::new_spanned(
258                            name_ident,
259                            "structure can't be both transparent and from-forwarded",
260                        ));
261                    }
262                };
263
264                let field_list = structure::FieldList::raw(fields)?;
265
266                let generics = generics.clone();
267
268                let name_ident = name_ident.clone();
269
270                Ok(Target::Struct(Structure {
271                    inline_opts,
272                    root_import,
273                    name_ident,
274                    generics,
275                    field_list,
276                    options,
277                }))
278            }
279            Data::Enum(DataEnum { variants, .. }) => {
280                let (param_list, ..) = common::Param::classify(attrs)?;
281
282                let mut inline_bucket = ParamBucket::<InlineOptions>::empty();
283                let mut root_bucket = ParamBucket::<ImportRoot>::empty();
284
285                for Param { name, kind } in param_list {
286                    match kind {
287                        ParamKind::Inline(inline) => inline_bucket.push((name, inline)),
288                        ParamKind::Import(root) => root_bucket.push((name, root)),
289                        _ => error_list.append(syn::Error::new_spanned(name, "extraneous parameter")),
290                    }
291                }
292
293                let inline_opts = inline_bucket.one_or_nothing()?;
294                let root_import = root_bucket.one_or_nothing()?;
295
296                let mut variant_list = Vec::with_capacity(variants.len());
297
298                for Variant { ident, attrs, fields, .. } in variants {
299                    let (param_list, ..) = common::Param::classify(attrs)?;
300
301                    let mut format_bucket = ParamBucket::<Format>::spanned(ident);
302                    let mut source_bucket = ParamBucket::<FieldRef>::empty();
303                    let mut transparent_bucket = ParamBucket::<Transparent>::empty();
304                    let mut from_bucket = ParamBucket::<()>::empty();
305
306                    for Param { name, kind } in param_list {
307                        match kind {
308                            ParamKind::Format(format) => format_bucket.push((name, format)),
309                            ParamKind::Source(source_field) => source_bucket.push((name, source_field)),
310                            ParamKind::Transparent(transparent) => transparent_bucket.push((name, transparent)),
311                            ParamKind::From => from_bucket.push((name, ())),
312                            _ => error_list.append(syn::Error::new_spanned(name, "extraneous parameter")),
313                        }
314                    }
315
316                    let transparent = transparent_bucket.one_or_nothing()?;
317                    let from = from_bucket.one_or_nothing()?;
318                    let source_field = source_bucket.one_or_nothing()?;
319
320                    let variant_name = ident.clone();
321
322                    let variant = match (transparent, from) {
323                        (Some(_), Some(())) => {
324                            error_list.append(syn::Error::new_spanned(
325                                ident,
326                                "variant can't be both transparent and from-forwarded",
327                            ));
328
329                            continue;
330                        }
331                        (None, Some(..)) => {
332                            let format = format_bucket.only_one()?;
333
334                            let (field_ref, field_type) = structure::FieldList::exactly_one(fields)?;
335
336                            enumerate::Variant::Forward {
337                                format,
338                                variant_name,
339                                field_ref,
340                                field_type,
341                            }
342                        }
343                        (Some(transparent), None) => enumerate::Variant::Transparent {
344                            transparent,
345                            variant_name,
346                            field_list: structure::FieldList::raw(fields)?,
347                        },
348                        (None, None) => {
349                            let format = format_bucket.only_one()?;
350
351                            enumerate::Variant::Struct {
352                                format,
353                                source_field,
354                                variant_name,
355                                field_list: structure::FieldList::raw(fields)?,
356                            }
357                        }
358                    };
359
360                    variant_list.push(variant);
361                }
362
363                let generics = generics.clone();
364
365                let name_ident = name_ident.clone();
366
367                let enumeration = enumerate::Enumeration {
368                    inline_opts,
369                    root_import,
370                    name_ident,
371                    generics,
372                    variant_list,
373                };
374
375                error_list.compose(enumeration).map(Target::Enum)
376            }
377            Data::Union(..) => Err(syn::Error::new_spanned(name_ident, "unions can't be errors")),
378        }
379    }
380}