| 1 | use crate::ast::Field; |
| 2 | use crate::attr::{Display, Trait}; |
| 3 | use crate::scan_expr::scan_expr; |
| 4 | use proc_macro2::{TokenStream, TokenTree}; |
| 5 | use quote::{format_ident, quote, quote_spanned}; |
| 6 | use std::collections::{BTreeSet as Set, HashMap as Map}; |
| 7 | use syn::ext::IdentExt; |
| 8 | use syn::parse::discouraged::Speculative; |
| 9 | use syn::parse::{ParseStream, Parser}; |
| 10 | use syn::{Expr, Ident, Index, LitStr, Member, Result, Token}; |
| 11 | |
| 12 | impl Display<'_> { |
| 13 | // Transform `"error {var}"` to `"error {}", var`. |
| 14 | pub fn expand_shorthand(&mut self, fields: &[Field]) { |
| 15 | let raw_args = self.args.clone(); |
| 16 | let mut named_args = explicit_named_args.parse2(raw_args).unwrap().named; |
| 17 | let mut member_index = Map::new(); |
| 18 | for (i, field) in fields.iter().enumerate() { |
| 19 | member_index.insert(&field.member, i); |
| 20 | } |
| 21 | |
| 22 | let span = self.fmt.span(); |
| 23 | let fmt = self.fmt.value(); |
| 24 | let mut read = fmt.as_str(); |
| 25 | let mut out = String::new(); |
| 26 | let mut args = self.args.clone(); |
| 27 | let mut has_bonus_display = false; |
| 28 | let mut implied_bounds = Set::new(); |
| 29 | |
| 30 | let mut has_trailing_comma = false; |
| 31 | if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() { |
| 32 | if punct.as_char() == ',' { |
| 33 | has_trailing_comma = true; |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | self.requires_fmt_machinery = self.requires_fmt_machinery || fmt.contains('}' ); |
| 38 | |
| 39 | while let Some(brace) = read.find('{' ) { |
| 40 | self.requires_fmt_machinery = true; |
| 41 | out += &read[..brace + 1]; |
| 42 | read = &read[brace + 1..]; |
| 43 | if read.starts_with('{' ) { |
| 44 | out.push('{' ); |
| 45 | read = &read[1..]; |
| 46 | continue; |
| 47 | } |
| 48 | let next = match read.chars().next() { |
| 49 | Some(next) => next, |
| 50 | None => return, |
| 51 | }; |
| 52 | let member = match next { |
| 53 | '0' ..='9' => { |
| 54 | let int = take_int(&mut read); |
| 55 | let member = match int.parse::<u32>() { |
| 56 | Ok(index) => Member::Unnamed(Index { index, span }), |
| 57 | Err(_) => return, |
| 58 | }; |
| 59 | if !member_index.contains_key(&member) { |
| 60 | out += ∫ |
| 61 | continue; |
| 62 | } |
| 63 | member |
| 64 | } |
| 65 | 'a' ..='z' | 'A' ..='Z' | '_' => { |
| 66 | let mut ident = take_ident(&mut read); |
| 67 | ident.set_span(span); |
| 68 | Member::Named(ident) |
| 69 | } |
| 70 | _ => continue, |
| 71 | }; |
| 72 | if let Some(&field) = member_index.get(&member) { |
| 73 | let end_spec = match read.find('}' ) { |
| 74 | Some(end_spec) => end_spec, |
| 75 | None => return, |
| 76 | }; |
| 77 | let bound = match read[..end_spec].chars().next_back() { |
| 78 | Some('?' ) => Trait::Debug, |
| 79 | Some('o' ) => Trait::Octal, |
| 80 | Some('x' ) => Trait::LowerHex, |
| 81 | Some('X' ) => Trait::UpperHex, |
| 82 | Some('p' ) => Trait::Pointer, |
| 83 | Some('b' ) => Trait::Binary, |
| 84 | Some('e' ) => Trait::LowerExp, |
| 85 | Some('E' ) => Trait::UpperExp, |
| 86 | Some(_) | None => Trait::Display, |
| 87 | }; |
| 88 | implied_bounds.insert((field, bound)); |
| 89 | } |
| 90 | let local = match &member { |
| 91 | Member::Unnamed(index) => format_ident!("_ {}" , index), |
| 92 | Member::Named(ident) => ident.clone(), |
| 93 | }; |
| 94 | let mut formatvar = local.clone(); |
| 95 | if formatvar.to_string().starts_with("r#" ) { |
| 96 | formatvar = format_ident!("r_ {}" , 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 | struct FmtArguments { |
| 123 | named: Set<Ident>, |
| 124 | unnamed: bool, |
| 125 | } |
| 126 | |
| 127 | #[allow (clippy::unnecessary_wraps)] |
| 128 | fn explicit_named_args(input: ParseStream) -> Result<FmtArguments> { |
| 129 | let ahead: ParseBuffer<'_> = input.fork(); |
| 130 | if let Ok(set: FmtArguments) = try_explicit_named_args(&ahead) { |
| 131 | input.advance_to(&ahead); |
| 132 | return Ok(set); |
| 133 | } |
| 134 | |
| 135 | let ahead: ParseBuffer<'_> = input.fork(); |
| 136 | if let Ok(set: FmtArguments) = fallback_explicit_named_args(&ahead) { |
| 137 | input.advance_to(&ahead); |
| 138 | return Ok(set); |
| 139 | } |
| 140 | |
| 141 | input.parse::<TokenStream>().unwrap(); |
| 142 | Ok(FmtArguments { |
| 143 | named: Set::new(), |
| 144 | unnamed: false, |
| 145 | }) |
| 146 | } |
| 147 | |
| 148 | fn try_explicit_named_args(input: ParseStream) -> Result<FmtArguments> { |
| 149 | let mut syn_full = None; |
| 150 | let mut args = FmtArguments { |
| 151 | named: Set::new(), |
| 152 | unnamed: false, |
| 153 | }; |
| 154 | |
| 155 | while !input.is_empty() { |
| 156 | input.parse::<Token![,]>()?; |
| 157 | if input.is_empty() { |
| 158 | break; |
| 159 | } |
| 160 | if input.peek(Ident::peek_any) && input.peek2(Token![=]) && !input.peek2(Token![==]) { |
| 161 | let ident = input.call(Ident::parse_any)?; |
| 162 | input.parse::<Token![=]>()?; |
| 163 | args.named.insert(ident); |
| 164 | } else { |
| 165 | args.unnamed = true; |
| 166 | } |
| 167 | if *syn_full.get_or_insert_with(is_syn_full) { |
| 168 | let ahead = input.fork(); |
| 169 | if ahead.parse::<Expr>().is_ok() { |
| 170 | input.advance_to(&ahead); |
| 171 | continue; |
| 172 | } |
| 173 | } |
| 174 | scan_expr(input)?; |
| 175 | } |
| 176 | |
| 177 | Ok(args) |
| 178 | } |
| 179 | |
| 180 | fn fallback_explicit_named_args(input: ParseStream) -> Result<FmtArguments> { |
| 181 | let mut args: FmtArguments = FmtArguments { |
| 182 | named: Set::new(), |
| 183 | unnamed: false, |
| 184 | }; |
| 185 | |
| 186 | while !input.is_empty() { |
| 187 | if input.peek(Token![,]) |
| 188 | && input.peek2(token:Ident::peek_any) |
| 189 | && input.peek3(Token![=]) |
| 190 | && !input.peek3(Token![==]) |
| 191 | { |
| 192 | input.parse::<Token![,]>()?; |
| 193 | let ident: Ident = input.call(function:Ident::parse_any)?; |
| 194 | input.parse::<Token![=]>()?; |
| 195 | args.named.insert(ident); |
| 196 | } else { |
| 197 | input.parse::<TokenTree>()?; |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | Ok(args) |
| 202 | } |
| 203 | |
| 204 | fn is_syn_full() -> bool { |
| 205 | // Expr::Block contains syn::Block which contains Vec<syn::Stmt>. In the |
| 206 | // current version of Syn, syn::Stmt is exhaustive and could only plausibly |
| 207 | // represent `trait Trait {}` in Stmt::Item which contains syn::Item. Most |
| 208 | // of the point of syn's non-"full" mode is to avoid compiling Item and the |
| 209 | // entire expansive syntax tree it comprises. So the following expression |
| 210 | // being parsed to Expr::Block is a reliable indication that "full" is |
| 211 | // enabled. |
| 212 | let test: TokenStream = quote!({ |
| 213 | trait Trait {} |
| 214 | }); |
| 215 | match syn::parse2(tokens:test) { |
| 216 | Ok(Expr::Verbatim(_)) | Err(_) => false, |
| 217 | Ok(Expr::Block(_)) => true, |
| 218 | Ok(_) => unreachable!(), |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | fn take_int(read: &mut &str) -> String { |
| 223 | let mut int: String = String::new(); |
| 224 | for (i: usize, ch: char) in read.char_indices() { |
| 225 | match ch { |
| 226 | '0' ..='9' => int.push(ch), |
| 227 | _ => { |
| 228 | *read = &read[i..]; |
| 229 | break; |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | int |
| 234 | } |
| 235 | |
| 236 | fn take_ident(read: &mut &str) -> Ident { |
| 237 | let mut ident: String = String::new(); |
| 238 | let raw: bool = read.starts_with("r#" ); |
| 239 | if raw { |
| 240 | ident.push_str(string:"r#" ); |
| 241 | *read = &read[2..]; |
| 242 | } |
| 243 | for (i: usize, ch: char) in read.char_indices() { |
| 244 | match ch { |
| 245 | 'a' ..='z' | 'A' ..='Z' | '0' ..='9' | '_' => ident.push(ch), |
| 246 | _ => { |
| 247 | *read = &read[i..]; |
| 248 | break; |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | Ident::parse_any.parse_str(&ident).unwrap() |
| 253 | } |
| 254 | |