thiserror_impl/
attr.rs

1use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
2use quote::{format_ident, quote, ToTokens};
3use std::collections::BTreeSet as Set;
4use syn::parse::discouraged::Speculative;
5use syn::parse::ParseStream;
6use syn::{
7    braced, bracketed, parenthesized, token, Attribute, Error, Ident, Index, LitFloat, LitInt,
8    LitStr, Meta, Result, Token,
9};
10
11pub struct Attrs<'a> {
12    pub display: Option<Display<'a>>,
13    pub source: Option<&'a Attribute>,
14    pub backtrace: Option<&'a Attribute>,
15    pub from: Option<&'a Attribute>,
16    pub transparent: Option<Transparent<'a>>,
17}
18
19#[derive(Clone)]
20pub struct Display<'a> {
21    pub original: &'a Attribute,
22    pub fmt: LitStr,
23    pub args: TokenStream,
24    pub requires_fmt_machinery: bool,
25    pub has_bonus_display: bool,
26    pub implied_bounds: Set<(usize, Trait)>,
27}
28
29#[derive(Copy, Clone)]
30pub struct Transparent<'a> {
31    pub original: &'a Attribute,
32    pub span: Span,
33}
34
35#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
36pub enum Trait {
37    Debug,
38    Display,
39    Octal,
40    LowerHex,
41    UpperHex,
42    Pointer,
43    Binary,
44    LowerExp,
45    UpperExp,
46}
47
48pub fn get(input: &[Attribute]) -> Result<Attrs> {
49    let mut attrs = Attrs {
50        display: None,
51        source: None,
52        backtrace: None,
53        from: None,
54        transparent: None,
55    };
56
57    for attr in input {
58        if attr.path().is_ident("error") {
59            parse_error_attribute(&mut attrs, attr)?;
60        } else if attr.path().is_ident("source") {
61            attr.meta.require_path_only()?;
62            if attrs.source.is_some() {
63                return Err(Error::new_spanned(attr, "duplicate #[source] attribute"));
64            }
65            attrs.source = Some(attr);
66        } else if attr.path().is_ident("backtrace") {
67            attr.meta.require_path_only()?;
68            if attrs.backtrace.is_some() {
69                return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute"));
70            }
71            attrs.backtrace = Some(attr);
72        } else if attr.path().is_ident("from") {
73            match attr.meta {
74                Meta::Path(_) => {}
75                Meta::List(_) | Meta::NameValue(_) => {
76                    // Assume this is meant for derive_more crate or something.
77                    continue;
78                }
79            }
80            if attrs.from.is_some() {
81                return Err(Error::new_spanned(attr, "duplicate #[from] attribute"));
82            }
83            attrs.from = Some(attr);
84        }
85    }
86
87    Ok(attrs)
88}
89
90fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> {
91    syn::custom_keyword!(transparent);
92
93    attr.parse_args_with(|input: ParseStream| {
94        if let Some(kw) = input.parse::<Option<transparent>>()? {
95            if attrs.transparent.is_some() {
96                return Err(Error::new_spanned(
97                    attr,
98                    "duplicate #[error(transparent)] attribute",
99                ));
100            }
101            attrs.transparent = Some(Transparent {
102                original: attr,
103                span: kw.span,
104            });
105            return Ok(());
106        }
107
108        let fmt: LitStr = input.parse()?;
109
110        let ahead = input.fork();
111        ahead.parse::<Option<Token![,]>>()?;
112        let args = if ahead.is_empty() {
113            input.advance_to(&ahead);
114            TokenStream::new()
115        } else {
116            parse_token_expr(input, false)?
117        };
118
119        let requires_fmt_machinery = !args.is_empty();
120
121        let display = Display {
122            original: attr,
123            fmt,
124            args,
125            requires_fmt_machinery,
126            has_bonus_display: false,
127            implied_bounds: Set::new(),
128        };
129        if attrs.display.is_some() {
130            return Err(Error::new_spanned(
131                attr,
132                "only one #[error(...)] attribute is allowed",
133            ));
134        }
135        attrs.display = Some(display);
136        Ok(())
137    })
138}
139
140fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> {
141    let mut tokens = Vec::new();
142    while !input.is_empty() {
143        if begin_expr && input.peek(Token![.]) {
144            if input.peek2(Ident) {
145                input.parse::<Token![.]>()?;
146                begin_expr = false;
147                continue;
148            } else if input.peek2(LitInt) {
149                input.parse::<Token![.]>()?;
150                let int: Index = input.parse()?;
151                tokens.push({
152                    let ident = format_ident!("_{}", int.index, span = int.span);
153                    TokenTree::Ident(ident)
154                });
155                begin_expr = false;
156                continue;
157            } else if input.peek2(LitFloat) {
158                let ahead = input.fork();
159                ahead.parse::<Token![.]>()?;
160                let float: LitFloat = ahead.parse()?;
161                let repr = float.to_string();
162                let mut indices = repr.split('.').map(syn::parse_str::<Index>);
163                if let (Some(Ok(first)), Some(Ok(second)), None) =
164                    (indices.next(), indices.next(), indices.next())
165                {
166                    input.advance_to(&ahead);
167                    tokens.push({
168                        let ident = format_ident!("_{}", first, span = float.span());
169                        TokenTree::Ident(ident)
170                    });
171                    tokens.push({
172                        let mut punct = Punct::new('.', Spacing::Alone);
173                        punct.set_span(float.span());
174                        TokenTree::Punct(punct)
175                    });
176                    tokens.push({
177                        let mut literal = Literal::u32_unsuffixed(second.index);
178                        literal.set_span(float.span());
179                        TokenTree::Literal(literal)
180                    });
181                    begin_expr = false;
182                    continue;
183                }
184            }
185        }
186
187        begin_expr = input.peek(Token![break])
188            || input.peek(Token![continue])
189            || input.peek(Token![if])
190            || input.peek(Token![in])
191            || input.peek(Token![match])
192            || input.peek(Token![mut])
193            || input.peek(Token![return])
194            || input.peek(Token![while])
195            || input.peek(Token![+])
196            || input.peek(Token![&])
197            || input.peek(Token![!])
198            || input.peek(Token![^])
199            || input.peek(Token![,])
200            || input.peek(Token![/])
201            || input.peek(Token![=])
202            || input.peek(Token![>])
203            || input.peek(Token![<])
204            || input.peek(Token![|])
205            || input.peek(Token![%])
206            || input.peek(Token![;])
207            || input.peek(Token![*])
208            || input.peek(Token![-]);
209
210        let token: TokenTree = if input.peek(token::Paren) {
211            let content;
212            let delimiter = parenthesized!(content in input);
213            let nested = parse_token_expr(&content, true)?;
214            let mut group = Group::new(Delimiter::Parenthesis, nested);
215            group.set_span(delimiter.span.join());
216            TokenTree::Group(group)
217        } else if input.peek(token::Brace) {
218            let content;
219            let delimiter = braced!(content in input);
220            let nested = parse_token_expr(&content, true)?;
221            let mut group = Group::new(Delimiter::Brace, nested);
222            group.set_span(delimiter.span.join());
223            TokenTree::Group(group)
224        } else if input.peek(token::Bracket) {
225            let content;
226            let delimiter = bracketed!(content in input);
227            let nested = parse_token_expr(&content, true)?;
228            let mut group = Group::new(Delimiter::Bracket, nested);
229            group.set_span(delimiter.span.join());
230            TokenTree::Group(group)
231        } else {
232            input.parse()?
233        };
234        tokens.push(token);
235    }
236    Ok(TokenStream::from_iter(tokens))
237}
238
239impl ToTokens for Display<'_> {
240    fn to_tokens(&self, tokens: &mut TokenStream) {
241        let fmt = &self.fmt;
242        let args = &self.args;
243
244        // Currently `write!(f, "text")` produces less efficient code than
245        // `f.write_str("text")`. We recognize the case when the format string
246        // has no braces and no interpolated values, and generate simpler code.
247        tokens.extend(if self.requires_fmt_machinery {
248            quote! {
249                ::core::write!(__formatter, #fmt #args)
250            }
251        } else {
252            quote! {
253                __formatter.write_str(#fmt)
254            }
255        });
256    }
257}
258
259impl ToTokens for Trait {
260    fn to_tokens(&self, tokens: &mut TokenStream) {
261        let trait_name = match self {
262            Trait::Debug => "Debug",
263            Trait::Display => "Display",
264            Trait::Octal => "Octal",
265            Trait::LowerHex => "LowerHex",
266            Trait::UpperHex => "UpperHex",
267            Trait::Pointer => "Pointer",
268            Trait::Binary => "Binary",
269            Trait::LowerExp => "LowerExp",
270            Trait::UpperExp => "UpperExp",
271        };
272        let ident = Ident::new(trait_name, Span::call_site());
273        tokens.extend(quote!(::core::fmt::#ident));
274    }
275}