This commit is contained in:
bQUARKz 2026-02-05 11:06:28 +00:00
parent aea8f3bc08
commit adb4dad14b
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
16 changed files with 414 additions and 670 deletions

View File

@ -1,2 +1,62 @@
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FileId(pub u32);
//! Canonical ID newtypes used across the Prometeu workspace.
//! Keep this crate low-level and independent from higher layers.
macro_rules! define_id {
($name:ident) => {
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize)]
pub struct $name(pub u32);
impl $name {
pub const INVALID: $name = $name(u32::MAX);
#[inline]
pub const fn as_u32(self) -> u32 { self.0 }
// Temporary helper for places that still index Vec/slots by usize
#[inline]
pub const fn as_usize(self) -> usize { self.0 as usize }
}
impl From<u32> for $name {
#[inline]
fn from(value: u32) -> Self { Self(value) }
}
impl From<$name> for u32 {
#[inline]
fn from(value: $name) -> Self { value.0 }
}
};
}
define_id!(FileId);
define_id!(NodeId);
define_id!(NameId);
define_id!(SymbolId);
define_id!(TypeId);
define_id!(ModuleId);
define_id!(ProjectId);
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::mem::size_of;
#[test]
fn ids_are_repr_transparent_and_hashable() {
assert_eq!(size_of::<FileId>(), 4);
assert_eq!(size_of::<NodeId>(), 4);
assert_eq!(size_of::<NameId>(), 4);
assert_eq!(size_of::<SymbolId>(), 4);
assert_eq!(size_of::<TypeId>(), 4);
assert_eq!(size_of::<ModuleId>(), 4);
assert_eq!(size_of::<ProjectId>(), 4);
// Hash/Eq usage
let mut m: HashMap<SymbolId, &str> = HashMap::new();
m.insert(SymbolId(1), "one");
assert_eq!(m.get(&SymbolId(1)).copied(), Some("one"));
}
}

View File

