use crate::common::spans::Span; use prometeu_analysis::NameInterner; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone)] pub struct RawSymbol { pub pc: u32, pub span: Span, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DebugSymbol { pub pc: u32, pub file: String, pub line: usize, pub col: usize, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Symbol { pub id: String, pub name: String, pub kind: String, pub exported: bool, pub module_path: String, pub decl_span: SpanRange, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SpanRange { pub file: String, pub start: Pos, pub end: Pos, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Pos { pub line: u32, pub col: u32, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ProjectSymbols { pub project: String, pub project_dir: String, pub symbols: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SymbolsFile { pub schema_version: u32, pub compiler_version: String, pub root_project: String, pub projects: Vec, } pub type SymbolInfo = Symbol; pub fn collect_symbols( project_id: &str, module_symbols: &HashMap, file_manager: &crate::common::files::FileManager, interner: &NameInterner, ) -> Vec { let mut result = Vec::new(); for (module_path, ms) in module_symbols { // Collect from type_symbols for sym in ms.type_symbols.symbols.values() { if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { result.push(s); } } // Collect from value_symbols for sym in ms.value_symbols.symbols.values() { if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) { result.push(s); } } } // Deterministic ordering: by file, then start pos, then name result.sort_by(|a, b| { a.decl_span.file.cmp(&b.decl_span.file) .then(a.decl_span.start.line.cmp(&b.decl_span.start.line)) .then(a.decl_span.start.col.cmp(&b.decl_span.start.col)) .then(a.name.cmp(&b.name)) }); result } fn convert_symbol( project_id: &str, module_path: &str, sym: &crate::frontends::pbs::symbols::Symbol, file_manager: &crate::common::files::FileManager, interner: &NameInterner, ) -> Option { use crate::frontends::pbs::symbols::{SymbolKind, Visibility}; let kind = match sym.kind { SymbolKind::Service => "service", SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => "type", SymbolKind::Function => "function", SymbolKind::Local => return None, // Ignore locals for v0 }; let exported = sym.visibility == Visibility::Pub; // According to v0 policy, only service and declare are exported. // Functions are NOT exportable yet. if exported && sym.kind == SymbolKind::Function { // This should have been caught by semantic analysis, but we enforce it here too // for the symbols.json output. // Actually, we'll just mark it exported=false if it's a function. } let span = sym.span; let file_path = file_manager.get_path(span.file_id) .map(|p| p.to_string_lossy().to_string()) .unwrap_or_else(|| format!("unknown_file_{}", span.file_id)); // Convert 1-based to 0-based let (s_line, s_col) = file_manager.lookup_pos(span.file_id, span.start); let (e_line, e_col) = file_manager.lookup_pos(span.file_id, span.end); let decl_span = SpanRange { file: file_path, start: Pos { line: (s_line - 1) as u32, col: (s_col - 1) as u32 }, end: Pos { line: (e_line - 1) as u32, col: (e_col - 1) as u32 }, }; let hash = decl_span.compute_hash(); let name = interner.resolve(sym.name).to_string(); let id = format!("{}:{}:{}:{}:{:016x}", project_id, kind, module_path, name, hash); Some(Symbol { id, name, kind: kind.to_string(), exported, module_path: module_path.to_string(), decl_span, }) } impl SpanRange { pub fn compute_hash(&self) -> u64 { let mut h = 0xcbf29ce484222325u64; let mut update = |bytes: &[u8]| { for b in bytes { h ^= *b as u64; h = h.wrapping_mul(0x100000001b3u64); } }; update(self.file.as_bytes()); update(&self.start.line.to_le_bytes()); update(&self.start.col.to_le_bytes()); update(&self.end.line.to_le_bytes()); update(&self.end.col.to_le_bytes()); h } } #[cfg(test)] mod tests { use super::*; #[test] fn test_symbol_id_is_stable() { let span = SpanRange { file: "main.pbs".to_string(), start: Pos { line: 10, col: 5 }, end: Pos { line: 10, col: 20 }, }; let hash1 = span.compute_hash(); let hash2 = span.compute_hash(); assert_eq!(hash1, hash2); assert_eq!(hash1, 7774626535098684588u64); // Fixed value for stability check } }