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