• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::operand::{Borrowed, Operand, Owned};
2 use crate::{file, lookup};
3 use anyhow::Result;
4 use proc_macro2::{Ident, Span, TokenStream};
5 use quote::{format_ident, quote};
6 use syn::Index;
7 use syn_codegen::{Data, Definitions, Node, Type};
8 
9 const TESTS_DEBUG_SRC: &str = "tests/debug/gen.rs";
10 
rust_type(ty: &Type) -> TokenStream11 fn 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 
is_printable(ty: &Type) -> bool53 fn 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 
format_field(val: &Operand, ty: &Type) -> Option<TokenStream>63 fn 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 
syntax_tree_enum<'a>(outer: &str, inner: &str, fields: &'a [Type]) -> Option<&'a str>125 fn 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 
expand_impl_body(defs: &Definitions, node: &Node, name: &str, val: &Operand) -> TokenStream146 fn 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 
expand_impl(defs: &Definitions, node: &Node) -> TokenStream317 fn 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 
expand_token_impl(name: &str, symbol: &str) -> TokenStream334 fn 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 
generate(defs: &Definitions) -> Result<()>347 pub 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