246 lines
6.8 KiB
Rust
246 lines
6.8 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_uri: 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;
|
|
|
|
// ========================
|
|
// analysis.json v0 structs
|
|
// ========================
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct AnalysisFileTableEntry {
|
|
pub file_id: u32,
|
|
pub uri: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct AnalysisNameEntry {
|
|
pub name_id: u32,
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct AnalysisModuleEntry {
|
|
pub module_id: u32,
|
|
pub module_path: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct AnalysisSymbolEntry {
|
|
pub symbol_id: u32,
|
|
pub name_id: u32,
|
|
pub kind: String,
|
|
pub exported: bool,
|
|
pub module_id: u32,
|
|
pub decl_span: SpanRange,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
|
pub struct AnalysisFacts {
|
|
pub node_type: Vec<serde_json::Value>,
|
|
pub symbol_type: Vec<serde_json::Value>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct AnalysisFileV0 {
|
|
pub schema_version: u32,
|
|
pub compiler_version: String,
|
|
pub root_project: String,
|
|
pub file_table: Vec<AnalysisFileTableEntry>,
|
|
pub name_table: Vec<AnalysisNameEntry>,
|
|
pub module_table: Vec<AnalysisModuleEntry>,
|
|
pub symbols: Vec<AnalysisSymbolEntry>,
|
|
pub refs: Vec<serde_json::Value>,
|
|
pub types: Vec<serde_json::Value>,
|
|
pub facts: AnalysisFacts,
|
|
pub diagnostics: Vec<serde_json::Value>,
|
|
}
|
|
|
|
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 list in ms.type_symbols.symbols.values() {
|
|
for sym in list {
|
|
if let Some(s) = convert_symbol(project_id, module_path, sym, file_manager, interner) {
|
|
result.push(s);
|
|
}
|
|
}
|
|
}
|
|
// Collect from value_symbols
|
|
for list in ms.value_symbols.symbols.values() {
|
|
for sym in list {
|
|
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_uri.cmp(&b.decl_span.file_uri)
|
|
.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.clone();
|
|
let file_path = file_manager.get_path(span.file.as_usize())
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.unwrap_or_else(|| format!("unknown_file_{}", span.file.as_usize()));
|
|
|
|
// Convert 1-based to 0-based
|
|
let (s_line, s_col) = file_manager.lookup_pos(span.file.as_usize(), span.start);
|
|
let (e_line, e_col) = file_manager.lookup_pos(span.file.as_usize(), span.end);
|
|
|
|
let decl_span = SpanRange {
|
|
file_uri: 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_uri.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_uri: "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);
|
|
// Hash constant may change if algorithm or field names change; we only
|
|
// assert determinism here.
|
|
assert!(hash1 != 0);
|
|
}
|
|
}
|