| use proc_macro2::{Delimiter, Group, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; | |
| use quote::{format_ident, quote, quote_spanned, ToTokens}; | |
| use std::collections::BTreeSet as Set; | |
| use syn::parse::discouraged::Speculative; | |
| use syn::parse::{End, ParseStream}; | |
| use syn::{ | |
| braced, bracketed, parenthesized, token, Attribute, Error, ExprPath, Ident, Index, LitFloat, | |
| LitInt, LitStr, Meta, Result, Token, | |
| }; | |
| pub struct Attrs<'a> { | |
| pub display: Option<Display<'a>>, | |
| pub source: Option<Source<'a>>, | |
| pub backtrace: Option<&'a Attribute>, | |
| pub from: Option<From<'a>>, | |
| pub transparent: Option<Transparent<'a>>, | |
| pub fmt: Option<Fmt<'a>>, | |
| } | |
| #[derive(Clone)] | |
| pub struct Display<'a> { | |
| pub original: &'a Attribute, | |
| pub fmt: LitStr, | |
| pub args: TokenStream, | |
| pub requires_fmt_machinery: bool, | |
| pub has_bonus_display: bool, | |
| pub infinite_recursive: bool, | |
| pub implied_bounds: Set<(usize, Trait)>, | |
| pub bindings: Vec<(Ident, TokenStream)>, | |
| } | |
| #[derive(Copy, Clone)] | |
| pub struct Source<'a> { | |
| pub original: &'a Attribute, | |
| pub span: Span, | |
| } | |
| #[derive(Copy, Clone)] | |
| pub struct From<'a> { | |
| pub original: &'a Attribute, | |
| pub span: Span, | |
| } | |
| #[derive(Copy, Clone)] | |
| pub struct Transparent<'a> { | |
| pub original: &'a Attribute, | |
| pub span: Span, | |
| } | |
| #[derive(Clone)] | |
| pub struct Fmt<'a> { | |
| pub original: &'a Attribute, | |
| pub path: ExprPath, | |
| } | |
| #[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, | |
| fmt: None, | |
| }; | |
| for attr in input { | |
| if attr.path().is_ident("error") { | |
| parse_error_attribute(&mut attrs, attr)?; | |
| } else if attr.path().is_ident("source") { | |
| attr.meta.require_path_only()?; | |
| if attrs.source.is_some() { | |
| return Err(Error::new_spanned(attr, "duplicate #[source] attribute")); | |
| } | |
| let span = (attr.pound_token.span) | |
| .join(attr.bracket_token.span.join()) | |
| .unwrap_or(attr.path().get_ident().unwrap().span()); | |
| attrs.source = Some(Source { | |
| original: attr, | |
| span, | |
| }); | |
| } else if attr.path().is_ident("backtrace") { | |
| attr.meta.require_path_only()?; | |
| 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")); | |
| } | |
| let span = (attr.pound_token.span) | |
| .join(attr.bracket_token.span.join()) | |
| .unwrap_or(attr.path().get_ident().unwrap().span()); | |
| attrs.from = Some(From { | |
| original: attr, | |
| span, | |
| }); | |
| } | |
| } | |
| Ok(attrs) | |
| } | |
| fn parse_error_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> { | |
| mod kw { | |
| syn::custom_keyword!(transparent); | |
| syn::custom_keyword!(fmt); | |
| } | |
| attr.parse_args_with(|input: ParseStream| { | |
| let lookahead = input.lookahead1(); | |
| let fmt = if lookahead.peek(LitStr) { | |
| input.parse::<LitStr>()? | |
| } else if lookahead.peek(kw::transparent) { | |
| let kw: kw::transparent = input.parse()?; | |
| 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(()); | |
| } else if lookahead.peek(kw::fmt) { | |
| input.parse::<kw::fmt>()?; | |
| input.parse::<Token![=]>()?; | |
| let path: ExprPath = input.parse()?; | |
| if attrs.fmt.is_some() { | |
| return Err(Error::new_spanned( | |
| attr, | |
| "duplicate #[error(fmt = ...)] attribute", | |
| )); | |
| } | |
| attrs.fmt = Some(Fmt { | |
| original: attr, | |
| path, | |
| }); | |
| return Ok(()); | |
| } else { | |
| return Err(lookahead.error()); | |
| }; | |
| let args = if input.is_empty() || input.peek(Token![,]) && input.peek2(End) { | |
| input.parse::<Option<Token![,]>>()?; | |
| TokenStream::new() | |
| } else { | |
| parse_token_expr(input, false)? | |
| }; | |
| let requires_fmt_machinery = !args.is_empty(); | |
| let display = Display { | |
| original: attr, | |
| fmt, | |
| args, | |
| requires_fmt_machinery, | |
| has_bonus_display: false, | |
| infinite_recursive: false, | |
| implied_bounds: Set::new(), | |
| bindings: Vec::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 input.peek(token::Group) { | |
| let group: TokenTree = input.parse()?; | |
| tokens.push(group); | |
| begin_expr = false; | |
| continue; | |
| } | |
| if begin_expr && input.peek(Token![.]) { | |
| if input.peek2(Ident) { | |
| input.parse::<Token![.]>()?; | |
| begin_expr = false; | |
| continue; | |
| } else if input.peek2(LitInt) { | |
| input.parse::<Token![.]>()?; | |
| let int: Index = input.parse()?; | |
| tokens.push({ | |
| let ident = format_ident!("_{}", int.index, span = int.span); | |
| TokenTree::Ident(ident) | |
| }); | |
| begin_expr = false; | |
| continue; | |
| } else if input.peek2(LitFloat) { | |
| let ahead = input.fork(); | |
| ahead.parse::<Token![.]>()?; | |
| let float: LitFloat = ahead.parse()?; | |
| let repr = float.to_string(); | |
| let mut indices = repr.split('.').map(syn::parse_str::<Index>); | |
| if let (Some(Ok(first)), Some(Ok(second)), None) = | |
| (indices.next(), indices.next(), indices.next()) | |
| { | |
| input.advance_to(&ahead); | |
| tokens.push({ | |
| let ident = format_ident!("_{}", first, span = float.span()); | |
| TokenTree::Ident(ident) | |
| }); | |
| tokens.push({ | |
| let mut punct = Punct::new('.', Spacing::Alone); | |
| punct.set_span(float.span()); | |
| TokenTree::Punct(punct) | |
| }); | |
| tokens.push({ | |
| let mut literal = Literal::u32_unsuffixed(second.index); | |
| literal.set_span(float.span()); | |
| TokenTree::Literal(literal) | |
| }); | |
| 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)) | |
| } | |
| impl ToTokens for Display<'_> { | |
| fn to_tokens(&self, tokens: &mut TokenStream) { | |
| if self.infinite_recursive { | |
| let span = self.fmt.span(); | |
| tokens.extend(quote_spanned! {span=> | |
| #[warn(unconditional_recursion)] | |
| fn _fmt() { _fmt() } | |
| }); | |
| } | |
| let fmt = &self.fmt; | |
| let args = &self.args; | |
| // Currently `write!(f, "text")` produces less efficient code than | |
| // `f.write_str("text")`. We recognize the case when the format string | |
| // has no braces and no interpolated values, and generate simpler code. | |
| let write = if self.requires_fmt_machinery { | |
| quote! { | |
| ::core::write!(__formatter, #fmt #args) | |
| } | |
| } else { | |
| quote! { | |
| __formatter.write_str(#fmt) | |
| } | |
| }; | |
| tokens.extend(if self.bindings.is_empty() { | |
| write | |
| } else { | |
| let locals = self.bindings.iter().map(|(local, _value)| local); | |
| let values = self.bindings.iter().map(|(_local, value)| value); | |
| quote! { | |
| match (#(#values,)*) { | |
| (#(#locals,)*) => #write | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| impl ToTokens for Trait { | |
| fn to_tokens(&self, tokens: &mut TokenStream) { | |
| let trait_name = match self { | |
| Trait::Debug => "Debug", | |
| Trait::Display => "Display", | |
| Trait::Octal => "Octal", | |
| Trait::LowerHex => "LowerHex", | |
| Trait::UpperHex => "UpperHex", | |
| Trait::Pointer => "Pointer", | |
| Trait::Binary => "Binary", | |
| Trait::LowerExp => "LowerExp", | |
| Trait::UpperExp => "UpperExp", | |
| }; | |
| let ident = Ident::new(trait_name, Span::call_site()); | |
| tokens.extend(quote!(::core::fmt::#ident)); | |
| } | |
| } |