use super::types::NormalizedFunctionName;
use std::borrow::Cow;
pub fn strip_trailing_generics(s: &str) -> Cow<'_, str> {
if let Some(pos) = s.rfind("::<") {
let mut depth = 0;
let mut end_pos = None;
for (i, ch) in s[pos + 3..].char_indices() {
match ch {
'<' => depth += 1,
'>' => {
if depth == 0 {
end_pos = Some(pos + 3 + i);
break;
}
depth -= 1;
}
_ => {}
}
}
if let Some(end) = end_pos {
let after = &s[end + 1..];
if after.is_empty() {
return Cow::Owned(s[..pos].to_string());
}
}
}
Cow::Borrowed(s)
}
pub fn normalize_demangled_name(demangled: &str) -> NormalizedFunctionName {
let without_impl_brackets = if demangled.starts_with('<') {
if let Some(angle_end) = demangled.find('>') {
let content = &demangled[1..angle_end];
let after = &demangled[(angle_end + 1)..];
let content_without_hash = if let Some(bracket_start) = content.find('[') {
if let Some(bracket_end) = content.find(']') {
format!(
"{}{}",
&content[..bracket_start],
&content[(bracket_end + 1)..]
)
} else {
content.to_string()
}
} else {
content.to_string()
};
format!("{}{}", content_without_hash, after)
} else {
demangled.to_string()
}
} else {
demangled.to_string()
};
let without_function_generics = strip_trailing_generics(&without_impl_brackets);
let result = without_function_generics
.chars()
.fold((String::new(), 0usize), |(mut acc, depth), ch| match ch {
'<' => (acc, depth + 1),
'>' if depth > 0 => (acc, depth - 1),
_ if depth == 0 => {
acc.push(ch);
(acc, depth)
}
_ => (acc, depth),
})
.0;
let method_name = result.rsplit("::").next().unwrap_or(&result).to_string();
NormalizedFunctionName {
full_path: result,
method_name,
original: demangled.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strip_trailing_generics_simple() {
assert_eq!(
strip_trailing_generics("Type::method::<WorkflowExecutor>"),
Cow::Borrowed("Type::method")
);
assert_eq!(
strip_trailing_generics("crate::Type::method::<T>"),
Cow::Borrowed("crate::Type::method")
);
assert_eq!(
strip_trailing_generics("Type::method"), Cow::Borrowed("Type::method")
);
}
#[test]
fn test_strip_trailing_generics_nested() {
assert_eq!(
strip_trailing_generics("method::<Vec<HashMap<K, V>>>"),
Cow::Borrowed("method")
);
assert_eq!(
strip_trailing_generics("method::<T, U, V>"),
Cow::Borrowed("method")
);
assert_eq!(
strip_trailing_generics("Type::method::<Result<Vec<T>, Error>>"),
Cow::Borrowed("Type::method")
);
}
#[test]
fn test_normalize_removes_generics() {
let result = normalize_demangled_name("HashMap<String, i32>::insert");
assert_eq!(result.full_path, "HashMap::insert");
assert_eq!(result.method_name, "insert");
let result = normalize_demangled_name("Vec<T>::push");
assert_eq!(result.full_path, "Vec::push");
assert_eq!(result.method_name, "push");
let result = normalize_demangled_name("simple_function");
assert_eq!(result.full_path, "simple_function");
assert_eq!(result.method_name, "simple_function");
}
#[test]
fn test_normalize_preserves_module_path() {
let result = normalize_demangled_name("std::collections::HashMap<K,V>::insert");
assert_eq!(result.full_path, "std::collections::HashMap::insert");
assert_eq!(result.method_name, "insert");
}
#[test]
fn test_normalize_removes_crate_hash() {
let result = normalize_demangled_name("<debtmap[71f4b4990cdcf1ab]::Foo>::bar");
assert_eq!(result.full_path, "debtmap::Foo::bar");
assert_eq!(result.method_name, "bar");
}
#[test]
fn test_normalize_extracts_method_name() {
let result = normalize_demangled_name("prodigy::cook::CommitTracker::create_auto_commit");
assert_eq!(
result.full_path,
"prodigy::cook::CommitTracker::create_auto_commit"
);
assert_eq!(result.method_name, "create_auto_commit");
let result = normalize_demangled_name("<Foo as Bar>::method");
assert_eq!(result.method_name, "method");
}
#[test]
fn test_normalize_strips_trailing_generics() {
let result = normalize_demangled_name("Type::method::<WorkflowExecutor>");
assert_eq!(result.full_path, "Type::method");
assert_eq!(result.method_name, "method");
let result = normalize_demangled_name("SetupPhaseExecutor::execute::<T>");
assert_eq!(result.full_path, "SetupPhaseExecutor::execute");
assert_eq!(result.method_name, "execute");
}
#[test]
fn test_normalize_impl_method_with_angle_brackets() {
let demangled =
"<prodigy::cook::workflow::resume::ResumeExecutor>::execute_remaining_steps";
let result = normalize_demangled_name(demangled);
assert_eq!(result.method_name, "execute_remaining_steps");
assert!(
result.full_path.contains("ResumeExecutor"),
"full_path should contain ResumeExecutor, got: {}",
result.full_path
);
assert!(
result.full_path.contains("execute_remaining_steps"),
"full_path should contain method name, got: {}",
result.full_path
);
}
#[test]
fn test_multiple_impl_methods() {
let test_cases = vec![
("<Type>::method", "method", "Simple impl method"),
(
"<crate::module::Type>::method",
"method",
"Fully qualified impl method",
),
(
"<impl Trait for Type>::method",
"method",
"Trait impl method",
),
(
"<Type<T>>::generic_method",
"generic_method",
"Generic impl method",
),
];
for (input, expected_method, description) in test_cases {
let result = normalize_demangled_name(input);
assert_eq!(
result.method_name, expected_method,
"{}: method_name mismatch for '{}'",
description, input
);
}
}
#[test]
fn test_normalize_preserves_impl_for_matching() {
let demangled = "<foo::bar::Baz>::do_something";
let normalized = normalize_demangled_name(demangled);
assert_eq!(normalized.method_name, "do_something");
assert!(
!normalized.full_path.starts_with('<'),
"full_path should not start with angle bracket, got: {}",
normalized.full_path
);
}
}