1 // NOTE: Most code in this file is taken straight from `thiserror`.
2 use std::collections::HashSet as Set;
3 use std::iter::FromIterator;
4 
5 use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
6 use quote::{format_ident, quote, quote_spanned, ToTokens};
7 use syn::ext::IdentExt;
8 use syn::parse::{ParseStream, Parser};
9 use syn::{braced, bracketed, parenthesized, Ident, Index, LitStr, Member, Result, Token};
10 
11 #[derive(Clone)]
12 pub struct Display {
13     pub fmt: LitStr,
14     pub args: TokenStream,
15     pub has_bonus_display: bool,
16 }
17 
18 impl ToTokens for Display {
to_tokens(&self, tokens: &mut TokenStream)19     fn to_tokens(&self, tokens: &mut TokenStream) {
20         let fmt = &self.fmt;
21         let args = &self.args;
22         tokens.extend(quote! {
23             write!(__formatter, #fmt #args)
24         });
25     }
26 }
27 
28 impl Display {
29     // Transform `"error {var}"` to `"error {}", var`.
expand_shorthand(&mut self, members: &Set<Member>)30     pub fn expand_shorthand(&mut self, members: &Set<Member>) {
31         let raw_args = self.args.clone();
32         let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
33 
34         let span = self.fmt.span();
35         let fmt = self.fmt.value();
36         let mut read = fmt.as_str();
37         let mut out = String::new();
38         let mut args = self.args.clone();
39         let mut has_bonus_display = false;
40 
41         let mut has_trailing_comma = false;
42         if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
43             if punct.as_char() == ',' {
44                 has_trailing_comma = true;
45             }
46         }
47 
48         while let Some(brace) = read.find('{') {
49             out += &read[..brace + 1];
50             read = &read[brace + 1..];
51             if read.starts_with('{') {
52                 out.push('{');
53                 read = &read[1..];
54                 continue;
55             }
56             let next = match read.chars().next() {
57                 Some(next) => next,
58                 None => return,
59             };
60             let member = match next {
61                 '0'..='9' => {
62                     let int = take_int(&mut read);
63                     let member = match int.parse::<u32>() {
64                         Ok(index) => Member::Unnamed(Index { index, span }),
65                         Err(_) => return,
66                     };
67                     if !members.contains(&member) {
68                         out += &int;
69                         continue;
70                     }
71                     member
72                 }
73                 'a'..='z' | 'A'..='Z' | '_' => {
74                     let mut ident = take_ident(&mut read);
75                     ident.set_span(span);
76                     Member::Named(ident)
77                 }
78                 _ => continue,
79             };
80             let local = match &member {
81                 Member::Unnamed(index) => format_ident!("_{}", index),
82                 Member::Named(ident) => ident.clone(),
83             };
84             let mut formatvar = local.clone();
85             if formatvar.to_string().starts_with("r#") {
86                 formatvar = format_ident!("r_{}", formatvar);
87             }
88             if formatvar.to_string().starts_with('_') {
89                 // Work around leading underscore being rejected by 1.40 and
90                 // older compilers. https://github.com/rust-lang/rust/pull/66847
91                 formatvar = format_ident!("field_{}", formatvar);
92             }
93             out += &formatvar.to_string();
94             if !named_args.insert(formatvar.clone()) {
95                 // Already specified in the format argument list.
96                 continue;
97             }
98             if !has_trailing_comma {
99                 args.extend(quote_spanned!(span=> ,));
100             }
101             args.extend(quote_spanned!(span=> #formatvar = #local));
102             if read.starts_with('}') && members.contains(&member) {
103                 has_bonus_display = true;
104                 // args.extend(quote_spanned!(span=> .as_display()));
105             }
106             has_trailing_comma = false;
107         }
108 
109         out += read;
110         self.fmt = LitStr::new(&out, self.fmt.span());
111         self.args = args;
112         self.has_bonus_display = has_bonus_display;
113     }
114 }
115 
explicit_named_args(input: ParseStream) -> Result<Set<Ident>>116 fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
117     let mut named_args = Set::new();
118 
119     while !input.is_empty() {
120         if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
121             input.parse::<Token![,]>()?;
122             let ident = input.call(Ident::parse_any)?;
123             input.parse::<Token![=]>()?;
124             named_args.insert(ident);
125         } else {
126             input.parse::<TokenTree>()?;
127         }
128     }
129 
130     Ok(named_args)
131 }
132 
take_int(read: &mut &str) -> String133 fn take_int(read: &mut &str) -> String {
134     let mut int = String::new();
135     for (i, ch) in read.char_indices() {
136         match ch {
137             '0'..='9' => int.push(ch),
138             _ => {
139                 *read = &read[i..];
140                 break;
141             }
142         }
143     }
144     int
145 }
146 
take_ident(read: &mut &str) -> Ident147 fn take_ident(read: &mut &str) -> Ident {
148     let mut ident = String::new();
149     let raw = read.starts_with("r#");
150     if raw {
151         ident.push_str("r#");
152         *read = &read[2..];
153     }
154     for (i, ch) in read.char_indices() {
155         match ch {
156             'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
157             _ => {
158                 *read = &read[i..];
159                 break;
160             }
161         }
162     }
163     Ident::parse_any.parse_str(&ident).unwrap()
164 }
165 
parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream>166 pub fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> {
167     let mut tokens = Vec::new();
168     while !input.is_empty() {
169         if begin_expr && input.peek(Token![.]) {
170             if input.peek2(Ident) {
171                 input.parse::<Token![.]>()?;
172                 begin_expr = false;
173                 continue;
174             }
175             if input.peek2(syn::LitInt) {
176                 input.parse::<Token![.]>()?;
177                 let int: Index = input.parse()?;
178                 let ident = format_ident!("_{}", int.index, span = int.span);
179                 tokens.push(TokenTree::Ident(ident));
180                 begin_expr = false;
181                 continue;
182             }
183         }
184 
185         begin_expr = input.peek(Token![break])
186             || input.peek(Token![continue])
187             || input.peek(Token![if])
188             || input.peek(Token![in])
189             || input.peek(Token![match])
190             || input.peek(Token![mut])
191             || input.peek(Token![return])
192             || input.peek(Token![while])
193             || input.peek(Token![+])
194             || input.peek(Token![&])
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 
208         let token: TokenTree = if input.peek(syn::token::Paren) {
209             let content;
210             let delimiter = parenthesized!(content in input);
211             let nested = parse_token_expr(&content, true)?;
212             let mut group = Group::new(Delimiter::Parenthesis, nested);
213             group.set_span(delimiter.span.join());
214             TokenTree::Group(group)
215         } else if input.peek(syn::token::Brace) {
216             let content;
217             let delimiter = braced!(content in input);
218             let nested = parse_token_expr(&content, true)?;
219             let mut group = Group::new(Delimiter::Brace, nested);
220             group.set_span(delimiter.span.join());
221             TokenTree::Group(group)
222         } else if input.peek(syn::token::Bracket) {
223             let content;
224             let delimiter = bracketed!(content in input);
225             let nested = parse_token_expr(&content, true)?;
226             let mut group = Group::new(Delimiter::Bracket, nested);
227             group.set_span(delimiter.span.join());
228             TokenTree::Group(group)
229         } else {
230             input.parse()?
231         };
232         tokens.push(token);
233     }
234     Ok(TokenStream::from_iter(tokens))
235 }
236