1use crate::operand::{Borrowed, Operand, Owned}; 2use crate::{file, lookup}; 3use anyhow::Result; 4use proc_macro2::{Ident, Span, TokenStream}; 5use quote::{format_ident, quote}; 6use syn::Index; 7use syn_codegen::{Data, Definitions, Node, Type}; 8 9const TESTS_DEBUG_SRC: &str = "tests/debug/gen.rs"; 10 11fn rust_type(ty: &Type) -> TokenStream { 12 match ty { 13 Type::Syn(ty) => { 14 let ident = Ident::new(ty, Span::call_site()); 15 quote!(syn::#ident) 16 } 17 Type::Std(ty) => { 18 let ident = Ident::new(ty, Span::call_site()); 19 quote!(#ident) 20 } 21 Type::Ext(ty) => { 22 let ident = Ident::new(ty, Span::call_site()); 23 quote!(proc_macro2::#ident) 24 } 25 Type::Token(ty) | Type::Group(ty) => { 26 let ident = Ident::new(ty, Span::call_site()); 27 quote!(syn::token::#ident) 28 } 29 Type::Punctuated(ty) => { 30 let element = rust_type(&ty.element); 31 let punct = Ident::new(&ty.punct, Span::call_site()); 32 quote!(syn::punctuated::Punctuated<#element, #punct>) 33 } 34 Type::Option(ty) => { 35 let inner = rust_type(ty); 36 quote!(Option<#inner>) 37 } 38 Type::Box(ty) => { 39 let inner = rust_type(ty); 40 quote!(Box<#inner>) 41 } 42 Type::Vec(ty) => { 43 let inner = rust_type(ty); 44 quote!(Vec<#inner>) 45 } 46 Type::Tuple(ty) => { 47 let inner = ty.iter().map(rust_type); 48 quote!((#(#inner,)*)) 49 } 50 } 51} 52 53fn is_printable(ty: &Type) -> bool { 54 match ty { 55 Type::Ext(name) => name != "Span", 56 Type::Box(ty) => is_printable(ty), 57 Type::Tuple(ty) => ty.iter().any(is_printable), 58 Type::Token(_) | Type::Group(_) => false, 59 Type::Syn(_) | Type::Std(_) | Type::Punctuated(_) | Type::Option(_) | Type::Vec(_) => true, 60 } 61} 62 63fn format_field(val: &Operand, ty: &Type) -> Option<TokenStream> { 64 if !is_printable(ty) { 65 return None; 66 } 67 let format = match ty { 68 Type::Option(ty) => { 69 if let Some(format) = format_field(&Borrowed(quote!(_val)), ty) { 70 let ty = rust_type(ty); 71 let val = val.ref_tokens(); 72 quote!({ 73 #[derive(RefCast)] 74 #[repr(transparent)] 75 struct Print(Option<#ty>); 76 impl Debug for Print { 77 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 78 match &self.0 { 79 Some(_val) => { 80 formatter.write_str("Some(")?; 81 Debug::fmt(#format, formatter)?; 82 formatter.write_str(")")?; 83 Ok(()) 84 } 85 None => formatter.write_str("None"), 86 } 87 } 88 } 89 Print::ref_cast(#val) 90 }) 91 } else { 92 let val = val.tokens(); 93 quote! { 94 &super::Option { present: #val.is_some() } 95 } 96 } 97 } 98 Type::Tuple(ty) => { 99 let printable: Vec<TokenStream> = ty 100 .iter() 101 .enumerate() 102 .filter_map(|(i, ty)| { 103 let index = Index::from(i); 104 let val = val.tokens(); 105 let inner = Owned(quote!(#val.#index)); 106 format_field(&inner, ty) 107 }) 108 .collect(); 109 if printable.len() == 1 { 110 printable.into_iter().next().unwrap() 111 } else { 112 quote! { 113 &(#(#printable),*) 114 } 115 } 116 } 117 _ => { 118 let val = val.ref_tokens(); 119 quote! { Lite(#val) } 120 } 121 }; 122 Some(format) 123} 124 125fn syntax_tree_enum<'a>(outer: &str, inner: &str, fields: &'a [Type]) -> Option<&'a str> { 126 if fields.len() != 1 { 127 return None; 128 } 129 const WHITELIST: &[(&str, &str)] = &[ 130 ("Meta", "Path"), 131 ("PathArguments", "AngleBracketed"), 132 ("PathArguments", "Parenthesized"), 133 ("Stmt", "Local"), 134 ("TypeParamBound", "Lifetime"), 135 ("Visibility", "Public"), 136 ("Visibility", "Restricted"), 137 ]; 138 match &fields[0] { 139 Type::Syn(ty) if WHITELIST.contains(&(outer, inner)) || outer.to_owned() + inner == *ty => { 140 Some(ty) 141 } 142 _ => None, 143 } 144} 145 146fn expand_impl_body(defs: &Definitions, node: &Node, name: &str, val: &Operand) -> TokenStream { 147 let ident = Ident::new(&node.ident, Span::call_site()); 148 149 match &node.data { 150 Data::Enum(variants) if variants.is_empty() => quote!(unreachable!()), 151 Data::Enum(variants) => { 152 let arms = variants.iter().map(|(v, fields)| { 153 let path = format!("{}::{}", name, v); 154 let variant = Ident::new(v, Span::call_site()); 155 if fields.is_empty() { 156 quote! { 157 syn::#ident::#variant => formatter.write_str(#path), 158 } 159 } else if let Some(inner) = syntax_tree_enum(name, v, fields) { 160 let format = expand_impl_body( 161 defs, 162 lookup::node(defs, inner), 163 &path, 164 &Borrowed(quote!(_val)), 165 ); 166 quote! { 167 syn::#ident::#variant(_val) => { 168 #format 169 } 170 } 171 } else if fields.len() == 1 { 172 let val = quote!(_val); 173 let format = if variant == "Verbatim" { 174 Some(quote! { 175 formatter.write_str("(`")?; 176 Display::fmt(#val, formatter)?; 177 formatter.write_str("`)")?; 178 }) 179 } else { 180 let ty = &fields[0]; 181 format_field(&Borrowed(val), ty).map(|format| { 182 quote! { 183 formatter.write_str("(")?; 184 Debug::fmt(#format, formatter)?; 185 formatter.write_str(")")?; 186 } 187 }) 188 }; 189 quote! { 190 syn::#ident::#variant(_val) => { 191 formatter.write_str(#path)?; 192 #format 193 Ok(()) 194 } 195 } 196 } else { 197 let pats = (0..fields.len()).map(|i| format_ident!("_v{}", i)); 198 let fields = fields.iter().enumerate().filter_map(|(i, ty)| { 199 let index = format_ident!("_v{}", i); 200 let val = quote!(#index); 201 let format = format_field(&Borrowed(val), ty)?; 202 Some(quote! { 203 formatter.field(#format); 204 }) 205 }); 206 quote! { 207 syn::#ident::#variant(#(#pats),*) => { 208 let mut formatter = formatter.debug_tuple(#path); 209 #(#fields)* 210 formatter.finish() 211 } 212 } 213 } 214 }); 215 let nonexhaustive = if node.exhaustive { 216 None 217 } else { 218 Some(quote!(_ => unreachable!())) 219 }; 220 let val = val.ref_tokens(); 221 quote! { 222 match #val { 223 #(#arms)* 224 #nonexhaustive 225 } 226 } 227 } 228 Data::Struct(fields) => { 229 let fields = fields.iter().filter_map(|(f, ty)| { 230 let ident = Ident::new(f, Span::call_site()); 231 if let Type::Option(ty) = ty { 232 Some(if let Some(format) = format_field(&Owned(quote!(self.0)), ty) { 233 let val = val.tokens(); 234 let ty = rust_type(ty); 235 quote! { 236 if let Some(val) = &#val.#ident { 237 #[derive(RefCast)] 238 #[repr(transparent)] 239 struct Print(#ty); 240 impl Debug for Print { 241 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 242 formatter.write_str("Some(")?; 243 Debug::fmt(#format, formatter)?; 244 formatter.write_str(")")?; 245 Ok(()) 246 } 247 } 248 formatter.field(#f, Print::ref_cast(val)); 249 } 250 } 251 } else { 252 let val = val.tokens(); 253 quote! { 254 if #val.#ident.is_some() { 255 formatter.field(#f, &Present); 256 } 257 } 258 }) 259 } else { 260 let val = val.tokens(); 261 let inner = Owned(quote!(#val.#ident)); 262 let format = format_field(&inner, ty)?; 263 let mut call = quote! { 264 formatter.field(#f, #format); 265 }; 266 if node.ident == "Block" && f == "stmts" { 267 // Format regardless of whether is_empty(). 268 } else if let Type::Vec(_) | Type::Punctuated(_) = ty { 269 call = quote! { 270 if !#val.#ident.is_empty() { 271 #call 272 } 273 }; 274 } else if let Type::Syn(inner) = ty { 275 for node in &defs.types { 276 if node.ident == *inner { 277 if let Data::Enum(variants) = &node.data { 278 if variants.get("None").map_or(false, Vec::is_empty) { 279 let ty = rust_type(ty); 280 call = quote! { 281 match #val.#ident { 282 #ty::None => {} 283 _ => { #call } 284 } 285 }; 286 } 287 } 288 break; 289 } 290 } 291 } 292 Some(call) 293 } 294 }); 295 quote! { 296 let mut formatter = formatter.debug_struct(#name); 297 #(#fields)* 298 formatter.finish() 299 } 300 } 301 Data::Private => { 302 if node.ident == "LitInt" || node.ident == "LitFloat" { 303 let val = val.ref_tokens(); 304 quote! { 305 write!(formatter, "{}", #val) 306 } 307 } else { 308 let val = val.tokens(); 309 quote! { 310 write!(formatter, "{:?}", #val.value()) 311 } 312 } 313 } 314 } 315} 316 317fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream { 318 let ident = Ident::new(&node.ident, Span::call_site()); 319 let body = expand_impl_body(defs, node, &node.ident, &Owned(quote!(self.value))); 320 let formatter = match &node.data { 321 Data::Enum(variants) if variants.is_empty() => quote!(_formatter), 322 _ => quote!(formatter), 323 }; 324 325 quote! { 326 impl Debug for Lite<syn::#ident> { 327 fn fmt(&self, #formatter: &mut fmt::Formatter) -> fmt::Result { 328 #body 329 } 330 } 331 } 332} 333 334fn expand_token_impl(name: &str, symbol: &str) -> TokenStream { 335 let ident = Ident::new(name, Span::call_site()); 336 let repr = format!("Token![{}]", symbol); 337 338 quote! { 339 impl Debug for Lite<syn::token::#ident> { 340 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 341 formatter.write_str(#repr) 342 } 343 } 344 } 345} 346 347pub fn generate(defs: &Definitions) -> Result<()> { 348 let mut impls = TokenStream::new(); 349 for node in &defs.types { 350 impls.extend(expand_impl(defs, node)); 351 } 352 for (name, symbol) in &defs.tokens { 353 impls.extend(expand_token_impl(name, symbol)); 354 } 355 356 file::write( 357 TESTS_DEBUG_SRC, 358 quote! { 359 // False positive: https://github.com/rust-lang/rust/issues/78586#issuecomment-1722680482 360 #![allow(repr_transparent_external_private_fields)] 361 362 #![allow(clippy::match_wildcard_for_single_variants)] 363 364 use super::{Lite, Present}; 365 use ref_cast::RefCast; 366 use std::fmt::{self, Debug, Display}; 367 368 #impls 369 }, 370 )?; 371 372 Ok(()) 373} 374