Skip to main content

redactable_derive/
lib.rs

1//! Derive macros for `redactable`.
2//!
3//! This crate generates traversal code behind `#[derive(Sensitive)]` and
4//! `#[derive(SensitiveDisplay)]`. It:
5//! - reads `#[sensitive(...)]` field attributes
6//! - emits a `RedactableContainer` implementation that calls into a mapper
7//!
8//! It does **not** define policy markers or text policies. Those live in the main
9//! `redactable` crate and are applied at runtime.
10
11// <https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html>
12#![warn(
13    anonymous_parameters,
14    bare_trait_objects,
15    elided_lifetimes_in_paths,
16    missing_copy_implementations,
17    rust_2018_idioms,
18    trivial_casts,
19    trivial_numeric_casts,
20    unreachable_pub,
21    unsafe_code,
22    unused_extern_crates,
23    unused_import_braces
24)]
25// <https://rust-lang.github.io/rust-clippy/stable>
26#![warn(
27    clippy::all,
28    clippy::cargo,
29    clippy::dbg_macro,
30    clippy::float_cmp_const,
31    clippy::get_unwrap,
32    clippy::mem_forget,
33    clippy::nursery,
34    clippy::pedantic,
35    clippy::todo,
36    clippy::unwrap_used,
37    clippy::uninlined_format_args
38)]
39// Allow some clippy lints
40#![allow(
41    clippy::default_trait_access,
42    clippy::doc_markdown,
43    clippy::if_not_else,
44    clippy::module_name_repetitions,
45    clippy::multiple_crate_versions,
46    clippy::must_use_candidate,
47    clippy::needless_pass_by_value,
48    clippy::needless_ifs,
49    clippy::use_self,
50    clippy::cargo_common_metadata,
51    clippy::missing_errors_doc,
52    clippy::enum_glob_use,
53    clippy::struct_excessive_bools,
54    clippy::missing_const_for_fn,
55    clippy::redundant_pub_crate,
56    clippy::result_large_err,
57    clippy::future_not_send,
58    clippy::option_if_let_else,
59    clippy::from_over_into,
60    clippy::manual_inspect
61)]
62// Allow some lints while testing
63#![cfg_attr(test, allow(clippy::non_ascii_literal, clippy::unwrap_used))]
64
65#[allow(unused_extern_crates)]
66extern crate proc_macro;
67
68use proc_macro_crate::{FoundCrate, crate_name};
69#[cfg(feature = "slog")]
70use proc_macro2::Span;
71use proc_macro2::{Ident, TokenStream};
72use quote::{format_ident, quote};
73#[cfg(feature = "slog")]
74use syn::parse_quote;
75use syn::{Data, DeriveInput, Result, parse_macro_input, spanned::Spanned};
76
77mod container;
78mod derive_enum;
79mod derive_struct;
80mod generics;
81mod redacted_display;
82mod strategy;
83mod transform;
84mod types;
85use container::{ContainerOptions, parse_container_options};
86use derive_enum::derive_enum;
87use derive_struct::derive_struct;
88use generics::{
89    add_container_bounds, add_debug_bounds, add_display_bounds, add_policy_applicable_bounds,
90    add_policy_applicable_ref_bounds, add_redacted_display_bounds,
91};
92use redacted_display::derive_redacted_display;
93
94/// Derives `redactable::RedactableContainer` (and related impls) for structs and enums.
95///
96/// # Container Attributes
97///
98/// These attributes are placed on the struct/enum itself:
99///
100/// - `#[sensitive(skip_debug)]` - Opt out of `Debug` impl generation. Use this when you need a
101///   custom `Debug` implementation or the type already derives `Debug` elsewhere.
102///
103/// # Field Attributes
104///
105/// - **No annotation**: The field is traversed by default. Scalars pass through unchanged; nested
106///   structs/enums are walked using `RedactableContainer` (so external types must implement it).
107///
108/// - `#[sensitive(Default)]`: For scalar types (i32, bool, char, etc.), redacts to default values
109///   (0, false, '*'). For string-like types, applies full redaction to `"[REDACTED]"`.
110///
111/// - `#[sensitive(Policy)]`: Applies the policy's redaction rules to string-like
112///   values. Works for `String`, `Option<String>`, `Vec<String>`, `Box<String>`. Scalars can only
113///   use `#[sensitive(Default)]`.
114///
115/// Unions are rejected at compile time.
116///
117/// # Additional Generated Impls
118///
119/// - `Debug`: when *not* building with `cfg(any(test, feature = "testing"))`, sensitive fields are
120///   formatted as the string `"[REDACTED]"` rather than their values. Use `#[sensitive(skip_debug)]`
121///   on the container to opt out.
122/// - `slog::Value` (behind `cfg(feature = "slog")`): implemented by cloning the value and routing
123///   it through `redactable::slog::SlogRedactedExt`. **Note:** this impl requires `Clone` and
124///   `serde::Serialize` because it emits structured JSON. The derive first looks for a top-level
125///   `slog` crate; if not found, it checks the `REDACTABLE_SLOG_CRATE` env var for an alternate path
126///   (e.g., `my_log::slog`). If neither is available, compilation fails with a clear error.
127#[proc_macro_derive(Sensitive, attributes(sensitive))]
128pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
129    let input = parse_macro_input!(input as DeriveInput);
130    match expand(input, SlogMode::RedactedJson) {
131        Ok(tokens) => tokens.into(),
132        Err(err) => err.into_compile_error().into(),
133    }
134}
135
136#[proc_macro_derive(SensitiveData, attributes(sensitive))]
137pub fn derive_sensitive_data(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
138    derive_sensitive_container(input)
139}
140
141/// Derives a no-op `redactable::RedactableContainer` implementation.
142///
143/// This is useful for types that are known to be non-sensitive but still need to
144/// satisfy `RedactableContainer` / `Redactable` bounds.
145#[proc_macro_derive(NotSensitive)]
146pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
147    let input = parse_macro_input!(input as DeriveInput);
148    let ident = input.ident;
149    let generics = input.generics;
150    let attrs = input.attrs;
151    let data = input.data;
152
153    let mut sensitive_attr_spans = Vec::new();
154    if let Some(attr) = attrs.iter().find(|attr| attr.path().is_ident("sensitive")) {
155        sensitive_attr_spans.push(attr.span());
156    }
157
158    match &data {
159        Data::Struct(data) => {
160            for field in &data.fields {
161                if field
162                    .attrs
163                    .iter()
164                    .any(|attr| attr.path().is_ident("sensitive"))
165                {
166                    sensitive_attr_spans.push(field.span());
167                }
168            }
169        }
170        Data::Enum(data) => {
171            for variant in &data.variants {
172                for field in &variant.fields {
173                    if field
174                        .attrs
175                        .iter()
176                        .any(|attr| attr.path().is_ident("sensitive"))
177                    {
178                        sensitive_attr_spans.push(field.span());
179                    }
180                }
181            }
182        }
183        Data::Union(data) => {
184            return syn::Error::new(
185                data.union_token.span(),
186                "`NotSensitive` cannot be derived for unions",
187            )
188            .into_compile_error()
189            .into();
190        }
191    }
192
193    if let Some(span) = sensitive_attr_spans.first() {
194        return syn::Error::new(
195            *span,
196            "`#[sensitive]` attributes are not allowed on `NotSensitive` types",
197        )
198        .into_compile_error()
199        .into();
200    }
201    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
202    let crate_root = crate_root();
203
204    let tokens = quote! {
205        impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
206            fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
207                self
208            }
209        }
210    };
211    tokens.into()
212}
213
214/// Derives `redactable::RedactableDisplay` using a display template.
215///
216/// This generates a redacted string representation without requiring `Clone`.
217/// Unannotated fields use `RedactableDisplay` by default (passthrough for scalars,
218/// redacted display for nested `SensitiveDisplay` types).
219///
220/// # Field Annotations
221///
222/// - *(none)*: Uses `RedactableDisplay` (requires the field type to implement it)
223/// - `#[sensitive(Policy)]`: Apply the policy's redaction rules
224/// - `#[not_sensitive]`: Render raw via `Display` (use for types without `RedactableDisplay`)
225///
226/// The display template is taken from `#[error("...")]` (thiserror-style) or from
227/// doc comments (displaydoc-style). If neither is present, the derive fails.
228///
229/// Fields are redacted by reference, so field types do not need `Clone`.
230#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
231pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
232    let input = parse_macro_input!(input as DeriveInput);
233    match expand(input, SlogMode::RedactedDisplay) {
234        Ok(tokens) => tokens.into(),
235        Err(err) => err.into_compile_error().into(),
236    }
237}
238
239/// Returns the token stream to reference the redactable crate root.
240///
241/// Handles crate renaming (e.g., `my_redact = { package = "redactable", ... }`)
242/// and internal usage (when derive is used inside the redactable crate itself).
243fn crate_root() -> proc_macro2::TokenStream {
244    match crate_name("redactable") {
245        Ok(FoundCrate::Itself) => quote! { crate },
246        Ok(FoundCrate::Name(name)) => {
247            let ident = format_ident!("{}", name);
248            quote! { ::#ident }
249        }
250        Err(_) => quote! { ::redactable },
251    }
252}
253
254/// Returns the token stream to reference the slog crate root.
255///
256/// Handles crate renaming (e.g., `my_slog = { package = "slog", ... }`).
257/// If the top-level `slog` crate is not available, falls back to the
258/// `REDACTABLE_SLOG_CRATE` env var, which should be a path like `my_log::slog`.
259#[cfg(feature = "slog")]
260fn slog_crate() -> Result<proc_macro2::TokenStream> {
261    match crate_name("slog") {
262        Ok(FoundCrate::Itself) => Ok(quote! { crate }),
263        Ok(FoundCrate::Name(name)) => {
264            let ident = format_ident!("{}", name);
265            Ok(quote! { ::#ident })
266        }
267        Err(_) => {
268            let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
269                syn::Error::new(
270                    Span::call_site(),
271                    "slog support is enabled, but no top-level `slog` crate was found. \
272Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
273`slog` as a direct dependency.",
274                )
275            })?;
276            let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
277                syn::Error::new(
278                    Span::call_site(),
279                    format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
280                )
281            })?;
282            Ok(quote! { #path })
283        }
284    }
285}
286
287fn crate_path(item: &str) -> proc_macro2::TokenStream {
288    let root = crate_root();
289    let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
290    quote! { #root::#item_ident }
291}
292
293struct DeriveOutput {
294    redaction_body: TokenStream,
295    used_generics: Vec<Ident>,
296    policy_applicable_generics: Vec<Ident>,
297    debug_redacted_body: TokenStream,
298    debug_redacted_generics: Vec<Ident>,
299    debug_unredacted_body: TokenStream,
300    debug_unredacted_generics: Vec<Ident>,
301    redacted_display_body: Option<TokenStream>,
302    redacted_display_generics: Vec<Ident>,
303    redacted_display_debug_generics: Vec<Ident>,
304    redacted_display_policy_ref_generics: Vec<Ident>,
305    redacted_display_nested_generics: Vec<Ident>,
306}
307
308enum SlogMode {
309    RedactedJson,
310    RedactedDisplay,
311}
312
313#[allow(clippy::too_many_lines)]
314fn expand(input: DeriveInput, slog_mode: SlogMode) -> Result<TokenStream> {
315    let DeriveInput {
316        ident,
317        generics,
318        data,
319        attrs,
320        ..
321    } = input;
322
323    let ContainerOptions { skip_debug } = parse_container_options(&attrs)?;
324
325    let crate_root = crate_root();
326
327    if matches!(slog_mode, SlogMode::RedactedDisplay) {
328        let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
329        let redacted_display_generics =
330            add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
331        let redacted_display_generics = add_debug_bounds(
332            redacted_display_generics,
333            &redacted_display_output.debug_generics,
334        );
335        let redacted_display_generics = add_policy_applicable_ref_bounds(
336            redacted_display_generics,
337            &redacted_display_output.policy_ref_generics,
338        );
339        let redacted_display_generics = add_redacted_display_bounds(
340            redacted_display_generics,
341            &redacted_display_output.nested_generics,
342        );
343        let (display_impl_generics, display_ty_generics, display_where_clause) =
344            redacted_display_generics.split_for_impl();
345        let redacted_display_body = redacted_display_output.body;
346        let redacted_display_impl = quote! {
347            impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
348                fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
349                    #redacted_display_body
350                }
351            }
352        };
353
354        // Only generate slog impl when the slog feature is enabled on redactable-derive.
355        // If slog is not available, emit a clear error with instructions.
356        #[cfg(feature = "slog")]
357        let slog_impl = {
358            let slog_crate = slog_crate()?;
359            let mut slog_generics = generics;
360            // Get ty_generics first (immutable borrow) before make_where_clause (mutable borrow)
361            let (_, ty_generics, _) = slog_generics.split_for_impl();
362            let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
363            slog_generics
364                .make_where_clause()
365                .predicates
366                .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
367            let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
368                slog_generics.split_for_impl();
369            quote! {
370                impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
371                    fn serialize(
372                        &self,
373                        _record: &#slog_crate::Record<'_>,
374                        key: #slog_crate::Key,
375                        serializer: &mut dyn #slog_crate::Serializer,
376                    ) -> #slog_crate::Result {
377                        let redacted = #crate_root::RedactableDisplay::redacted_display(self);
378                        serializer.emit_arguments(key, &format_args!("{}", redacted))
379                    }
380                }
381            }
382        };
383
384        #[cfg(not(feature = "slog"))]
385        let slog_impl = quote! {};
386
387        return Ok(quote! {
388            #redacted_display_impl
389            #slog_impl
390        });
391    }
392
393    let redacted_display_output = if matches!(slog_mode, SlogMode::RedactedDisplay) {
394        Some(derive_redacted_display(&ident, &data, &attrs, &generics)?)
395    } else {
396        None
397    };
398
399    let derive_output = match &data {
400        Data::Struct(data) => {
401            let output = derive_struct(&ident, data.clone(), &generics)?;
402            DeriveOutput {
403                redaction_body: output.redaction_body,
404                used_generics: output.used_generics,
405                policy_applicable_generics: output.policy_applicable_generics,
406                debug_redacted_body: output.debug_redacted_body,
407                debug_redacted_generics: output.debug_redacted_generics,
408                debug_unredacted_body: output.debug_unredacted_body,
409                debug_unredacted_generics: output.debug_unredacted_generics,
410                redacted_display_body: redacted_display_output
411                    .as_ref()
412                    .map(|output| output.body.clone()),
413                redacted_display_generics: redacted_display_output
414                    .as_ref()
415                    .map(|output| output.display_generics.clone())
416                    .unwrap_or_default(),
417                redacted_display_debug_generics: redacted_display_output
418                    .as_ref()
419                    .map(|output| output.debug_generics.clone())
420                    .unwrap_or_default(),
421                redacted_display_policy_ref_generics: redacted_display_output
422                    .as_ref()
423                    .map(|output| output.policy_ref_generics.clone())
424                    .unwrap_or_default(),
425                redacted_display_nested_generics: redacted_display_output
426                    .as_ref()
427                    .map(|output| output.nested_generics.clone())
428                    .unwrap_or_default(),
429            }
430        }
431        Data::Enum(data) => {
432            let output = derive_enum(&ident, data.clone(), &generics)?;
433            DeriveOutput {
434                redaction_body: output.redaction_body,
435                used_generics: output.used_generics,
436                policy_applicable_generics: output.policy_applicable_generics,
437                debug_redacted_body: output.debug_redacted_body,
438                debug_redacted_generics: output.debug_redacted_generics,
439                debug_unredacted_body: output.debug_unredacted_body,
440                debug_unredacted_generics: output.debug_unredacted_generics,
441                redacted_display_body: redacted_display_output
442                    .as_ref()
443                    .map(|output| output.body.clone()),
444                redacted_display_generics: redacted_display_output
445                    .as_ref()
446                    .map(|output| output.display_generics.clone())
447                    .unwrap_or_default(),
448                redacted_display_debug_generics: redacted_display_output
449                    .as_ref()
450                    .map(|output| output.debug_generics.clone())
451                    .unwrap_or_default(),
452                redacted_display_policy_ref_generics: redacted_display_output
453                    .as_ref()
454                    .map(|output| output.policy_ref_generics.clone())
455                    .unwrap_or_default(),
456                redacted_display_nested_generics: redacted_display_output
457                    .as_ref()
458                    .map(|output| output.nested_generics.clone())
459                    .unwrap_or_default(),
460            }
461        }
462        Data::Union(u) => {
463            return Err(syn::Error::new(
464                u.union_token.span(),
465                "`Sensitive` cannot be derived for unions",
466            ));
467        }
468    };
469
470    let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
471    let policy_generics =
472        add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
473    let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
474    let debug_redacted_generics =
475        add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
476    let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
477        debug_redacted_generics.split_for_impl();
478    let debug_unredacted_generics =
479        add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
480    let (
481        debug_unredacted_impl_generics,
482        debug_unredacted_ty_generics,
483        debug_unredacted_where_clause,
484    ) = debug_unredacted_generics.split_for_impl();
485    let redaction_body = &derive_output.redaction_body;
486    let debug_redacted_body = &derive_output.debug_redacted_body;
487    let debug_unredacted_body = &derive_output.debug_unredacted_body;
488    let debug_impl = if skip_debug {
489        quote! {}
490    } else {
491        quote! {
492            #[cfg(any(test, feature = "testing"))]
493            impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
494                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
495                    #debug_unredacted_body
496                }
497            }
498
499            #[cfg(not(any(test, feature = "testing")))]
500            #[allow(unused_variables)]
501            impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
502                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
503                    #debug_redacted_body
504                }
505            }
506        }
507    };
508
509    let redacted_display_body = derive_output.redacted_display_body.as_ref();
510    let redacted_display_impl = if matches!(slog_mode, SlogMode::RedactedDisplay) {
511        let redacted_display_generics =
512            add_display_bounds(generics.clone(), &derive_output.redacted_display_generics);
513        let redacted_display_generics = add_debug_bounds(
514            redacted_display_generics,
515            &derive_output.redacted_display_debug_generics,
516        );
517        let redacted_display_generics = add_policy_applicable_ref_bounds(
518            redacted_display_generics,
519            &derive_output.redacted_display_policy_ref_generics,
520        );
521        let redacted_display_generics = add_redacted_display_bounds(
522            redacted_display_generics,
523            &derive_output.redacted_display_nested_generics,
524        );
525        let (display_impl_generics, display_ty_generics, display_where_clause) =
526            redacted_display_generics.split_for_impl();
527        let redacted_display_body = redacted_display_body
528            .cloned()
529            .unwrap_or_else(TokenStream::new);
530        quote! {
531            impl #display_impl_generics #crate_root::RedactableDisplay for #ident #display_ty_generics #display_where_clause {
532                fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
533                    #redacted_display_body
534                }
535            }
536        }
537    } else {
538        quote! {}
539    };
540
541    // Only generate slog impl when the slog feature is enabled on redactable-derive.
542    // If slog is not available, emit a clear error with instructions.
543    #[cfg(feature = "slog")]
544    let slog_impl = {
545        let slog_crate = slog_crate()?;
546        let mut slog_generics = generics;
547        let slog_where_clause = slog_generics.make_where_clause();
548        let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
549        match slog_mode {
550            SlogMode::RedactedJson => {
551                slog_where_clause
552                    .predicates
553                    .push(parse_quote!(#self_ty: ::core::clone::Clone));
554                // SlogRedactedExt requires Self: Serialize, so we add this bound to enable
555                // generic types to work with slog when their type parameters implement Serialize.
556                slog_where_clause
557                    .predicates
558                    .push(parse_quote!(#self_ty: ::serde::Serialize));
559                slog_where_clause
560                    .predicates
561                    .push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
562                let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
563                    slog_generics.split_for_impl();
564                quote! {
565                    impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
566                        fn serialize(
567                            &self,
568                            _record: &#slog_crate::Record<'_>,
569                            key: #slog_crate::Key,
570                            serializer: &mut dyn #slog_crate::Serializer,
571                        ) -> #slog_crate::Result {
572                            let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
573                            #slog_crate::Value::serialize(&redacted, _record, key, serializer)
574                        }
575                    }
576                }
577            }
578            SlogMode::RedactedDisplay => {
579                slog_where_clause
580                    .predicates
581                    .push(parse_quote!(#self_ty: #crate_root::RedactableDisplay));
582                let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
583                    slog_generics.split_for_impl();
584                quote! {
585                    impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
586                        fn serialize(
587                            &self,
588                            _record: &#slog_crate::Record<'_>,
589                            key: #slog_crate::Key,
590                            serializer: &mut dyn #slog_crate::Serializer,
591                        ) -> #slog_crate::Result {
592                            let redacted = #crate_root::RedactableDisplay::redacted_display(self);
593                            serializer.emit_arguments(key, &format_args!("{}", redacted))
594                        }
595                    }
596                }
597            }
598        }
599    };
600
601    #[cfg(not(feature = "slog"))]
602    let slog_impl = quote! {};
603
604    let trait_impl = quote! {
605        impl #impl_generics #crate_root::RedactableContainer for #ident #ty_generics #where_clause {
606            fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
607                use #crate_root::RedactableContainer as _;
608                #redaction_body
609            }
610        }
611
612        #debug_impl
613
614        #redacted_display_impl
615
616        #slog_impl
617
618        // `slog` already provides `impl<V: Value> Value for &V`, so a reference
619        // impl here would conflict with the blanket impl.
620    };
621    Ok(trait_impl)
622}