pub fn peel_groups(ty: &syn::Type) -> &syn::Type {
let mut ty = ty;
loop {
match ty {
syn::Type::Paren(p) => ty = &p.elem,
syn::Type::Group(g) => ty = &g.elem,
_ => return ty,
}
}
}
pub fn extract_boxed_inner(ty: &syn::Type) -> Option<&syn::Type> {
let syn::Type::Path(type_path) = ty else {
return None;
};
let last = type_path.path.segments.last()?;
if last.ident != "Box" {
return None;
}
let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
return None;
};
args.args.iter().find_map(|arg| match arg {
syn::GenericArgument::Type(inner_ty) => Some(inner_ty),
_ => None,
})
}
pub fn is_backtrace_type(ty: &syn::Type) -> bool {
is_ident_type(ty, "Backtrace") || is_boxed_ident_type(ty, "Backtrace")
}
pub fn is_spantrace_type(ty: &syn::Type) -> bool {
is_ident_type(ty, "SpanTrace") || is_boxed_ident_type(ty, "SpanTrace")
}
pub fn is_traces_type(ty: &syn::Type) -> bool {
let inner = extract_boxed_inner(ty).unwrap_or(ty);
let syn::Type::Tuple(tuple) = inner else {
return false;
};
let mut elems = tuple.elems.iter();
let (Some(a), Some(b), None) = (elems.next(), elems.next(), elems.next()) else {
return false;
};
is_ident_type(a, "Backtrace") && is_ident_type(b, "SpanTrace")
}
pub fn is_timestamp_type(ty: &syn::Type) -> bool {
is_ident_type(ty, "SystemTime") || is_ident_type(ty, "DateTime")
}
pub fn is_location_type(ty: &syn::Type) -> bool {
let syn::Type::Reference(reference) = peel_groups(ty) else {
return false;
};
is_ident_type(peel_groups(&reference.elem), "Location")
}
fn is_ident_type(ty: &syn::Type, ident: &str) -> bool {
let syn::Type::Path(type_path) = ty else {
return false;
};
type_path
.path
.segments
.last()
.is_some_and(|seg| seg.ident == ident)
}
fn is_boxed_ident_type(ty: &syn::Type, ident: &str) -> bool {
let syn::Type::Path(type_path) = ty else {
return false;
};
let Some(last) = type_path.path.segments.last() else {
return false;
};
if last.ident != "Box" {
return false;
}
let syn::PathArguments::AngleBracketed(args) = &last.arguments else {
return false;
};
args.args.iter().any(|arg| match arg {
syn::GenericArgument::Type(inner_ty) => is_ident_type(inner_ty, ident),
_ => false,
})
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn extract_boxed_inner_simple() {
let ty: syn::Type = parse_quote!(Box<io::Error>);
let inner = extract_boxed_inner(&ty).unwrap();
let expected: syn::Type = parse_quote!(io::Error);
assert_eq!(
quote::quote!(#inner).to_string(),
quote::quote!(#expected).to_string()
);
}
#[test]
fn extract_boxed_inner_plain_type() {
let ty: syn::Type = parse_quote!(io::Error);
assert!(extract_boxed_inner(&ty).is_none());
}
#[test]
fn extract_boxed_inner_not_box() {
let ty: syn::Type = parse_quote!(Vec<io::Error>);
assert!(extract_boxed_inner(&ty).is_none());
}
#[test]
fn extract_boxed_inner_reference() {
let ty: syn::Type = parse_quote!(&str);
assert!(extract_boxed_inner(&ty).is_none());
}
#[test]
fn extract_boxed_inner_simple_type() {
let ty: syn::Type = parse_quote!(Box<String>);
let inner = extract_boxed_inner(&ty).unwrap();
let expected: syn::Type = parse_quote!(String);
assert_eq!(
quote::quote!(#inner).to_string(),
quote::quote!(#expected).to_string()
);
}
#[test]
fn backtrace_type_direct() {
let ty: syn::Type = parse_quote!(Backtrace);
assert!(is_backtrace_type(&ty));
}
#[test]
fn backtrace_type_boxed() {
let ty: syn::Type = parse_quote!(Box<Backtrace>);
assert!(is_backtrace_type(&ty));
}
#[test]
fn backtrace_type_qualified() {
let ty: syn::Type = parse_quote!(oopsie_core::Backtrace);
assert!(is_backtrace_type(&ty));
}
#[test]
fn backtrace_type_negative() {
let ty: syn::Type = parse_quote!(String);
assert!(!is_backtrace_type(&ty));
}
#[test]
fn spantrace_type_direct() {
let ty: syn::Type = parse_quote!(SpanTrace);
assert!(is_spantrace_type(&ty));
}
#[test]
fn spantrace_type_boxed() {
let ty: syn::Type = parse_quote!(Box<SpanTrace>);
assert!(is_spantrace_type(&ty));
}
#[test]
fn spantrace_type_qualified() {
let ty: syn::Type = parse_quote!(tracing_error::SpanTrace);
assert!(is_spantrace_type(&ty));
}
#[test]
fn spantrace_type_negative() {
let ty: syn::Type = parse_quote!(String);
assert!(!is_spantrace_type(&ty));
}
#[test]
fn ident_type_matching() {
let ty: syn::Type = parse_quote!(Backtrace);
assert!(is_ident_type(&ty, "Backtrace"));
}
#[test]
fn ident_type_not_matching() {
let ty: syn::Type = parse_quote!(String);
assert!(!is_ident_type(&ty, "Backtrace"));
}
#[test]
fn ident_type_non_path() {
let ty: syn::Type = parse_quote!(&str);
assert!(!is_ident_type(&ty, "str"));
}
#[test]
fn ident_type_tuple() {
let ty: syn::Type = parse_quote!(());
assert!(!is_ident_type(&ty, "anything"));
}
#[test]
fn boxed_ident_type_matching() {
let ty: syn::Type = parse_quote!(Box<Backtrace>);
assert!(is_boxed_ident_type(&ty, "Backtrace"));
}
#[test]
fn boxed_ident_type_wrong_inner() {
let ty: syn::Type = parse_quote!(Box<String>);
assert!(!is_boxed_ident_type(&ty, "Backtrace"));
}
#[test]
fn boxed_ident_type_not_box() {
let ty: syn::Type = parse_quote!(Vec<Backtrace>);
assert!(!is_boxed_ident_type(&ty, "Backtrace"));
}
#[test]
fn boxed_ident_type_plain_type() {
let ty: syn::Type = parse_quote!(Backtrace);
assert!(!is_boxed_ident_type(&ty, "Backtrace"));
}
#[test]
fn boxed_ident_type_non_path() {
let ty: syn::Type = parse_quote!(&str);
assert!(!is_boxed_ident_type(&ty, "str"));
}
#[test]
fn boxed_ident_type_tuple() {
let ty: syn::Type = parse_quote!(());
assert!(!is_boxed_ident_type(&ty, "anything"));
}
#[test]
fn traces_type_inline_tuple() {
let ty: syn::Type = parse_quote!((Backtrace, SpanTrace));
assert!(is_traces_type(&ty));
}
#[test]
fn traces_type_boxed_tuple() {
let ty: syn::Type = parse_quote!(Box<(Backtrace, SpanTrace)>);
assert!(is_traces_type(&ty));
}
#[test]
fn traces_type_qualified_elements() {
let ty: syn::Type = parse_quote!((oopsie::Backtrace, tracing_error::SpanTrace));
assert!(is_traces_type(&ty));
}
#[test]
fn traces_type_wrong_arity() {
let ty: syn::Type = parse_quote!((Backtrace, SpanTrace, u32));
assert!(!is_traces_type(&ty));
}
#[test]
fn traces_type_wrong_elements() {
let ty: syn::Type = parse_quote!((String, SpanTrace));
assert!(!is_traces_type(&ty));
}
#[test]
fn traces_type_not_tuple() {
let ty: syn::Type = parse_quote!(Backtrace);
assert!(!is_traces_type(&ty));
}
#[test]
fn location_type_static_ref() {
let ty: syn::Type = parse_quote!(&'static Location<'static>);
assert!(is_location_type(&ty));
}
#[test]
fn location_type_qualified() {
let ty: syn::Type = parse_quote!(&'static ::core::panic::Location<'static>);
assert!(is_location_type(&ty));
}
#[test]
fn location_type_plain_ref() {
let ty: syn::Type = parse_quote!(&Location);
assert!(is_location_type(&ty));
}
#[test]
fn location_type_not_reference() {
let ty: syn::Type = parse_quote!(Location);
assert!(!is_location_type(&ty));
}
#[test]
fn location_type_wrong_referent() {
let ty: syn::Type = parse_quote!(&'static str);
assert!(!is_location_type(&ty));
}
#[test]
fn peel_groups_unwraps_parenthesized_trait_object() {
let ty: syn::Type = parse_quote!((dyn Error + Send));
assert!(matches!(peel_groups(&ty), syn::Type::TraitObject(_)));
}
#[test]
fn peel_groups_passes_through_plain_type() {
let ty: syn::Type = parse_quote!(io::Error);
assert!(matches!(peel_groups(&ty), syn::Type::Path(_)));
}
}