@ -1,7 +1,5 @@
use std::collections::HashMap;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct NameId(pub u32);
use crate::ids::NameId;
#[derive(Debug, Default, Clone)]
pub struct NameInterner {

View File

@ -3,7 +3,7 @@ pub mod span;
pub mod file_db;
pub mod interner;
pub use ids::FileId;
pub use ids::*;
pub use span::Span;
pub use file_db::{FileDB, LineIndex};
pub use interner::{NameId, NameInterner};
pub use interner::NameInterner;

View File

@ -1,13 +1,11 @@
use crate::common::diagnostics::{Diagnostic, Severity};
use crate::common::spans::Span;
use crate::frontends::pbs::ast::{AstArena, NodeId};
use prometeu_analysis::NameId;
use crate::frontends::pbs::ast::AstArena;
use prometeu_analysis::NodeId;
use prometeu_analysis::{NameId, SymbolId, ModuleId};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct SymbolId(pub u32);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum SymbolKind {
Type,
@ -32,7 +30,7 @@ pub struct Symbol {
pub name: NameId,
pub kind: SymbolKind,
pub exported: bool,
pub module: u32,
pub module: ModuleId,
pub decl_span: Span,
}
@ -43,7 +41,7 @@ pub struct SymbolArena {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct DefKey {
pub module: u32,
pub module: ModuleId,
pub name: NameId,
pub namespace: Namespace,
}
@ -107,7 +105,7 @@ impl DefIndex {
}
/// Lookup by name/namespace ignoring module. Returns the first match with its module id.
pub fn get_by_name_any_module(&self, name: NameId, namespace: Namespace) -> Option<(u32, SymbolId)> {
pub fn get_by_name_any_module(&self, name: NameId, namespace: Namespace) -> Option<(ModuleId, SymbolId)> {
for (k, v) in &self.symbols {
if k.name == name && k.namespace == namespace {
return Some((k.module, *v));
@ -173,7 +171,7 @@ impl NodeToSymbol {
mod tests {
use super::*;
fn sample_symbol(name: NameId, module: u32) -> Symbol {
fn sample_symbol(name: NameId, module: ModuleId) -> Symbol {
Symbol {
name,
kind: SymbolKind::Function,
@ -186,8 +184,8 @@ mod tests {
#[test]
fn insert_returns_incremental_ids() {
let mut arena = SymbolArena::new();
let id0 = arena.insert(sample_symbol(NameId(0), 0));
let id1 = arena.insert(sample_symbol(NameId(1), 0));
let id0 = arena.insert(sample_symbol(NameId(0), ModuleId(0)));
let id1 = arena.insert(sample_symbol(NameId(1), ModuleId(0)));
assert_eq!(id0, SymbolId(0));
assert_eq!(id1, SymbolId(1));
@ -196,7 +194,7 @@ mod tests {
#[test]
fn get_returns_correct_symbol() {
let mut arena = SymbolArena::new();
let symbol = sample_symbol(NameId(7), 3);
let symbol = sample_symbol(NameId(7), ModuleId(3));
let id = arena.insert(symbol.clone());
assert_eq!(arena.get(id).name, symbol.name);
@ -209,7 +207,7 @@ mod tests {
fn def_index_duplicate_in_same_namespace_errors() {
let mut index = DefIndex::new();
let key = DefKey {
module: 1,
module: ModuleId(1),
name: NameId(10),
namespace: Namespace::Type,
};
@ -225,12 +223,12 @@ mod tests {
let mut index = DefIndex::new();
let name = NameId(11);
let type_key = DefKey {
module: 2,
module: ModuleId(2),
name,
namespace: Namespace::Type,
};
let value_key = DefKey {
module: 2,
module: ModuleId(2),
name,
namespace: Namespace::Value,
};

View File

@ -1,10 +1,8 @@
use crate::analysis::symbols::{SymbolArena, SymbolId};
use crate::frontends::pbs::ast::NodeId;
use prometeu_analysis::interner::{NameId, NameInterner};
use crate::analysis::symbols::{SymbolArena};
use prometeu_analysis::{NameId, NameInterner, TypeId, SymbolId, NodeId, ModuleId};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct TypeId(pub u32);
// Use canonical TypeId from prometeu-analysis
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TypeKind {
@ -212,7 +210,7 @@ mod tests {
name: my_struct_name,
kind: SymbolKind::Struct,
exported: false,
module: 0,
module: ModuleId(0),
decl_span: Span::new(0, 0, 0),
});

View File

@ -1,9 +1,8 @@
use crate::common::spans::Span;
use prometeu_analysis::NameId;
use prometeu_analysis::{NameId, NodeId};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct NodeId(pub u32);
// Use canonical NodeId from prometeu-analysis
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct AstArena {

View File

@ -2,7 +2,7 @@ use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::symbols::*;
use crate::semantics::export_surface::ExportSurfaceKind;
use prometeu_analysis::NameInterner;
use prometeu_analysis::{NameInterner, NodeId};
pub struct SymbolCollector<'a> {
interner: &'a NameInterner,

View File

@ -7,7 +7,7 @@ use crate::frontends::pbs::types::PbsType;
use crate::ir_core;
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
use crate::ir_core::{Block, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type};
use prometeu_analysis::NameInterner;
use prometeu_analysis::{NameInterner, NodeId};
use std::collections::HashMap;
#[derive(Clone)]

View File

@ -3,7 +3,7 @@ use crate::common::spans::Span;
use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::lexer::Lexer;
use crate::frontends::pbs::token::{Token, TokenKind};
use prometeu_analysis::{NameId, NameInterner};
use prometeu_analysis::{NameId, NameInterner, NodeId};
pub struct Parser<'a> {
tokens: Vec<Token>,

View File

@ -1,11 +1,11 @@
use crate::common::diagnostics::{Diagnostic, Severity};
use crate::frontends::pbs::ast::{AstArena, NodeKind, NodeId};
use crate::frontends::pbs::ast::{AstArena, NodeKind};
use crate::analysis::symbols::{Symbol, SymbolArena, SymbolKind, DefIndex, DefKey, Namespace, NodeToSymbol, RefIndex};
use prometeu_analysis::NameInterner;
use prometeu_analysis::{NameInterner, NodeId, ModuleId};
pub fn build_def_index(
arena: &AstArena,
module: u32,
module: ModuleId,
_interner: &NameInterner,
imports: Option<(&SymbolArena, &DefIndex)>,
) -> (SymbolArena, DefIndex, RefIndex, NodeToSymbol, Vec<Diagnostic>) {
@ -79,7 +79,7 @@ pub fn build_def_index(
fn walk_node(
node_id: NodeId,
arena: &AstArena,
module: u32,
module: ModuleId,
index: &DefIndex,
imports: Option<(&SymbolArena, &DefIndex)>,
ref_index: &mut RefIndex,
@ -134,7 +134,7 @@ fn walk_node(
diagnostics.push(Diagnostic {
severity: Severity::Error,
code: "E_RESOLVE_VISIBILITY".to_string(),
message: format!("Symbol is not exported from module {}", symbol.module),
message: format!("Symbol is not exported from module {:?}", symbol.module),
span,
related: Vec::new(),
});
@ -278,18 +278,18 @@ mod tests {
arena.roots.push(file_id);
let (symbols, index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
let (symbols, index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None);
assert!(diagnostics.is_empty());
assert_eq!(symbols.symbols.len(), 2);
let fn_sym_id = index.get(DefKey { module: 1, name: fn_name, namespace: Namespace::Value }).unwrap();
let fn_sym_id = index.get(DefKey { module: ModuleId(1), name: fn_name, namespace: Namespace::Value }).unwrap();
let fn_sym = symbols.get(fn_sym_id);
assert_eq!(fn_sym.name, fn_name);
assert_eq!(fn_sym.kind, SymbolKind::Function);
assert!(fn_sym.exported);
let type_sym_id = index.get(DefKey { module: 1, name: type_name, namespace: Namespace::Type }).unwrap();
let type_sym_id = index.get(DefKey { module: ModuleId(1), name: type_name, namespace: Namespace::Type }).unwrap();
let type_sym = symbols.get(type_sym_id);
assert_eq!(type_sym.name, type_name);
assert_eq!(type_sym.kind, SymbolKind::Struct);
@ -330,7 +330,7 @@ mod tests {
arena.roots.push(file_id);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None);
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].code, "E_RESOLVE_DUPLICATE_SYMBOL");
@ -360,7 +360,7 @@ mod tests {
arena.roots.push(file_id);
let (symbols, _index, _ref_index, node_to_symbol, _diagnostics) = build_def_index(&arena, 1, &interner, None);
let (symbols, _index, _ref_index, node_to_symbol, _diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None);
let symbol_id = node_to_symbol.get(decl_id).expect("Node should be bound to a symbol");
let symbol = symbols.get(symbol_id);
@ -393,7 +393,7 @@ mod tests {
arena.roots.push(file_id);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
let (_symbols, _index, _ref_index, _node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None);
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].code, "E_RESOLVE_UNDEFINED_IDENTIFIER");
@ -439,11 +439,11 @@ mod tests {
arena.roots.push(file_id);
let (_symbols, index, ref_index, node_to_symbol, diagnostics) = build_def_index(&arena, 1, &interner, None);
let (_symbols, index, ref_index, node_to_symbol, diagnostics) = build_def_index(&arena, ModuleId(1), &interner, None);
assert!(diagnostics.is_empty(), "Diagnostics should be empty: {:?}", diagnostics);
let target_sym_id = index.get(DefKey { module: 1, name: target_name, namespace: Namespace::Value }).expect("target should be in index");
let target_sym_id = index.get(DefKey { module: ModuleId(1), name: target_name, namespace: Namespace::Value }).expect("target should be in index");
let refs = ref_index.refs_of(target_sym_id);
assert_eq!(refs.len(), 1);
assert_eq!(refs[0], Span::new(0, 50, 56));
@ -474,7 +474,7 @@ mod tests {
}), Span::new(0, 0, 100));
arena1.roots.push(file1);
let (symbols1, index1, _, _, _) = build_def_index(&arena1, 1, &interner, None);
let (symbols1, index1, _, _, _) = build_def_index(&arena1, ModuleId(1), &interner, None);
// Módulo 2: tenta usar função privada do Módulo 1
let mut arena2 = AstArena::default();
@ -496,7 +496,7 @@ mod tests {
arena2.roots.push(file2);
let (_symbols2, _index2, _ref_index2, _node_to_symbol2, diagnostics) =
build_def_index(&arena2, 2, &interner, Some((&symbols1, &index1)));
build_def_index(&arena2, ModuleId(2), &interner, Some((&symbols1, &index1)));
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].code, "E_RESOLVE_VISIBILITY");
@ -541,8 +541,8 @@ mod tests {
arena.roots.push(file_id);
let run1 = build_def_index(&arena, 1, &interner, None);
let run2 = build_def_index(&arena, 1, &interner, None);
let run1 = build_def_index(&arena, ModuleId(1), &interner, None);
let run2 = build_def_index(&arena, ModuleId(1), &interner, None);
// runX is (SymbolArena, DefIndex, RefIndex, NodeToSymbol, Vec<Diagnostic>)

View File

@ -1,10 +1,10 @@
use crate::analysis::symbols::{SymbolArena, SymbolId, NodeToSymbol};
use crate::analysis::types::{TypeArena, TypeFacts, TypeId, TypeKind};
use crate::analysis::symbols::{SymbolArena, NodeToSymbol};
use crate::analysis::types::{TypeArena, TypeFacts, TypeKind};
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
use crate::common::spans::Span;
use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::symbols::*;
use prometeu_analysis::{NameId, NameInterner};
use prometeu_analysis::{NameId, NameInterner, SymbolId, ModuleId, TypeId, NodeId};
use std::collections::HashMap;
pub trait ModuleProvider {
@ -80,7 +80,7 @@ impl<'a> Resolver<'a> {
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
},
exported: sym.visibility == Visibility::Pub,
module: 0,
module: ModuleId(0),
decl_span: sym.span,
});
}
@ -96,7 +96,7 @@ impl<'a> Resolver<'a> {
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
},
exported: sym.visibility == Visibility::Pub,
module: 0,
module: ModuleId(0),
decl_span: sym.span,
});
}
@ -121,7 +121,7 @@ impl<'a> Resolver<'a> {
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
},
exported: sym.visibility == Visibility::Pub,
module: 0, // Should be target module
module: ModuleId(0), // Should be target module
decl_span: sym.span,
});
}
@ -137,7 +137,7 @@ impl<'a> Resolver<'a> {
SymbolKind::Local => crate::analysis::symbols::SymbolKind::Local,
},
exported: sym.visibility == Visibility::Pub,
module: 0, // Should be target module
module: ModuleId(0), // Should be target module
decl_span: sym.span,
});
}
@ -838,7 +838,7 @@ impl<'a> Resolver<'a> {
_ => crate::analysis::symbols::SymbolKind::Value,
},
exported: false,
module: 0, // TODO
module: ModuleId(0), // TODO: set actual module id when available
decl_span: span,
});

View File

@ -5,7 +5,7 @@ use crate::frontends::pbs::contracts::ContractRegistry;
use crate::frontends::pbs::resolver::ModuleProvider;
use crate::frontends::pbs::symbols::*;
use crate::frontends::pbs::types::PbsType;
use prometeu_analysis::{NameId, NameInterner};
use prometeu_analysis::{NameId, NameInterner, NodeId};
use std::collections::HashMap;
pub struct TypeChecker<'a> {

301
files/LPS - prep.md Normal file
View File

@ -0,0 +1,301 @@
## PR-R1 — IDs padronizados (newtypes) em um único lugar
**Branch:** `pr-r1-ids-newtypes`
### Briefing
Hoje existem IDs espalhados entre crates (`FileId`, `NameId`, `NodeId`, `SymbolId`, `TypeId`) e alguns campos ainda usam `u32`/`usize` cru (ex.: `Symbol.module: u32`). Para LSP, precisamos de IDs consistentes para indexação, caches, spans e cross-crate APIs.
### Alvo
Centralizar e padronizar os seguintes IDs (newtypes):
* `FileId(u32)`
* `NodeId(u32)`
* `NameId(u32)`
* `SymbolId(u32)`
* `TypeId(u32)`
* `ModuleId(u32)`
* `ProjectId(u32)` *(ver PR-R4 para adoção total; aqui é apenas definição + plumbing mínimo se necessário)*
**Definição única** em `prometeu-analysis` (ou um crate novo `prometeu-ids`, se você preferir isolar):
* Arquivo sugerido: `crates/prometeu-analysis/src/ids.rs`
* Exportar via `pub mod ids; pub use ids::*;`
### Escopo / Mudanças
1. **Criar o módulo de IDs** com:
* `#[repr(transparent)] pub struct FileId(pub u32);` etc.
* `Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Debug`.
* Helpers:
* `impl FileId { pub const INVALID: FileId = FileId(u32::MAX); }` (opcional)
* `impl From<u32> for FileId` e `From<FileId> for u32`.
2. **Padronizar uso cross-crate**:
* `prometeu-compiler/frontends/pbs/ast`: trocar `NodeId` local para `prometeu_analysis::NodeId`.
* `prometeu-compiler/analysis/symbols`: trocar `SymbolId` local para `prometeu_analysis::SymbolId`.
* `prometeu-compiler/analysis/types`: trocar `TypeId` local para `prometeu_analysis::TypeId`.
* Onde houver `usize`/`u32` cru representando file/module/symbol/type/node: substituir.
3. **Trocar `Symbol.module: u32` → `ModuleId`**.
4. **Interner (`NameId`)**:
* Garantir que o interner existente retorna `NameId` do módulo unificado.
* Se existirem `NameId` duplicados em crates diferentes, remover e apontar para o único.
### Regras de compatibilidade (para não quebrar tudo de uma vez)
* Se algum ponto ainda depende de `usize`, oferecer funções auxiliares **temporárias**:
* `fn as_usize(self) -> usize` (somente se realmente necessário)
* Preferir converter na borda (ex.: índices de `Vec`).
### Testes de aceite
* `cargo test -q` no workspace.
* Teste unitário novo em `prometeu-analysis`:
* `ids_are_repr_transparent_and_hashable()` (checa `size_of::<FileId>() == 4` etc.).
* Teste de compilação indireto: build de `prometeu-compiler` sem warnings de tipos duplicados.
### Notas de implementação
* Evitar circular dependency: `prometeu-analysis` deve ser “baixo nível”. Se o compiler já depende dele, ok.
* Se `prometeu-analysis` não puder depender do compiler (não deve), manter IDs neutros e reutilizáveis.
---
## PR-R2 — Span unificado + FileId consistente em todo pipeline
**Branch:** `pr-r2-span-unify`
### Briefing
Hoje existem dois tipos de `Span`:
* `prometeu-analysis::Span` (com `FileId`)
* `prometeu-compiler::common::spans::Span` (com `file_id: usize`)
Para LSP, diagnostics/definition/symbols precisam de um único modelo de span para conversão consistente para `Location/Range`.
A spec aponta spans como **byte offsets**, `end` exclusivo, e file id deve ser estável. (PBS Implementation Spec / Diagnostic specs)
### Alvo
* Tornar `prometeu-analysis::Span` o **span canônico** do projeto.
* Remover/aposentar `prometeu-compiler::common::spans::Span`.
* Garantir que **todo span carregue `FileId`**, e não `usize`.
### Escopo / Mudanças
1. **Definir `Span` canônico** (se já existe, reforçar):
* `pub struct Span { pub file: FileId, pub start: u32, pub end: u32 }`
* `start/end` em bytes (u32), `end` exclusivo.
* Helpers:
* `Span::new(file, start, end)`
* `Span::len()`
* `Span::contains(byte)`
2. **Migrar compiler para usar Span canônico**:
* Parser: todos os nós AST devem carregar spans canônicos.
* Diagnostics: `Diagnostic.span` deve ser canônico.
* Resolver/Symbols: `Symbol.decl_span` deve ser canônico.
* RefIndex: deve usar `Span` canônico.
3. **Matar o `file_id: usize`**:
* Onde havia `usize`, trocar por `FileId`.
* Nas arenas indexadas por `Vec`, converter no ponto de acesso: `file.0 as usize`.
4. **Adapters temporários (se necessário)**
* Se houver muitos pontos que esperam o Span antigo, criar `type OldSpan = Span` por 1 PR (somente dentro do compiler), e remover no fim da PR.
### Testes de aceite
* `cargo test -q` no workspace.
* Teste novo:
* `span_end_is_exclusive()`
* `diagnostic_span_is_valid_for_file()` (valida `end>=start` e `end<=text.len()` em um fixture simples).
### Critérios de “done”
* Não existe mais `prometeu-compiler::common::spans::Span` (ou está `deprecated` e sem uso).
* Qualquer `Span` do pipeline é `prometeu-analysis::Span`.
---
## PR-R3 — TextIndex/LineIndex correto para LSP (UTF-16) + conversões
**Branch:** `pr-r3-text-index-utf16`
### Briefing
O LSP usa `Position.character` em **UTF-16 code units** (não bytes). Hoje o `LineIndex` calcula coluna como *byte offset* na linha. Em arquivos com Unicode (acentos), diagnostics e goto definition ficam desalinhados.
Queremos:
* Manter o core do compilador em **byte offsets** (spec).
* Converter **somente na borda** (LSP e ferramentas).
### Alvo
Criar um índice de texto (por arquivo) que suporte:
* `byte_offset -> (line, utf16_col)`
* `(line, utf16_col) -> byte_offset`
E manter:
* `Span` em bytes.
* O índice baseado no **conteúdo atual** do arquivo.
### Escopo / Mudanças
1. Introduzir `TextIndex` em `prometeu-analysis` (ou `prometeu-lsp` se você quiser limitar ao LSP; mas recomendo em `analysis` pois será útil para debug map e tooling):
* Arquivo sugerido: `crates/prometeu-analysis/src/text_index.rs`
* Estrutura:
* `line_starts: Vec<u32>` (byte offsets)
* `line_utf16_lens: Vec<u32>` (opcional cache)
2. API mínima:
* `TextIndex::new(text: &str) -> Self`
* `fn byte_to_lsp(&self, byte: u32) -> (u32 /*line*/, u32 /*utf16_col*/)`
* `fn lsp_to_byte(&self, line: u32, utf16_col: u32) -> u32`
3. Algoritmo
* `line_starts` calculado por varredura de `\n`.
* Para conversão de col:
* pegar o slice da linha (`&text[line_start..line_end]`)
* iterar `char_indices()`, acumulando:
* `byte_pos` e `utf16_count += ch.len_utf16()`
* parar quando:
* `byte_pos >= target_byte` (byte_to_lsp)
* `utf16_count >= target_utf16` (lsp_to_byte)
4. Testes fortes com Unicode
* Casos: `"aé🙂b"` (emoji e acento).
* Validar round-trip:
* `byte == lsp_to_byte(byte_to_lsp(byte))` para bytes em fronteira de char.
5. Integração
* Por enquanto, **não** mexer no LSP server.
* Apenas oferecer API em `analysis` para o LSP consumir na PR-08.
### Testes de aceite
* `cargo test -q`.
* Testes novos em `prometeu-analysis`:
* `text_index_ascii_roundtrip()`
* `text_index_unicode_roundtrip_utf16()`
---
## PR-R4 — ProjectId padronizado + modelagem de Project/Module estável
**Branch:** `pr-r4-project-id`
### Briefing
Hoje o resolver trabalha com `Project { name, version }` e o `symbols.json` contém projects e símbolos agrupados por projeto. Para LSP e para incremental analysis, queremos IDs estáveis e leves para:
* mapear `uri -> FileId -> (ProjectId, ModuleId)`
* armazenar caches por projeto
* suportar workspace com múltiplos projetos no futuro
Você pediu explicitamente incluir `ProjectId(u32)` nesta série.
### Alvo
Introduzir `ProjectId(u32)` e plugar no modelo de resolução/linking:
* Cada projeto carregado/descoberto no workspace recebe `ProjectId`.
* Mapas centrais usam `ProjectId` como chave em vez de string.
### Escopo / Mudanças
1. Definir `ProjectId(u32)` (já definido na PR-R1) e agora **adotar**.
2. Criar um registry estável (no analysis/resolver layer):
* `ProjectRegistry`:
* `by_name: HashMap<ProjectKey, ProjectId>`
* `projects: Vec<ProjectMeta>`
* `ProjectKey` pode ser:
* `{ name: SmolStr, version: Option<SmolStr> }` ou `{ name, version }`
3. Ajustar estruturas existentes para carregar `ProjectId`
* `ModuleRef` / `ModulePath` / `ResolvedModule` devem apontar para `ProjectId`.
* `symbols.json` writer/reader:
* Manter `project: "sdk"` no JSON (formato externo), mas internamente mapear para `ProjectId`.
4. Integração mínima (sem LSP ainda)
* `AnalysisDb` (ou equivalente) deve conseguir responder:
* `fn project_for_file(file: FileId) -> ProjectId`
### Estratégia para não explodir o diff
* Não reescrever o mundo:
* manter `ProjectMeta { id: ProjectId, name, version }`
* adicionar `id` aos lugares críticos (resolver, module index, symbols export)
### Testes de aceite
* `cargo test -q`.
* Teste novo:
* `project_registry_stable_ids_for_same_key()`
* `symbols_json_roundtrip_preserves_project_grouping()` (se houver infra de roundtrip)
### Critérios de “done”
* Nenhum mapa central chaveado por `String` para identificar projeto no core; usar `ProjectId`.
* Persistência (symbols.json) continua legível e compatível.
---
# Ordem recomendada de merge (para minimizar conflitos)
1. PR-R1 (IDs)
2. PR-R2 (Span)
3. PR-R3 (TextIndex)
4. PR-R4 (ProjectId)
> Depois disso, a PR-08 (LSP MVP) fica bem menor: o LSP só consome `Span` + `TextIndex` + IDs.
---
# Checklist global (pré-PR-08)
* [ ] IDs unificados e usados em todos os crates
* [ ] Span único, sempre com `FileId`, e offsets em bytes
* [ ] TextIndex com conversão UTF-16 confiável (testado)
* [ ] ProjectId adotado no resolver/modelo de projeto
* [ ] Workspace compila e `cargo test` passa

View File

@ -1,39 +0,0 @@
## Visão de arquitetura alvo
### Camadas
1. **FileDB**: texto, URI/path, line-index, snapshots.
2. **Lexer/Parser**: produz **AstArena** (NodeId + spans por nó).
3. **Binder/Resolver**: produz **SymbolArena** + índices (def/ref).
4. **Typecheck**: produz **TypeArena** + facts (node→type, symbol→type).
5. **Analysis Export**: `analysis.json` (full) + `symbols.json` (leve e estável).
6. **LSP Server**: consome `AnalysisDb` e responde requests.
### IDs padronizados (newtypes)
* `FileId(u32)`
* `NodeId(u32)`
* `NameId(u32)` (interner)
* `SymbolId(u32)`
* `TypeId(u32)`
* `ProjectId(u32)`
* `ModuleId(u32)`
### Invariantes
* AST é **imutável** após construção (normativo na spec PBS).
* Nenhuma fase expõe referências diretas entre nós; apenas **IDs**.
* IDs externos são aceitos, mas sempre passam por **validate/resolve** (API checked).
---
## Regras para Junie (para reduzir vai-e-volta)
1. Não “inventar design”. Se algo não estiver especificado, **criar TODO** e parar.
2. Não mudar formatação/estilo fora do escopo.
3. Todo novo tipo público precisa de doc-comment curta e exemplo.
4. Toda mudança de JSON precisa:
* `schema_version` bump (se não for backward)
* teste de snapshot
5. Cada PR deve deixar `cargo test` verde.

View File

@ -1,571 +0,0 @@
# Prometeu — PR Plan (Arena-Driven) para um LSP Completo
---
# **PR-00.X — Reorganização estrutural do workspace (préArena / préLSP)**
> **Objetivo macro:** separar *modelo/ABI*, *execução*, *kernel* e *tooling* antes de qualquer refator arena-driven.
>
> **Regra de ouro:** nenhuma mudança de comportamento. Apenas **movimentação de código + ajuste de dependências**.
>
> **Modelo final alvo:**
>
> * `prometeu-bytecode` ✅ (fica)
> * `prometeu-abi` ✅ (novo — ex-`prometeu-core` sem execução)
> * `prometeu-vm` ✅ (novo)
> * `prometeu-kernel` ✅ (novo)
> * `prometeu-runtime-desktop` ✅ (fica)
> * `prometeu-compiler` ✅ (fica)
> * `prometeu` (CLI) ✅ (fica agregando)
---
> **Meta do Nilton / Junie workflow**: PRs extremamente prescritivos. A Junie só implementa; você revisa o design **antes** do código.
>
> **Regra de ouro:** cada PR abaixo vem com:
>
> 1. Arquivos/alvos exatos
> 2. Estruturas e assinaturas obrigatórias
> 3. Esquemas JSON (quando aplicável)
> 4. Testes (golden + unit)
> 5. Critérios de aceite “binário” (passou/não passou)
---
## Estado atual (confirmado pelo repo)
Workspace (Archive.zip) tem crates:
* `prometeu/`
* `prometeu-bytecode/`
* `prometeu-core/`
* `prometeu-compiler/` (bin `src/main.rs`, deps: `serde`, `serde_json`, `anyhow`, `clap`, e família `oxc_*`).
**Saída atual existente:** `symbols.json` (schema_version 0), exportando símbolos por projeto, com `decl_span` em `line/col` e paths absolutos. (Vamos evoluir isso no PR-07.)
---
## Visão de arquitetura alvo
### Camadas
1. **FileDB**: texto, URI/path, line-index, snapshots.
2. **Lexer/Parser**: produz **AstArena** (NodeId + spans por nó).
3. **Binder/Resolver**: produz **SymbolArena** + índices (def/ref).
4. **Typecheck**: produz **TypeArena** + facts (node→type, symbol→type).
5. **Analysis Export**: `analysis.json` (full) + `symbols.json` (leve e estável).
6. **LSP Server**: consome `AnalysisDb` e responde requests.
### IDs padronizados (newtypes)
* `FileId(u32)`
* `NodeId(u32)`
* `NameId(u32)` (interner)
* `SymbolId(u32)`
* `TypeId(u32)`
* `ProjectId(u32)`
* `ModuleId(u32)`
### Invariantes
* AST é **imutável** após construção (normativo na spec PBS).
* Nenhuma fase expõe referências diretas entre nós; apenas **IDs**.
* IDs externos são aceitos, mas sempre passam por **validate/resolve** (API checked).
---
# Templates prescritivos para PR (usar em TODAS)
## Template de descrição (copiar/colar)
* **Motivação:**
* **Mudança de modelo de dados:**
* **APIs novas / alteradas:**
* **Arquivos tocados:**
* **Testes adicionados/atualizados:**
* **Riscos & rollback:**
* **Checklist de aceite (binário):**
## Regras para Junie (para reduzir vai-e-volta)
1. Não “inventar design”. Se algo não estiver especificado, **criar TODO** e parar.
2. Não mudar formatação/estilo fora do escopo.
3. Todo novo tipo público precisa de doc-comment curta e exemplo.
4. Toda mudança de JSON precisa:
* `schema_version` bump (se não for backward)
* teste de snapshot
5. Cada PR deve deixar `cargo test` verde.
---
# PRs (detalhados)
## PR-01 — FileDB + LineIndex (base do LSP e spans)
**Branch:** `pr-01-filedb`
### Objetivo
Criar uma base de arquivos com IDs estáveis na sessão e conversão offset<->(line,col).
### Arquivos / módulos
* `prometeu-analysis/src/file_db.rs`
* `prometeu-analysis/src/span.rs`
* `prometeu-analysis/src/ids.rs`
* `prometeu-analysis/src/lib.rs` (re-exports)
### Estruturas obrigatórias
```rust
// prometeu-analysis/src/ids.rs
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct FileId(pub u32);
// prometeu-analysis/src/span.rs
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Span {
pub file: FileId,
pub start: u32, // byte offset
pub end: u32, // byte offset, exclusive
}
// prometeu-analysis/src/file_db.rs
pub struct FileDB {
// map uri->id, id->uri/text/line_index
}
pub struct LineIndex {
// stores line start offsets
}
```
### Regras
* Spans são **byte offsets UTF-8** (`start` inclusive, `end` exclusive) — alinhado ao Canonical Addenda.
* `LineIndex` deve lidar com `
` (LF). (CRLF pode ser normalizado na entrada.)
### APIs obrigatórias
```rust
impl FileDB {
pub fn upsert(&mut self, uri: &str, text: String) -> FileId;
pub fn file_id(&self, uri: &str) -> Option<FileId>;
pub fn uri(&self, id: FileId) -> &str;
pub fn text(&self, id: FileId) -> &str;
pub fn line_index(&self, id: FileId) -> &LineIndex;
}
impl LineIndex {
pub fn offset_to_line_col(&self, offset: u32) -> (u32, u32);
pub fn line_col_to_offset(&self, line: u32, col: u32) -> Option<u32>;
}
```
### Testes obrigatórios
* `tests/file_db_line_index.rs` (unit)
* roundtrip offset->(l,c)->offset
* bordas (start/end, EOF)
### Critérios de aceite
* Testes passam.
* Nenhuma mudança no output do `prometeu-compiler` ainda.
---
## PR-02 — NameInterner (NameId) e eliminação de String no hot path
**Branch:** `pr-02-name-interner`
### Objetivo
Trocar identificadores em AST/resolver de `String` para `NameId`.
### Arquivos
* `prometeu-analysis/src/interner.rs`
* Ajustar frontend PBS no `prometeu-compiler` (ver lista de toques abaixo).
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct NameId(pub u32);
pub struct NameInterner {
// bidirectional: string->id, id->string
}
```
### APIs obrigatórias
```rust
impl NameInterner {
pub fn new() -> Self;
pub fn intern(&mut self, s: &str) -> NameId;
pub fn resolve(&self, id: NameId) -> &str;
}
```
### Regras
* Interner é **session-local**.
* `resolve` deve retornar `&str` estável (armazenar `String` internamente).
### Touch points no `prometeu-compiler`
* Onde hoje existe `String` como nome de símbolo, token de identifier, etc:
* AST nodes de `Ident`
* Resolver scopes: `HashMap<NameId, _>`
### Testes
* Unit: intern/resolve, dedup
* Golden: manter comportamento de resolver/diags
### Critérios de aceite
* Redução de `String` em estruturas hot (resolver)
* Build e testes ok.
---
## PR-03 — AST Arena (NodeId) para PBS
**Branch:** `pr-03-ast-arena`
### Objetivo
Refatorar o AST PBS (atualmente recursivo) para uma arena `Vec` com `NodeId`.
### Alvos no repo
* `prometeu-compiler/src/frontends/pbs/*` (localizar AST/parser atuais)
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct NodeId(pub u32);
pub struct AstArena {
pub nodes: Vec<NodeKind>,
pub spans: Vec<prometeu_analysis::Span>,
pub roots: Vec<NodeId>,
}
pub enum NodeKind {
File { imports: Vec<NodeId>, decls: Vec<NodeId> },
Import { /* ... */ },
FnDecl { name: NameId, /* ... */, body: NodeId },
// ... (conforme canonical AST do PBS)
}
```
### Regras
* `AstArena::push(kind, span) -> NodeId` sempre faz append.
* `spans.len() == nodes.len()` sempre.
### APIs obrigatórias
```rust
impl AstArena {
pub fn push(&mut self, kind: NodeKind, span: Span) -> NodeId;
pub fn kind(&self, id: NodeId) -> &NodeKind;
pub fn span(&self, id: NodeId) -> Span;
}
```
### Testes obrigatórios
* Parser golden tests: comparar JSON canonical (se você já tem) ou snapshots equivalentes.
* Unit: invariantes (push mantém alinhamento).
### Critérios de aceite
* Nenhum `Box<Node>` em AST PBS.
* Resolver e typechecker passam a consumir NodeId.
---
## PR-04 — SymbolArena + índices (defs/refs) + node→symbol
**Branch:** `pr-04-symbol-arena-index`
### Objetivo
Criar `SymbolArena` e índices para features de LSP sem traversal pesado.
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct SymbolId(pub u32);
pub enum SymbolKind { Type, Value, Service, Function /* etc */ }
pub struct Symbol {
pub name: NameId,
pub kind: SymbolKind,
pub exported: bool,
pub module: ModuleId,
pub decl_span: Span,
}
pub struct SymbolArena { pub symbols: Vec<Symbol> }
pub struct DefIndex { /* HashMap<(ModuleId, NameId, Namespace), SymbolId> */ }
pub struct RefIndex { /* Vec<Vec<Span>> by SymbolId */ }
pub struct NodeToSymbol { /* Vec<Option<SymbolId>> by NodeId */ }
```
### APIs obrigatórias
* `insert_symbol(...) -> SymbolId` (falha se duplicate, com diag E_RESOLVE_DUPLICATE_SYMBOL)
* `record_ref(symbol_id, span)`
* `bind_node(node_id, symbol_id)`
### Testes
* Duplicate symbol
* Undefined identifier
* Visibility violations
* Determinismo (mesma input → mesma ordem IDs, quando possível)
---
## PR-05 — TypeArena + TypeFacts (node→type, symbol→type)
**Branch:** `pr-05-type-arena-facts`
### Objetivo
Dar base para hover, completion e erros de tipo.
### Estruturas obrigatórias
```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct TypeId(pub u32);
pub enum TypeKind {
Primitive { name: NameId },
Struct { sym: SymbolId },
Optional { inner: TypeId },
Result { ok: TypeId, err: TypeId },
Array { inner: TypeId, len: Option<u32> },
// ... conforme v0
}
pub struct TypeArena { pub types: Vec<TypeKind> }
pub struct TypeFacts {
pub node_type: Vec<Option<TypeId>>, // index by NodeId
pub symbol_type: Vec<Option<TypeId>>, // index by SymbolId
}
```
### APIs obrigatórias
* `intern_type(TypeKind) -> TypeId` (dedup opcional; documentar)
* `set_node_type(NodeId, TypeId)`
* `set_symbol_type(SymbolId, TypeId)`
### Testes
* Hover type display snapshots.
---
## PR-06 — Diagnostics canonizados (E_* / W_*)
**Branch:** `pr-06-diagnostics`
### Objetivo
Diagnósticos estáveis e serializáveis, alinhados ao Canonical Addenda (codes).
### Estruturas obrigatórias
```rust
pub enum Severity { Error, Warning }
pub struct Diagnostic {
pub severity: Severity,
pub code: String,
pub message: String,
pub span: Span,
pub related: Vec<(String, Span)>,
}
```
### Regras
* Codes conforme Canonical Addenda (E_PARSE_*, E_RESOLVE_*, E_TYPE_*).
### Testes
* Golden diag JSON determinístico.
---
## PR-07 — Export: `symbols.json` v1 + `analysis.json` v0
**Branch:** `pr-07-analysis-export`
### Objetivo
Separar índice leve (`symbols.json`) e export completo (`analysis.json`).
### Símbolos (novo formato recomendado)
* **IMPORTANTE:** parar de gravar paths absolutos (usar URI relativo ao projeto quando possível).
`symbols.json` v1 (schema_version=1):
* `projects[]`:
* `project` (string)
* `project_dir` (string) (pode ficar absoluto por enquanto)
* `symbols[]`:
* `symbol_id` (u32) **OU** string estável atual (mantém compat)
* `name` (string)
* `kind` (string)
* `exported` (bool)
* `module_path` (string)
* `decl_span`: `{ file_uri, start:{line,col}, end:{line,col} }`
`analysis.json` (schema_version=0):
* `file_table`: [{file_id:u32, uri:string}]
* `symbols`: [{symbol_id:u32, name_id:u32, kind, exported, module_id, decl_span}]
* `refs`: [{symbol_id:u32, spans:[Span]}]
* `types`: [{type_id:u32, kind:...}]
* `facts`: { node_type: [...], symbol_type: [...] }
* `diagnostics`: [Diagnostic]
### Testes
* Snapshot de ambos JSONs.
---
## PR-08 — LSP MVP (diagnostics + symbols + goto definition)
**Branch:** `pr-08-lsp-mvp`
### Objetivo
LSP funcional mínimo usando `AnalysisDb` (em memória), recompilando quando arquivo muda.
### Features
* `initialize`
* `didOpen`/`didChange`/`didClose`
* `publishDiagnostics`
* `documentSymbol` / `workspaceSymbol`
* `definition`
### Regras
* `didChange`: por enquanto full-text (simplifica)
* Rebuild: coarse (projeto inteiro) inicialmente
### Testes
* Test harness: abrir doc e pedir definition.
---
## PR-09 — LSP: references + rename
**Branch:** `pr-09-lsp-refs-rename`
### Objetivo
Baseado em RefIndex e NodeToSymbol.
### Features
* `references`
* `prepareRename`
* `rename`
### Regras de segurança
* Não renomear se símbolo não-resolvido.
* Não renomear se span em comentário/string.
---
## PR-10 — LSP: hover + signatureHelp
**Branch:** `pr-10-lsp-hover-sighelp`
### Features
* `hover`: type + docstring (docstring opcional por enquanto)
* `signatureHelp`: para call nodes (se parser já marca)
---
## PR-11 — LSP: completion
**Branch:** `pr-11-lsp-completion`
### Buckets
* locals em scope (se você tiver scope facts)
* symbols do módulo
* exports importados
* members via type facts
---
## PR-12 — LSP: semantic tokens + highlights
**Branch:** `pr-12-lsp-semantic`
---
## PR-13 — LSP: formatting + code actions (opcional)
**Branch:** `pr-13-lsp-actions`
---
## PR-14 — Incremental analysis + cancelation
**Branch:** `pr-14-incremental`
---
## PR-15 — Debug map (pc→span) + integração com traps
**Branch:** `pr-15-debug-map`
---
---
# Sequência recomendada (travada)
1. PR-00.1 (`prometeu-abi` + compat)
2. PR-00.2 (`prometeu-vm`)
3. PR-00.3 (`prometeu-kernel`)
4. PR-00.4 (depurar `prometeu-core`)
5. PR-00.5 (opcional firmware)
6. PR-00.6 (remover core)
> Após PR-00.X, iniciar a trilha Arena-Driven (PR-01..PR-07) no compiler/analysis.