2026-03-24 13:40:25 +00:00

187 lines
5.3 KiB
Rust

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<Symbol>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SymbolsFile {
pub schema_version: u32,
pub compiler_version: String,
pub root_project: String,
pub projects: Vec<ProjectSymbols>,
}
pub type SymbolInfo = Symbol;
pub fn collect_symbols(
project_id: &str,
module_symbols: &HashMap<String, crate::frontends::pbs::symbols::ModuleSymbols>,
file_manager: &crate::common::files::FileManager,
interner: &NameInterner,
) -> Vec<Symbol> {
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<Symbol> {
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
}
}