prometeu-runtime/files/LPS - prep.md
2026-03-24 13:40:27 +00:00

302 lines
9.3 KiB
Markdown

## 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