fack_codegen/
common.rs

1//! Shared error characteristics for the error type generation process.
2
3use alloc::{string::ToString, vec::Vec};
4
5use proc_macro2::{Span, TokenStream};
6use quote::ToTokens;
7
8use syn::{
9    Expr, Ident, LitInt, LitStr, Path, Token,
10    parse::{Parse, ParseStream},
11    punctuated::Punctuated,
12    token::Paren,
13};
14
15use super::ErrorList;
16
17/// A meta-parameter for an error type.
18///
19/// This is the conjuction of all meta-parameters that are used in the error
20/// type generation process.
21///
22/// This is used to parse the meta-parameters of an error type, to then be
23/// processed accordingly to the position in where they were found.
24#[derive(Clone, Debug, PartialEq, Eq, Hash)]
25pub struct Param {
26    /// The identifier of the meta-parameter.
27    ///
28    /// In most cases, this is the identifier which is used to identify which
29    /// [`ParamKind`] to parse.
30    pub name: Option<Ident>,
31
32    /// The parameter kind of the meta-parameter.
33    pub kind: ParamKind,
34}
35
36impl Param {
37    /// A new meta-parameter that has no specific name.
38    #[inline]
39    #[must_use]
40    pub const fn lone(kind: ParamKind) -> Self {
41        Self { name: None, kind }
42    }
43
44    /// A new meta-parameter that has a specific name.
45    #[inline]
46    #[must_use]
47    pub const fn identified(name: Ident, kind: ParamKind) -> Self {
48        Self { name: Some(name), kind }
49    }
50}
51
52impl Param {
53    /// Classify an iterator of borrowed [`syn::Attribute`]s into a list of
54    /// [`Param`]s where it is deemed possible.
55    pub fn classify<'a>(iter: impl IntoIterator<Item = &'a syn::Attribute>) -> syn::Result<(Vec<Self>, Vec<&'a syn::Attribute>)> {
56        let attr_iter = iter.into_iter();
57
58        let (attr_len, ..) = attr_iter.size_hint();
59
60        let mut param_list = Vec::with_capacity(attr_len);
61
62        let mut attr_list = Vec::new();
63
64        let mut error_list = ErrorList::empty();
65
66        for attr in attr_iter {
67            if let Some(ident) = attr.path().get_ident() {
68                if ident == "error" {
69                    match attr.parse_args_with(Param::parse) {
70                        Ok(param) => param_list.push(param),
71                        Err(error) => error_list.append(error),
72                    }
73                } else {
74                    attr_list.push(attr);
75                }
76            } else {
77                attr_list.push(attr);
78            }
79        }
80
81        error_list.compose((param_list, attr_list))
82    }
83}
84
85impl Parse for Param {
86    fn parse(input: ParseStream) -> syn::Result<Self> {
87        let lookahead = input.lookahead1();
88
89        if lookahead.peek(Ident) {
90            let ident: Ident = input.parse()?;
91
92            let param = if ident == "inline" {
93                let inline_opts: InlineOptions = if input.peek(Paren) {
94                    let inline_content;
95
96                    syn::parenthesized!(inline_content in input);
97
98                    inline_content.parse()?
99                } else {
100                    InlineOptions::default()
101                };
102
103                Param::identified(ident, ParamKind::Inline(inline_opts))
104            } else if ident == "import" {
105                let import_content;
106
107                syn::parenthesized!(import_content in input);
108
109                let import_root: ImportRoot = import_content.parse()?;
110
111                Param::identified(ident, ParamKind::Import(import_root))
112            } else if ident == "source" {
113                let source_content;
114
115                syn::parenthesized!(source_content in input);
116
117                let source_field: FieldRef = source_content.parse()?;
118
119                Param::identified(ident, ParamKind::Source(source_field))
120            } else if ident == "transparent" {
121                let trans_content;
122
123                syn::parenthesized!(trans_content in input);
124
125                let source_field: Transparent = trans_content.parse()?;
126
127                Param::identified(ident, ParamKind::Transparent(source_field))
128            } else if ident == "from" {
129                Param::identified(ident, ParamKind::From)
130            } else {
131                return Err(syn::Error::new_spanned(
132                    ident,
133                    "expected `inline`, `import`, `source`, `transparent`, or `from`",
134                ));
135            };
136
137            Ok(param)
138        } else if lookahead.peek(LitStr) {
139            Ok(Param::lone(ParamKind::Format(Format::parse(input)?)))
140        } else {
141            Err(lookahead.error())
142        }
143    }
144}
145
146impl Param {
147    /// Attempt to parse a meta-parameter from a [`syn::Meta`].
148    ///
149    /// Note that will reinterpret the supplied [`syn::Meta`] as a [`Param`]
150    /// using [`Param::parse`].
151    #[inline]
152    pub fn attribute(target_meta: &syn::Meta) -> syn::Result<Self> {
153        syn::parse2::<Self>(target_meta.to_token_stream()).map_err(|error| syn::Error::new_spanned(target_meta, error))
154    }
155}
156
157/// The kind of meta-parameter.
158#[derive(Clone, Debug, PartialEq, Eq, Hash)]
159pub enum ParamKind {
160    /// The inline behavior of the error type.
161    Inline(InlineOptions),
162
163    /// The import root of the error type.
164    Import(ImportRoot),
165
166    /// The format string of the error type.
167    Format(Format),
168
169    /// The source-forwarding behavior of the error type.
170    Source(FieldRef),
171
172    /// A transparent error type.
173    ///
174    /// This is useful for new-type wrappers that are transparent over some
175    /// other type.
176    Transparent(Transparent),
177
178    /// The from forwarding behavior of the error type.
179    ///
180    /// This is useful for new-type wrappers that are completely opaque to the
181    /// error type.
182    From,
183}
184
185/// The inline options for an error type.
186#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, Copy)]
187pub enum InlineOptions {
188    /// Neutral inline behavior.
189    ///
190    /// This will mark all implemented methods as `#[inline]`, which will hint
191    /// the compiler to inline them if it deems it beneficial.
192    ///
193    /// This is the default behavior.
194    #[default]
195    Neutral,
196
197    /// Never attempt to inline implemented methods for this error type.
198    Never,
199
200    /// Always attempt to inline implemented methods for this error type.
201    Always,
202}
203
204impl Parse for InlineOptions {
205    #[inline]
206    fn parse(input: ParseStream) -> syn::Result<Self> {
207        let ident: Ident = input.parse()?;
208
209        match ident.to_string().as_str() {
210            "neutral" => Ok(Self::Neutral),
211            "never" => Ok(Self::Never),
212            "always" => Ok(Self::Always),
213            _ => Err(syn::Error::new_spanned(ident, "expected `neutral`, `never` or `always`")),
214        }
215    }
216}
217
218/// The root of all imports for error-related types.
219#[derive(Clone, Debug, PartialEq, Eq, Hash)]
220pub struct ImportRoot(pub Path);
221
222impl Parse for ImportRoot {
223    #[inline]
224    fn parse(input: ParseStream) -> syn::Result<Self> {
225        let path: Path = input.parse()?;
226
227        Ok(Self(path))
228    }
229}
230
231/// The format string for an error's `Display` implementation.
232#[derive(Debug, Clone, PartialEq, Eq, Hash)]
233pub struct Format {
234    /// The format string of the error variant.
235    pub format: LitStr,
236
237    /// The format arguments of the error variant.
238    pub format_args: Punctuated<Expr, Token![,]>,
239}
240
241impl Parse for Format {
242    #[inline]
243    fn parse(input: ParseStream) -> syn::Result<Self> {
244        let format: LitStr = input.parse()?;
245
246        let format_args = if input.peek(Token![,]) {
247            let _ = input.parse::<Token![,]>()?;
248
249            Punctuated::<Expr, Token![,]>::parse_terminated(input)?
250        } else {
251            Punctuated::new()
252        };
253
254        Ok(Self { format, format_args })
255    }
256}
257
258/// A reference to a field in any structure.
259#[derive(Debug, Clone, PartialEq, Eq, Hash)]
260pub enum FieldRef {
261    /// A reference to a field by its distinctive name.
262    Named(Ident),
263
264    /// A reference to an unnamed field by its index.
265    Indexed(usize),
266}
267
268impl Parse for FieldRef {
269    #[inline]
270    fn parse(input: ParseStream) -> syn::Result<Self> {
271        let lookahead = input.lookahead1();
272
273        if lookahead.peek(Ident) {
274            let ident: Ident = input.parse()?;
275
276            Ok(Self::Named(ident))
277        } else if lookahead.peek(LitInt) {
278            let lit_int: LitInt = input.parse()?;
279
280            let index = lit_int
281                .base10_parse::<usize>()
282                .map_err(|_| syn::Error::new_spanned(lit_int, "expected valid field index"))?;
283
284            Ok(Self::Indexed(index))
285        } else {
286            Err(lookahead.error())
287        }
288    }
289}
290
291impl ToTokens for FieldRef {
292    fn to_tokens(&self, tokens: &mut TokenStream) {
293        match self {
294            FieldRef::Named(ident) => ident.to_tokens(tokens),
295            FieldRef::Indexed(index) => LitInt::new(index.to_string().as_str(), Span::call_site()).to_tokens(tokens),
296        }
297    }
298}
299
300/// An enumeration that determines whether a field is the source of the error or
301/// not.
302#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
303pub enum ForwardSource {
304    /// No forwarding.
305    ///
306    /// This is the default.
307    #[default]
308    No,
309
310    /// Yes, forward to the field.
311    Yes,
312}
313
314impl Parse for ForwardSource {
315    fn parse(input: ParseStream) -> syn::Result<Self> {
316        let ident: Ident = input.parse()?;
317
318        if ident == "forward" {
319            Ok(Self::Yes)
320        } else {
321            Err(syn::Error::new_spanned(
322                ident,
323                "expected just `source`, no internal further parameters",
324            ))
325        }
326    }
327}
328
329/// A structure that determines transparence over a field.
330#[derive(Clone, Debug, PartialEq, Eq, Hash)]
331pub struct Transparent(pub FieldRef);
332
333impl Parse for Transparent {
334    #[inline]
335    fn parse(input: ParseStream) -> syn::Result<Self> {
336        let field_ref: FieldRef = input.parse()?;
337
338        Ok(Self(field_ref))
339    }
340}