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, clippy::all, clippy::pedantic)]
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: 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)]
71pub enum Target {
72    /// An error type that is an enumeration.
73    Enum(enumerate::Enumeration),
74
75    /// An error type that is a structure.
76    Struct(structure::Structure),
77}
78
79impl Target {
80    /// Parse an error [`Target`] from a target [`DeriveInput`].
81    pub fn input(input: &DeriveInput) -> syn::Result<Self> {
82        struct ParamBucket<P> {
83            /// The span of the subject parameter receptor.
84            span_subject: Span,
85
86            /// The target parameter that was found.
87            first_found: Option<(Span, P)>,
88
89            /// The extraneous parameters that were found.
90            extra_found: Vec<(Span, P)>,
91        }
92
93        impl<P> ParamBucket<P> {
94            /// Construct a new, empty parameter bucket.
95            #[inline]
96            pub fn empty() -> Self {
97                Self {
98                    span_subject: Span::call_site(),
99                    first_found: None,
100                    extra_found: Vec::new(),
101                }
102            }
103
104            /// Construct a new, empty parameter bucket, but with an associated
105            /// span.
106            #[inline]
107            pub fn spanned<S>(span_subject: S) -> Self
108            where
109                S: Spanned,
110            {
111                Self {
112                    first_found: None,
113                    extra_found: Vec::new(),
114                    span_subject: span_subject.span(),
115                }
116            }
117
118            /// Push the param `P` into this bucket.
119            #[inline]
120            pub fn push<T>(&mut self, (spanned, param): (T, P))
121            where
122                T: Spanned,
123            {
124                let &mut Self {
125                    ref mut first_found,
126                    ref mut extra_found,
127                    ..
128                } = self;
129
130                let span = spanned.span();
131
132                let value = (span, param);
133
134                match first_found {
135                    Some(_) => extra_found.push(value),
136                    None => *first_found = Some(value),
137                }
138            }
139
140            /// Expect either one or none of the parameters to be found.
141            #[inline]
142            pub fn one_or_nothing(self) -> syn::Result<Option<P>> {
143                let Self {
144                    first_found, extra_found, ..
145                } = self;
146
147                match first_found {
148                    Some((span, param)) => {
149                        if extra_found.is_empty() {
150                            Ok(Some(param))
151                        } else {
152                            let mut error = syn::Error::new(span, "multiple parameters found");
153
154                            for (span, _) in extra_found {
155                                error.combine(syn::Error::new(span, "extraneous parameter"));
156                            }
157
158                            Err(error)
159                        }
160                    }
161                    None => Ok(None),
162                }
163            }
164
165            /// Expect exactly one of the parameters to be found.
166            #[inline]
167            pub fn only_one(self) -> syn::Result<P> {
168                let Self {
169                    span_subject,
170                    first_found,
171                    extra_found,
172                } = self;
173
174                match first_found {
175                    Some((span, param)) => {
176                        if extra_found.is_empty() {
177                            Ok(param)
178                        } else {
179                            let mut error = syn::Error::new(span, "multiple parameters found");
180
181                            for (span, _) in extra_found {
182                                error.combine(syn::Error::new(span, "extraneous parameter"));
183                            }
184
185                            Err(error)
186                        }
187                    }
188                    None => Err(syn::Error::new(
189                        span_subject,
190                        format_args!("{}: no parameters found, but one was expected", any::type_name::<P>()),
191                    )),
192                }
193            }
194        }
195
196        let DeriveInput {
197            attrs,
198            ident: name_ident,
199            generics,
200            data,
201            ..
202        } = input;
203
204        let mut error_list = ErrorList::empty();
205
206        match data {
207            Data::Struct(DataStruct { fields, .. }) => {
208                let (param_list, ..) = common::Param::classify(attrs)?;
209
210                let mut source_bucket = ParamBucket::<FieldRef>::empty();
211                let mut format_bucket = ParamBucket::<Format>::empty();
212                let mut inline_bucket = ParamBucket::<InlineOptions>::empty();
213                let mut root_bucket = ParamBucket::<ImportRoot>::empty();
214
215                let mut transparent_bucket = ParamBucket::<Transparent>::empty();
216                let mut from_bucket = ParamBucket::<()>::empty();
217
218                for Param { name, kind } in param_list {
219                    match kind {
220                        ParamKind::Source(source) => source_bucket.push((name, source)),
221                        ParamKind::Format(format) => format_bucket.push((name, format)),
222                        ParamKind::Inline(inline) => inline_bucket.push((name, inline)),
223                        ParamKind::Import(root) => root_bucket.push((name, root)),
224                        ParamKind::Transparent(transparent) => transparent_bucket.push((name, transparent)),
225                        ParamKind::From => from_bucket.push((name, ())),
226                    }
227                }
228
229                let source_field = source_bucket.one_or_nothing()?;
230                let inline_opts = inline_bucket.one_or_nothing()?;
231                let root_import = root_bucket.one_or_nothing()?;
232                let transparent = transparent_bucket.one_or_nothing()?;
233
234                let from = from_bucket.one_or_nothing()?;
235
236                let options = match (transparent, from) {
237                    (Some(transparent), None) => StructureOptions::Transparent(transparent),
238                    (None, Some(..)) => {
239                        let format_args = format_bucket.only_one()?;
240
241                        let (field_ref, field_type) = structure::FieldList::exactly_one(fields)?;
242
243                        StructureOptions::Forward {
244                            source_field,
245                            field_ref,
246                            field_type,
247                            format_args,
248                        }
249                    }
250                    (None, None) => {
251                        let format_args = format_bucket.only_one()?;
252
253                        StructureOptions::Standalone { source_field, format_args }
254                    }
255                    (Some(_), Some(_)) => {
256                        return Err(syn::Error::new_spanned(
257                            name_ident,
258                            "structure can't be both transparent and from-forwarded",
259                        ));
260                    }
261                };
262
263                let field_list = structure::FieldList::raw(fields)?;
264
265                let generics = generics.clone();
266
267                let name_ident = name_ident.clone();
268
269                Ok(Target::Struct(Structure {
270                    inline_opts,
271                    root_import,
272                    name_ident,
273                    generics,
274                    field_list,
275                    options,
276                }))
277            }
278            Data::Enum(DataEnum { variants, .. }) => {
279                let (param_list, ..) = common::Param::classify(attrs)?;
280
281                let mut inline_bucket = ParamBucket::<InlineOptions>::empty();
282                let mut root_bucket = ParamBucket::<ImportRoot>::empty();
283
284                for Param { name, kind } in param_list {
285                    match kind {
286                        ParamKind::Inline(inline) => inline_bucket.push((name, inline)),
287                        ParamKind::Import(root) => root_bucket.push((name, root)),
288                        _ => error_list.append(syn::Error::new_spanned(name, "extraneous parameter")),
289                    }
290                }
291
292                let inline_opts = inline_bucket.one_or_nothing()?;
293                let root_import = root_bucket.one_or_nothing()?;
294
295                let mut variant_list = Vec::with_capacity(variants.len());
296
297                for Variant { ident, attrs, fields, .. } in variants {
298                    let (param_list, ..) = common::Param::classify(attrs)?;
299
300                    let mut format_bucket = ParamBucket::<Format>::spanned(ident);
301                    let mut source_bucket = ParamBucket::<FieldRef>::empty();
302                    let mut transparent_bucket = ParamBucket::<Transparent>::empty();
303                    let mut from_bucket = ParamBucket::<()>::empty();
304
305                    for Param { name, kind } in param_list {
306                        match kind {
307                            ParamKind::Format(format) => format_bucket.push((name, format)),
308                            ParamKind::Source(source_field) => source_bucket.push((name, source_field)),
309                            ParamKind::Transparent(transparent) => transparent_bucket.push((name, transparent)),
310                            ParamKind::From => from_bucket.push((name, ())),
311                            _ => error_list.append(syn::Error::new_spanned(name, "extraneous parameter")),
312                        }
313                    }
314
315                    let transparent = transparent_bucket.one_or_nothing()?;
316                    let from = from_bucket.one_or_nothing()?;
317                    let source_field = source_bucket.one_or_nothing()?;
318
319                    let variant_name = ident.clone();
320
321                    let variant = match (transparent, from) {
322                        (Some(_), Some(_)) => {
323                            error_list.append(syn::Error::new_spanned(
324                                ident,
325                                "variant can't be both transparent and from-forwarded",
326                            ));
327
328                            continue;
329                        }
330                        (None, Some(..)) => {
331                            let format = format_bucket.only_one()?;
332
333                            let (field_ref, field_type) = structure::FieldList::exactly_one(fields)?;
334
335                            enumerate::Variant::Forward {
336                                format,
337                                variant_name,
338                                field_ref,
339                                field_type,
340                            }
341                        }
342                        (Some(transparent), None) => enumerate::Variant::Transparent {
343                            transparent,
344                            variant_name,
345                            field_list: structure::FieldList::raw(fields)?,
346                        },
347                        (None, None) => {
348                            let format = format_bucket.only_one()?;
349
350                            enumerate::Variant::Struct {
351                                format,
352                                source_field,
353                                variant_name,
354                                field_list: structure::FieldList::raw(fields)?,
355                            }
356                        }
357                    };
358
359                    variant_list.push(variant);
360                }
361
362                let generics = generics.clone();
363
364                let name_ident = name_ident.clone();
365
366                let enumeration = enumerate::Enumeration {
367                    inline_opts,
368                    root_import,
369                    name_ident,
370                    generics,
371                    variant_list,
372                };
373
374                error_list.compose(enumeration).map(Target::Enum)
375            }
376            Data::Union(..) => Err(syn::Error::new_spanned(name_ident, "unions can't be errors")),
377        }
378    }
379}