| 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 = Punctuated::<PathSegment, PathSep>::new(); | 
|---|
| 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> = extract_trait_constraints_from_source(where_clause, type_params); | 
|---|
| 318 |  | 
|---|
| 319 | for (ident: Ident, known_bounds: Vec) in param_constraint_mapping.into_iter() { | 
|---|
| 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 |  | 
|---|