xref: /third_party/rust/crates/syn/codegen/src/debug.rs (revision fad3a1d3)
1use crate::{cfg, file, lookup};
2use anyhow::Result;
3use proc_macro2::{Ident, Span, TokenStream};
4use quote::{format_ident, quote};
5use std::collections::BTreeSet as Set;
6use syn_codegen::{Data, Definitions, Node, Type};
7
8const DEBUG_SRC: &str = "src/gen/debug.rs";
9
10fn syntax_tree_enum<'a>(
11    enum_name: &str,
12    variant_name: &str,
13    fields: &'a [Type],
14) -> Option<&'a str> {
15    if fields.len() != 1 {
16        return None;
17    }
18    const WHITELIST: &[(&str, &str)] = &[
19        ("Meta", "Path"),
20        ("Pat", "Const"),
21        ("Pat", "Lit"),
22        ("Pat", "Macro"),
23        ("Pat", "Path"),
24        ("Pat", "Range"),
25        ("PathArguments", "AngleBracketed"),
26        ("PathArguments", "Parenthesized"),
27        ("Stmt", "Local"),
28        ("TypeParamBound", "Lifetime"),
29        ("Visibility", "Public"),
30        ("Visibility", "Restricted"),
31    ];
32    match &fields[0] {
33        Type::Syn(ty)
34            if WHITELIST.contains(&(enum_name, variant_name))
35                || enum_name.to_owned() + variant_name == *ty =>
36        {
37            Some(ty)
38        }
39        _ => None,
40    }
41}
42
43fn expand_impl_body(
44    defs: &Definitions,
45    node: &Node,
46    syntax_tree_variants: &Set<&str>,
47) -> TokenStream {
48    let type_name = &node.ident;
49    let ident = Ident::new(type_name, Span::call_site());
50    let is_syntax_tree_variant = syntax_tree_variants.contains(type_name.as_str());
51
52    let body = match &node.data {
53        Data::Enum(variants) if variants.is_empty() => quote!(match *self {}),
54        Data::Enum(variants) => {
55            assert!(!is_syntax_tree_variant);
56            let arms = variants.iter().map(|(variant_name, fields)| {
57                let variant = Ident::new(variant_name, Span::call_site());
58                if fields.is_empty() {
59                    quote! {
60                        #ident::#variant => formatter.write_str(#variant_name),
61                    }
62                } else {
63                    let mut cfg = None;
64                    if node.ident == "Expr" {
65                        if let Type::Syn(ty) = &fields[0] {
66                            if !lookup::node(defs, ty).features.any.contains("derive") {
67                                cfg = Some(quote!(#[cfg(feature = "full")]));
68                            }
69                        }
70                    }
71                    if syntax_tree_enum(type_name, variant_name, fields).is_some() {
72                        quote! {
73                            #cfg
74                            #ident::#variant(v0) => v0.debug(formatter, #variant_name),
75                        }
76                    } else {
77                        let pats = (0..fields.len())
78                            .map(|i| format_ident!("v{}", i))
79                            .collect::<Vec<_>>();
80                        quote! {
81                            #cfg
82                            #ident::#variant(#(#pats),*) => {
83                                let mut formatter = formatter.debug_tuple(#variant_name);
84                                #(formatter.field(#pats);)*
85                                formatter.finish()
86                            }
87                        }
88                    }
89                }
90            });
91            let nonexhaustive = if node.ident == "Expr" {
92                Some(quote! {
93                    #[cfg(not(feature = "full"))]
94                    _ => unreachable!(),
95                })
96            } else {
97                None
98            };
99            let prefix = format!("{}::", type_name);
100            quote! {
101                formatter.write_str(#prefix)?;
102                match self {
103                    #(#arms)*
104                    #nonexhaustive
105                }
106            }
107        }
108        Data::Struct(fields) => {
109            let type_name = if is_syntax_tree_variant {
110                quote!(name)
111            } else {
112                quote!(#type_name)
113            };
114            let fields = fields.keys().map(|f| {
115                let ident = Ident::new(f, Span::call_site());
116                quote! {
117                    formatter.field(#f, &self.#ident);
118                }
119            });
120            quote! {
121                let mut formatter = formatter.debug_struct(#type_name);
122                #(#fields)*
123                formatter.finish()
124            }
125        }
126        Data::Private => unreachable!(),
127    };
128
129    if is_syntax_tree_variant {
130        quote! {
131            impl #ident {
132                fn debug(&self, formatter: &mut fmt::Formatter, name: &str) -> fmt::Result {
133                    #body
134                }
135            }
136            self.debug(formatter, #type_name)
137        }
138    } else {
139        body
140    }
141}
142
143fn expand_impl(defs: &Definitions, node: &Node, syntax_tree_variants: &Set<&str>) -> TokenStream {
144    let manual_debug = node.data == Data::Private || node.ident == "LitBool";
145    if manual_debug {
146        return TokenStream::new();
147    }
148
149    let ident = Ident::new(&node.ident, Span::call_site());
150    let cfg_features = cfg::features(&node.features, "extra-traits");
151    let body = expand_impl_body(defs, node, syntax_tree_variants);
152    let formatter = match &node.data {
153        Data::Enum(variants) if variants.is_empty() => quote!(_formatter),
154        _ => quote!(formatter),
155    };
156
157    quote! {
158        #cfg_features
159        impl Debug for #ident {
160            fn fmt(&self, #formatter: &mut fmt::Formatter) -> fmt::Result {
161                #body
162            }
163        }
164    }
165}
166
167pub fn generate(defs: &Definitions) -> Result<()> {
168    let mut syntax_tree_variants = Set::new();
169    for node in &defs.types {
170        if let Data::Enum(variants) = &node.data {
171            let enum_name = &node.ident;
172            for (variant_name, fields) in variants {
173                if let Some(inner) = syntax_tree_enum(enum_name, variant_name, fields) {
174                    syntax_tree_variants.insert(inner);
175                }
176            }
177        }
178    }
179
180    let mut impls = TokenStream::new();
181    for node in &defs.types {
182        impls.extend(expand_impl(defs, node, &syntax_tree_variants));
183    }
184
185    file::write(
186        DEBUG_SRC,
187        quote! {
188            use crate::*;
189            use std::fmt::{self, Debug};
190
191            #impls
192        },
193    )?;
194
195    Ok(())
196}
197