1 | use super::attr::AttrsHelper; |
---|---|
2 | use proc_macro2::{Span, TokenStream}; |
3 | use quote::{format_ident, quote}; |
4 | use syn::{ |
5 | punctuated::Punctuated, |
6 | token::{Colon, Comma, PathSep, Plus, Where}, |
7 | Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Path, PathArguments, |
8 | PathSegment, PredicateType, Result, TraitBound, TraitBoundModifier, Type, TypeParam, |
9 | TypeParamBound, TypePath, WhereClause, WherePredicate, |
10 | }; |
11 | |
12 | use std::collections::HashMap; |
13 | |
14 | pub(crate) fn derive(input: &DeriveInput) -> Result<TokenStream> { |
15 | let impls: TokenStream = match &input.data { |
16 | Data::Struct(data: &DataStruct) => impl_struct(input, data), |
17 | Data::Enum(data: &DataEnum) => impl_enum(input, data), |
18 | Data::Union(_) => Err(Error::new_spanned(tokens:input, message:"Unions are not supported")), |
19 | }?; |
20 | |
21 | let helpers: TokenStream = specialization(); |
22 | Ok(quote! { |
23 | #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] |
24 | const _: () = { |
25 | #helpers |
26 | #impls |
27 | }; |
28 | }) |
29 | } |
30 | |
31 | #[cfg(feature = "std")] |
32 | fn specialization() -> TokenStream { |
33 | quote! { |
34 | trait DisplayToDisplayDoc { |
35 | fn __displaydoc_display(&self) -> Self; |
36 | } |
37 | |
38 | impl<T: ::core::fmt::Display> DisplayToDisplayDoc for &T { |
39 | fn __displaydoc_display(&self) -> Self { |
40 | self |
41 | } |
42 | } |
43 | |
44 | // If the `std` feature gets enabled we want to ensure that any crate |
45 | // using displaydoc can still reference the std crate, which is already |
46 | // being compiled in by whoever enabled the `std` feature in |
47 | // `displaydoc`, even if the crates using displaydoc are no_std. |
48 | extern crate std; |
49 | |
50 | trait PathToDisplayDoc { |
51 | fn __displaydoc_display(&self) -> std::path::Display<'_>; |
52 | } |
53 | |
54 | impl PathToDisplayDoc for std::path::Path { |
55 | fn __displaydoc_display(&self) -> std::path::Display<'_> { |
56 | self.display() |
57 | } |
58 | } |
59 | |
60 | impl PathToDisplayDoc for std::path::PathBuf { |
61 | fn __displaydoc_display(&self) -> std::path::Display<'_> { |
62 | self.display() |
63 | } |
64 | } |
65 | } |
66 | } |
67 | |
68 | #[cfg(not(feature = "std"))] |
69 | fn specialization() -> TokenStream { |
70 | quote! {} |
71 | } |
72 | |
73 | fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> { |
74 | let ty = &input.ident; |
75 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); |
76 | let where_clause = generate_where_clause(&input.generics, where_clause); |
77 | |
78 | let helper = AttrsHelper::new(&input.attrs); |
79 | |
80 | let display = helper.display(&input.attrs)?.map(|display| { |
81 | let pat = match &data.fields { |
82 | Fields::Named(fields) => { |
83 | let var = fields.named.iter().map(|field| &field.ident); |
84 | quote!(Self { #(#var),* }) |
85 | } |
86 | Fields::Unnamed(fields) => { |
87 | let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{} ", i)); |
88 | quote!(Self(#(#var),*)) |
89 | } |
90 | Fields::Unit => quote!(_), |
91 | }; |
92 | quote! { |
93 | impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { |
94 | fn fmt(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
95 | // NB: This destructures the fields of `self` into named variables (for unnamed |
96 | // fields, it uses _0, _1, etc as above). The `#[allow(unused_variables)]` |
97 | // section means it doesn't have to parse the individual field references out of |
98 | // the docstring. |
99 | #[allow(unused_variables)] |
100 | let #pat = self; |
101 | #display |
102 | } |
103 | } |
104 | } |
105 | }); |
106 | |
107 | Ok(quote! { #display }) |
108 | } |
109 | |
110 | /// Create a `where` predicate for `ident`, without any [bound][TypeParamBound]s yet. |
111 | fn new_empty_where_type_predicate(ident: Ident) -> PredicateType { |
112 | let mut path_segments: Punctuated |
113 | path_segments.push_value(PathSegment { |
114 | ident, |
115 | arguments: PathArguments::None, |
116 | }); |
117 | PredicateType { |
118 | lifetimes: None, |
119 | bounded_ty: Type::Path(TypePath { |
120 | qself: None, |
121 | path: Path { |
122 | leading_colon: None, |
123 | segments: path_segments, |
124 | }, |
125 | }), |
126 | colon_token: Colon { |
127 | spans: [Span::call_site()], |
128 | }, |
129 | bounds: Punctuated::<TypeParamBound, Plus>::new(), |
130 | } |
131 | } |
132 | |
133 | /// Create a `where` clause that we can add [WherePredicate]s to. |
134 | fn new_empty_where_clause() -> WhereClause { |
135 | WhereClause { |
136 | where_token: Where { |
137 | span: Span::call_site(), |
138 | }, |
139 | predicates: Punctuated::<WherePredicate, Comma>::new(), |
140 | } |
141 | } |
142 | |
143 | enum UseGlobalPrefix { |
144 | LeadingColon, |
145 | #[allow(dead_code)] |
146 | NoLeadingColon, |
147 | } |
148 | |
149 | /// Create a path with segments composed of [Idents] *without* any [PathArguments]. |
150 | fn join_paths(name_segments: &[&str], use_global_prefix: UseGlobalPrefix) -> Path { |
151 | let mut segments = Punctuated::<PathSegment, PathSep>::new(); |
152 | assert!(!name_segments.is_empty()); |
153 | segments.push_value(PathSegment { |
154 | ident: Ident::new(name_segments[0], Span::call_site()), |
155 | arguments: PathArguments::None, |
156 | }); |
157 | for name in name_segments[1..].iter() { |
158 | segments.push_punct(PathSep { |
159 | spans: [Span::call_site(), Span::mixed_site()], |
160 | }); |
161 | segments.push_value(PathSegment { |
162 | ident: Ident::new(name, Span::call_site()), |
163 | arguments: PathArguments::None, |
164 | }); |
165 | } |
166 | Path { |
167 | leading_colon: match use_global_prefix { |
168 | UseGlobalPrefix::LeadingColon => Some(PathSep { |
169 | spans: [Span::call_site(), Span::mixed_site()], |
170 | }), |
171 | UseGlobalPrefix::NoLeadingColon => None, |
172 | }, |
173 | segments, |
174 | } |
175 | } |
176 | |
177 | /// Push `new_type_predicate` onto the end of `where_clause`. |
178 | fn append_where_clause_type_predicate( |
179 | where_clause: &mut WhereClause, |
180 | new_type_predicate: PredicateType, |
181 | ) { |
182 | // Push a comma at the end if there are already any `where` predicates. |
183 | if !where_clause.predicates.is_empty() { |
184 | where_clause.predicates.push_punct(punctuation:Comma { |
185 | spans: [Span::call_site()], |
186 | }); |
187 | } |
188 | where_clausePunctuated |
189 | .predicates |
190 | .push_value(WherePredicate::Type(new_type_predicate)); |
191 | } |
192 | |
193 | /// Add a requirement for [core::fmt::Display] to a `where` predicate for some type. |
194 | fn add_display_constraint_to_type_predicate( |
195 | predicate_that_needs_a_display_impl: &mut PredicateType, |
196 | ) { |
197 | // Create a `Path` of `::core::fmt::Display`. |
198 | let display_path: Path = join_paths(&["core", "fmt", "Display"], UseGlobalPrefix::LeadingColon); |
199 | |
200 | let display_bound: TypeParamBound = TypeParamBound::Trait(TraitBound { |
201 | paren_token: None, |
202 | modifier: TraitBoundModifier::None, |
203 | lifetimes: None, |
204 | path: display_path, |
205 | }); |
206 | if !predicate_that_needs_a_display_impl.bounds.is_empty() { |
207 | predicate_that_needs_a_display_impl.bounds.push_punct(punctuation:Plus { |
208 | spans: [Span::call_site()], |
209 | }); |
210 | } |
211 | |
212 | predicate_that_needs_a_display_implPunctuated |
213 | .bounds |
214 | .push_value(display_bound); |
215 | } |
216 | |
217 | /// Map each declared generic type parameter to the set of all trait boundaries declared on it. |
218 | /// |
219 | /// These boundaries may come from the declaration site: |
220 | /// pub enum E<T: MyTrait> { ... } |
221 | /// or a `where` clause after the parameter declarations: |
222 | /// pub enum E<T> where T: MyTrait { ... } |
223 | /// This method will return the boundaries from both of those cases. |
224 | fn extract_trait_constraints_from_source( |
225 | where_clause: &WhereClause, |
226 | type_params: &[&TypeParam], |
227 | ) -> HashMap<Ident, Vec<TraitBound>> { |
228 | // Add trait bounds provided at the declaration site of type parameters for the struct/enum. |
229 | let mut param_constraint_mapping: HashMap<Ident, Vec<TraitBound>> = type_params |
230 | .iter() |
231 | .map(|type_param| { |
232 | let trait_bounds: Vec<TraitBound> = type_param |
233 | .bounds |
234 | .iter() |
235 | .flat_map(|bound| match bound { |
236 | TypeParamBound::Trait(trait_bound) => Some(trait_bound), |
237 | _ => None, |
238 | }) |
239 | .cloned() |
240 | .collect(); |
241 | (type_param.ident.clone(), trait_bounds) |
242 | }) |
243 | .collect(); |
244 | |
245 | // Add trait bounds from `where` clauses, which may be type parameters or types containing |
246 | // those parameters. |
247 | for predicate in where_clause.predicates.iter() { |
248 | // We only care about type and not lifetime constraints here. |
249 | if let WherePredicate::Type(ref pred_ty) = predicate { |
250 | let ident = match &pred_ty.bounded_ty { |
251 | Type::Path(TypePath { path, qself: None }) => match path.get_ident() { |
252 | None => continue, |
253 | Some(ident) => ident, |
254 | }, |
255 | _ => continue, |
256 | }; |
257 | // We ignore any type constraints that aren't direct references to type |
258 | // parameters of the current enum of struct definition. No types can be |
259 | // constrained in a `where` clause unless they are a type parameter or a generic |
260 | // type instantiated with one of the type parameters, so by only allowing single |
261 | // identifiers, we can be sure that the constrained type is a type parameter |
262 | // that is contained in `param_constraint_mapping`. |
263 | if let Some((_, ref mut known_bounds)) = param_constraint_mapping |
264 | .iter_mut() |
265 | .find(|(id, _)| *id == ident) |
266 | { |
267 | for bound in pred_ty.bounds.iter() { |
268 | // We only care about trait bounds here. |
269 | if let TypeParamBound::Trait(ref bound) = bound { |
270 | known_bounds.push(bound.clone()); |
271 | } |
272 | } |
273 | } |
274 | } |
275 | } |
276 | |
277 | param_constraint_mapping |
278 | } |
279 | |
280 | /// Hygienically add `where _: Display` to the set of [TypeParamBound]s for `ident`, creating such |
281 | /// a set if necessary. |
282 | fn ensure_display_in_where_clause_for_type(where_clause: &mut WhereClause, ident: Ident) { |
283 | for pred_ty in where_clause |
284 | .predicates |
285 | .iter_mut() |
286 | // Find the `where` predicate constraining the current type param, if it exists. |
287 | .flat_map(|predicate| match predicate { |
288 | WherePredicate::Type(pred_ty) => Some(pred_ty), |
289 | // We're looking through type constraints, not lifetime constraints. |
290 | _ => None, |
291 | }) |
292 | { |
293 | // Do a complicated destructuring in order to check if the type being constrained in this |
294 | // `where` clause is the type we're looking for, so we can use the mutable reference to |
295 | // `pred_ty` if so. |
296 | let matches_desired_type = matches!( |
297 | &pred_ty.bounded_ty, |
298 | Type::Path(TypePath { path, .. }) if Some(&ident) == path.get_ident()); |
299 | if matches_desired_type { |
300 | add_display_constraint_to_type_predicate(pred_ty); |
301 | return; |
302 | } |
303 | } |
304 | |
305 | // If there is no `where` predicate for the current type param, we will construct one. |
306 | let mut new_type_predicate = new_empty_where_type_predicate(ident); |
307 | add_display_constraint_to_type_predicate(&mut new_type_predicate); |
308 | append_where_clause_type_predicate(where_clause, new_type_predicate); |
309 | } |
310 | |
311 | /// For all declared type parameters, add a [core::fmt::Display] constraint, unless the type |
312 | /// parameter already has any type constraint. |
313 | fn ensure_where_clause_has_display_for_all_unconstrained_members( |
314 | where_clause: &mut WhereClause, |
315 | type_params: &[&TypeParam], |
316 | ) { |
317 | let param_constraint_mapping: HashMap |
318 | |
319 | for (ident: Ident, known_bounds: Vec |
320 | // If the type parameter has any constraints already, we don't want to touch it, to avoid |
321 | // breaking use cases where a type parameter only needs to impl `Debug`, for example. |
322 | if known_bounds.is_empty() { |
323 | ensure_display_in_where_clause_for_type(where_clause, ident); |
324 | } |
325 | } |
326 | } |
327 | |
328 | /// Generate a `where` clause that ensures all generic type parameters `impl` |
329 | /// [core::fmt::Display] unless already constrained. |
330 | /// |
331 | /// This approach allows struct/enum definitions deriving [crate::Display] to avoid hardcoding |
332 | /// a [core::fmt::Display] constraint into every type parameter. |
333 | /// |
334 | /// If the type parameter isn't already constrained, we add a `where _: Display` clause to our |
335 | /// display implementation to expect to be able to format every enum case or struct member. |
336 | /// |
337 | /// In fact, we would preferably only require `where _: Display` or `where _: Debug` where the |
338 | /// format string actually requires it. However, while [`std::fmt` defines a formal syntax for |
339 | /// `format!()`][format syntax], it *doesn't* expose the actual logic to parse the format string, |
340 | /// which appears to live in [`rustc_parse_format`]. While we use the [`syn`] crate to parse rust |
341 | /// syntax, it also doesn't currently provide any method to introspect a `format!()` string. It |
342 | /// would be nice to contribute this upstream in [`syn`]. |
343 | /// |
344 | /// [format syntax]: std::fmt#syntax |
345 | /// [`rustc_parse_format`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_parse_format/index.html |
346 | fn generate_where_clause(generics: &Generics, where_clause: Option<&WhereClause>) -> WhereClause { |
347 | let mut where_clause: WhereClause = where_clause.cloned().unwrap_or_else(new_empty_where_clause); |
348 | let type_params: Vec<&TypeParam> = generics.type_params().collect(); |
349 | ensure_where_clause_has_display_for_all_unconstrained_members(&mut where_clause, &type_params); |
350 | where_clause |
351 | } |
352 | |
353 | fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream> { |
354 | let ty = &input.ident; |
355 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); |
356 | let where_clause = generate_where_clause(&input.generics, where_clause); |
357 | |
358 | let helper = AttrsHelper::new(&input.attrs); |
359 | |
360 | let displays = data |
361 | .variants |
362 | .iter() |
363 | .map(|variant| helper.display_with_input(&input.attrs, &variant.attrs)) |
364 | .collect::<Result<Vec<_>>>()?; |
365 | |
366 | if data.variants.is_empty() { |
367 | Ok(quote! { |
368 | impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { |
369 | fn fmt(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
370 | unreachable!("empty enums cannot be instantiated and thus cannot be printed") |
371 | } |
372 | } |
373 | }) |
374 | } else if displays.iter().any(Option::is_some) { |
375 | let arms = data |
376 | .variants |
377 | .iter() |
378 | .zip(displays) |
379 | .map(|(variant, display)| { |
380 | let display = |
381 | display.ok_or_else(|| Error::new_spanned(variant, "missing doc comment"))?; |
382 | let ident = &variant.ident; |
383 | Ok(match &variant.fields { |
384 | Fields::Named(fields) => { |
385 | let var = fields.named.iter().map(|field| &field.ident); |
386 | quote!(Self::#ident { #(#var),* } => { #display }) |
387 | } |
388 | Fields::Unnamed(fields) => { |
389 | let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{} ", i)); |
390 | quote!(Self::#ident(#(#var),*) => { #display }) |
391 | } |
392 | Fields::Unit => quote!(Self::#ident => { #display }), |
393 | }) |
394 | }) |
395 | .collect::<Result<Vec<_>>>()?; |
396 | Ok(quote! { |
397 | impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { |
398 | fn fmt(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { |
399 | #[allow(unused_variables)] |
400 | match self { |
401 | #(#arms,)* |
402 | } |
403 | } |
404 | } |
405 | }) |
406 | } else { |
407 | Err(Error::new_spanned(input, "Missing doc comments")) |
408 | } |
409 | } |
410 |
Definitions
- derive
- specialization
- impl_struct
- new_empty_where_type_predicate
- new_empty_where_clause
- UseGlobalPrefix
- LeadingColon
- NoLeadingColon
- join_paths
- append_where_clause_type_predicate
- add_display_constraint_to_type_predicate
- extract_trait_constraints_from_source
- path
- ensure_display_in_where_clause_for_type
- path
- ensure_where_clause_has_display_for_all_unconstrained_members
- generate_where_clause
Learn Rust with the experts
Find out more