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