Skip to main content

resext_macro/
lib.rs

1//! Procedural macro for resext.
2//!
3//! This crate provides the `#[resext]` proc-macro attribute for ergonomic error handling.
4//! It is not meant to be used directly - use the `resext` crate instead.
5//!
6//! # Overview
7//!
8//! The proc macro generates all necessary error handling code from a simple attribute:
9//!
10//! ```rust
11//! # use resext_macro::resext;
12//! #[resext]
13//! enum MyError {
14//!     Io(std::io::Error),
15//!     Utf8(core::str::Utf8Error),
16//! }
17//! ```
18//!
19//! This expands to approximately 300 lines of boilerplate including:
20//!
21//! - `Display`, `Debug` and `Error` trait implementations
22//! - Wrapper struct with inline, zero-alloc context storage
23//! - Trait with context method
24//! - `From<E>` implementations for automatic conversion
25//! - Type alias for `Result<T, ResErr>`
26//!
27//! # Attribute Options
28//!
29//! See the [resext crate documentation](https://docs.rs/resext) for detailed
30//! information on all available options.
31
32use proc_macro::{
33    Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree,
34};
35use quote::quote;
36
37/// Generate error handling boilerplate for an enum.
38///
39/// # Usage
40///
41/// Basic usage with default settings:
42///
43/// ```rust
44/// # use resext_macro::resext;
45/// #[resext(alias = Resext)]
46/// enum MyError {
47///     Io(std::io::Error),
48///     Parse(std::num::ParseIntError),
49/// }
50/// ```
51///
52/// With custom formatting:
53///
54/// ```rust
55/// # use resext_macro::resext;
56/// #[resext(
57///     prefix = "ERROR: ",
58///     delimiter = " -> ",
59///     include_variant = true,
60///     alias = MyRes
61/// )]
62/// enum MyError {
63///     Fmt(std::fmt::Error),
64///     EnvVar(std::env::VarError),
65/// }
66/// ```
67///
68/// ---
69///
70/// # Options
71///
72/// - `prefix` - Prepend to all error messages
73/// - `suffix` - Append to all error messages
74/// - `msg_prefix` - Prepend to each context message
75/// - `msg_suffix` - Append to each context message
76/// - `delimiter` - Separator between context messages (default: "\n - ")
77/// - `source_prefix` - Prepend to underlying error (default: "Error: ")
78/// - `include_variant` - Show variant name in output (default: false)
79/// - `alias` - Custom type alias name which is used for getting the names for other items generated by the proc-macro (default: `Res`)
80/// - `buf_size` - Size for the context message byte buffer (default: 64)
81/// - `alloc` Enable heap-spilling if context exceeds `buf_size`
82///
83/// ---
84///
85/// # Examples
86///
87/// ```rust
88/// # macro_rules! ctx {
89/// #     ($fmt:expr, $($args:tt)*) => {
90/// #         {
91/// #             struct Writer<W: core::fmt::Write + ?Sized>(W);
92/// #
93/// #             impl<W: core::fmt::Write + ?Sized> core::fmt::Write for Writer<W> {
94/// #                 fn write_str(&mut self, s: &str) -> core::fmt::Result {
95/// #                     self.0.write_str(s)
96/// #                 }
97/// #             }
98/// #
99/// #             |w, d, mp, ms| {
100/// #                 use core::fmt::Write;
101/// #
102/// #                 let mut w = Writer(w);
103/// #
104/// #                 let _ = w.write_str(d);
105/// #                 let _ = w.write_str(mp);
106/// #                 let _ = write!(w, $fmt, $($args)*);
107/// #                 let _ = w.write_str(ms);
108/// #
109/// #                 w.0
110/// #             }
111/// #         }
112/// #     };
113/// # }
114/// # use resext_macro::resext;
115/// #[resext(alias = AppResult)]
116/// enum AppError {
117///     Io(std::io::Error),
118///     EnvVar(std::env::VarError),
119/// }
120///
121/// fn example(path: &str) -> AppResult<()> {
122///     let content = std::fs::read(path)
123///         .context(ctx!("Failed to read file: {}", path))?;
124///
125///     let var = std::env::var("ENV_VAR")
126///         .context("Failed to get environment variable")?;
127///
128///     Ok(())
129/// }
130/// ```
131#[proc_macro_attribute]
132pub fn resext(attr: TokenStream, item: TokenStream) -> TokenStream {
133    let mut errs = vec![];
134    let input = match EnumInput::from_tokens(item, &mut errs) {
135        Some(i) => i,
136        None => return errs.into_iter().collect(),
137    };
138
139    let original: &proc_macro2::TokenStream = &input.original.into();
140
141    let args = parse_args(attr, &mut errs).unwrap_or_default();
142
143    let manual_name = &input.name;
144    let enum_name = proc_macro2::Ident::new(
145        manual_name.to_string().as_str(),
146        manual_name.span().into(),
147    );
148    let vis: &proc_macro2::TokenStream = &input.vis.into();
149
150    let alias = args.alias.unwrap_or_else(|| String::from("Res"));
151    let struct_name = quote::format_ident!("{}Err", alias);
152    let buf_name = quote::format_ident!("{}Buf", alias);
153    let trait_name = quote::format_ident!("{}Ext", alias);
154    let alias = quote::format_ident!("{}", alias);
155
156    let alloc = args.alloc;
157
158    let variants = &input.variants;
159
160    let include_variant = args.include_variant;
161    let display_match_arms = variants.iter().map(|variant| {
162        let variant_name = proc_macro2::Ident::new(variant.name.to_string().as_str(), variant.name.span().into());
163
164        match &variant.kind {
165            VariantKind::Unnamed(_) => {
166                if include_variant {
167                    quote! {
168                        #enum_name::#variant_name(var) => write!(f, "{}: {}", stringify!(#variant_name), var),
169                    }
170                } else {
171                    quote! {
172                        #enum_name::#variant_name(var) => write!(f, "{}", var),
173                    }
174                }
175            }
176
177            VariantKind::Named(variant_field, _) => {
178                let variant_field = proc_macro2::Ident::new(variant_field.to_string().as_str(), variant_field.span().into());
179
180                if include_variant {
181                    quote! {
182                        #enum_name::#variant_name { #variant_field } => write!(f, "{}: {}: {}", stringify!(#variant_name), stringify!(#variant_field), #variant_field),
183                    }
184                } else {
185                    quote! {
186                        #enum_name::#variant_name { #variant_field } => write!(f, "{}", #variant_field),
187                    }
188                }
189            }
190
191            VariantKind::Unit => {
192                quote! {
193                    #enum_name::#variant_name => write!(f, "{}", stringify!(#variant_name)),
194                }
195            }
196        }
197    });
198
199    let from_impls = variants.iter().filter_map(|variant| {
200        let variant_name = proc_macro2::Ident::new(variant.name.to_string().as_str(), variant.name.span().into());
201
202        match &variant.kind {
203            VariantKind::Unnamed(field_type) => {
204                let field_type: proc_macro2::TokenStream = field_type.clone().into();
205                Some(quote! {
206                    impl From<#field_type> for #enum_name {
207                        fn from(value: #field_type) -> Self {
208                            Self::#variant_name(value)
209                        }
210                    }
211
212                    impl From<#field_type> for #struct_name {
213                        fn from(value: #field_type) -> Self {
214                            Self { msg: #buf_name::new(), source: #enum_name::#variant_name(value) }
215                        }
216                    }
217                })
218            }
219
220            VariantKind::Named(field_name, field_type) => {
221                let field_type: proc_macro2::TokenStream = field_type.clone().into();
222                let field_name = proc_macro2::Ident::new(field_name.to_string().as_str(), field_name.span().into());
223                Some(quote! {
224                    impl From<#field_type> for #enum_name {
225                        fn from(value: #field_type) -> Self {
226                            Self::#variant_name { #field_name: value }
227                        }
228                    }
229
230                    impl From<#field_type> for #struct_name {
231                        fn from(value: #field_type) -> Self {
232                            Self { msg: #buf_name::new(), source: #enum_name::#variant_name { #field_name: value } }
233                        }
234                    }
235                })
236            }
237
238            _ => None,
239        }
240    });
241
242    let prefix = args.prefix.unwrap_or_default();
243    let suffix = args.suffix.unwrap_or_default();
244    let msg_prefix = args.msg_prefix.unwrap_or_default();
245    let msg_suffix = args.msg_suffix.unwrap_or_default();
246    let delimiter = args.delimiter.unwrap_or_else(|| String::from("\n - "));
247    let source_prefix =
248        args.source_prefix.unwrap_or_else(|| String::from("Error: "));
249    let buf_size = args.buf_size.unwrap_or(64);
250
251    let gen_buf = {
252        if !alloc {
253            quote! {
254                struct #buf_name {
255                    curr_pos: u16,
256                    buf: [u8; #buf_size],
257                    truncate: bool,
258                }
259
260                impl #buf_name {
261                    fn new() -> Self {
262                        Self { buf: [0; #buf_size], curr_pos: 0, truncate: false }
263                    }
264
265                    fn get_slice(&self) -> &[u8] {
266                        &self.buf[..self.curr_pos as usize]
267                    }
268
269                    fn is_empty(&self) -> bool {
270                        self.curr_pos == 0
271                    }
272
273                    fn truncate(&self) -> bool {
274                        self.truncate
275                    }
276                }
277
278                impl core::fmt::Write for #buf_name {
279                    fn write_str(&mut self, s: &str) -> core::fmt::Result {
280                        let bytes = s.as_bytes();
281                        let pos = self.curr_pos as usize;
282                        let cap = #buf_size - pos;
283
284                        let limit = if cap < bytes.len() {
285                            self.truncate = true;
286                            cap
287                        } else {
288                            bytes.len()
289                        };
290
291                        let to_copy = match bytes[..limit]
292                            .iter()
293                            .rposition(|&b| (b & 0xC0) != 0x80)
294                        {
295                            Some(start_of_last_char) => {
296                                let last_char_byte = bytes[start_of_last_char];
297                                let width = match last_char_byte {
298                                    0..=127 => 1,
299                                    192..=223 => 2,
300                                    224..=239 => 3,
301                                    240..=247 => 4,
302                                    _ => 1,
303                                };
304                                if start_of_last_char + width <= limit {
305                                    start_of_last_char + width
306                                } else {
307                                    start_of_last_char
308                                }
309                            }
310                            None => 0,
311                        };
312
313                        self.buf[pos..pos + to_copy].copy_from_slice(&bytes[..to_copy]);
314                        self.curr_pos += to_copy as u16;
315
316                        Ok(())
317                    }
318                }
319            }
320        } else {
321            quote! {
322                mod __private_alloc {
323                    extern crate alloc;
324                    pub(crate) use alloc::vec::Vec;
325                }
326
327                enum #buf_name {
328                    Stack { buf: [u8; #buf_size], curr_pos: u16 },
329                    Heap(__private_alloc::Vec<u8>),
330                }
331
332                impl #buf_name {
333                    fn new() -> Self {
334                        Self::Stack { buf: [0; #buf_size], curr_pos: 0 }
335                    }
336
337                    fn get_slice(&self) -> &[u8] {
338                        match self {
339                            Self::Stack { buf, curr_pos } => &buf[..*curr_pos as usize],
340                            Self::Heap(buf) => buf,
341                        }
342                    }
343
344                    fn truncate(&self) -> bool {
345                        false
346                    }
347
348                    fn is_empty(&self) -> bool {
349                        match self {
350                            Self::Heap(buf) => buf.is_empty(),
351                            Self::Stack { buf: _, curr_pos } => *curr_pos == 0,
352                        }
353                    }
354                }
355
356                impl core::fmt::Write for #buf_name {
357                    fn write_str(&mut self, s: &str) -> core::fmt::Result {
358                        match self {
359                            Self::Heap(buf) => buf.extend_from_slice(s.as_bytes()),
360                            Self::Stack { buf, curr_pos } => {
361                                let bytes = s.as_bytes();
362                                let pos = *curr_pos as usize;
363                                let cap = #buf_size - pos;
364
365                                if bytes.len() > cap {
366                                    {
367                                        extern crate alloc;
368                                        let mut vec = alloc::vec::Vec::new();
369                                        vec.reserve_exact(pos + bytes.len());
370
371                                        vec.extend_from_slice(&buf[..pos]);
372                                        vec.extend_from_slice(bytes);
373
374                                        *self = #buf_name::Heap(vec);
375                                    }
376                                } else {
377                                    buf[pos..pos + bytes.len()].copy_from_slice(bytes);
378                                    *curr_pos += bytes.len() as u16;
379                                }
380                            }
381                        }
382
383                        Ok(())
384                    }
385                }
386            }
387        }
388    };
389
390    let expanded = quote! {
391        #[derive(Debug)]
392        #original
393
394        impl core::fmt::Display for #enum_name {
395            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
396                match self {
397                    #(#display_match_arms)*
398                }
399            }
400        }
401
402        /// Wrapper type that holds your error with optional context messages.
403        ///
404        /// This type is automatically created when you use `.context()` or
405        /// `.context()` on a Result.
406        #[doc(hidden)]
407        #vis struct #struct_name {
408            msg: #buf_name,
409            #vis source: #enum_name
410        }
411        impl core::error::Error for #struct_name {}
412
413
414        impl core::fmt::Write for #struct_name {
415            fn write_str(&mut self, s: &str) -> core::fmt::Result {
416                if s.is_empty() {
417                    Ok(())
418                } else {
419                    self.msg.write_str(s)
420                }
421            }
422        }
423
424        impl core::fmt::Display for #struct_name {
425            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
426                if self.msg.is_empty() {
427                    write!(f, "{}{}{}", #source_prefix, &self.source, #suffix)
428                } else {
429                    write!(
430                        f,
431                        "{}{}{}\n{}{}{}",
432                        #prefix,
433                        unsafe { core::str::from_utf8_unchecked(&self.msg.get_slice()) },
434                        if self.msg.truncate() { "..." } else { "" },
435                        #source_prefix,
436                        self.source,
437                        #suffix,
438                    )
439                }
440            }
441        }
442
443        impl core::fmt::Debug for #struct_name {
444            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
445                if self.msg.is_empty() {
446                    write!(f, "{}{:?}{}", #source_prefix, &self.source, #suffix)
447                } else {
448                    write!(
449                        f,
450                        "{}{}{}\n{}{:?}{}",
451                        #prefix,
452                        unsafe { core::str::from_utf8_unchecked(&self.msg.get_slice()) },
453                        if self.msg.truncate() { "..." } else { "" },
454                        #source_prefix,
455                        self.source,
456                        #suffix,
457                    )
458                }
459            }
460        }
461
462        impl #struct_name {
463            /// Helper method for constructing `ResErr` structs without using `.context()`
464            /// on a Result.
465            ///
466            /// # Examples
467            ///
468            /// ```rust,ignore
469            /// ResErr::new("Failed to read file", std::io::Error::other(""));
470            /// ```
471            #[doc(hidden)]
472            #vis fn new<E>(msg: &str, source: E) -> Self where #enum_name: From<E> {
473                use core::fmt::Write;
474                let mut buf = #buf_name::new();
475                let _ = buf.write_str(msg);
476                Self { msg: buf, source: #enum_name::from(source) }
477            }
478
479            /// Helper method for constructing `ResErr` structs without using `.context()`
480            /// on a Result.
481            ///
482            /// This method constructs `ResErr` structs using `ctx!()` macro for optimized, lazily
483            /// evaluated construction
484            ///
485            /// # Examples
486            ///
487            /// ```rust,ignore
488            /// ResErr::from_args(ctx!("Failed to read file: {}", &file_name), std::io::Error::other(""));
489            /// ```
490            #[doc(hidden)]
491            #vis fn from_args<E, F: FnOnce(#struct_name, &str, &str, &str) -> #struct_name>(msg: F, source: E) -> Self where #enum_name: From<E> {
492                use core::fmt::Write;
493
494                let err = Self { msg: #buf_name::new(), source: #enum_name::from(source) };
495
496                msg(err, "", "", "")
497            }
498        }
499
500        impl From<#enum_name> for #struct_name {
501            fn from(value: #enum_name) -> Self {
502                Self { msg: #buf_name::new(), source: value }
503            }
504        }
505
506        #(#from_impls)*
507
508        /// Extension trait for adding context to Result types.
509        ///
510        /// Automatically implemented for all `Result<T, E>` where `E` can be
511        /// converted into your error enum.
512        ///
513        /// # Examples
514        ///
515        /// ```rust,ignore
516        /// std::fs::read("file.txt")
517        ///     .context("Failed to read file")?;
518        /// ```
519        #[doc(hidden)]
520        #vis trait #trait_name<T, S> {
521            /// Add context to an error.
522            ///
523            /// Accepts `&str` or `core::fmt::Arguments<'_>`. The message is only allocated if an
524            /// error occurs.
525            ///
526            /// # Examples
527            ///
528            /// ```rust,ignore
529            /// std::fs::read("config.toml")
530            ///     .context("Failed to read config")?;
531            /// ```
532            #[doc(hidden)]
533            fn context(self, msg: S) -> Result<T, #struct_name>;
534        }
535
536        impl<T> #trait_name<T, &str> for Result<T, #struct_name> {
537            fn context(self, msg: &str) -> Result<T, #struct_name> {
538                match self {
539                    Ok(ok) => Ok(ok),
540                    Err(mut err) => {
541                        use core::fmt::Write;
542
543                        if err.msg.is_empty() {
544                            let _ = err.write_str(msg);
545                        } else {
546                            let _ = err.write_str(#delimiter);
547                            let _ = err.write_str(#msg_prefix);
548                            let _ = err.write_str(msg);
549                            let _ = err.write_str(#msg_suffix);
550                        }
551
552                        Err(err)
553                    }
554                }
555            }
556        }
557
558        impl<T, E> #trait_name<T, &str> for Result<T, E> where #enum_name: From<E> {
559            fn context(self, msg: &str) -> Result<T, #struct_name> {
560                match self {
561                    Ok(ok) => Ok(ok),
562                    Err(err) => Err(#struct_name::new(msg, err)),
563                }
564            }
565        }
566
567        impl<'a, T, F: FnOnce(#struct_name, &'a str, &'a str, &'a str) -> #struct_name>  #trait_name<T, F> for Result<T, #struct_name> {
568            fn context(self, msg: F) -> Result<T, #struct_name> {
569                match self {
570                    Ok(ok) => Ok(ok),
571                    Err(mut err) => {
572                        use core::fmt::Write;
573
574                        let err = if err.msg.is_empty() {
575                            msg(err, "", "", "")
576                        } else {
577                            msg(err, #delimiter, #msg_prefix, #msg_suffix)
578                        };
579
580                        Err(err)
581                    }
582                }
583            }
584        }
585
586        impl<'a, T, F: FnOnce(#struct_name, &'a str, &'a str, &'a str) -> #struct_name, E> #trait_name<T, F> for Result<T, E> where #enum_name: From<E> {
587            fn context(self, msg: F) -> Result<T, #struct_name> {
588                match self {
589                    Ok(ok) => Ok(ok),
590                    Err(err) => {
591                        use core::fmt::Write;
592
593                        let buf = #buf_name::new();
594                        let mut err = #struct_name { msg: buf, source: #enum_name::from(err) };
595
596                        let err = msg(err, "", "", "");
597
598                        Err(err)
599                    }
600                }
601            }
602        }
603
604        #vis type #alias<T> = Result<T, #struct_name>;
605
606        #gen_buf
607    };
608
609    if !errs.is_empty() {
610        errs.into_iter().collect()
611    } else {
612        TokenStream::from(expanded)
613    }
614}
615
616struct EnumInput {
617    vis: TokenStream,
618    name: proc_macro::Ident,
619    variants: Vec<Variant>,
620    original: TokenStream,
621}
622
623struct Variant {
624    name: proc_macro::Ident,
625    kind: VariantKind,
626}
627
628enum VariantKind {
629    Unnamed(TokenStream),
630    Named(proc_macro::Ident, TokenStream),
631    Unit,
632}
633
634impl EnumInput {
635    fn from_tokens(
636        tokens: TokenStream,
637        errs: &mut Vec<TokenStream>,
638    ) -> Option<Self> {
639        let original = tokens.clone();
640        let mut iter = tokens.into_iter().peekable();
641
642        let mut vis = TokenStream::new();
643        loop {
644            match iter.peek() {
645                Some(TokenTree::Ident(i))
646                    if i.to_string().as_str() == "enum" =>
647                {
648                    break;
649                }
650                Some(_) => vis.extend(iter.next()),
651                None => {
652                    errs.push(construct_err(
653                        "expected enum item",
654                        Span::call_site(),
655                    ));
656                    break;
657                }
658            }
659        }
660
661        match iter.next() {
662            Some(TokenTree::Ident(i)) if i.to_string().as_str() == "enum" => {}
663            Some(tt) => errs.push(construct_err(
664                &format!("expected enum keyword, found: {}", tt),
665                tt.span(),
666            )),
667            None => errs.push(construct_err(
668                "expected enum keyword",
669                Span::call_site(),
670            )),
671        }
672
673        let mut name = proc_macro::Ident::new("Var", Span::call_site());
674        match iter.next() {
675            Some(TokenTree::Ident(i)) => name = i,
676            Some(tt) => errs.push(construct_err(
677                &format!("expected enum identifier, found: {}", tt),
678                tt.span(),
679            )),
680            None => errs.push(construct_err(
681                "expected enum item name",
682                Span::call_site(),
683            )),
684        }
685
686        let body = match iter.next() {
687            Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
688                Some(g)
689            }
690            Some(tt) => {
691                errs.push(construct_err("expected enum body", tt.span()));
692                None
693            }
694            None => {
695                errs.push(construct_err(
696                    "expected enum body",
697                    Span::call_site(),
698                ));
699                None
700            }
701        };
702
703        let variants = Self::parse_variants(body, errs);
704
705        if errs.is_empty() {
706            Some(EnumInput { vis, name, variants, original })
707        } else {
708            None
709        }
710    }
711
712    fn parse_variants(
713        group: Option<Group>,
714        errs: &mut Vec<TokenStream>,
715    ) -> Vec<Variant> {
716        if group.is_none() {
717            return vec![];
718        }
719
720        let mut iter =
721            unsafe { group.unwrap_unchecked() }.stream().into_iter().peekable();
722        let mut out = vec![];
723
724        let mut first = true;
725
726        while let Some(token) = iter.next() {
727            let token = if first {
728                first = false;
729                token
730            } else {
731                match token {
732                    TokenTree::Punct(p) if p.as_char() == ',' => {}
733                    _ => errs.push(construct_err(
734                        "expected comma separator between enum variants",
735                        token.span(),
736                    )),
737                }
738
739                match iter.next() {
740                    Some(tt) => tt,
741                    None => break,
742                }
743            };
744
745            let name = match token {
746                TokenTree::Ident(i) => i,
747                _ => {
748                    errs.push(construct_err(
749                        "expected variant name identifier",
750                        token.span(),
751                    ));
752                    proc_macro::Ident::new("Var", Span::call_site())
753                }
754            };
755
756            let mut kind = VariantKind::Unit;
757            match iter.peek() {
758                Some(TokenTree::Punct(p)) if p.as_char() == ',' => {}
759                Some(TokenTree::Group(g))
760                    if g.delimiter() == Delimiter::Parenthesis =>
761                {
762                    let stream = g.stream().into_iter();
763                    iter.next();
764
765                    let mut ty = TokenStream::new();
766
767                    for token in stream {
768                        ty.extend([token]);
769                    }
770
771                    if !ty.is_empty() {
772                        kind = VariantKind::Unnamed(ty);
773                    }
774                }
775
776                Some(TokenTree::Group(g))
777                    if g.delimiter() == Delimiter::Brace =>
778                {
779                    let mut stream = g.stream().into_iter();
780                    iter.next();
781
782                    let token = stream.next();
783                    let name = match token {
784                        Some(TokenTree::Ident(i)) => i,
785                        _ => {
786                            errs.push(construct_err(
787                                "expected field name identifier",
788                                Span::call_site(),
789                            ));
790                            proc_macro::Ident::new("Var", Span::call_site())
791                        }
792                    };
793
794                    match stream.next() {
795                        Some(TokenTree::Punct(p)) if p.as_char() == ':' => {}
796                        Some(tt) =>
797                            errs.push(construct_err(
798                                &format!("expected field name colon separator, found: {}", tt),
799                                tt.span()
800                            )),
801                        _ =>
802                            errs.push(construct_err(
803                                "expected field name colon separator",
804                                Span::call_site(),
805                            )),
806                    }
807
808                    let mut ty = TokenStream::new();
809
810                    for token in stream {
811                        ty.extend([token]);
812                    }
813
814                    if !ty.is_empty() {
815                        kind = VariantKind::Named(name, ty);
816                    }
817                }
818
819                None => {}
820
821                Some(tt) => errs.push(construct_err(
822                    "expected named or unnamed variant",
823                    tt.span(),
824                )),
825            }
826
827            out.push(Variant { name, kind });
828        }
829
830        out
831    }
832}
833
834#[derive(Default)]
835struct ResExtArgs {
836    prefix: Option<String>,
837    suffix: Option<String>,
838    msg_prefix: Option<String>,
839    msg_suffix: Option<String>,
840    delimiter: Option<String>,
841    source_prefix: Option<String>,
842    include_variant: bool,
843    alias: Option<String>,
844    buf_size: Option<usize>,
845    alloc: bool,
846}
847
848fn parse_args(
849    input: TokenStream,
850    errs: &mut Vec<TokenStream>,
851) -> Option<ResExtArgs> {
852    let mut args = ResExtArgs {
853        prefix: None,
854        suffix: None,
855        msg_prefix: None,
856        msg_suffix: None,
857        delimiter: None,
858        source_prefix: None,
859        include_variant: false,
860        alias: None,
861        buf_size: None,
862        alloc: false,
863    };
864
865    let mut iter = input.into_iter().peekable();
866
867    while let Some(token) = iter.next() {
868        let key = match token {
869            TokenTree::Ident(ident) => Some(ident),
870            _ => {
871                errs.push(construct_err(
872                    &format!("expected key identifier, found: {}", token),
873                    token.span(),
874                ));
875                None
876            }
877        };
878
879        let token = iter.next();
880        match token {
881            Some(TokenTree::Punct(p)) if p.as_char() == '=' => {}
882            _ => {
883                errs.push(construct_err(
884                    &format!(
885                        "expected '=' separator between key and value, found: '{}'",
886                        token.as_ref().map(|s| s.to_string()).unwrap_or_default()
887                    ),
888                    token.map(|t| t.span()).unwrap_or(Span::call_site()),
889                ));
890            }
891        }
892
893        let val = iter.next();
894
895        if val.is_none() {
896            errs.push(construct_err(
897                "expected value after '='",
898                Span::call_site(),
899            ));
900        }
901
902        if let Some(key) = key {
903            match key.to_string().as_str() {
904                "prefix" => {
905                    args.prefix = val
906                        .map(|s| s.to_string().trim_matches('"').to_string());
907                }
908
909                "suffix" => {
910                    args.suffix = val
911                        .map(|s| s.to_string().trim_matches('"').to_string());
912                }
913
914                "msg_prefix" => {
915                    args.msg_prefix = val
916                        .map(|s| s.to_string().trim_matches('"').to_string());
917                }
918
919                "msg_suffix" => {
920                    args.msg_suffix = val
921                        .map(|s| s.to_string().trim_matches('"').to_string());
922                }
923
924                "delimiter" => {
925                    args.delimiter = val
926                        .map(|s| s.to_string().trim_matches('"').to_string());
927                }
928
929                "source_prefix" => {
930                    args.source_prefix = val
931                        .map(|s| s.to_string().trim_matches('"').to_string());
932                }
933
934                "include_variant" => {
935                    let v = val
936                        .as_ref()
937                        .map(|t| t.to_string())
938                        .unwrap_or_default()
939                        .parse();
940
941                    args.include_variant = match v {
942                        Ok(v) => v,
943                        Err(err) => {
944                            errs.push(construct_err(&format!("invalid bool value for `include_variant` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site)));
945
946                            // does not matter since the errors will be returned
947                            false
948                        }
949                    };
950                }
951
952                "alias" => {
953                    args.alias = val
954                        .map(|s| s.to_string().trim_matches('"').to_string());
955                }
956
957                "buf_size" => {
958                    let v = val
959                        .as_ref()
960                        .map(|t| t.to_string())
961                        .unwrap_or_default()
962                        .parse();
963
964                    args.buf_size = match v {
965                        Ok(v) => Some(v),
966                        Err(err) => {
967                            errs.push(construct_err(&format!("invalid usize value for `buf_size` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site) ));
968
969                            None
970                        }
971                    };
972                }
973
974                "alloc" => {
975                    let v = val
976                        .as_ref()
977                        .map(|t| t.to_string())
978                        .unwrap_or_default()
979                        .parse();
980
981                    args.alloc = match v {
982                        Ok(v) => v,
983                        Err(err) => {
984                            errs.push(construct_err(&format!("invalid bool value for `alloc` attribute\nError: {}", err), val.map(|v| v.span()).unwrap_or_else(Span::call_site)));
985
986                            // does not matter since the errors will be returned
987                            false
988                        }
989                    };
990                }
991
992                _ => errs.push(construct_err(
993                    &format!("unrecognized attribute: {}", &key),
994                    key.span(),
995                )),
996            }
997        }
998
999        match iter.peek() {
1000            Some(TokenTree::Punct(p)) if p.as_char() == ',' => {
1001                iter.next();
1002            }
1003            Some(TokenTree::Ident(_)) => {}
1004            None => {}
1005            _ => {
1006                errs.push(
1007                    construct_err(
1008                        &format!("expected comma, newline or whitespace delimiter, found: '{}'", iter.peek().map(|t| t.to_string()).unwrap_or_default()),
1009                        iter.peek().map(|t| t.span()).unwrap_or_else(Span::call_site)
1010                    ),
1011                );
1012                iter.next();
1013            }
1014        }
1015    }
1016
1017    if !errs.is_empty() { None } else { Some(args) }
1018}
1019
1020fn construct_err(msg: &str, span: Span) -> TokenStream {
1021    let mut ts = TokenStream::new();
1022    ts.extend([TokenTree::Ident(proc_macro::Ident::new(
1023        "compile_error",
1024        span,
1025    ))]);
1026
1027    ts.extend([TokenTree::Punct(Punct::new('!', Spacing::Alone))]);
1028
1029    let mut inner = TokenStream::new();
1030    let mut lit = Literal::string(msg);
1031    lit.set_span(span);
1032    inner.extend([TokenTree::Literal(lit)]);
1033
1034    ts.extend([TokenTree::Group(Group::new(Delimiter::Parenthesis, inner))]);
1035    ts.extend([TokenTree::Punct(Punct::new(';', Spacing::Alone))]);
1036
1037    ts
1038}