Skip to content

Commit b77c6c6

Browse files
committed
placeholder::PartialDebug for Enums
1 parent 523e2fb commit b77c6c6

File tree

3 files changed

+206
-49
lines changed

3 files changed

+206
-49
lines changed

partialdebug-derive/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ proc-macro = true
1313
[dependencies]
1414
syn = { version = "1", features = ["full"] }
1515
quote = "1"
16+
proc-macro2 = "1"

partialdebug-derive/src/lib.rs

Lines changed: 131 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use proc_macro::TokenStream;
22

3+
use proc_macro2::TokenStream as TokenStream2;
34
use quote::{quote, ToTokens};
45
use syn::parse::{Parse, ParseStream};
6+
use syn::spanned::Spanned;
57
use syn::*;
68

79
/// The non exhaustive version of `PartialDebug`
@@ -54,70 +56,158 @@ pub fn derive_non_exhaustive(input: TokenStream) -> TokenStream {
5456
/// The placeholder version of `PartialDebug`
5557
#[proc_macro_derive(PlaceholderPartialDebug, attributes(debug_placeholder))]
5658
pub fn derive_placeholder(input: TokenStream) -> TokenStream {
57-
let input = parse_macro_input!(input as ItemStruct);
59+
let input = parse_macro_input!(input as DeriveInput);
5860
let placeholder = match get_placeholder(&input) {
5961
Ok(placeholder) => placeholder,
6062
Err(err) => {
6163
return err.to_compile_error().into();
6264
}
6365
};
6466

65-
let name = &input.ident;
67+
let name = input.ident;
6668
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
6769

68-
let no_fields = punctuated::Punctuated::new();
70+
let implementation = match input.data {
71+
Data::Struct(DataStruct { fields, .. }) => gen_variant_debug(
72+
&fields,
73+
&name,
74+
struct_field_conversions(&fields, &placeholder),
75+
),
76+
Data::Enum(data_enum) => gen_enum_debug(&data_enum, &name, &placeholder),
77+
Data::Union(_) => unimplemented!(),
78+
};
6979

70-
let (fields, constructor) = match &input.fields {
71-
Fields::Named(FieldsNamed { named, .. }) => (named, quote! {debug_struct}),
72-
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => (unnamed, quote! {debug_tuple}),
73-
Fields::Unit => (&no_fields, quote! {debug_tuple}),
80+
let expanded = quote! {
81+
impl #impl_generics ::core::fmt::Debug for #name #ty_generics #where_clause{
82+
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
83+
#implementation
84+
}
85+
}
7486
};
7587

76-
let as_debug_all_fields = fields.iter().enumerate().map(|(idx, field)| {
77-
let type_name = get_type_name(&field.ty);
88+
TokenStream::from(expanded)
89+
}
90+
91+
fn gen_variant_debug(
92+
fields: &Fields,
93+
variant_name: &Ident,
94+
field_conversions: impl Iterator<Item = TokenStream2>,
95+
) -> TokenStream2 {
96+
let constructor = match fields {
97+
Fields::Named(_) => quote! {debug_struct},
98+
Fields::Unnamed(_) | Fields::Unit => quote! {debug_tuple},
99+
};
78100

79-
// type name or given placeholder string
80-
let placeholder_string = placeholder.as_ref().unwrap_or(&type_name);
101+
quote! {
102+
f.#constructor(stringify!(#variant_name))
103+
#(#field_conversions)*
104+
.finish()
105+
}
106+
}
81107

82-
match &field.ident {
83-
None => {
84-
let idx = Index::from(idx);
85-
quote! {
86-
.field(
87-
match ::partialdebug::AsDebug::as_debug(&self.#idx){
88-
None => &::partialdebug::Placeholder(#placeholder_string),
89-
Some(__field) => __field,
90-
},
91-
)
92-
}
93-
}
94-
Some(name) => {
95-
quote! {
96-
.field(
97-
stringify!(#name),
98-
match ::partialdebug::AsDebug::as_debug(&self.#name){
99-
None => &::partialdebug::Placeholder(#placeholder_string),
100-
Some(__field) => __field,
101-
},
102-
)
103-
}
108+
fn gen_enum_debug(
109+
data_enum: &DataEnum,
110+
enum_name: &Ident,
111+
placeholder: &Option<String>,
112+
) -> TokenStream2 {
113+
let all_variants = data_enum.variants.iter().map(|variant| {
114+
let variant_name = &variant.ident;
115+
let match_content = gen_variant_debug(
116+
&variant.fields,
117+
variant_name,
118+
enum_field_conversions(&variant.fields, placeholder),
119+
);
120+
let match_pattern = gen_match_pattern(enum_name, variant);
121+
quote! {
122+
#match_pattern => {
123+
#match_content
104124
}
105125
}
106126
});
107127

108-
let expanded = quote! {
109-
impl #impl_generics ::core::fmt::Debug for #name #ty_generics #where_clause{
110-
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
111-
f.#constructor(stringify!(#name))
128+
quote! {
129+
match self {
130+
#(#all_variants)*
131+
}
132+
}
133+
}
112134

113-
#(#as_debug_all_fields)*
135+
fn struct_field_conversions<'a>(
136+
fields: &'a Fields,
137+
placeholder: &'a Option<String>,
138+
) -> impl Iterator<Item = TokenStream2> + 'a {
139+
fields.iter().enumerate().map(move |(idx, field)| {
140+
let (field_handle, name_arg) = match &field.ident {
141+
None => {
142+
let index = Index::from(idx);
143+
(quote! {self.#index}, None)
144+
}
145+
Some(name) => (quote! {self.#name}, Some(quote! {stringify!(#name),})),
146+
};
147+
gen_field_as_debug(field, placeholder, field_handle, name_arg)
148+
})
149+
}
114150

115-
.finish()
151+
fn enum_field_conversions<'a>(
152+
fields: &'a Fields,
153+
placeholder: &'a Option<String>,
154+
) -> impl Iterator<Item = TokenStream2> + 'a {
155+
fields.iter().enumerate().map(move |(idx, field)| {
156+
let (field_handle, name_arg) = match &field.ident {
157+
None => {
158+
let ident = Ident::new(&format!("__{}", idx), field.span());
159+
(quote! {#ident}, None)
160+
}
161+
Some(name) => (quote! {#name}, Some(quote! {stringify!(#name),})),
162+
};
163+
gen_field_as_debug(field, placeholder, field_handle, name_arg)
164+
})
165+
}
166+
167+
fn gen_field_as_debug(
168+
field: &Field,
169+
placeholder: &Option<String>,
170+
field_handle: TokenStream2,
171+
name_arg: Option<TokenStream2>,
172+
) -> TokenStream2 {
173+
let type_name = get_type_name(&field.ty);
174+
175+
// type name or given placeholder string
176+
let placeholder_string = placeholder.as_ref().unwrap_or(&type_name);
177+
178+
quote! {
179+
.field(
180+
#name_arg
181+
match ::partialdebug::AsDebug::as_debug(&#field_handle){
182+
None => &::partialdebug::Placeholder(#placeholder_string),
183+
Some(__field) => __field,
184+
},
185+
)
186+
}
187+
}
188+
189+
fn gen_match_pattern(enum_name: &Ident, variant: &Variant) -> TokenStream2 {
190+
let variant_name = &variant.ident;
191+
let destructuring_pattern = match &variant.fields {
192+
Fields::Named(FieldsNamed { named, .. }) => {
193+
let patterns = named.iter().map(|field| &field.ident);
194+
quote! {
195+
{#(#patterns),*}
196+
}
197+
}
198+
Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
199+
let patterns = unnamed
200+
.iter()
201+
.enumerate()
202+
.map(|(idx, field)| Ident::new(&format!("__{}", idx), field.span()));
203+
quote! {
204+
(#(#patterns),*)
116205
}
117206
}
207+
Fields::Unit => TokenStream2::new(),
118208
};
119209

120-
TokenStream::from(expanded)
210+
quote! {#enum_name::#variant_name #destructuring_pattern}
121211
}
122212

123213
struct Placeholder(String);
@@ -130,7 +220,7 @@ impl Parse for Placeholder {
130220
}
131221

132222
/// Tries to parse a placeholder string if there is one
133-
fn get_placeholder(input: &ItemStruct) -> Result<Option<String>> {
223+
fn get_placeholder(input: &DeriveInput) -> Result<Option<String>> {
134224
let placeholders: Vec<_> = input
135225
.attrs
136226
.iter()

tests/placeholder.rs

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,78 @@
1-
use partialdebug::placeholder::PartialDebug;
1+
mod partial {
2+
use partialdebug::placeholder::PartialDebug;
23

3-
#[derive(PartialDebug)]
4-
struct UnitStruct;
4+
#[derive(PartialDebug)]
5+
pub struct UnitStruct;
56

6-
#[derive(PartialDebug)]
7-
struct TupleStruct(&'static str);
7+
#[derive(PartialDebug)]
8+
pub struct TupleStruct(pub &'static str);
89

9-
#[derive(PartialDebug)]
10-
struct NormalStruct {
11-
field: &'static str,
10+
#[derive(PartialDebug)]
11+
pub struct NormalStruct {
12+
pub field: &'static str,
13+
}
14+
15+
#[derive(PartialDebug)]
16+
pub enum Mixed {
17+
Unit,
18+
Tuple(Box<u8>),
19+
Struct { a: u8 },
20+
}
21+
}
22+
23+
mod normal {
24+
#[derive(Debug)]
25+
pub struct UnitStruct;
26+
27+
#[derive(Debug)]
28+
pub struct TupleStruct(pub &'static str);
29+
30+
#[derive(Debug)]
31+
pub struct NormalStruct {
32+
pub field: &'static str,
33+
}
34+
35+
#[derive(Debug)]
36+
pub enum Mixed {
37+
Unit,
38+
Tuple(Box<u8>),
39+
Struct { a: u8 },
40+
}
41+
}
42+
43+
#[test]
44+
fn test_unit() {
45+
assert_eq!(
46+
format!("{:?}", normal::UnitStruct),
47+
format!("{:?}", partial::UnitStruct)
48+
);
49+
}
50+
#[test]
51+
fn test_tuple() {
52+
assert_eq!(
53+
format!("{:?}", normal::TupleStruct("asdf")),
54+
format!("{:?}", partial::TupleStruct("asdf"))
55+
);
56+
}
57+
#[test]
58+
fn test_normal() {
59+
assert_eq!(
60+
format!("{:?}", normal::NormalStruct { field: "asdf" }),
61+
format!("{:?}", partial::NormalStruct { field: "asdf" })
62+
);
63+
}
64+
#[test]
65+
fn test_enum() {
66+
assert_eq!(
67+
format!("{:?}", normal::Mixed::Unit),
68+
format!("{:?}", partial::Mixed::Unit)
69+
);
70+
assert_eq!(
71+
format!("{:?}", normal::Mixed::Tuple(Box::new(42))),
72+
format!("{:?}", partial::Mixed::Tuple(Box::new(42)))
73+
);
74+
assert_eq!(
75+
format!("{:?}", normal::Mixed::Struct { a: 42 }),
76+
format!("{:?}", partial::Mixed::Struct { a: 42 })
77+
);
1278
}

0 commit comments

Comments
 (0)