1 use crate::{file, lookup};
2 use anyhow::Result;
3 use proc_macro2::{Ident, Span, TokenStream};
4 use quote::{format_ident, quote};
5 use syn::Index;
6 use syn_codegen::{Data, Definitions, Node, Type};
7
8 const DEBUG_SRC: &str = "../tests/debug/gen.rs";
9
rust_type(ty: &Type) -> TokenStream10 fn rust_type(ty: &Type) -> TokenStream {
11 match ty {
12 Type::Syn(ty) => {
13 let ident = Ident::new(ty, Span::call_site());
14 quote!(syn::#ident)
15 }
16 Type::Std(ty) => {
17 let ident = Ident::new(ty, Span::call_site());
18 quote!(#ident)
19 }
20 Type::Ext(ty) => {
21 let ident = Ident::new(ty, Span::call_site());
22 quote!(proc_macro2::#ident)
23 }
24 Type::Token(ty) | Type::Group(ty) => {
25 let ident = Ident::new(ty, Span::call_site());
26 quote!(syn::token::#ident)
27 }
28 Type::Punctuated(ty) => {
29 let element = rust_type(&ty.element);
30 let punct = Ident::new(&ty.punct, Span::call_site());
31 quote!(syn::punctuated::Punctuated<#element, #punct>)
32 }
33 Type::Option(ty) => {
34 let inner = rust_type(ty);
35 quote!(Option<#inner>)
36 }
37 Type::Box(ty) => {
38 let inner = rust_type(ty);
39 quote!(Box<#inner>)
40 }
41 Type::Vec(ty) => {
42 let inner = rust_type(ty);
43 quote!(Vec<#inner>)
44 }
45 Type::Tuple(ty) => {
46 let inner = ty.iter().map(rust_type);
47 quote!((#(#inner,)*))
48 }
49 }
50 }
51
is_printable(ty: &Type) -> bool52 fn is_printable(ty: &Type) -> bool {
53 match ty {
54 Type::Ext(name) => name != "Span",
55 Type::Box(ty) => is_printable(ty),
56 Type::Tuple(ty) => ty.iter().any(is_printable),
57 Type::Token(_) | Type::Group(_) => false,
58 Type::Syn(name) => name != "Reserved",
59 Type::Std(_) | Type::Punctuated(_) | Type::Option(_) | Type::Vec(_) => true,
60 }
61 }
62
format_field(val: &TokenStream, ty: &Type) -> Option<TokenStream>63 fn format_field(val: &TokenStream, ty: &Type) -> Option<TokenStream> {
64 if !is_printable(ty) {
65 return None;
66 }
67 let format = match ty {
68 Type::Option(ty) => {
69 let inner = quote!(_val);
70 let format = format_field(&inner, ty).map(|format| {
71 quote! {
72 formatter.write_str("(")?;
73 Debug::fmt(#format, formatter)?;
74 formatter.write_str(")")?;
75 }
76 });
77 let ty = rust_type(ty);
78 quote!({
79 #[derive(RefCast)]
80 #[repr(transparent)]
81 struct Print(Option<#ty>);
82 impl Debug for Print {
83 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
84 match &self.0 {
85 Some(#inner) => {
86 formatter.write_str("Some")?;
87 #format
88 Ok(())
89 }
90 None => formatter.write_str("None"),
91 }
92 }
93 }
94 Print::ref_cast(#val)
95 })
96 }
97 Type::Tuple(ty) => {
98 let printable: Vec<TokenStream> = ty
99 .iter()
100 .enumerate()
101 .filter_map(|(i, ty)| {
102 let index = Index::from(i);
103 let val = quote!(&#val.#index);
104 format_field(&val, ty)
105 })
106 .collect();
107 if printable.len() == 1 {
108 printable.into_iter().next().unwrap()
109 } else {
110 quote! {
111 &(#(#printable),*)
112 }
113 }
114 }
115 _ => quote! { Lite(#val) },
116 };
117 Some(format)
118 }
119
syntax_tree_enum<'a>(outer: &str, inner: &str, fields: &'a [Type]) -> Option<&'a str>120 fn syntax_tree_enum<'a>(outer: &str, inner: &str, fields: &'a [Type]) -> Option<&'a str> {
121 if fields.len() != 1 {
122 return None;
123 }
124 const WHITELIST: &[&str] = &["PathArguments", "Visibility"];
125 match &fields[0] {
126 Type::Syn(ty) if WHITELIST.contains(&outer) || outer.to_owned() + inner == *ty => Some(ty),
127 _ => None,
128 }
129 }
130
expand_impl_body(defs: &Definitions, node: &Node, name: &str) -> TokenStream131 fn expand_impl_body(defs: &Definitions, node: &Node, name: &str) -> TokenStream {
132 let ident = Ident::new(&node.ident, Span::call_site());
133
134 match &node.data {
135 Data::Enum(variants) => {
136 let arms = variants.iter().map(|(v, fields)| {
137 let variant = Ident::new(v, Span::call_site());
138 if fields.is_empty() {
139 quote! {
140 syn::#ident::#variant => formatter.write_str(#v),
141 }
142 } else if let Some(inner) = syntax_tree_enum(name, v, fields) {
143 let path = format!("{}::{}", name, v);
144 let format = expand_impl_body(defs, lookup::node(defs, inner), &path);
145 quote! {
146 syn::#ident::#variant(_val) => {
147 #format
148 }
149 }
150 } else if fields.len() == 1 {
151 let val = quote!(_val);
152 let format = if variant == "Verbatim" {
153 Some(quote! {
154 formatter.write_str("(`")?;
155 Display::fmt(#val, formatter)?;
156 formatter.write_str("`)")?;
157 })
158 } else {
159 let ty = &fields[0];
160 format_field(&val, ty).map(|format| {
161 quote! {
162 formatter.write_str("(")?;
163 Debug::fmt(#format, formatter)?;
164 formatter.write_str(")")?;
165 }
166 })
167 };
168 quote! {
169 syn::#ident::#variant(_val) => {
170 formatter.write_str(#v)?;
171 #format
172 Ok(())
173 }
174 }
175 } else {
176 let pats = (0..fields.len()).map(|i| format_ident!("_v{}", i));
177 let fields = fields.iter().enumerate().filter_map(|(i, ty)| {
178 let index = format_ident!("_v{}", i);
179 let val = quote!(#index);
180 let format = format_field(&val, ty)?;
181 Some(quote! {
182 formatter.field(#format);
183 })
184 });
185 quote! {
186 syn::#ident::#variant(#(#pats),*) => {
187 let mut formatter = formatter.debug_tuple(#v);
188 #(#fields)*
189 formatter.finish()
190 }
191 }
192 }
193 });
194 let nonexhaustive = if node.exhaustive {
195 None
196 } else {
197 Some(quote!(_ => unreachable!()))
198 };
199 quote! {
200 match _val {
201 #(#arms)*
202 #nonexhaustive
203 }
204 }
205 }
206 Data::Struct(fields) => {
207 let fields = fields.iter().filter_map(|(f, ty)| {
208 let ident = Ident::new(f, Span::call_site());
209 if let Type::Option(ty) = ty {
210 let inner = quote!(_val);
211 let format = format_field(&inner, ty).map(|format| {
212 quote! {
213 let #inner = &self.0;
214 formatter.write_str("(")?;
215 Debug::fmt(#format, formatter)?;
216 formatter.write_str(")")?;
217 }
218 });
219 let ty = rust_type(ty);
220 Some(quote! {
221 if let Some(val) = &_val.#ident {
222 #[derive(RefCast)]
223 #[repr(transparent)]
224 struct Print(#ty);
225 impl Debug for Print {
226 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
227 formatter.write_str("Some")?;
228 #format
229 Ok(())
230 }
231 }
232 formatter.field(#f, Print::ref_cast(val));
233 }
234 })
235 } else {
236 let val = quote!(&_val.#ident);
237 let format = format_field(&val, ty)?;
238 let mut call = quote! {
239 formatter.field(#f, #format);
240 };
241 if let Type::Vec(_) | Type::Punctuated(_) = ty {
242 call = quote! {
243 if !_val.#ident.is_empty() {
244 #call
245 }
246 };
247 }
248 Some(call)
249 }
250 });
251 quote! {
252 let mut formatter = formatter.debug_struct(#name);
253 #(#fields)*
254 formatter.finish()
255 }
256 }
257 Data::Private => {
258 if node.ident == "LitInt" || node.ident == "LitFloat" {
259 quote! {
260 write!(formatter, "{}", _val)
261 }
262 } else {
263 quote! {
264 write!(formatter, "{:?}", _val.value())
265 }
266 }
267 }
268 }
269 }
270
expand_impl(defs: &Definitions, node: &Node) -> TokenStream271 fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream {
272 if node.ident == "Reserved" {
273 return TokenStream::new();
274 }
275
276 let ident = Ident::new(&node.ident, Span::call_site());
277 let body = expand_impl_body(defs, node, &node.ident);
278
279 quote! {
280 impl Debug for Lite<syn::#ident> {
281 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
282 let _val = &self.value;
283 #body
284 }
285 }
286 }
287 }
288
generate(defs: &Definitions) -> Result<()>289 pub fn generate(defs: &Definitions) -> Result<()> {
290 let mut impls = TokenStream::new();
291 for node in &defs.types {
292 impls.extend(expand_impl(&defs, node));
293 }
294
295 file::write(
296 DEBUG_SRC,
297 quote! {
298 use super::{Lite, RefCast};
299 use std::fmt::{self, Debug, Display};
300
301 #impls
302 },
303 )?;
304
305 Ok(())
306 }
307