const_format_proc_macros/
derive_debug.rs1use 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
148fn 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
260fn call_debug_fmt(
262 cratep: &TokenStream2,
263 field: impl ToTokens,
264 formatter: impl ToTokens,
265 span: Span,
266) -> TokenStream2 {
267 quote_spanned!(span=>{
268 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}