1#![doc = include_str!("../README.md")]
2
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote, ToTokens};
5use syn::{
6 parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DataEnum,
7 DataStruct, DeriveInput, Error, Expr, Fields, FieldsNamed, FieldsUnnamed, Ident, LitInt,
8 LitStr, Meta, Path, Token, Variant,
9};
10
11#[proc_macro_derive(Dbg, attributes(dbg))]
16pub fn derive_debug(target: proc_macro::TokenStream) -> proc_macro::TokenStream {
17 let item = parse_macro_input!(target as DeriveInput);
18 derive_debug_impl(item).into()
19}
20
21fn derive_debug_impl(item: DeriveInput) -> TokenStream {
22 let name = &item.ident;
23 let (impl_generics, type_generics, where_clause) = &item.generics.split_for_impl();
24
25 let options = match parse_options(&item.attrs, OptionsTarget::DeriveItem) {
26 Ok(options) => options,
27 Err(e) => return e.to_compile_error(),
28 };
29
30 let display_name = if let Some(alias) = options.alias {
31 alias
32 } else {
33 let alias = name.to_string();
34 syn::parse_quote_spanned! { name.span() => #alias }
35 };
36
37 let res = match &item.data {
38 syn::Data::Struct(data) => derive_struct(&display_name, data),
39 syn::Data::Enum(data) => derive_enum(data),
40 syn::Data::Union(data) => Err(syn::Error::new_spanned(
41 data.union_token,
42 "#[derive(Dbg)] not supported on unions",
43 )),
44 };
45
46 match res {
47 Ok(res) => quote! {
48 impl #impl_generics ::std::fmt::Debug for #name #type_generics #where_clause {
49 fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
50 #res
51 }
52 }
53 },
54 Err(e) => e.to_compile_error(),
55 }
56}
57
58fn derive_struct(display_name: &Expr, data: &DataStruct) -> Result<TokenStream, syn::Error> {
59 match &data.fields {
60 Fields::Named(fields) => {
61 let fields = derive_named_fields(fields, true)?;
62 Ok(quote! {
63 let mut fd = f.debug_struct(#display_name);
64 #fields
65 fd.finish()
66 })
67 }
68 Fields::Unnamed(fields) => {
69 let fields = derive_unnamed_fields(fields, true)?;
70 Ok(quote! {
71 let mut fd = f.debug_tuple(#display_name);
72 #fields
73 fd.finish()
74 })
75 }
76 Fields::Unit => Ok(quote! {
77 f.debug_struct(#display_name).finish()
78 }),
79 }
80}
81
82fn derive_enum(data: &DataEnum) -> Result<TokenStream, syn::Error> {
83 if data.variants.is_empty() {
84 return Ok(quote! {
85 unsafe { ::core::hint::unreachable_unchecked() }
86 });
87 }
88
89 let variants = derive_enum_variants(data.variants.iter())?;
90
91 Ok(quote! {
92 match self {
93 #variants
94 }
95 })
96}
97
98fn derive_enum_variants<'a>(
99 variants: impl Iterator<Item = &'a Variant>,
100) -> Result<TokenStream, syn::Error> {
101 let mut res = TokenStream::new();
102
103 for variant in variants {
104 let name = &variant.ident;
105
106 let options = parse_options(&variant.attrs, OptionsTarget::EnumVariant)?;
107
108 let display_name = if let Some(alias) = options.alias {
109 alias
110 } else {
111 let alias = name.to_string();
112 syn::parse_quote_spanned! { name.span() => #alias }
113 };
114
115 let derive_variant = match options.print_type {
116 FieldPrintType::Normal => derive_variant(name, &display_name, &variant.fields)?,
117 FieldPrintType::Skip => skip_variant(name, &display_name, &variant.fields)?,
118 _ => return Err(syn::Error::new_spanned(variant, "Internal error")),
119 };
120
121 res.extend(derive_variant);
122 }
123
124 Ok(res)
125}
126
127fn derive_variant(
128 name: &Ident,
129 display_name: &Expr,
130 fields: &Fields,
131) -> Result<TokenStream, syn::Error> {
132 let match_list = derive_match_list(fields)?;
133
134 match fields {
135 Fields::Named(fields) => {
136 let fields = derive_named_fields(fields, false)?;
137 Ok(quote! {
138 Self::#name #match_list => {
139 let mut fd = f.debug_struct(#display_name);
140 #fields
141 fd.finish()
142 },
143 })
144 }
145 Fields::Unnamed(fields) => {
146 let fields = derive_unnamed_fields(fields, false)?;
147 Ok(quote! {
148 Self::#name #match_list => {
149 let mut fd = f.debug_tuple(#display_name);
150 #fields
151 fd.finish()
152 },
153 })
154 }
155 Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
156 }
157}
158
159fn skip_variant(
160 name: &Ident,
161 display_name: &Expr,
162 fields: &Fields,
163) -> Result<TokenStream, syn::Error> {
164 match fields {
165 Fields::Named(_) => {
166 Ok(quote! { Self::#name{..} => f.debug_struct(#display_name).finish(), })
167 }
168 Fields::Unnamed(_) => {
169 Ok(quote! { Self::#name(..) => f.debug_tuple(#display_name).finish(), })
170 }
171 Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
172 }
173}
174
175fn derive_match_list(fields: &Fields) -> Result<TokenStream, syn::Error> {
176 match fields {
177 Fields::Named(fields) => {
178 let mut res = TokenStream::new();
179 for field in &fields.named {
180 let name = field.ident.as_ref().unwrap();
181 let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
182
183 match options.print_type {
184 FieldPrintType::Skip => res.extend(quote! { #name: _, }),
185 _ => res.extend(quote! { #name, }),
186 }
187 }
188 Ok(quote! { { #res } })
189 }
190 Fields::Unnamed(fields) => {
191 let mut res = TokenStream::new();
192 for (i, field) in fields.unnamed.iter().enumerate() {
193 let name = format_ident!("field_{}", i);
194 let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
195
196 match options.print_type {
197 FieldPrintType::Skip => res.extend(quote! { _, }),
198 _ => res.extend(quote! { #name, }),
199 }
200 }
201 Ok(quote! { (#res) })
202 }
203 Fields::Unit => Ok(quote! {}),
204 }
205}
206
207fn derive_named_fields(fields: &FieldsNamed, use_self: bool) -> Result<TokenStream, syn::Error> {
208 let mut res = TokenStream::new();
209
210 let mut _fields: Vec<_> = vec![];
211
212 for (i, field) in fields.named.iter().enumerate() {
213 let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
214
215 _fields.push((i, field, options));
216 }
217
218 _fields.sort_by_key(|(i, _, ref options)| (options.sort, *i));
219
220 for (_, field, options) in _fields.into_iter() {
221 if let FieldPrintType::Skip = options.print_type {
222 continue;
223 }
224
225 let name = field.ident.as_ref().unwrap();
226
227 let name_str = if let Some(alias) = options.alias {
228 alias
229 } else {
230 let alias = name.to_string();
231 syn::parse_quote_spanned! { name.span() => #alias }
232 };
233
234 let field_ref = if use_self {
235 quote! { &self.#name }
236 } else {
237 quote! { #name }
238 };
239
240 let alias_ident = format_ident!("_it");
241
242 if !matches!(
243 options.print_type,
244 FieldPrintType::Placeholder(_)
245 ) {
246 res.extend(quote! {
247 let #alias_ident = #field_ref;
248 });
249 }
250
251 let q = match options.print_type {
252 FieldPrintType::Normal => {
253 quote! { fd.field(#name_str, #alias_ident); }
254 }
255 FieldPrintType::Placeholder(placeholder) => {
256 quote! { fd.field(#name_str, &format_args!(#placeholder)); }
257 }
258 FieldPrintType::Format(fmt) => {
259 quote! { fd.field(#name_str, &format_args!(#fmt, #alias_ident)); }
260 }
261 FieldPrintType::Custom(formatter) => {
262 quote! { fd.field(#name_str, &format_args!("{}", #formatter(#alias_ident))); }
263 }
264 FieldPrintType::Expr(expr) => {
265 quote! { fd.field(#name_str, #expr); }
266 }
267 FieldPrintType::Skip => {
268 quote! {}
269 }
270 };
271
272 if options.flat_option {
273 res.extend(quote! { if let Some(#alias_ident) = #alias_ident { #q } });
274 } else {
275 res.extend(q)
276 }
277 }
278
279 Ok(res)
280}
281
282fn derive_unnamed_fields(
283 fields: &FieldsUnnamed,
284 use_self: bool,
285) -> Result<TokenStream, syn::Error> {
286 let mut res = TokenStream::new();
287
288 let mut _fields: Vec<_> = vec![];
289
290 for (i, field) in fields.unnamed.iter().enumerate() {
291 let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
292
293 _fields.push((i, field, options));
294 }
295
296 _fields.sort_by_key(|(i, _, ref options)| (options.sort, *i));
297
298 for (i, _field, options) in _fields.into_iter() {
299 if let FieldPrintType::Skip = options.print_type {
300 continue;
301 }
302
303 let field_ref = if use_self {
304 let index = syn::Index::from(i);
305 quote! { &self.#index }
306 } else {
307 format_ident!("field_{}", i).to_token_stream()
308 };
309
310 let alias_ident = format_ident!("_it");
311
312 if !matches!(
313 options.print_type,
314 FieldPrintType::Placeholder(_)
315 ) {
316 res.extend(quote! {
317 let #alias_ident = #field_ref;
318 });
319 }
320
321 let q = match options.print_type {
322 FieldPrintType::Normal => {
323 quote! { fd.field(#alias_ident); }
324 }
325 FieldPrintType::Placeholder(placeholder) => {
326 quote! { fd.field(&format_args!(#placeholder)); }
327 }
328 FieldPrintType::Format(fmt) => {
329 quote! { fd.field(&format_args!(#fmt, #alias_ident)); }
330 }
331 FieldPrintType::Custom(formatter) => {
332 quote! { fd.field(&format_args!("{}", #formatter(#alias_ident))); }
333 }
334 FieldPrintType::Expr(expr) => {
335 quote! { fd.field(#expr); }
336 }
337 FieldPrintType::Skip => {
338 quote! {}
339 }
340 };
341
342 if options.flat_option {
343 res.extend(quote! { if let Some(#alias_ident) = #alias_ident { #q } });
344 } else {
345 res.extend(q)
346 }
347 }
348
349 Ok(res)
350}
351
352enum FieldPrintType {
353 Normal,
354 Placeholder(String),
355 Skip,
356 Format(LitStr),
357 Custom(Path),
358 Expr(Expr),
359}
360
361struct FieldOutputOptions {
362 print_type: FieldPrintType,
363 alias: Option<Expr>,
364 flat_option: bool,
365 sort: isize,
366}
367
368#[derive(PartialEq, Eq)]
369enum OptionsTarget {
370 DeriveItem,
371 EnumVariant,
372 NamedField,
373 UnnamedField,
374}
375
376fn parse_options(
377 attributes: &[Attribute],
378 target: OptionsTarget,
379) -> Result<FieldOutputOptions, syn::Error> {
380 let mut res = FieldOutputOptions {
381 print_type: FieldPrintType::Normal,
382 alias: None,
383 flat_option: false,
384 sort: 0,
385 };
386
387 for attrib in attributes {
388 let meta = &attrib.meta;
389
390 if !meta.path().is_ident("dbg") {
391 continue;
392 }
393
394 let meta = if let Meta::List(m) = meta {
395 m
396 } else {
397 return Err(syn::Error::new_spanned(
398 meta,
399 "invalid #[dbg(...)] attribute",
400 ));
401 };
402
403 let options: OptionItems = syn::parse2(meta.tokens.clone())?;
404
405 for option in options.options {
406 match option.option {
407 TheOption::Sort(sort) if target != OptionsTarget::DeriveItem => {
408 res.sort = sort.base10_parse()?
409 }
410 TheOption::Skip if target != OptionsTarget::DeriveItem => {
411 res.print_type = FieldPrintType::Skip
412 }
413 TheOption::FlatOption if target != OptionsTarget::DeriveItem => {
414 res.flat_option = true
415 }
416 TheOption::Alias(alias) if target != OptionsTarget::UnnamedField => {
417 res.alias = Some(alias)
418 }
419 TheOption::Placeholder(placeholder)
420 if target == OptionsTarget::NamedField
421 || target == OptionsTarget::UnnamedField =>
422 {
423 res.print_type = FieldPrintType::Placeholder(placeholder.value())
424 }
425 TheOption::Fmt(fmt)
426 if target == OptionsTarget::NamedField
427 || target == OptionsTarget::UnnamedField =>
428 {
429 res.print_type = FieldPrintType::Format(fmt)
430 }
431 TheOption::Expr(expr) => res.print_type = FieldPrintType::Expr(expr),
432 TheOption::Formatter(path)
433 if target == OptionsTarget::NamedField
434 || target == OptionsTarget::UnnamedField =>
435 {
436 res.print_type = FieldPrintType::Custom(path)
437 }
438 _ => return Err(syn::Error::new_spanned(option.path, "invalid option")),
439 }
440 }
441 }
442
443 Ok(res)
444}
445
446struct OptionItems {
447 pub options: Punctuated<OptionItem, Comma>,
448}
449
450impl syn::parse::Parse for OptionItems {
451 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
452 let options = Punctuated::<OptionItem, Comma>::parse_separated_nonempty(input)?;
453 Ok(Self { options })
454 }
455}
456
457struct OptionItem {
458 pub path: Path,
459 pub option: TheOption,
460}
461
462enum TheOption {
463 Skip,
464 FlatOption,
465 Alias(Expr),
466 Placeholder(LitStr),
467 Fmt(LitStr),
468 Expr(Expr),
469 Formatter(Path),
470 Sort(LitInt),
471}
472
473impl syn::parse::Parse for OptionItem {
474 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
475 let path: Path = input.parse()?;
476
477 if path.is_ident("skip") {
478 return Ok(Self {
479 path,
480 option: TheOption::Skip,
481 });
482 }
483 if path.is_ident("flat_option") {
484 return Ok(Self {
485 path,
486 option: TheOption::FlatOption,
487 });
488 }
489 if path.is_ident("alias") {
490 let _ = input.parse::<Token![=]>()?;
491 let a = input.parse()?;
492 return Ok(Self {
493 path,
494 option: TheOption::Alias(a),
495 });
496 }
497 if path.is_ident("placeholder") {
498 let _ = input.parse::<Token![=]>()?;
499 let a = input.parse()?;
500 return Ok(Self {
501 path,
502 option: TheOption::Placeholder(a),
503 });
504 }
505 if path.is_ident("fmt") {
506 let _ = input.parse::<Token![=]>()?;
507 let a = input.parse()?;
508 return Ok(Self {
509 path,
510 option: TheOption::Fmt(a),
511 });
512 }
513 if path.is_ident("expr") {
514 let _ = input.parse::<Token![=]>()?;
515 let a = input.parse()?;
516 return Ok(Self {
517 path,
518 option: TheOption::Expr(a),
519 });
520 }
521 if path.is_ident("formatter") {
522 let _ = input.parse::<Token![=]>()?;
523 let a = input.parse()?;
524 return Ok(Self {
525 path,
526 option: TheOption::Formatter(a),
527 });
528 }
529 if path.is_ident("sort") {
530 let _ = input.parse::<Token![=]>()?;
531 let a = input.parse()?;
532 return Ok(Self {
533 path,
534 option: TheOption::Sort(a),
535 });
536 }
537
538 Err(Error::new(path.span(), "invalid option"))
539 }
540}