#![warn(
anonymous_parameters,
bare_trait_objects,
elided_lifetimes_in_paths,
missing_copy_implementations,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unreachable_pub,
unsafe_code,
unused_extern_crates,
unused_import_braces
)]
#![warn(
clippy::all,
clippy::cargo,
clippy::dbg_macro,
clippy::float_cmp_const,
clippy::get_unwrap,
clippy::mem_forget,
clippy::nursery,
clippy::pedantic,
clippy::todo,
clippy::unwrap_used,
clippy::uninlined_format_args
)]
#![allow(
clippy::default_trait_access,
clippy::doc_markdown,
clippy::if_not_else,
clippy::module_name_repetitions,
clippy::multiple_crate_versions,
clippy::must_use_candidate,
clippy::needless_pass_by_value,
clippy::needless_ifs,
clippy::use_self,
clippy::cargo_common_metadata,
clippy::missing_errors_doc,
clippy::enum_glob_use,
clippy::struct_excessive_bools,
clippy::missing_const_for_fn,
clippy::redundant_pub_crate,
clippy::result_large_err,
clippy::future_not_send,
clippy::option_if_let_else,
clippy::from_over_into,
clippy::manual_inspect
)]
#![cfg_attr(test, allow(clippy::non_ascii_literal, clippy::unwrap_used))]
#[allow(unused_extern_crates)]
extern crate proc_macro;
use proc_macro_crate::{FoundCrate, crate_name};
#[cfg(feature = "slog")]
use proc_macro2::Span;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
#[cfg(feature = "slog")]
use syn::parse_quote;
use syn::{
Data, DataEnum, DataStruct, DeriveInput, Fields, Result, parse_macro_input, spanned::Spanned,
};
mod container;
mod derive_enum;
mod derive_struct;
mod generics;
mod redacted_display;
mod strategy;
mod transform;
mod types;
use container::{ContainerOptions, parse_container_options};
use derive_enum::derive_enum;
use derive_struct::derive_struct;
use generics::{
add_container_bounds, add_debug_bounds, add_display_bounds, add_policy_applicable_bounds,
add_policy_applicable_ref_bounds, add_redacted_display_bounds, collect_generics_from_type,
};
use redacted_display::derive_redacted_display;
#[proc_macro_derive(Sensitive, attributes(sensitive, not_sensitive))]
pub fn derive_sensitive_container(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand(input, DeriveKind::Sensitive) {
Ok(tokens) => tokens.into(),
Err(err) => err.into_compile_error().into(),
}
}
#[proc_macro_derive(NotSensitive, attributes(not_sensitive))]
pub fn derive_not_sensitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand_not_sensitive(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.into_compile_error().into(),
}
}
fn reject_sensitivity_attrs(attrs: &[syn::Attribute], data: &Data, macro_name: &str) -> Result<()> {
let check_attr = |attr: &syn::Attribute| -> Result<()> {
if attr.path().is_ident("sensitive") {
return Err(syn::Error::new(
attr.span(),
format!("`#[sensitive]` attributes are not allowed on `{macro_name}` types"),
));
}
if attr.path().is_ident("not_sensitive") {
return Err(syn::Error::new(
attr.span(),
format!(
"`#[not_sensitive]` attributes are not needed on `{macro_name}` types (the entire type is already non-sensitive)"
),
));
}
Ok(())
};
for attr in attrs {
check_attr(attr)?;
}
match data {
Data::Struct(data) => {
for field in &data.fields {
for attr in &field.attrs {
check_attr(attr)?;
}
}
}
Data::Enum(data) => {
for variant in &data.variants {
for field in &variant.fields {
for attr in &field.attrs {
check_attr(attr)?;
}
}
}
}
Data::Union(_) => {}
}
Ok(())
}
fn expand_not_sensitive(input: DeriveInput) -> Result<TokenStream> {
let DeriveInput {
ident,
generics,
data,
attrs,
..
} = input;
if let Data::Union(u) = &data {
return Err(syn::Error::new(
u.union_token.span(),
"`NotSensitive` cannot be derived for unions",
));
}
reject_sensitivity_attrs(&attrs, &data, "NotSensitive")?;
let crate_root = crate_root();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let container_impl = quote! {
impl #impl_generics #crate_root::RedactableWithMapper for #ident #ty_generics #where_clause {
fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
self
}
}
};
#[cfg(feature = "slog")]
let slog_impl = {
let slog_crate = slog_crate()?;
let mut slog_generics = generics.clone();
let (_, ty_generics, _) = slog_generics.split_for_impl();
let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
slog_generics
.make_where_clause()
.predicates
.push(parse_quote!(#self_ty: ::serde::Serialize));
let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
slog_generics.split_for_impl();
quote! {
impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
fn serialize(
&self,
_record: &#slog_crate::Record<'_>,
key: #slog_crate::Key,
serializer: &mut dyn #slog_crate::Serializer,
) -> #slog_crate::Result {
#crate_root::slog::__slog_serialize_not_sensitive(self, _record, key, serializer)
}
}
impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
}
};
#[cfg(not(feature = "slog"))]
let slog_impl = quote! {};
#[cfg(feature = "tracing")]
let tracing_impl = {
let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
generics.split_for_impl();
quote! {
impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
}
};
#[cfg(not(feature = "tracing"))]
let tracing_impl = quote! {};
Ok(quote! {
#container_impl
#slog_impl
#tracing_impl
})
}
#[proc_macro_derive(NotSensitiveDisplay)]
pub fn derive_not_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand_not_sensitive_display(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.into_compile_error().into(),
}
}
fn expand_not_sensitive_display(input: DeriveInput) -> Result<TokenStream> {
let DeriveInput {
ident,
generics,
data,
attrs,
..
} = input;
if let Data::Union(u) = &data {
return Err(syn::Error::new(
u.union_token.span(),
"`NotSensitiveDisplay` cannot be derived for unions",
));
}
reject_sensitivity_attrs(&attrs, &data, "NotSensitiveDisplay")?;
let crate_root = crate_root();
let (container_impl_generics, container_ty_generics, container_where_clause) =
generics.split_for_impl();
let container_impl = quote! {
impl #container_impl_generics #crate_root::RedactableWithMapper for #ident #container_ty_generics #container_where_clause {
fn redact_with<M: #crate_root::RedactableMapper>(self, _mapper: &M) -> Self {
self
}
}
};
let mut display_generics = generics.clone();
let display_where_clause = display_generics.make_where_clause();
for param in generics.type_params() {
let ident = ¶m.ident;
display_where_clause
.predicates
.push(syn::parse_quote!(#ident: ::core::fmt::Display));
}
let (display_impl_generics, display_ty_generics, display_where_clause) =
display_generics.split_for_impl();
let redacted_display_impl = quote! {
impl #display_impl_generics #crate_root::RedactableWithFormatter for #ident #display_ty_generics #display_where_clause {
fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
::core::fmt::Display::fmt(self, f)
}
}
};
#[cfg(feature = "slog")]
let slog_impl = {
let slog_crate = slog_crate()?;
let mut slog_generics = generics.clone();
let (_, ty_generics, _) = slog_generics.split_for_impl();
let self_ty: syn::Type = syn::parse_quote!(#ident #ty_generics);
slog_generics
.make_where_clause()
.predicates
.push(syn::parse_quote!(#self_ty: #crate_root::RedactableWithFormatter));
let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
slog_generics.split_for_impl();
quote! {
impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
fn serialize(
&self,
_record: &#slog_crate::Record<'_>,
key: #slog_crate::Key,
serializer: &mut dyn #slog_crate::Serializer,
) -> #slog_crate::Result {
let redacted = #crate_root::RedactableWithFormatter::redacted_display(self);
serializer.emit_arguments(key, &format_args!("{}", redacted))
}
}
impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
}
};
#[cfg(not(feature = "slog"))]
let slog_impl = quote! {};
#[cfg(feature = "tracing")]
let tracing_impl = {
let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
generics.split_for_impl();
quote! {
impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
}
};
#[cfg(not(feature = "tracing"))]
let tracing_impl = quote! {};
Ok(quote! {
#container_impl
#redacted_display_impl
#slog_impl
#tracing_impl
})
}
#[proc_macro_derive(SensitiveDisplay, attributes(sensitive, not_sensitive, error))]
pub fn derive_sensitive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand(input, DeriveKind::SensitiveDisplay) {
Ok(tokens) => tokens.into(),
Err(err) => err.into_compile_error().into(),
}
}
fn crate_root() -> proc_macro2::TokenStream {
match crate_name("redactable") {
Ok(FoundCrate::Itself) => quote! { crate },
Ok(FoundCrate::Name(name)) => {
let ident = format_ident!("{}", name);
quote! { ::#ident }
}
Err(_) => quote! { ::redactable },
}
}
#[cfg(feature = "slog")]
fn slog_crate() -> Result<proc_macro2::TokenStream> {
match crate_name("slog") {
Ok(FoundCrate::Itself) => Ok(quote! { crate }),
Ok(FoundCrate::Name(name)) => {
let ident = format_ident!("{}", name);
Ok(quote! { ::#ident })
}
Err(_) => {
let env_value = std::env::var("REDACTABLE_SLOG_CRATE").map_err(|_| {
syn::Error::new(
Span::call_site(),
"slog support is enabled, but no top-level `slog` crate was found. \
Set the REDACTABLE_SLOG_CRATE env var to a path (e.g., `my_log::slog`) or add \
`slog` as a direct dependency.",
)
})?;
let path = syn::parse_str::<syn::Path>(&env_value).map_err(|_| {
syn::Error::new(
Span::call_site(),
format!("REDACTABLE_SLOG_CRATE must be a valid Rust path (got `{env_value}`)"),
)
})?;
Ok(quote! { #path })
}
}
}
fn crate_path(item: &str) -> proc_macro2::TokenStream {
let root = crate_root();
let item_ident = syn::parse_str::<syn::Path>(item).expect("redactable crate path should parse");
quote! { #root::#item_ident }
}
pub(crate) struct DeriveOutput {
pub(crate) redaction_body: TokenStream,
pub(crate) used_generics: Vec<Ident>,
pub(crate) policy_applicable_generics: Vec<Ident>,
pub(crate) debug_redacted_body: TokenStream,
pub(crate) debug_redacted_generics: Vec<Ident>,
pub(crate) debug_unredacted_body: TokenStream,
pub(crate) debug_unredacted_generics: Vec<Ident>,
}
struct DebugOutput {
body: TokenStream,
generics: Vec<Ident>,
}
enum DeriveKind {
Sensitive,
SensitiveDisplay,
}
#[allow(clippy::too_many_lines)]
fn expand(input: DeriveInput, kind: DeriveKind) -> Result<TokenStream> {
let DeriveInput {
ident,
generics,
data,
attrs,
..
} = input;
let ContainerOptions { dual } = parse_container_options(&attrs)?;
let crate_root = crate_root();
if matches!(kind, DeriveKind::SensitiveDisplay) {
let redacted_display_output = derive_redacted_display(&ident, &data, &attrs, &generics)?;
let redacted_display_generics =
add_display_bounds(generics.clone(), &redacted_display_output.display_generics);
let redacted_display_generics = add_debug_bounds(
redacted_display_generics,
&redacted_display_output.debug_generics,
);
let redacted_display_generics = add_policy_applicable_ref_bounds(
redacted_display_generics,
&redacted_display_output.policy_ref_generics,
);
let redacted_display_generics = add_redacted_display_bounds(
redacted_display_generics,
&redacted_display_output.nested_generics,
);
let (display_impl_generics, display_ty_generics, display_where_clause) =
redacted_display_generics.split_for_impl();
let redacted_display_body = redacted_display_output.body;
let redacted_display_impl = quote! {
impl #display_impl_generics #crate_root::RedactableWithFormatter for #ident #display_ty_generics #display_where_clause {
fn fmt_redacted(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#redacted_display_body
}
}
};
let to_redacted_output_impl = quote! {
impl #display_impl_generics #crate_root::ToRedactedOutput for #ident #display_ty_generics #display_where_clause {
fn to_redacted_output(&self) -> #crate_root::RedactedOutput {
#crate_root::RedactedOutput::Text(
#crate_root::RedactableWithFormatter::redacted_display(self).to_string(),
)
}
}
};
let debug_output = derive_unredacted_debug(&ident, &data, &generics)?;
let debug_unredacted_generics = add_debug_bounds(generics.clone(), &debug_output.generics);
let (
debug_unredacted_impl_generics,
debug_unredacted_ty_generics,
debug_unredacted_where_clause,
) = debug_unredacted_generics.split_for_impl();
let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
redacted_display_generics.split_for_impl();
let debug_unredacted_body = debug_output.body;
let debug_impl = quote! {
#[cfg(any(test, feature = "testing"))]
impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#debug_unredacted_body
}
}
#[cfg(not(any(test, feature = "testing")))]
impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#crate_root::RedactableWithFormatter::fmt_redacted(self, f)
}
}
};
let slog_impl = if dual {
quote! {}
} else {
#[cfg(feature = "slog")]
{
let slog_crate = slog_crate()?;
let mut slog_generics = generics;
let (_, ty_generics, _) = slog_generics.split_for_impl();
let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
slog_generics
.make_where_clause()
.predicates
.push(parse_quote!(#self_ty: #crate_root::RedactableWithFormatter));
let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
slog_generics.split_for_impl();
quote! {
impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
fn serialize(
&self,
_record: &#slog_crate::Record<'_>,
key: #slog_crate::Key,
serializer: &mut dyn #slog_crate::Serializer,
) -> #slog_crate::Result {
let redacted = #crate_root::RedactableWithFormatter::redacted_display(self);
serializer.emit_arguments(key, &format_args!("{}", redacted))
}
}
impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
}
}
#[cfg(not(feature = "slog"))]
{
quote! {}
}
};
let tracing_impl = if dual {
quote! {}
} else {
#[cfg(feature = "tracing")]
{
let (tracing_impl_generics, tracing_ty_generics, tracing_where_clause) =
redacted_display_generics.split_for_impl();
quote! {
impl #tracing_impl_generics #crate_root::tracing::TracingRedacted for #ident #tracing_ty_generics #tracing_where_clause {}
}
}
#[cfg(not(feature = "tracing"))]
{
quote! {}
}
};
return Ok(quote! {
#redacted_display_impl
#to_redacted_output_impl
#debug_impl
#slog_impl
#tracing_impl
});
}
let derive_output = match data {
Data::Struct(data) => derive_struct(&ident, data, &generics)?,
Data::Enum(data) => derive_enum(&ident, data, &generics)?,
Data::Union(u) => {
return Err(syn::Error::new(
u.union_token.span(),
"`Sensitive` cannot be derived for unions",
));
}
};
let policy_generics = add_container_bounds(generics.clone(), &derive_output.used_generics);
let policy_generics =
add_policy_applicable_bounds(policy_generics, &derive_output.policy_applicable_generics);
let (impl_generics, ty_generics, where_clause) = policy_generics.split_for_impl();
let debug_redacted_generics =
add_debug_bounds(generics.clone(), &derive_output.debug_redacted_generics);
let (debug_redacted_impl_generics, debug_redacted_ty_generics, debug_redacted_where_clause) =
debug_redacted_generics.split_for_impl();
let debug_unredacted_generics =
add_debug_bounds(generics.clone(), &derive_output.debug_unredacted_generics);
let (
debug_unredacted_impl_generics,
debug_unredacted_ty_generics,
debug_unredacted_where_clause,
) = debug_unredacted_generics.split_for_impl();
let redaction_body = &derive_output.redaction_body;
let debug_redacted_body = &derive_output.debug_redacted_body;
let debug_unredacted_body = &derive_output.debug_unredacted_body;
let debug_impl = if dual {
quote! {}
} else {
quote! {
#[cfg(any(test, feature = "testing"))]
impl #debug_unredacted_impl_generics ::core::fmt::Debug for #ident #debug_unredacted_ty_generics #debug_unredacted_where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#debug_unredacted_body
}
}
#[cfg(not(any(test, feature = "testing")))]
impl #debug_redacted_impl_generics ::core::fmt::Debug for #ident #debug_redacted_ty_generics #debug_redacted_where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
#debug_redacted_body
}
}
}
};
#[cfg(feature = "slog")]
let slog_impl = {
let slog_crate = slog_crate()?;
let mut slog_generics = generics;
let slog_where_clause = slog_generics.make_where_clause();
let self_ty: syn::Type = parse_quote!(#ident #ty_generics);
slog_where_clause
.predicates
.push(parse_quote!(#self_ty: ::core::clone::Clone));
slog_where_clause
.predicates
.push(parse_quote!(#self_ty: ::serde::Serialize));
slog_where_clause
.predicates
.push(parse_quote!(#self_ty: #crate_root::slog::SlogRedactedExt));
let (slog_impl_generics, slog_ty_generics, slog_where_clause) =
slog_generics.split_for_impl();
quote! {
impl #slog_impl_generics #slog_crate::Value for #ident #slog_ty_generics #slog_where_clause {
fn serialize(
&self,
_record: &#slog_crate::Record<'_>,
key: #slog_crate::Key,
serializer: &mut dyn #slog_crate::Serializer,
) -> #slog_crate::Result {
let redacted = #crate_root::slog::SlogRedactedExt::slog_redacted_json(self.clone());
#slog_crate::Value::serialize(&redacted, _record, key, serializer)
}
}
impl #slog_impl_generics #crate_root::slog::SlogRedacted for #ident #slog_ty_generics #slog_where_clause {}
}
};
#[cfg(not(feature = "slog"))]
let slog_impl = quote! {};
#[cfg(feature = "tracing")]
let tracing_impl = quote! {
impl #impl_generics #crate_root::tracing::TracingRedacted for #ident #ty_generics #where_clause {}
};
#[cfg(not(feature = "tracing"))]
let tracing_impl = quote! {};
let trait_impl = quote! {
impl #impl_generics #crate_root::RedactableWithMapper for #ident #ty_generics #where_clause {
fn redact_with<M: #crate_root::RedactableMapper>(self, mapper: &M) -> Self {
use #crate_root::RedactableWithMapper as _;
#redaction_body
}
}
#debug_impl
#slog_impl
#tracing_impl
};
Ok(trait_impl)
}
fn derive_unredacted_debug(
name: &Ident,
data: &Data,
generics: &syn::Generics,
) -> Result<DebugOutput> {
match data {
Data::Struct(data) => Ok(derive_unredacted_debug_struct(name, data, generics)),
Data::Enum(data) => Ok(derive_unredacted_debug_enum(name, data, generics)),
Data::Union(u) => Err(syn::Error::new(
u.union_token.span(),
"`SensitiveDisplay` cannot be derived for unions",
)),
}
}
fn derive_unredacted_debug_struct(
name: &Ident,
data: &DataStruct,
generics: &syn::Generics,
) -> DebugOutput {
let mut debug_generics = Vec::new();
match &data.fields {
Fields::Named(fields) => {
let mut bindings = Vec::new();
let mut debug_fields = Vec::new();
for field in &fields.named {
let ident = field
.ident
.clone()
.expect("named field should have identifier");
bindings.push(ident.clone());
collect_generics_from_type(&field.ty, generics, &mut debug_generics);
debug_fields.push(quote! {
debug.field(stringify!(#ident), #ident);
});
}
DebugOutput {
body: quote! {
match self {
Self { #(#bindings),* } => {
let mut debug = f.debug_struct(stringify!(#name));
#(#debug_fields)*
debug.finish()
}
}
},
generics: debug_generics,
}
}
Fields::Unnamed(fields) => {
let mut bindings = Vec::new();
let mut debug_fields = Vec::new();
for (index, field) in fields.unnamed.iter().enumerate() {
let ident = format_ident!("field_{index}");
bindings.push(ident.clone());
collect_generics_from_type(&field.ty, generics, &mut debug_generics);
debug_fields.push(quote! {
debug.field(#ident);
});
}
DebugOutput {
body: quote! {
match self {
Self ( #(#bindings),* ) => {
let mut debug = f.debug_tuple(stringify!(#name));
#(#debug_fields)*
debug.finish()
}
}
},
generics: debug_generics,
}
}
Fields::Unit => DebugOutput {
body: quote! {
f.write_str(stringify!(#name))
},
generics: debug_generics,
},
}
}
fn derive_unredacted_debug_enum(
name: &Ident,
data: &DataEnum,
generics: &syn::Generics,
) -> DebugOutput {
let mut debug_generics = Vec::new();
let mut debug_arms = Vec::new();
for variant in &data.variants {
let variant_ident = &variant.ident;
match &variant.fields {
Fields::Unit => {
debug_arms.push(quote! {
#name::#variant_ident => f.write_str(stringify!(#name::#variant_ident))
});
}
Fields::Named(fields) => {
let mut bindings = Vec::new();
let mut debug_fields = Vec::new();
for field in &fields.named {
let ident = field
.ident
.clone()
.expect("named field should have identifier");
bindings.push(ident.clone());
collect_generics_from_type(&field.ty, generics, &mut debug_generics);
debug_fields.push(quote! {
debug.field(stringify!(#ident), #ident);
});
}
debug_arms.push(quote! {
#name::#variant_ident { #(#bindings),* } => {
let mut debug = f.debug_struct(stringify!(#name::#variant_ident));
#(#debug_fields)*
debug.finish()
}
});
}
Fields::Unnamed(fields) => {
let mut bindings = Vec::new();
let mut debug_fields = Vec::new();
for (index, field) in fields.unnamed.iter().enumerate() {
let ident = format_ident!("field_{index}");
bindings.push(ident.clone());
collect_generics_from_type(&field.ty, generics, &mut debug_generics);
debug_fields.push(quote! {
debug.field(#ident);
});
}
debug_arms.push(quote! {
#name::#variant_ident ( #(#bindings),* ) => {
let mut debug = f.debug_tuple(stringify!(#name::#variant_ident));
#(#debug_fields)*
debug.finish()
}
});
}
}
}
DebugOutput {
body: quote! {
match self {
#(#debug_arms),*
}
},
generics: debug_generics,
}
}