use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::Type;
use super::model::{ResolvedEnum, ResolvedStruct, field_binding_pats};
use super::parse::{CategorizedFields, DisplayAttr, ExitCodeAttr, ProvideAttr};
fn exit_code_nonzero(exit: ExitCodeAttr, oopsie_path: &syn::Path) -> TokenStream2 {
let lit = proc_macro2::Literal::u8_unsuffixed(exit.value);
let lit = quote::quote_spanned! { exit.span => #lit };
quote! { #oopsie_path::__private::nonzero_u8_unchecked(#lit) }
}
fn exit_code_some(exit: ExitCodeAttr, oopsie_path: &syn::Path) -> TokenStream2 {
let nonzero = exit_code_nonzero(exit, oopsie_path);
quote! { ::core::option::Option::Some(#nonzero) }
}
fn trace_accessor_body(
own: Option<TokenStream2>,
source_access: Option<TokenStream2>,
probe: Option<TokenStream2>,
source_fn: &syn::Ident,
oopsie_path: &syn::Path,
) -> Option<TokenStream2> {
match (own, source_access) {
(Some(own), Some(src)) => {
let head = provider_then_probe(&src, probe, source_fn, oopsie_path);
Some(quote! { #head.or(::core::option::Option::Some(#own)) })
}
(Some(own), None) => Some(quote! { ::core::option::Option::Some(#own) }),
(None, Some(src)) => Some(provider_then_probe(&src, probe, source_fn, oopsie_path)),
(None, None) => None,
}
}
fn location_accessor_body(
own: Option<TokenStream2>,
probe: Option<TokenStream2>,
) -> Option<TokenStream2> {
match (own, probe) {
(Some(own), Some(probe)) => Some(quote! { #probe.or(::core::option::Option::Some(#own)) }),
(Some(own), None) => Some(quote! { ::core::option::Option::Some(#own) }),
(None, Some(probe)) => Some(probe),
(None, None) => None,
}
}
fn provider_then_probe(
src: &TokenStream2,
probe: Option<TokenStream2>,
source_fn: &syn::Ident,
oopsie_path: &syn::Path,
) -> TokenStream2 {
let base = quote! { #oopsie_path::__private::#source_fn(#src) };
match probe {
Some(probe) => quote! { #base.or(#probe) },
None => base,
}
}
fn accessor_pattern_binds(
own: Option<&syn::Ident>,
source: Option<&syn::Ident>,
) -> Vec<syn::Ident> {
let mut binds = Vec::new();
if let Some(own) = own {
binds.push(own.clone());
}
if let Some(source) = source {
binds.push(source.clone());
}
binds
}
fn gen_diag_forward(
target: impl quote::ToTokens,
method: &str,
oopsie_path: &syn::Path,
) -> TokenStream2 {
let method = format_ident!("{method}");
quote! {
{
use #oopsie_path::__private::{DiagForwardExt as _, DiagForwardFallback as _};
(&#oopsie_path::__private::DiagProbe(#target)).#method()
}
}
}
fn diagnostic_where_clause(
generics: &syn::Generics,
forwarded_generic_sources: &[&Type],
oopsie_path: &syn::Path,
) -> TokenStream2 {
let mut predicates: Vec<TokenStream2> = generics
.where_clause
.iter()
.flat_map(|wc| wc.predicates.iter())
.map(|pred| quote! { #pred })
.collect();
let mut seen: Vec<String> = Vec::new();
for ty in forwarded_generic_sources {
let key = quote! { #ty }.to_string();
if seen.contains(&key) {
continue;
}
seen.push(key);
predicates.push(quote! { #ty: #oopsie_path::Diagnostic });
}
if predicates.is_empty() {
quote! {}
} else {
quote! { where #(#predicates),* }
}
}
fn forwarded_generic_source_ty<'a>(
source: Option<&'a super::parse::SourceField>,
generics: &syn::Generics,
) -> Option<&'a Type> {
let source = source?;
if !source.forward.any() {
return None;
}
let declared = super::generics::DeclaredParams::from_generics(generics);
declared
.type_references_param(&source.ty)
.then_some(&source.ty)
}
pub fn gen_enum_error(
resolved: &ResolvedEnum,
oopsie_path: &syn::Path,
) -> syn::Result<TokenStream2> {
let input = resolved.input;
let enum_ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let req = format_ident!("__request");
let mut source_arms = Vec::new();
let mut provide_arms = Vec::new();
let mut bt_arms = Vec::new();
let mut st_arms = Vec::new();
let mut loc_arms = Vec::new();
let mut code_arms = Vec::new();
let mut help_arms = Vec::new();
let mut exit_arms = Vec::new();
let mut accessor_uses_source = false;
let container_exit = resolved.container.exit_code;
for v in &resolved.variants {
let variant = v.variant;
let variant_ident = v.ident();
let categorized = &v.fields;
let variant_attrs = &v.attrs;
let cfg_attrs = &v.cfg_attrs;
if let Some(source_field) = &categorized.source {
let source_ident = &source_field.ident;
source_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { #source_ident, .. } => ::core::option::Option::Some(#source_ident.as_error_source()),
});
} else {
source_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { .. } => ::core::option::Option::None,
});
}
let mut provide_stmts = Vec::new();
if let Some(source_field) = &categorized.source {
let source_ident = &source_field.ident;
provide_stmts.push(quote! {
::core::error::Error::provide(#source_ident.as_error_source(), #req);
});
}
for (_field_ident, provide_attr) in &categorized.provides {
provide_stmts.push(gen_provide_call(provide_attr, &req));
}
if let Some(tf) = &categorized.traces_field {
let field_cfg = trace_field_cfg(categorized, Some(tf));
provide_stmts.push(quote! {
#(#field_cfg)*
if #tf.0.is_captured() {
#req.provide_ref::<#oopsie_path::Backtrace>(&#tf.0);
}
});
provide_stmts.push(quote! {
#(#field_cfg)*
if #tf.1.is_captured() {
#req.provide_ref::<#oopsie_path::SpanTrace>(&#tf.1);
}
});
} else {
if let Some(bt_field) = &categorized.backtrace_field {
let field_cfg = trace_field_cfg(categorized, Some(bt_field));
provide_stmts.push(quote! {
#(#field_cfg)*
{
let __bt = ::core::borrow::Borrow::<#oopsie_path::Backtrace>::borrow(#bt_field);
if __bt.is_captured() {
#req.provide_ref::<#oopsie_path::Backtrace>(__bt);
}
}
});
}
if let Some(st_field) = &categorized.spantrace_field {
let field_cfg = trace_field_cfg(categorized, Some(st_field));
provide_stmts.push(quote! {
#(#field_cfg)*
{
let __st = ::core::borrow::Borrow::<#oopsie_path::SpanTrace>::borrow(#st_field);
if __st.is_captured() {
#req.provide_ref::<#oopsie_path::SpanTrace>(__st);
}
}
});
}
}
for provide_attr in &variant_attrs.provides {
provide_stmts.push(gen_provide_call(provide_attr, &req));
}
if let Some(help_field) = &categorized.help_field {
let help_cfg = field_cfg_for(categorized, help_field);
provide_stmts.push(quote! {
#(#help_cfg)*
#req.provide_value_with::<#oopsie_path::HelpText>(
|| #oopsie_path::HelpText::from(#help_field.to_string())
);
});
} else if let Some(help) = &variant_attrs.help {
provide_stmts.push(gen_help_provide(help, oopsie_path, &req)?);
}
if let Some(code) = &variant_attrs.code {
provide_stmts.push(gen_code_provide(code, oopsie_path, &req)?);
}
if let Some(exit) = variant_attrs.exit_code.or(container_exit) {
provide_stmts.push(gen_exit_code_provide(exit, oopsie_path, &req));
}
let field_binds = collect_provide_field_binds(categorized);
provide_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { #(#field_binds)* .. } => {
#(#provide_stmts)*
}
});
let source_ident = categorized.source.as_ref().map(|s| &s.ident);
let forward = categorized
.source
.as_ref()
.map(|s| s.forward)
.unwrap_or_default();
let src_access = source_ident.map(|s| quote! { #s.as_error_source() });
let bt_probe = source_ident
.filter(|_| variant_attrs.transparent || forward.backtrace)
.map(|s| gen_diag_forward(s, "fwd_backtrace", oopsie_path));
let st_probe = source_ident
.filter(|_| variant_attrs.transparent || forward.spantrace)
.map(|s| gen_diag_forward(s, "fwd_spantrace", oopsie_path));
let bt_fn = format_ident!("source_backtrace");
let st_fn = format_ident!("source_spantrace");
let (bt_own, bt_bind) = if let Some(tf) = &categorized.traces_field {
(Some(quote! { &#tf.0 }), Some(tf))
} else if let Some(bt_field) = &categorized.backtrace_field {
(
Some(quote! {
::core::borrow::Borrow::<#oopsie_path::Backtrace>::borrow(#bt_field)
}),
Some(bt_field),
)
} else {
(None, None)
};
if let Some(body) =
trace_accessor_body(bt_own, src_access.clone(), bt_probe, &bt_fn, oopsie_path)
{
let binds = accessor_pattern_binds(bt_bind, source_ident);
let field_cfg = trace_field_cfg(categorized, bt_bind);
bt_arms.push(quote! {
#(#cfg_attrs)*
#(#field_cfg)*
Self::#variant_ident { #(#binds,)* .. } => #body,
});
accessor_uses_source |= source_ident.is_some();
}
let (st_own, st_bind) = if let Some(tf) = &categorized.traces_field {
(Some(quote! { &#tf.1 }), Some(tf))
} else if let Some(st_field) = &categorized.spantrace_field {
(
Some(quote! {
::core::borrow::Borrow::<#oopsie_path::SpanTrace>::borrow(#st_field)
}),
Some(st_field),
)
} else {
(None, None)
};
if let Some(body) =
trace_accessor_body(st_own, src_access.clone(), st_probe, &st_fn, oopsie_path)
{
let binds = accessor_pattern_binds(st_bind, source_ident);
let field_cfg = trace_field_cfg(categorized, st_bind);
st_arms.push(quote! {
#(#cfg_attrs)*
#(#field_cfg)*
Self::#variant_ident { #(#binds,)* .. } => #body,
});
accessor_uses_source |= source_ident.is_some();
}
let loc_own = categorized
.location_field
.as_ref()
.map(|lf| quote! { *#lf });
let loc_source = source_ident.filter(|_| variant_attrs.transparent || forward.location);
let loc_probe = loc_source.map(|s| gen_diag_forward(s, "fwd_location", oopsie_path));
if let Some(body) = location_accessor_body(loc_own, loc_probe) {
let binds = accessor_pattern_binds(categorized.location_field.as_ref(), loc_source);
let field_cfg = trace_field_cfg(categorized, categorized.location_field.as_ref());
loc_arms.push(quote! {
#(#cfg_attrs)*
#(#field_cfg)*
Self::#variant_ident { #(#binds,)* .. } => #body,
});
}
if let Some(code) = &variant_attrs.code {
if code.is_static() {
let lit = code.static_lit()?;
code_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { .. } => ::core::option::Option::Some(#oopsie_path::ErrorCode::from(#lit)),
});
} else {
let fmt = &code.format_str;
let args = code.args.iter();
let code_field_binds = field_binding_pats(&variant.fields);
code_arms.push(quote! {
#(#cfg_attrs)*
#[allow(unused_variables)]
Self::#variant_ident { #(#code_field_binds)* .. } => ::core::option::Option::Some(#oopsie_path::ErrorCode::from(#oopsie_path::__private::alloc::format!(#fmt #(, #args)*))),
});
}
} else if let Some(provide_attr) = categorized
.provides
.iter()
.map(|(_, p)| p)
.chain(variant_attrs.provides.iter())
.find(|p| is_error_code_provide(p))
{
let expr = &provide_attr.expr;
let binds = collect_provide_field_binds(categorized);
let value = if provide_attr.is_ref() {
quote! { ::core::option::Option::Some(::core::clone::Clone::clone(#expr)) }
} else {
quote! { ::core::option::Option::Some(#expr) }
};
code_arms.push(quote! {
#(#cfg_attrs)*
#[allow(unused_variables)]
Self::#variant_ident { #(#binds)* .. } => #value,
});
} else if let (true, Some(source_field)) = (variant_attrs.transparent, &categorized.source)
{
let source_ident = &source_field.ident;
let fwd = gen_diag_forward(source_ident, "fwd_code", oopsie_path);
code_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { #source_ident, .. } => #fwd,
});
}
if let Some(help_field) = &categorized.help_field {
let field_cfg = field_cfg_for(categorized, help_field);
help_arms.push(quote! {
#(#cfg_attrs)*
#(#field_cfg)*
Self::#variant_ident { #help_field, .. } => ::core::option::Option::Some(#oopsie_path::HelpText::from(#help_field.to_string())),
});
} else if let Some(help) = &variant_attrs.help {
if help.is_static() {
let lit = help.static_lit()?;
help_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { .. } => ::core::option::Option::Some(#oopsie_path::HelpText::from_static(#lit)),
});
} else {
let fmt = &help.format_str;
let args = help.args.iter();
let help_field_binds = field_binding_pats(&variant.fields);
help_arms.push(quote! {
#(#cfg_attrs)*
#[allow(unused_variables)]
Self::#variant_ident { #(#help_field_binds)* .. } => ::core::option::Option::Some(#oopsie_path::HelpText::from(#oopsie_path::__private::alloc::format!(#fmt #(, #args)*))),
});
}
} else if let (true, Some(source_field)) = (variant_attrs.transparent, &categorized.source)
{
let source_ident = &source_field.ident;
let fwd = gen_diag_forward(source_ident, "fwd_help", oopsie_path);
help_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { #source_ident, .. } => #fwd,
});
}
if let Some(exit) = variant_attrs.exit_code.or(container_exit) {
let some = exit_code_some(exit, oopsie_path);
exit_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { .. } => #some,
});
} else if let (true, Some(source_field)) = (variant_attrs.transparent, &categorized.source)
{
let source_ident = &source_field.ident;
let fwd = gen_diag_forward(source_ident, "fwd_exit_code", oopsie_path);
exit_arms.push(quote! {
#(#cfg_attrs)*
Self::#variant_ident { #source_ident, .. } => #fwd,
});
}
}
let cfg_fallback_arm = if resolved.any_variant_cfg {
quote! { _ => ::core::unreachable!() }
} else {
quote! {}
};
let provide_method =
if provide_arms.is_empty() || !cfg!(feature = "unstable-error-generic-member-access") {
quote! {}
} else {
quote! {
#[allow(unused_variables)]
fn provide<'__a>(&'__a self, #req: &mut ::core::error::Request<'__a>) {
use #oopsie_path::AsErrorSource as _;
match self {
#(#provide_arms)*
#cfg_fallback_arm
}
}
}
};
let accessor_use_aes = if accessor_uses_source {
quote! { use #oopsie_path::AsErrorSource as _; }
} else {
quote! {}
};
let bt_method = if bt_arms.is_empty() {
quote! {}
} else {
quote! {
fn oopsie_backtrace(&self) -> ::core::option::Option<&#oopsie_path::Backtrace> {
#accessor_use_aes
match self {
#(#bt_arms)*
_ => ::core::option::Option::None,
}
}
}
};
let st_method = if st_arms.is_empty() {
quote! {}
} else {
quote! {
fn oopsie_spantrace(&self) -> ::core::option::Option<&#oopsie_path::SpanTrace> {
#accessor_use_aes
match self {
#(#st_arms)*
_ => ::core::option::Option::None,
}
}
}
};
let loc_method = if loc_arms.is_empty() {
quote! {}
} else {
quote! {
fn oopsie_location(&self) -> ::core::option::Option<&'static ::core::panic::Location<'static>> {
match self {
#(#loc_arms)*
_ => ::core::option::Option::None,
}
}
}
};
let code_method = if code_arms.is_empty() {
quote! {}
} else {
quote! {
fn oopsie_error_code(&self) -> ::core::option::Option<#oopsie_path::ErrorCode> {
match self {
#(#code_arms)*
_ => ::core::option::Option::None,
}
}
}
};
let help_method = if help_arms.is_empty() {
quote! {}
} else {
quote! {
fn oopsie_help_text(&self) -> ::core::option::Option<#oopsie_path::HelpText> {
match self {
#(#help_arms)*
_ => ::core::option::Option::None,
}
}
}
};
let exit_method = if exit_arms.is_empty() {
quote! {}
} else {
quote! {
fn oopsie_exit_code(&self) -> ::core::option::Option<::core::num::NonZeroU8> {
match self {
#(#exit_arms)*
_ => ::core::option::Option::None,
}
}
}
};
let source_body = if source_arms.is_empty() {
quote! { match *self {} }
} else {
quote! {
use #oopsie_path::AsErrorSource as _;
match self {
#(#source_arms)*
#cfg_fallback_arm
}
}
};
let forwarded_generic_sources: Vec<&Type> = resolved
.variants
.iter()
.filter_map(|v| forwarded_generic_source_ty(v.fields.source.as_ref(), &input.generics))
.collect();
let diag_where =
diagnostic_where_clause(&input.generics, &forwarded_generic_sources, oopsie_path);
Ok(quote! {
impl #impl_generics ::core::error::Error for #enum_ident #ty_generics #where_clause {
fn source(&self) -> ::core::option::Option<&(dyn ::core::error::Error + 'static)> {
#source_body
}
#provide_method
}
impl #impl_generics #oopsie_path::Diagnostic for #enum_ident #ty_generics #diag_where {
#bt_method
#st_method
#loc_method
#code_method
#help_method
#exit_method
}
})
}
pub fn gen_struct_error(
resolved: &ResolvedStruct,
oopsie_path: &syn::Path,
) -> syn::Result<TokenStream2> {
let input = resolved.input;
let attrs = resolved.attrs;
let struct_ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let syn::Data::Struct(data) = &input.data else {
unreachable!()
};
let categorized = &resolved.fields;
let variant_attrs = attrs;
let req = format_ident!("__request");
let source_body = if let Some(source_field) = &categorized.source {
let source_ident = &source_field.ident;
quote! {
{
use #oopsie_path::AsErrorSource as _;
::core::option::Option::Some(self.#source_ident.as_error_source())
}
}
} else {
quote! { ::core::option::Option::None }
};
let mut provide_stmts = Vec::new();
if let Some(source_field) = &categorized.source {
let source_ident = &source_field.ident;
provide_stmts.push(quote! {
::core::error::Error::provide(#source_ident.as_error_source(), #req);
});
}
for (_field_ident, provide_attr) in &categorized.provides {
provide_stmts.push(gen_provide_call(provide_attr, &req));
}
if let Some(tf) = &categorized.traces_field {
let field_cfg = trace_field_cfg(categorized, Some(tf));
provide_stmts.push(quote! {
#(#field_cfg)*
if #tf.0.is_captured() {
#req.provide_ref::<#oopsie_path::Backtrace>(&#tf.0);
}
});
provide_stmts.push(quote! {
#(#field_cfg)*
if #tf.1.is_captured() {
#req.provide_ref::<#oopsie_path::SpanTrace>(&#tf.1);
}
});
} else {
if let Some(bt_field) = &categorized.backtrace_field {
let field_cfg = trace_field_cfg(categorized, Some(bt_field));
provide_stmts.push(quote! {
#(#field_cfg)*
{
let __bt = ::core::borrow::Borrow::<#oopsie_path::Backtrace>::borrow(#bt_field);
if __bt.is_captured() {
#req.provide_ref::<#oopsie_path::Backtrace>(__bt);
}
}
});
}
if let Some(st_field) = &categorized.spantrace_field {
let field_cfg = trace_field_cfg(categorized, Some(st_field));
provide_stmts.push(quote! {
#(#field_cfg)*
{
let __st = ::core::borrow::Borrow::<#oopsie_path::SpanTrace>::borrow(#st_field);
if __st.is_captured() {
#req.provide_ref::<#oopsie_path::SpanTrace>(__st);
}
}
});
}
}
for provide_attr in &attrs.provides {
provide_stmts.push(gen_provide_call(provide_attr, &req));
}
if let Some(help_field) = &categorized.help_field {
let help_cfg = field_cfg_for(categorized, help_field);
provide_stmts.push(quote! {
#(#help_cfg)*
#req.provide_value_with::<#oopsie_path::HelpText>(
|| #oopsie_path::HelpText::from(#help_field.to_string())
);
});
} else if let Some(help) = &variant_attrs.help {
provide_stmts.push(gen_help_provide(help, oopsie_path, &req)?);
}
if let Some(code) = &variant_attrs.code {
provide_stmts.push(gen_code_provide(code, oopsie_path, &req)?);
}
if let Some(exit) = attrs.container.exit_code {
provide_stmts.push(gen_exit_code_provide(exit, oopsie_path, &req));
}
let provide_field_binds = collect_provide_field_binds(categorized);
let destructure = if provide_stmts.is_empty() || provide_field_binds.is_empty() {
quote! {}
} else {
quote! { let Self { #(#provide_field_binds)* .. } = self; }
};
let provide_method =
if provide_stmts.is_empty() || !cfg!(feature = "unstable-error-generic-member-access") {
quote! {}
} else {
quote! {
#[allow(unused_variables)]
fn provide<'__a>(&'__a self, #req: &mut ::core::error::Request<'__a>) {
use #oopsie_path::AsErrorSource as _;
#destructure
#(#provide_stmts)*
}
}
};
let struct_source = categorized.source.as_ref().map(|s| &s.ident);
let forward = categorized
.source
.as_ref()
.map(|s| s.forward)
.unwrap_or_default();
let struct_src_access = struct_source.map(|s| quote! { self.#s.as_error_source() });
let struct_use_aes = if struct_source.is_some() {
quote! { use #oopsie_path::AsErrorSource as _; }
} else {
quote! {}
};
let bt_probe = struct_source
.filter(|_| variant_attrs.transparent || forward.backtrace)
.map(|s| gen_diag_forward(quote! { &self.#s }, "fwd_backtrace", oopsie_path));
let st_probe = struct_source
.filter(|_| variant_attrs.transparent || forward.spantrace)
.map(|s| gen_diag_forward(quote! { &self.#s }, "fwd_spantrace", oopsie_path));
let bt_fn = format_ident!("source_backtrace");
let st_fn = format_ident!("source_spantrace");
let bt_field_ident = categorized
.traces_field
.as_ref()
.or(categorized.backtrace_field.as_ref());
let st_field_ident = categorized
.traces_field
.as_ref()
.or(categorized.spantrace_field.as_ref());
let bt_own = if let Some(tf) = &categorized.traces_field {
Some(quote! { &self.#tf.0 })
} else {
categorized.backtrace_field.as_ref().map(|bt_field| {
quote! {
::core::borrow::Borrow::<#oopsie_path::Backtrace>::borrow(&self.#bt_field)
}
})
};
let bt_sig =
quote! { fn oopsie_backtrace(&self) -> ::core::option::Option<&#oopsie_path::Backtrace> };
let bt_method = gen_struct_trace_method(
&bt_sig,
&struct_use_aes,
trace_field_cfg(categorized, bt_field_ident),
bt_own,
struct_src_access.clone(),
bt_probe,
&bt_fn,
oopsie_path,
);
let st_own = if let Some(tf) = &categorized.traces_field {
Some(quote! { &self.#tf.1 })
} else {
categorized.spantrace_field.as_ref().map(|st_field| {
quote! {
::core::borrow::Borrow::<#oopsie_path::SpanTrace>::borrow(&self.#st_field)
}
})
};
let st_sig =
quote! { fn oopsie_spantrace(&self) -> ::core::option::Option<&#oopsie_path::SpanTrace> };
let st_method = gen_struct_trace_method(
&st_sig,
&struct_use_aes,
trace_field_cfg(categorized, st_field_ident),
st_own,
struct_src_access,
st_probe,
&st_fn,
oopsie_path,
);
let loc_own = categorized
.location_field
.as_ref()
.map(|lf| quote! { self.#lf });
let loc_probe = struct_source
.filter(|_| variant_attrs.transparent || forward.location)
.map(|s| gen_diag_forward(quote! { &self.#s }, "fwd_location", oopsie_path));
let loc_sig = quote! {
fn oopsie_location(&self) -> ::core::option::Option<&'static ::core::panic::Location<'static>>
};
let loc_field_cfg = trace_field_cfg(categorized, categorized.location_field.as_ref());
let loc_method = match location_accessor_body(loc_own, loc_probe.clone()) {
Some(body) => {
let full = quote! { #loc_sig { #body } };
if has_existence_cfg(loc_field_cfg) {
let not_cfg = negated_existence_cfg(loc_field_cfg);
let stripped = location_accessor_body(None, loc_probe)
.map(|b| quote! { #not_cfg #loc_sig { #b } });
quote! { #(#loc_field_cfg)* #full #stripped }
} else {
full
}
}
None => quote! {},
};
let code_method = if let Some(code) = &variant_attrs.code {
if code.is_static() {
let lit = code.static_lit()?;
quote! {
fn oopsie_error_code(&self) -> ::core::option::Option<#oopsie_path::ErrorCode> {
::core::option::Option::Some(#oopsie_path::ErrorCode::from(#lit))
}
}
} else {
let fmt = &code.format_str;
let args = code.args.iter();
let field_binds = field_binding_pats(&data.fields);
let destructure = if field_binds.is_empty() {
quote! {}
} else {
quote! { #[allow(unused_variables)] let Self { #(#field_binds)* .. } = self; }
};
quote! {
fn oopsie_error_code(&self) -> ::core::option::Option<#oopsie_path::ErrorCode> {
#destructure
::core::option::Option::Some(#oopsie_path::ErrorCode::from(#oopsie_path::__private::alloc::format!(#fmt #(, #args)*)))
}
}
}
} else {
let code_provide = categorized
.provides
.iter()
.map(|(_, p)| p)
.chain(attrs.provides.iter())
.find(|p| is_error_code_provide(p));
if let Some(provide_attr) = code_provide {
let expr = &provide_attr.expr;
let field_binds = collect_provide_field_binds(categorized);
let code_destructure = if field_binds.is_empty() {
quote! {}
} else {
quote! { #[allow(unused_variables)] let Self { #(#field_binds)* .. } = self; }
};
let value = if provide_attr.is_ref() {
quote! { ::core::option::Option::Some(::core::clone::Clone::clone(#expr)) }
} else {
quote! { ::core::option::Option::Some(#expr) }
};
quote! {
fn oopsie_error_code(&self) -> ::core::option::Option<#oopsie_path::ErrorCode> {
#code_destructure
#value
}
}
} else if let (true, Some(s)) = (variant_attrs.transparent, struct_source) {
let fwd = gen_diag_forward(quote! { &self.#s }, "fwd_code", oopsie_path);
quote! {
fn oopsie_error_code(&self) -> ::core::option::Option<#oopsie_path::ErrorCode> {
#fwd
}
}
} else {
quote! {}
}
};
let help_method = if let Some(help_field) = &categorized.help_field {
let help_cfg = field_cfg_for(categorized, help_field);
quote! {
#(#help_cfg)*
fn oopsie_help_text(&self) -> ::core::option::Option<#oopsie_path::HelpText> {
::core::option::Option::Some(#oopsie_path::HelpText::from(self.#help_field.to_string()))
}
}
} else if let Some(help) = &variant_attrs.help {
if help.is_static() {
let lit = help.static_lit()?;
quote! {
fn oopsie_help_text(&self) -> ::core::option::Option<#oopsie_path::HelpText> {
::core::option::Option::Some(#oopsie_path::HelpText::from_static(#lit))
}
}
} else {
let fmt = &help.format_str;
let args = help.args.iter();
let field_binds = field_binding_pats(&data.fields);
let destructure = if field_binds.is_empty() {
quote! {}
} else {
quote! { #[allow(unused_variables)] let Self { #(#field_binds)* .. } = self; }
};
quote! {
fn oopsie_help_text(&self) -> ::core::option::Option<#oopsie_path::HelpText> {
#destructure
::core::option::Option::Some(#oopsie_path::HelpText::from(#oopsie_path::__private::alloc::format!(#fmt #(, #args)*)))
}
}
}
} else if let (true, Some(s)) = (variant_attrs.transparent, struct_source) {
let fwd = gen_diag_forward(quote! { &self.#s }, "fwd_help", oopsie_path);
quote! {
fn oopsie_help_text(&self) -> ::core::option::Option<#oopsie_path::HelpText> {
#fwd
}
}
} else {
quote! {}
};
let exit_method = if let Some(exit) = attrs.container.exit_code {
let some = exit_code_some(exit, oopsie_path);
quote! {
fn oopsie_exit_code(&self) -> ::core::option::Option<::core::num::NonZeroU8> {
#some
}
}
} else if let (true, Some(s)) = (variant_attrs.transparent, struct_source) {
let fwd = gen_diag_forward(quote! { &self.#s }, "fwd_exit_code", oopsie_path);
quote! {
fn oopsie_exit_code(&self) -> ::core::option::Option<::core::num::NonZeroU8> {
#fwd
}
}
} else {
quote! {}
};
let forwarded_generic_sources: Vec<&Type> =
forwarded_generic_source_ty(categorized.source.as_ref(), &input.generics)
.into_iter()
.collect();
let diag_where =
diagnostic_where_clause(&input.generics, &forwarded_generic_sources, oopsie_path);
Ok(quote! {
impl #impl_generics ::core::error::Error for #struct_ident #ty_generics #where_clause {
fn source(&self) -> ::core::option::Option<&(dyn ::core::error::Error + 'static)> {
#source_body
}
#provide_method
}
impl #impl_generics #oopsie_path::Diagnostic for #struct_ident #ty_generics #diag_where {
#bt_method
#st_method
#loc_method
#code_method
#help_method
#exit_method
}
})
}
fn gen_help_provide(
help: &DisplayAttr,
oopsie_path: &syn::Path,
req: &syn::Ident,
) -> syn::Result<TokenStream2> {
if help.is_static() {
let lit = help.static_lit()?;
Ok(quote! {
#req.provide_value_with::<#oopsie_path::HelpText>(|| #oopsie_path::HelpText::from_static(#lit));
})
} else {
let fmt = &help.format_str;
let args = help.args.iter();
Ok(quote! {
#req.provide_value_with::<#oopsie_path::HelpText>(|| #oopsie_path::HelpText::from(#oopsie_path::__private::alloc::format!(#fmt #(, #args)*)));
})
}
}
fn gen_code_provide(
code: &DisplayAttr,
oopsie_path: &syn::Path,
req: &syn::Ident,
) -> syn::Result<TokenStream2> {
if code.is_static() {
let lit = code.static_lit()?;
Ok(quote! {
#req.provide_value_with::<#oopsie_path::ErrorCode>(|| #oopsie_path::ErrorCode::from(#lit));
})
} else {
let fmt = &code.format_str;
let args = code.args.iter();
Ok(quote! {
#req.provide_value_with::<#oopsie_path::ErrorCode>(|| #oopsie_path::ErrorCode::from(#oopsie_path::__private::alloc::format!(#fmt #(, #args)*)));
})
}
}
fn gen_exit_code_provide(
exit: ExitCodeAttr,
oopsie_path: &syn::Path,
req: &syn::Ident,
) -> TokenStream2 {
let nonzero = exit_code_nonzero(exit, oopsie_path);
quote! {
#req.provide_value::<::core::num::NonZeroU8>(#nonzero);
}
}
fn gen_provide_call(attr: &ProvideAttr, req: &syn::Ident) -> TokenStream2 {
let ty = &attr.provided_type;
let expr = &attr.expr;
if attr.is_ref() {
quote! { #req.provide_ref_with::<#ty>(|| #expr); }
} else {
quote! { #req.provide_value_with::<#ty>(|| #expr); }
}
}
fn is_error_code_provide(attr: &ProvideAttr) -> bool {
let Type::Path(type_path) = &attr.provided_type else {
return false;
};
let segments = &type_path.path.segments;
let Some(last) = segments.last() else {
return false;
};
if last.ident != "ErrorCode" {
return false;
}
match segments.len() {
1 => true,
n => {
let qualifier = &segments[n - 2].ident;
qualifier == "oopsie" || qualifier == "oopsie_core"
}
}
}
fn collect_provide_field_binds(categorized: &CategorizedFields) -> Vec<TokenStream2> {
let mut seen: Vec<&syn::Ident> = Vec::new();
let mut binds = Vec::new();
if let Some(source) = &categorized.source {
seen.push(&source.ident);
let cfg = &source.cfg_attrs;
let ident = &source.ident;
binds.push(quote! { #(#cfg)* #ident, });
}
for (ident, _) in &categorized.provides {
if seen.contains(&ident) {
continue;
}
seen.push(ident);
let cfg = field_cfg_for(categorized, ident);
binds.push(quote! { #(#cfg)* #ident, });
}
for af in &categorized.auto_fields {
if seen.contains(&&af.ident) {
continue;
}
seen.push(&af.ident);
let cfg = &af.cfg_attrs;
let ident = &af.ident;
binds.push(quote! { #(#cfg)* #ident, });
}
for uf in &categorized.user_fields {
if seen.contains(&&uf.ident) {
continue;
}
seen.push(&uf.ident);
let cfg = &uf.cfg_attrs;
let ident = &uf.ident;
binds.push(quote! { #(#cfg)* #ident, });
}
binds
}
fn trace_field_cfg<'a>(
categorized: &'a CategorizedFields,
ident: Option<&syn::Ident>,
) -> &'a [syn::Attribute] {
ident.map_or(&[], |ident| field_cfg_for(categorized, ident))
}
fn has_existence_cfg(field_cfg: &[syn::Attribute]) -> bool {
field_cfg.iter().any(|a| a.path().is_ident("cfg"))
}
fn negated_existence_cfg(field_cfg: &[syn::Attribute]) -> TokenStream2 {
let preds: Vec<TokenStream2> = field_cfg
.iter()
.filter(|a| a.path().is_ident("cfg"))
.filter_map(|a| a.meta.require_list().ok())
.map(|list| list.tokens.clone())
.collect();
match preds.as_slice() {
[pred] => quote! { #[cfg(not(#pred))] },
preds => quote! { #[cfg(not(all(#(#preds),*)))] },
}
}
#[expect(
clippy::too_many_arguments,
reason = "wraps one accessor's full inputs"
)]
fn gen_struct_trace_method(
sig: &TokenStream2,
use_aes: &TokenStream2,
field_cfg: &[syn::Attribute],
own: Option<TokenStream2>,
source_access: Option<TokenStream2>,
probe: Option<TokenStream2>,
source_fn: &syn::Ident,
oopsie_path: &syn::Path,
) -> TokenStream2 {
let with_body = |body: TokenStream2| quote! { #sig { #use_aes #body } };
let full = trace_accessor_body(
own,
source_access.clone(),
probe.clone(),
source_fn,
oopsie_path,
)
.map(with_body);
if !has_existence_cfg(field_cfg) {
return full.unwrap_or_default();
}
let gated = full.map(|m| quote! { #(#field_cfg)* #m });
let not_cfg = negated_existence_cfg(field_cfg);
let stripped =
trace_accessor_body(None, source_access, probe, source_fn, oopsie_path).map(with_body);
let stripped = stripped.map(|m| quote! { #not_cfg #m });
quote! { #gated #stripped }
}
fn field_cfg_for<'a>(
categorized: &'a CategorizedFields,
ident: &syn::Ident,
) -> &'a [syn::Attribute] {
if let Some(source) = &categorized.source
&& &source.ident == ident
{
return &source.cfg_attrs;
}
if let Some(af) = categorized.auto_fields.iter().find(|f| &f.ident == ident) {
return &af.cfg_attrs;
}
if let Some(uf) = categorized.user_fields.iter().find(|f| &f.ident == ident) {
return &uf.cfg_attrs;
}
&[]
}