fack_codegen/
common.rs

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