Skip to main content

rustbrain_core/ast/
symbol.rs

1//! Symbol anchors and location-independent hashing.
2
3use serde::{Deserialize, Serialize};
4
5/// Errors from AST hashing / parsing helpers.
6#[derive(Debug, thiserror::Error)]
7pub enum AstError {
8    /// Tree-sitter or language load failure.
9    #[error("AST error: {0}")]
10    Message(String),
11}
12
13/// AST code symbol anchor details.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub struct SymbolAnchor {
16    /// Stable 64-bit BLAKE3-derived hash of the symbol signature.
17    pub symbol_hash: u64,
18    /// Crate name owning the symbol.
19    pub crate_name: String,
20    /// Module path (logical, not absolute filesystem).
21    pub module_path: String,
22    /// Symbol name (function, struct, trait, …).
23    pub symbol_name: String,
24    /// Repository-relative file path for navigation.
25    pub file_path: String,
26    /// 1-based start line.
27    pub start_line: u32,
28    /// 1-based end line.
29    pub end_line: u32,
30    /// Adjacent doc comment, if any.
31    pub doc_comment: Option<String>,
32}
33
34/// Compute a 64-bit BLAKE3 hash for a code symbol signature.
35///
36/// Signature format: `crate_name::module_path::symbol_name`
37///
38/// **Does not include file path** so the hash is stable across file moves
39/// within the same logical module path.
40pub fn compute_symbol_hash(crate_name: &str, module_path: &str, symbol_name: &str) -> u64 {
41    let mut hasher = blake3::Hasher::new();
42    hasher.update(crate_name.as_bytes());
43    hasher.update(b"::");
44    hasher.update(module_path.as_bytes());
45    hasher.update(b"::");
46    hasher.update(symbol_name.as_bytes());
47
48    let result = hasher.finalize();
49    let bytes = &result.as_bytes()[0..8];
50    u64::from_le_bytes(bytes.try_into().unwrap())
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn stable_symbol_hash() {
59        let hash1 = compute_symbol_hash("rustbrain", "storage::engine", "CsrMatrix");
60        let hash2 = compute_symbol_hash("rustbrain", "storage::engine", "CsrMatrix");
61        assert_eq!(hash1, hash2);
62        assert_ne!(hash1, 0);
63    }
64
65    #[test]
66    fn hash_independent_of_path_param() {
67        // Same module path → same hash (file path is not an input).
68        let a = compute_symbol_hash("c", "foo::bar", "Baz");
69        let b = compute_symbol_hash("c", "foo::bar", "Baz");
70        assert_eq!(a, b);
71        let c = compute_symbol_hash("c", "foo::other", "Baz");
72        assert_ne!(a, c);
73    }
74}