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