1 use crate::ast::Field;
2 use crate::attr::Display;
3 use proc_macro2::TokenTree;
4 use quote::{format_ident, quote_spanned};
5 use std::collections::HashSet as Set;
6 use syn::ext::IdentExt;
7 use syn::parse::{ParseStream, Parser};
8 use syn::{Ident, Index, LitStr, Member, Result, Token};
9 
10 impl Display<'_> {
11     // Transform `"error {var}"` to `"error {}", var`.
expand_shorthand(&mut self, fields: &[Field])12     pub fn expand_shorthand(&mut self, fields: &[Field]) {
13         let raw_args = self.args.clone();
14         let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
15         let fields: Set<Member> = fields.iter().map(|f| f.member.clone()).collect();
16 
17         let span = self.fmt.span();
18         let fmt = self.fmt.value();
19         let mut read = fmt.as_str();
20         let mut out = String::new();
21         let mut args = self.args.clone();
22         let mut has_bonus_display = false;
23 
24         let mut has_trailing_comma = false;
25         if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
26             if punct.as_char() == ',' {
27                 has_trailing_comma = true;
28             }
29         }
30 
31         while let Some(brace) = read.find('{') {
32             out += &read[..brace + 1];
33             read = &read[brace + 1..];
34             if read.starts_with('{') {
35                 out.push('{');
36                 read = &read[1..];
37                 continue;
38             }
39             let next = match read.chars().next() {
40                 Some(next) => next,
41                 None => return,
42             };
43             let member = match next {
44                 '0'..='9' => {
45                     let int = take_int(&mut read);
46                     let member = match int.parse::<u32>() {
47                         Ok(index) => Member::Unnamed(Index { index, span }),
48                         Err(_) => return,
49                     };
50                     if !fields.contains(&member) {
51                         out += &int;
52                         continue;
53                     }
54                     member
55                 }
56                 'a'..='z' | 'A'..='Z' | '_' => {
57                     let mut ident = take_ident(&mut read);
58                     ident.set_span(span);
59                     Member::Named(ident)
60                 }
61                 _ => continue,
62             };
63             let local = match &member {
64                 Member::Unnamed(index) => format_ident!("_{}", index),
65                 Member::Named(ident) => ident.clone(),
66             };
67             let mut formatvar = local.clone();
68             if formatvar.to_string().starts_with("r#") {
69                 formatvar = format_ident!("r_{}", formatvar);
70             }
71             if formatvar.to_string().starts_with('_') {
72                 // Work around leading underscore being rejected by 1.40 and
73                 // older compilers. https://github.com/rust-lang/rust/pull/66847
74                 formatvar = format_ident!("field_{}", formatvar);
75             }
76             out += &formatvar.to_string();
77             if !named_args.insert(formatvar.clone()) {
78                 // Already specified in the format argument list.
79                 continue;
80             }
81             if !has_trailing_comma {
82                 args.extend(quote_spanned!(span=> ,));
83             }
84             args.extend(quote_spanned!(span=> #formatvar = #local));
85             if read.starts_with('}') && fields.contains(&member) {
86                 has_bonus_display = true;
87                 args.extend(quote_spanned!(span=> .as_display()));
88             }
89             has_trailing_comma = false;
90         }
91 
92         out += read;
93         self.fmt = LitStr::new(&out, self.fmt.span());
94         self.args = args;
95         self.has_bonus_display = has_bonus_display;
96     }
97 }
98 
explicit_named_args(input: ParseStream) -> Result<Set<Ident>>99 fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
100     let mut named_args = Set::new();
101 
102     while !input.is_empty() {
103         if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
104             input.parse::<Token![,]>()?;
105             let ident = input.call(Ident::parse_any)?;
106             input.parse::<Token![=]>()?;
107             named_args.insert(ident);
108         } else {
109             input.parse::<TokenTree>()?;
110         }
111     }
112 
113     Ok(named_args)
114 }
115 
take_int(read: &mut &str) -> String116 fn take_int(read: &mut &str) -> String {
117     let mut int = String::new();
118     for (i, ch) in read.char_indices() {
119         match ch {
120             '0'..='9' => int.push(ch),
121             _ => {
122                 *read = &read[i..];
123                 break;
124             }
125         }
126     }
127     int
128 }
129 
take_ident(read: &mut &str) -> Ident130 fn take_ident(read: &mut &str) -> Ident {
131     let mut ident = String::new();
132     let raw = read.starts_with("r#");
133     if raw {
134         ident.push_str("r#");
135         *read = &read[2..];
136     }
137     for (i, ch) in read.char_indices() {
138         match ch {
139             'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
140             _ => {
141                 *read = &read[i..];
142                 break;
143             }
144         }
145     }
146     Ident::parse_any.parse_str(&ident).unwrap()
147 }
148