1#![doc = include_str!("../README.md")]
2
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote, ToTokens};
5use syn::{
6 parse_macro_input, Attribute, DataEnum, DataStruct, DeriveInput, Fields,
7 FieldsNamed, FieldsUnnamed, Ident, Lit, LitStr, Meta, MetaNameValue, NestedMeta, Path, Variant,
8};
9
10#[proc_macro_derive(Dbg, attributes(dbg))]
15pub fn derive_debug(target: proc_macro::TokenStream) -> proc_macro::TokenStream {
16 let item = parse_macro_input!(target as DeriveInput);
17 derive_debug_impl(item).into()
18}
19
20fn derive_debug_impl(item: DeriveInput) -> TokenStream {
21 let name = &item.ident;
22 let (impl_generics, type_generics, where_clause) = &item.generics.split_for_impl();
23
24 let options = match parse_options(&item.attrs, OptionsTarget::DeriveItem) {
25 Ok(options) => options,
26 Err(e) => return e.to_compile_error(),
27 };
28
29 let display_name = if let Some(alias) = options.alias {
30 alias
31 } else {
32 name.to_string()
33 };
34
35 let res = match &item.data {
36 syn::Data::Struct(data) => derive_struct(&display_name, data),
37 syn::Data::Enum(data) => derive_enum(data),
38 syn::Data::Union(data) => Err(syn::Error::new_spanned(
39 data.union_token,
40 "#[derive(Dbg)] not supported on unions",
41 )),
42 };
43
44 match res {
45 Ok(res) => quote! {
46 impl #impl_generics ::std::fmt::Debug for #name #type_generics #where_clause {
47 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
48 #res
49 }
50 }
51 },
52 Err(e) => e.to_compile_error(),
53 }
54}
55
56fn derive_struct(display_name: &str, data: &DataStruct) -> Result<TokenStream, syn::Error> {
57 match &data.fields {
58 Fields::Named(fields) => {
59 let fields = derive_named_fields(fields, true)?;
60 Ok(quote! {
61 f.debug_struct(#display_name)
62 #fields
63 .finish()
64 })
65 }
66 Fields::Unnamed(fields) => {
67 let fields = derive_unnamed_fields(fields, true)?;
68 Ok(quote! {
69 f.debug_tuple(#display_name)
70 #fields
71 .finish()
72 })
73 }
74 Fields::Unit => Ok(quote! {
75 f.debug_struct(#display_name).finish()
76 }),
77 }
78}
79
80fn derive_enum(data: &DataEnum) -> Result<TokenStream, syn::Error> {
81 if data.variants.is_empty() {
82 return Ok(quote! {
83 unsafe { ::core::hint::unreachable_unchecked() }
84 });
85 }
86
87 let variants = derive_enum_variants(data.variants.iter())?;
88
89 Ok(quote! {
90 match self {
91 #variants
92 }
93 })
94}
95
96fn derive_enum_variants<'a>(
97 variants: impl Iterator<Item = &'a Variant>,
98) -> Result<TokenStream, syn::Error> {
99 let mut res = TokenStream::new();
100
101 for variant in variants {
102 let name = &variant.ident;
103
104 let options = parse_options(&variant.attrs, OptionsTarget::EnumVariant)?;
105
106 let display_name = if let Some(alias) = options.alias {
107 alias
108 } else {
109 name.to_string()
110 };
111
112 let derive_variant = match options.print_type {
113 FieldPrintType::Normal => derive_variant(name, &display_name, &variant.fields)?,
114 FieldPrintType::Skip => skip_variant(name, &display_name, &variant.fields)?,
115 _ => return Err(syn::Error::new_spanned(variant, "Internal error")),
116 };
117
118 res.extend(derive_variant);
119 }
120
121 Ok(res)
122}
123
124fn derive_variant(
125 name: &Ident,
126 display_name: &str,
127 fields: &Fields,
128) -> Result<TokenStream, syn::Error> {
129 let match_list = derive_match_list(fields)?;
130
131 match fields {
132 Fields::Named(fields) => {
133 let fields = derive_named_fields(fields, false)?;
134 Ok(quote! {
135 Self::#name #match_list => f.debug_struct(#display_name) #fields .finish(),
136 })
137 }
138 Fields::Unnamed(fields) => {
139 let fields = derive_unnamed_fields(fields, false)?;
140 Ok(quote! {
141 Self::#name #match_list => f.debug_tuple(#display_name) #fields .finish(),
142 })
143 }
144 Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
145 }
146}
147
148fn skip_variant(
149 name: &Ident,
150 display_name: &str,
151 fields: &Fields,
152) -> Result<TokenStream, syn::Error> {
153 match fields {
154 Fields::Named(_) => {
155 Ok(quote! { Self::#name{..} => f.debug_struct(#display_name).finish(), })
156 }
157 Fields::Unnamed(_) => {
158 Ok(quote! { Self::#name(..) => f.debug_tuple(#display_name).finish(), })
159 }
160 Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
161 }
162}
163
164fn derive_match_list(fields: &Fields) -> Result<TokenStream, syn::Error> {
165 match fields {
166 Fields::Named(fields) => {
167 let mut res = TokenStream::new();
168 for field in &fields.named {
169 let name = field.ident.as_ref().unwrap();
170 let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
171
172 match options.print_type {
173 FieldPrintType::Skip => res.extend(quote! { #name: _, }),
174 _ => res.extend(quote! { #name, }),
175 }
176 }
177 Ok(quote! { { #res } })
178 }
179 Fields::Unnamed(fields) => {
180 let mut res = TokenStream::new();
181 for (i, field) in fields.unnamed.iter().enumerate() {
182 let name = format_ident!("field_{}", i);
183 let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
184
185 match options.print_type {
186 FieldPrintType::Skip => res.extend(quote! { _, }),
187 _ => res.extend(quote! { #name, }),
188 }
189 }
190 Ok(quote! { (#res) })
191 }
192 Fields::Unit => Ok(quote! {}),
193 }
194}
195
196fn derive_named_fields(fields: &FieldsNamed, use_self: bool) -> Result<TokenStream, syn::Error> {
197 let mut res = TokenStream::new();
198
199 for field in &fields.named {
200 let name = field.ident.as_ref().unwrap();
201
202 let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
203
204 let name_str = if let Some(alias) = options.alias {
205 alias
206 } else {
207 name.to_string()
208 };
209
210 match options.print_type {
211 FieldPrintType::Normal => {
212 let field_ref = if use_self {
213 quote! { &self.#name }
214 } else {
215 quote! { #name }
216 };
217 res.extend(quote! { .field(#name_str, #field_ref) });
218 }
219 FieldPrintType::Placeholder(placeholder) => {
220 res.extend(quote! { .field(#name_str, &format_args!(#placeholder)) })
221 }
222 FieldPrintType::Format(fmt) => {
223 let field_ref = if use_self {
224 quote! { self.#name }
225 } else {
226 quote! { #name }
227 };
228 res.extend(quote! { .field(#name_str, &format_args!(#fmt, #field_ref)) })
229 }
230 FieldPrintType::Custom(formatter) => {
231 let field_ref = if use_self {
232 quote! { &self.#name }
233 } else {
234 quote! { #name }
235 };
236 res.extend(quote! { .field(#name_str, &format_args!("{}", #formatter(#field_ref))) })
237 }
238 FieldPrintType::Skip => {}
239 }
240 }
241
242 Ok(res)
243}
244
245fn derive_unnamed_fields(
246 fields: &FieldsUnnamed,
247 use_self: bool,
248) -> Result<TokenStream, syn::Error> {
249 let mut res = TokenStream::new();
250
251 for (i, field) in fields.unnamed.iter().enumerate() {
252 let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
253
254 match options.print_type {
255 FieldPrintType::Normal => {
256 let field_ref = if use_self {
257 let index = syn::Index::from(i);
258 quote! { &self.#index }
259 } else {
260 format_ident!("field_{}", i).to_token_stream()
261 };
262 res.extend(quote! { .field(#field_ref) });
263 }
264 FieldPrintType::Placeholder(placeholder) => {
265 res.extend(quote! { .field(&format_args!(#placeholder)) })
266 }
267 FieldPrintType::Format(fmt) => {
268 let field_ref = if use_self {
269 let index = syn::Index::from(i);
270 quote! { self.#index }
271 } else {
272 format_ident!("field_{}", i).to_token_stream()
273 };
274 res.extend(quote! { .field(&format_args!(#fmt, #field_ref)) })
275 }
276 FieldPrintType::Custom(formatter) => {
277 let field_ref = if use_self {
278 let index = syn::Index::from(i);
279 quote! { &self.#index }
280 } else {
281 format_ident!("field_{}", i).to_token_stream()
282 };
283 res.extend(quote! { .field(&format_args!("{}", #formatter(#field_ref))) });
284 }
285 FieldPrintType::Skip => {}
286 }
287 }
288
289 Ok(res)
290}
291
292enum FieldPrintType {
293 Normal,
294 Placeholder(String),
295 Skip,
296 Format(LitStr),
297 Custom(Path),
298}
299
300struct FieldOutputOptions {
301 print_type: FieldPrintType,
302 alias: Option<String>,
303}
304
305#[derive(PartialEq, Eq)]
306enum OptionsTarget {
307 DeriveItem,
308 EnumVariant,
309 NamedField,
310 UnnamedField,
311}
312
313fn parse_options(
314 attributes: &[Attribute],
315 target: OptionsTarget,
316) -> Result<FieldOutputOptions, syn::Error> {
317 let mut res = FieldOutputOptions {
318 print_type: FieldPrintType::Normal,
319 alias: None,
320 };
321
322 for attrib in attributes {
323 if !attrib.path.is_ident("dbg") {
324 continue;
325 }
326
327 let meta = attrib.parse_meta()?;
328 let meta = if let Meta::List(m) = meta {
329 m
330 } else {
331 return Err(syn::Error::new_spanned(
332 meta,
333 "invalid #[dbg(...)] attribute",
334 ));
335 };
336
337 for option in meta.nested {
338 match option {
339 NestedMeta::Meta(Meta::Path(option))
340 if option.is_ident("skip") && target != OptionsTarget::DeriveItem =>
341 {
342 res.print_type = FieldPrintType::Skip
343 }
344 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
345 path,
346 lit: Lit::Str(placeholder),
347 ..
348 })) if path.is_ident("placeholder")
349 && (target == OptionsTarget::NamedField
350 || target == OptionsTarget::UnnamedField) =>
351 {
352 res.print_type = FieldPrintType::Placeholder(placeholder.value())
353 }
354 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
355 path,
356 lit: Lit::Str(alias),
357 ..
358 })) if path.is_ident("alias") && target != OptionsTarget::UnnamedField => {
359 res.alias = Some(alias.value())
360 }
361 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
362 path,
363 lit: Lit::Str(fmt),
364 ..
365 })) if path.is_ident("fmt")
366 && (target == OptionsTarget::NamedField
367 || target == OptionsTarget::UnnamedField) =>
368 {
369 res.print_type = FieldPrintType::Format(fmt)
370 }
371 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
372 path,
373 lit: Lit::Str(custom),
374 ..
375 })) if path.is_ident("formatter")
376 && (target == OptionsTarget::NamedField
377 || target == OptionsTarget::UnnamedField) =>
378 {
379 let path = syn::parse_str::<Path>(&custom.value()).map_err(|e| syn::Error::new(custom.span(), e.to_string()))?;
380 res.print_type = FieldPrintType::Custom(path);
381 }
382 _ => return Err(syn::Error::new_spanned(option, "invalid option")),
383 }
384 }
385 }
386
387 Ok(res)
388}