const_format_proc_macros/
derive_debug.rs

1use crate::datastructure::{DataStructure, DataVariant, Field, StructKind};
2
3use proc_macro2::{Span, TokenStream as TokenStream2};
4
5use quote::{quote, quote_spanned, quote_spanned as quote_s, ToTokens, TokenStreamExt};
6
7use syn::{DeriveInput, Ident};
8
9mod attribute_parsing;
10mod syntax;
11mod type_detection;
12
13use self::attribute_parsing::HowToFmt;
14
15pub(crate) fn derive_constdebug_impl(input: DeriveInput) -> Result<TokenStream2, crate::Error> {
16    let ds = &DataStructure::new(&input);
17    let config = attribute_parsing::parse_attrs_for_derive(ds)?;
18    let cratep = match &config.crate_path {
19        Some(p) => p.to_token_stream(),
20        None => quote!(::const_format),
21    };
22
23    let vis = ds.vis;
24
25    let name = ds.name;
26
27    let mut impl_headers;
28
29    if config.impls.is_empty() {
30        let impl_params = ds.generics.params.iter();
31        let (_, tygen, _) = ds.generics.split_for_impl();
32        let where_clause = get_where_clause_tokens(&ds.generics.where_clause);
33
34        impl_headers = quote!(
35            impl[#( #impl_params ,)*] #name #tygen
36            #where_clause;
37        );
38    } else {
39        impl_headers = TokenStream2::new();
40
41        for imp in config.impls.iter() {
42            let params = imp.generics.params.iter();
43            let self_ty = &imp.self_ty;
44            let where_clause = get_where_clause_tokens(&imp.generics.where_clause);
45
46            impl_headers.append_all(quote!(
47                impl[#(#params)*] #self_ty
48                #where_clause;
49            ));
50        }
51    };
52
53    let enum_prefix = match ds.data_variant {
54        DataVariant::Enum => quote!(#name::),
55        DataVariant::Struct => TokenStream2::new(),
56        DataVariant::Union => panic!("Cannot derive ConstDebug on unions"),
57    };
58
59    let variant_branches = ds.variants.iter().map(|variant| {
60        let vname = variant.name;
61
62        let debug_method = match variant.kind {
63            StructKind::Braced => Ident::new("debug_struct", Span::call_site()),
64            StructKind::Tupled => Ident::new("debug_tuple", Span::call_site()),
65        };
66
67        let patt = variant
68            .fields
69            .iter()
70            .filter_map(|f| -> Option<TokenStream2> {
71                if let HowToFmt::Ignore = config.field_map[f].how_to_fmt {
72                    return None;
73                }
74
75                let pat = &f.ident;
76                let variable = f.pattern_ident();
77
78                Some(quote!(#pat : #variable,))
79            });
80
81        let fmt_call = variant
82            .fields
83            .iter()
84            .filter_map(|f| -> Option<TokenStream2> {
85                let how_to_fmt = &config.field_map[f].how_to_fmt;
86                if let HowToFmt::Ignore = how_to_fmt {
87                    return None;
88                }
89
90                let fspan = f.pattern_ident().span();
91
92                let field_name_str = match variant.kind {
93                    StructKind::Braced => Some(f.ident.to_string()),
94                    StructKind::Tupled => None,
95                }
96                .into_iter();
97
98                let mut field_ts = quote_spanned!(fspan=>
99                    let mut field_formatter = formatter.field(#(#field_name_str)*);
100                );
101
102                field_ts.append_all(match &how_to_fmt {
103                    HowToFmt::Regular => coerce_and_fmt(&cratep, f),
104                    HowToFmt::Ignore => unreachable!(),
105                    HowToFmt::Slice => fmt_slice(&cratep, f),
106                    HowToFmt::Option_ => fmt_option(&cratep, f),
107                    HowToFmt::Newtype(newtype) => fmt_newtype(&cratep, newtype, f),
108                    HowToFmt::With(with) => call_with_function(&cratep, f, with),
109                    HowToFmt::WithMacro(with) => call_with_macro(&cratep, f, with),
110                    HowToFmt::WithWrapper(with) => call_with_wrapper(&cratep, f, with),
111                });
112
113                Some(field_ts)
114            });
115
116        quote!(
117            #enum_prefix #vname { #(#patt)* .. } => {
118                let mut formatter = formatter.#debug_method(stringify!(#vname));
119                #(#fmt_call)*
120                formatter.finish()
121            }
122        )
123    });
124
125    let ret = quote!(
126        #cratep::impl_fmt!{
127            #impl_headers
128
129            #vis const fn const_debug_fmt(
130                &self,
131                formatter: &mut #cratep::pmr::Formatter<'_>,
132            ) -> #cratep::pmr::Result<(), #cratep::pmr::Error> {
133                match self {
134                    #(
135                        #variant_branches
136                    )*
137                }
138            }
139        }
140    );
141
142    if config.debug_print {
143        panic!("\n\n\n{}\n\n\n", ret);
144    }
145    Ok(ret)
146}
147
148// Copying the definitino of the `const_format::coerce_to_fn` macro here
149// because the compiler points inside the coerce_to_fn macro otherwise
150fn coerce_and_fmt(cratep: &TokenStream2, field: &Field<'_>) -> TokenStream2 {
151    let var = field.pattern_ident();
152    let fspan = var.span();
153
154    quote_spanned!(fspan=>
155        let mut marker = #cratep::pmr::IsAFormatMarker::NEW;
156        if false {
157            marker = marker.infer_type(#var);
158        }
159        #cratep::try_!(
160            marker.coerce(marker.unreference(#var))
161                .const_debug_fmt(field_formatter)
162        );
163    )
164}
165
166fn fmt_slice(cratep: &TokenStream2, field: &Field<'_>) -> TokenStream2 {
167    let var = field.pattern_ident();
168    let fspan = var.span();
169
170    let call = call_debug_fmt(
171        cratep,
172        quote_s!(fspan=> &#var[n]),
173        quote_s!(fspan=> slice_fmt.entry()),
174        fspan,
175    );
176
177    quote_spanned!(fspan=>{
178        let mut slice_fmt = field_formatter.debug_list();
179        let mut n = 0;
180        let len = #var.len();
181        while n != len {
182            #call
183            n += 1;
184        }
185        #cratep::try_!(slice_fmt.finish());
186    })
187}
188
189fn fmt_option(cratep: &TokenStream2, field: &Field<'_>) -> TokenStream2 {
190    let var = field.pattern_ident();
191    let fspan = var.span();
192
193    let call = call_debug_fmt(
194        cratep,
195        quote_s!(fspan=>val),
196        quote_s!(fspan=>f.field()),
197        fspan,
198    );
199
200    quote_spanned!(fspan=>
201        #cratep::try_!(match #var {
202            #cratep::pmr::Some(val) => {
203                let mut f = field_formatter.debug_tuple("Some");
204                #call
205                f.finish()
206            }
207            #cratep::pmr::None => field_formatter.write_str("None"),
208        });
209    )
210}
211
212fn fmt_newtype(cratep: &TokenStream2, newtype: &syn::Ident, field: &Field<'_>) -> TokenStream2 {
213    let var = field.pattern_ident();
214    let fspan = var.span();
215    let ty_str = newtype.to_token_stream().to_string();
216
217    let call = call_debug_fmt(
218        cratep,
219        quote_s!(fspan=> &#var.0 ),
220        quote_s!(fspan=> f.field() ),
221        fspan,
222    );
223
224    quote_spanned!(fspan=>{
225        #cratep::try_!({
226            let mut f = field_formatter.debug_tuple(#ty_str);
227            #call
228            f.finish()
229        });
230    })
231}
232
233fn call_with_function(cratep: &TokenStream2, field: &Field<'_>, func: &syn::Path) -> TokenStream2 {
234    let var = field.pattern_ident();
235    let fspan = var.span();
236
237    quote_spanned!(fspan=> #cratep::try_!(#func(#var, field_formatter)); )
238}
239
240fn call_with_macro(cratep: &TokenStream2, field: &Field<'_>, macr: &syn::Path) -> TokenStream2 {
241    let var = field.pattern_ident();
242    let fspan = var.span();
243
244    quote_spanned!(fspan=> #cratep::try_!(#macr!(#var, field_formatter)); )
245}
246
247fn call_with_wrapper(
248    cratep: &TokenStream2,
249    field: &Field<'_>,
250    newtype: &syn::Path,
251) -> TokenStream2 {
252    let var = field.pattern_ident();
253    let fspan = var.span();
254
255    quote_spanned!(fspan=>
256        #cratep::try_!(#newtype(#var).const_debug_fmt(field_formatter));
257    )
258}
259
260// Helper of the other `call_` functions
261fn call_debug_fmt(
262    cratep: &TokenStream2,
263    field: impl ToTokens,
264    formatter: impl ToTokens,
265    span: Span,
266) -> TokenStream2 {
267    quote_spanned!(span=>{
268        // Importing it like this because the error span is wrong otherwise
269        use #cratep::pmr::IsAFormatMarker as __IsAFormatMarker;
270
271        let mut marker = __IsAFormatMarker::NEW;
272        if false {
273            marker = marker.infer_type(#field);
274        }
275        #cratep::try_!(
276            marker.coerce(marker.unreference(#field)).const_debug_fmt(#formatter)
277        );
278    })
279}
280
281fn get_where_clause_tokens(where_clause: &Option<syn::WhereClause>) -> TokenStream2 {
282    match where_clause {
283        Some(x) => {
284            let preds = x.predicates.iter();
285            quote!(where[ #(#preds,)* ])
286        }
287        None => TokenStream2::new(),
288    }
289}