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`. |
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 += ∫ |
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 | |
125 | fn 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 | |
142 | fn 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 | |
156 | fn 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 | |