use syn::Path;
pub(crate) fn sort_paths(mut paths: Vec<Path>) -> Vec<Path> {
for path in &mut paths {
path.leading_colon = None;
}
paths.sort_by(compare_paths_blind);
paths
}
fn compare_paths_blind(a: &Path, b: &Path) -> std::cmp::Ordering {
let a_segments = extract_segments(a);
let b_segments = extract_segments(b);
let max_len = a_segments.len().max(b_segments.len());
for i in 0..max_len {
let a_idx = a_segments.len().saturating_sub(1 + i);
let b_idx = b_segments.len().saturating_sub(1 + i);
let a_has = i < a_segments.len();
let b_has = i < b_segments.len();
if a_has && b_has {
let cmp = a_segments[a_idx].cmp(&b_segments[b_idx]);
if cmp != std::cmp::Ordering::Equal {
return cmp;
}
} else if a_has && !b_has {
panic_on_ambiguity(&a_segments, &b_segments);
} else if !a_has && b_has {
panic_on_ambiguity(&a_segments, &b_segments);
}
}
std::cmp::Ordering::Equal
}
fn extract_segments(path: &Path) -> Vec<String> {
path.segments
.iter()
.map(|seg| seg.ident.to_string())
.collect()
}
fn panic_on_ambiguity(a: &[String], b: &[String]) {
let a_full = a.join("::");
let b_full = b.join("::");
let base = a.last().unwrap_or(&String::new()).clone();
let error_msg = format!(
"Ambiguous error types: `{base}` may conflict with `{a_full}` and `{b_full}`. \
Please explicitly qualify the module path for `{base}` to guarantee safe sorting."
);
let tokens = quote::quote! {
compile_error!(#error_msg);
};
panic!("STRUCT_ERROR_AMBIGUITY:{}:{}", base, tokens);
}
pub(crate) fn dedup_paths(paths: Vec<Path>) -> Vec<Path> {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
for path in paths {
let key = path_to_string(&path);
if seen.insert(key) {
result.push(path);
}
}
result
}
pub(crate) fn path_to_string(path: &Path) -> String {
path.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}
pub(crate) fn build_unt_nesting(
index: usize,
_total: usize,
inner: &dyn quote::ToTokens,
) -> proc_macro2::TokenStream {
let mut result = quote::quote! { ::struct_error::Unt::Here(#inner) };
for _ in 0..index {
result = quote::quote! { ::struct_error::Unt::There(#result) };
}
result
}
pub(crate) fn build_unt_pattern(
index: usize,
_total: usize,
pat: &syn::Pat,
) -> proc_macro2::TokenStream {
let mut result = quote::quote! { ::struct_error::Unt::Here(#pat) };
for _ in 0..index {
result = quote::quote! { ::struct_error::Unt::There(#result) };
}
quote::quote! { ::core::result::Result::Err(#result) }
}
pub(crate) fn build_unt_type(paths: &[Path]) -> proc_macro2::TokenStream {
let mut result = quote::quote! { ::struct_error::End };
for path in paths.iter().rev() {
result = quote::quote! { ::struct_error::Unt<#path, #result> };
}
result
}
pub(crate) fn is_pascal_case(ident: &syn::Ident) -> bool {
let s = ident.to_string();
s.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
}