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 | 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 += ∫ |
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 | |
122 | fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> { |
123 | let mut named_args: BTreeSet = Set::new(); |
124 | |
125 | while !input.is_empty() { |
126 | if input.peek(Token![,]) && input.peek2(token:Ident::peek_any) && input.peek3(Token![=]) { |
127 | input.parse::<Token![,]>()?; |
128 | let ident: Ident = input.call(function: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 | |
139 | fn take_int(read: &mut &str) -> String { |
140 | let mut int: String = String::new(); |
141 | for (i: usize, ch: char) 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 | |
153 | fn take_ident(read: &mut &str) -> Ident { |
154 | let mut ident: String = String::new(); |
155 | let raw: bool = read.starts_with("r#" ); |
156 | if raw { |
157 | ident.push_str(string:"r#" ); |
158 | *read = &read[2..]; |
159 | } |
160 | for (i: usize, ch: char) 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 | |