| 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 |  | 
|---|