| use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree}; |
| use quote::{format_ident, quote, ToTokens}; |
| use std::collections::BTreeSet as Set; |
| use std::iter::FromIterator; |
| use syn::parse::ParseStream; |
| use syn::{ |
| braced, bracketed, parenthesized, token, Attribute, Error, Ident, Index, LitInt, LitStr, Meta, |
| Result, Token, |
| }; |
| |
| pub struct Attrs<'a> { |
| pub display: Option<Display<'a>>, |
| pub source: Option<&'a Attribute>, |
| pub backtrace: Option<&'a Attribute>, |
| pub from: Option<&'a Attribute>, |
| pub transparent: Option<Transparent<'a>>, |
| } |
| |
| #[derive(Clone)] |
| pub struct Display<'a> { |
| pub original: &'a Attribute, |
| pub fmt: LitStr, |
| pub args: TokenStream, |
| pub has_bonus_display: bool, |
| pub implied_bounds: Set<(usize, Trait)>, |
| } |
| |
| #[derive(Copy, Clone)] |
| pub struct Transparent<'a> { |
| pub original: &'a Attribute, |
| pub span: Span, |
| } |
| |
| #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] |
| pub enum Trait { |
| Debug, |
| Display, |
| Octal, |
| LowerHex, |
| UpperHex, |
| Pointer, |
| Binary, |
| LowerExp, |
| UpperExp, |
| } |
| |
| pub fn get(input: &[Attribute]) -> Result<Attrs> { |
| let mut attrs = Attrs { |
| display: None, |
| source: None, |
| backtrace: None, |
| from: None, |
| transparent: None, |
| }; |
| |
| for attr in input { |
| if attr.path().is_ident("error") { |
| parse_error_attribute(&mut attrs, attr)?; |
| } else if attr.path().is_ident("source") { |
| require_empty_attribute(attr)?; |
| if attrs.source.is_some() { |
| return Err(Error::new_spanned(attr, "duplicate #[source] attribute")); |
| } |
| attrs.source = Some(attr); |
| } else if attr.path().is_ident("backtrace") { |
| require_empty_attribute(attr)?; |
| if attrs.backtrace.is_some() { |
| return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute")); |
| } |
| attrs.backtrace = Some(attr); |
| } else if attr.path().is_ident("from") { |
| match attr.meta { |
| Meta::Path(_) => {} |
| Meta::List(_) | Meta::NameValue(_) => { |
| // Assume this is meant for derive_more crate or something. |
| continue; |
| } |
| } |
| if attrs.from.is_some() { |
| return Err(Error::new_spanned(attr, "duplicate #[from] attribute")); |
| } |
| attrs.from = Some(attr); |
| } |
| } |
| |
| Ok(attrs) |
| } |
| |
| fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> { |
| syn::custom_keyword!(transparent); |
| |
| attr.parse_args_with(|input: ParseStream| { |
| if let Some(kw) = input.parse::<Option<transparent>>()? { |
| if attrs.transparent.is_some() { |
| return Err(Error::new_spanned( |
| attr, |
| "duplicate #[error(transparent)] attribute", |
| )); |
| } |
| attrs.transparent = Some(Transparent { |
| original: attr, |
| span: kw.span, |
| }); |
| return Ok(()); |
| } |
| |
| let display = Display { |
| original: attr, |
| fmt: input.parse()?, |
| args: parse_token_expr(input, false)?, |
| has_bonus_display: false, |
| implied_bounds: Set::new(), |
| }; |
| if attrs.display.is_some() { |
| return Err(Error::new_spanned( |
| attr, |
| "only one #[error(...)] attribute is allowed", |
| )); |
| } |
| attrs.display = Some(display); |
| Ok(()) |
| }) |
| } |
| |
| fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> { |
| let mut tokens = Vec::new(); |
| while !input.is_empty() { |
| if begin_expr && input.peek(Token![.]) { |
| if input.peek2(Ident) { |
| input.parse::<Token![.]>()?; |
| begin_expr = false; |
| continue; |
| } |
| if input.peek2(LitInt) { |
| input.parse::<Token![.]>()?; |
| let int: Index = input.parse()?; |
| let ident = format_ident!("_{}", int.index, span = int.span); |
| tokens.push(TokenTree::Ident(ident)); |
| begin_expr = false; |
| continue; |
| } |
| } |
| |
| begin_expr = input.peek(Token![break]) |
| || input.peek(Token![continue]) |
| || input.peek(Token![if]) |
| || input.peek(Token![in]) |
| || input.peek(Token![match]) |
| || input.peek(Token![mut]) |
| || input.peek(Token![return]) |
| || input.peek(Token![while]) |
| || input.peek(Token![+]) |
| || input.peek(Token![&]) |
| || input.peek(Token![!]) |
| || input.peek(Token![^]) |
| || input.peek(Token![,]) |
| || input.peek(Token![/]) |
| || input.peek(Token![=]) |
| || input.peek(Token![>]) |
| || input.peek(Token![<]) |
| || input.peek(Token![|]) |
| || input.peek(Token![%]) |
| || input.peek(Token![;]) |
| || input.peek(Token![*]) |
| || input.peek(Token![-]); |
| |
| let token: TokenTree = if input.peek(token::Paren) { |
| let content; |
| let delimiter = parenthesized!(content in input); |
| let nested = parse_token_expr(&content, true)?; |
| let mut group = Group::new(Delimiter::Parenthesis, nested); |
| group.set_span(delimiter.span.join()); |
| TokenTree::Group(group) |
| } else if input.peek(token::Brace) { |
| let content; |
| let delimiter = braced!(content in input); |
| let nested = parse_token_expr(&content, true)?; |
| let mut group = Group::new(Delimiter::Brace, nested); |
| group.set_span(delimiter.span.join()); |
| TokenTree::Group(group) |
| } else if input.peek(token::Bracket) { |
| let content; |
| let delimiter = bracketed!(content in input); |
| let nested = parse_token_expr(&content, true)?; |
| let mut group = Group::new(Delimiter::Bracket, nested); |
| group.set_span(delimiter.span.join()); |
| TokenTree::Group(group) |
| } else { |
| input.parse()? |
| }; |
| tokens.push(token); |
| } |
| Ok(TokenStream::from_iter(tokens)) |
| } |
| |
| fn require_empty_attribute(attr: &Attribute) -> Result<()> { |
| let error_span = match &attr.meta { |
| Meta::Path(_) => return Ok(()), |
| Meta::List(meta) => meta.delimiter.span().open(), |
| Meta::NameValue(meta) => meta.eq_token.span, |
| }; |
| Err(Error::new( |
| error_span, |
| "unexpected token in thiserror attribute", |
| )) |
| } |
| |
| impl ToTokens for Display<'_> { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let fmt = &self.fmt; |
| let args = &self.args; |
| tokens.extend(quote! { |
| write!(__formatter, #fmt #args) |
| }); |
| } |
| } |
| |
| impl ToTokens for Trait { |
| fn to_tokens(&self, tokens: &mut TokenStream) { |
| let trait_name = format_ident!("{}", format!("{:?}", self)); |
| tokens.extend(quote!(std::fmt::#trait_name)); |
| } |
| } |