stackwise-core 0.3.1

Artifact-first Rust stack analysis engine used by Stackwise
Documentation
pub fn demangle(name: &str) -> String {
    rustc_demangle::try_demangle(name)
        .map(|demangled| format!("{demangled:#}"))
        .unwrap_or_else(|_| name.to_owned())
}

pub fn crate_and_module(demangled: &str) -> (Option<String>, Vec<String>) {
    let cleaned = strip_hash_suffix(demangled);
    if let Some(parsed) = impl_crate_and_module(cleaned) {
        return parsed;
    }

    path_crate_and_module(cleaned).unwrap_or((None, Vec::new()))
}

fn impl_crate_and_module(name: &str) -> Option<(Option<String>, Vec<String>)> {
    let impl_head = leading_impl_head(name)?;
    let (subject, trait_path) = split_impl_subject_and_trait(impl_head);

    path_crate_and_module(strip_type_prefixes(subject))
        .or_else(|| trait_path.and_then(path_crate_and_module))
}

fn path_crate_and_module(path: &str) -> Option<(Option<String>, Vec<String>)> {
    let parts = split_rust_path(path);
    if parts.is_empty() {
        return None;
    }

    let segments = parts
        .iter()
        .filter_map(|part| sanitize_segment(part))
        .collect::<Vec<_>>();
    let crate_name = segments.first().filter(|part| is_crate_like(part)).cloned();
    let crate_name = crate_name?;

    let module_path = if segments.len() > 1 {
        segments[..segments.len() - 1].to_vec()
    } else {
        vec![crate_name.clone()]
    };

    Some((Some(crate_name), module_path))
}

fn leading_impl_head(name: &str) -> Option<&str> {
    if !name.starts_with('<') {
        return None;
    }

    let mut depth = 0i32;
    for (index, ch) in name.char_indices() {
        match ch {
            '<' => depth += 1,
            '>' => {
                depth -= 1;
                if depth == 0 {
                    return Some(&name[1..index]);
                }
            }
            _ => {}
        }
    }

    None
}

fn split_impl_subject_and_trait(impl_head: &str) -> (&str, Option<&str>) {
    let mut depth = 0i32;
    for (index, ch) in impl_head.char_indices() {
        if depth == 0 && impl_head[index..].starts_with(" as ") {
            return (&impl_head[..index], Some(&impl_head[index + 4..]));
        }

        match ch {
            '<' | '(' | '[' => depth += 1,
            '>' | ')' | ']' => depth -= 1,
            _ => {}
        }
    }

    (impl_head, None)
}

fn strip_type_prefixes(mut path: &str) -> &str {
    loop {
        let trimmed = path.trim();
        if let Some(rest) = trimmed.strip_prefix('&') {
            path = rest;
        } else if let Some(rest) = trimmed.strip_prefix("*const ") {
            path = rest;
        } else if let Some(rest) = trimmed.strip_prefix("*mut ") {
            path = rest;
        } else if let Some(rest) = trimmed.strip_prefix("mut ") {
            path = rest;
        } else if let Some(rest) = trimmed.strip_prefix("const ") {
            path = rest;
        } else if let Some(rest) = trimmed.strip_prefix("dyn ") {
            path = rest;
        } else {
            return trimmed;
        }
    }
}

fn strip_hash_suffix(name: &str) -> &str {
    let Some((before, after)) = name.rsplit_once("::h") else {
        return name;
    };

    if after.chars().all(|ch| ch.is_ascii_hexdigit()) {
        before
    } else {
        name
    }
}

fn split_rust_path(name: &str) -> Vec<&str> {
    let mut parts = Vec::new();
    let mut depth = 0i32;
    let mut start = 0usize;
    let bytes = name.as_bytes();
    let mut index = 0usize;

    while index + 1 < bytes.len() {
        match bytes[index] {
            b'<' | b'(' | b'[' => depth += 1,
            b'>' | b')' | b']' => depth -= 1,
            b':' if depth == 0 && bytes[index + 1] == b':' => {
                parts.push(name[start..index].trim());
                index += 1;
                start = index + 1;
            }
            _ => {}
        }
        index += 1;
    }

    let tail = name[start..].trim();
    if !tail.is_empty() {
        parts.push(tail);
    }

    parts
}

fn sanitize_segment(segment: &str) -> Option<String> {
    let sanitized = segment
        .split_once('<')
        .map(|(head, _)| head)
        .unwrap_or(segment)
        .trim()
        .to_owned();
    (!sanitized.is_empty()).then_some(sanitized)
}

fn is_crate_like(segment: &str) -> bool {
    !segment.starts_with('{') && !segment.starts_with('[') && !is_primitive_type(segment)
}

fn is_primitive_type(segment: &str) -> bool {
    matches!(
        segment,
        "bool"
            | "char"
            | "str"
            | "i8"
            | "i16"
            | "i32"
            | "i64"
            | "i128"
            | "isize"
            | "u8"
            | "u16"
            | "u32"
            | "u64"
            | "u128"
            | "usize"
            | "f32"
            | "f64"
    )
}

#[cfg(test)]
mod tests {
    use super::crate_and_module;

    #[test]
    fn extracts_crate_and_module_from_rust_path() {
        let (krate, module) = crate_and_module("demo::inner::work");
        assert_eq!(krate.as_deref(), Some("demo"));
        assert_eq!(module, ["demo", "inner"]);
    }

    #[test]
    fn extracts_module_from_trait_impl_subject() {
        let (krate, module) =
            crate_and_module("<rustc_demangle::legacy::Demangle as core::fmt::Display>::fmt");

        assert_eq!(krate.as_deref(), Some("rustc_demangle"));
        assert_eq!(module, ["rustc_demangle", "legacy"]);
    }

    #[test]
    fn extracts_module_from_generic_trait_impl_subject() {
        let (krate, module) = crate_and_module(
            "<rustc_demangle::SizeLimitedFmtAdapter<&mut core::fmt::Formatter> as core::fmt::Write>::write_str",
        );

        assert_eq!(krate.as_deref(), Some("rustc_demangle"));
        assert_eq!(module, ["rustc_demangle"]);
    }

    #[test]
    fn falls_back_to_trait_for_primitive_impl_subject() {
        let (krate, module) = crate_and_module("<&str as core::fmt::Debug>::fmt");

        assert_eq!(krate.as_deref(), Some("core"));
        assert_eq!(module, ["core", "fmt"]);
    }
}