1 use crate::ast::Field;
2 use crate::attr::{Display, Trait};
3 use proc_macro2::TokenTree;
4 use quote::{format_ident, quote_spanned};
5 use std::collections::{BTreeSet as Set, HashMap as Map};
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 mut member_index = Map::new();
16         for (i, field) in fields.iter().enumerate() {
17             member_index.insert(&field.member, i);
18         }
19 
20         let span = self.fmt.span();
21         let fmt = self.fmt.value();
22         let mut read = fmt.as_str();
23         let mut out = String::new();
24         let mut args = self.args.clone();
25         let mut has_bonus_display = false;
26         let mut implied_bounds = Set::new();
27 
28         let mut has_trailing_comma = false;
29         if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
30             if punct.as_char() == ',' {
31                 has_trailing_comma = true;
32             }
33         }
34 
35         while let Some(brace) = read.find('{') {
36             out += &read[..brace + 1];
37             read = &read[brace + 1..];
38             if read.starts_with('{') {
39                 out.push('{');
40                 read = &read[1..];
41                 continue;
42             }
43             let next = match read.chars().next() {
44                 Some(next) => next,
45                 None => return,
46             };
47             let member = match next {
48                 '0'..='9' => {
49                     let int = take_int(&mut read);
50                     let member = match int.parse::<u32>() {
51                         Ok(index) => Member::Unnamed(Index { index, span }),
52                         Err(_) => return,
53                     };
54                     if !member_index.contains_key(&member) {
55                         out += &int;
56                         continue;
57                     }
58                     member
59                 }
60                 'a'..='z' | 'A'..='Z' | '_' => {
61                     let mut ident = take_ident(&mut read);
62                     ident.set_span(span);
63                     Member::Named(ident)
64                 }
65                 _ => continue,
66             };
67             if let Some(&field) = member_index.get(&member) {
68                 let end_spec = match read.find('}') {
69                     Some(end_spec) => end_spec,
70                     None => return,
71                 };
72                 let bound = match read[..end_spec].chars().next_back() {
73                     Some('?') => Trait::Debug,
74                     Some('o') => Trait::Octal,
75                     Some('x') => Trait::LowerHex,
76                     Some('X') => Trait::UpperHex,
77                     Some('p') => Trait::Pointer,
78                     Some('b') => Trait::Binary,
79                     Some('e') => Trait::LowerExp,
80                     Some('E') => Trait::UpperExp,
81                     Some(_) | None => Trait::Display,
82                 };
83                 implied_bounds.insert((field, bound));
84             }
85             let local = match &member {
86                 Member::Unnamed(index) => format_ident!("_{}", index),
87                 Member::Named(ident) => ident.clone(),
88             };
89             let mut formatvar = local.clone();
90             if formatvar.to_string().starts_with("r#") {
91                 formatvar = format_ident!("r_{}", formatvar);
92             }
93             if formatvar.to_string().starts_with('_') {
94                 // Work around leading underscore being rejected by 1.40 and
95                 // older compilers. https://github.com/rust-lang/rust/pull/66847
96                 formatvar = format_ident!("field_{}", formatvar);
97             }
98             out += &formatvar.to_string();
99             if !named_args.insert(formatvar.clone()) {
100                 // Already specified in the format argument list.
101                 continue;
102             }
103             if !has_trailing_comma {
104                 args.extend(quote_spanned!(span=> ,));
105             }
106             args.extend(quote_spanned!(span=> #formatvar = #local));
107             if read.starts_with('}') && member_index.contains_key(&member) {
108                 has_bonus_display = true;
109                 args.extend(quote_spanned!(span=> .as_display()));
110             }
111             has_trailing_comma = false;
112         }
113 
114         out += read;
115         self.fmt = LitStr::new(&out, self.fmt.span());
116         self.args = args;
117         self.has_bonus_display = has_bonus_display;
118         self.implied_bounds = implied_bounds;
119     }
120 }
121 
explicit_named_args(input: ParseStream) -> Result<Set<Ident>>122 fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
123     let mut named_args = Set::new();
124 
125     while !input.is_empty() {
126         if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
127             input.parse::<Token![,]>()?;
128             let ident = input.call(Ident::parse_any)?;
129             input.parse::<Token![=]>()?;
130             named_args.insert(ident);
131         } else {
132             input.parse::<TokenTree>()?;
133         }
134     }
135 
136     Ok(named_args)
137 }
138 
take_int(read: &mut &str) -> String139 fn take_int(read: &mut &str) -> String {
140     let mut int = String::new();
141     for (i, ch) in read.char_indices() {
142         match ch {
143             '0'..='9' => int.push(ch),
144             _ => {
145                 *read = &read[i..];
146                 break;
147             }
148         }
149     }
150     int
151 }
152 
take_ident(read: &mut &str) -> Ident153 fn take_ident(read: &mut &str) -> Ident {
154     let mut ident = String::new();
155     let raw = read.starts_with("r#");
156     if raw {
157         ident.push_str("r#");
158         *read = &read[2..];
159     }
160     for (i, ch) in read.char_indices() {
161         match ch {
162             'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
163             _ => {
164                 *read = &read[i..];
165                 break;
166             }
167         }
168     }
169     Ident::parse_any.parse_str(&ident).unwrap()
170 }
171