prometeu-studio/discussion/lessons/DSC-0041-pbs-lsp-document-symbols-outline/LSN-0070-pbs-outline-is-an-ast-backed-document-symbol-tree.md
bQUARKz 5d3d48e802
Some checks are pending
Intrepid/Prometeu/Studio/pipeline/pr-master Build started...
JaCoCo Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 62.84% (18504/29445) * Branch Coverage: 53.33% (7215/13528) * Lines of Code: 29445 * Cyclomatic Complexity: 11850 #### Quality Gates Summary Output truncated.
Test / Build skipped: 15, passed: 669
Intrepid/Prometeu/Studio/pipeline/head This commit looks good
implements PLN-0133
Add PBS document symbols as an AST-backed hierarchical outline of
the open .pbs file on FrontendLanguageService. Recovered syntax is
enough; unnamed InvalidDecl is omitted; barrels, imports, locals,
and compile-only frontends stay empty. Advertise documentSymbolProvider
with hierarchical DocumentSymbol.

Housekeep DSC-0041 with LSN-0070.
2026-09-21 10:16:14 +01:00

142 lines
7.7 KiB
Markdown

---
id: LSN-0070
ticket: pbs-lsp-document-symbols-outline
title: PBS outline is an AST-backed document-symbol tree
created: 2026-09-21
tags: [studio, lsp, vscode, compiler-pbs, editor, outline, document-symbols]
---
# PBS outline is an AST-backed document-symbol tree
## Original Problem
PBS already had compiler-backed completion, hover, signature help, go-to-definition (`LSN-0068`), and find-references (`LSN-0069`). VS Code Outline stayed empty. Long `.pbs` files had to be scanned by hand.
The risk was building outline from semantic success (so the tree vanishes while typing), flattening to `SymbolInformation`, inventing a Document Symbol SPI, or stuffing workspace search and folding into the same payload.
## Consolidated Decision
PBS Document Symbols is the LSP projection of the **named declaration tree of the open `.pbs` file**, with the **recovered AST as backbone**.
Durable locks from `DEC-0054` (AGD-0044 Q1 A, Q2 AST tree, Q3 A, Q4 A, Q5 A, Q6 `.pbs`-only, Q7 existing kinds):
1. Presence, hierarchy, and ranges come from the document AST. Kind and `detail` may use cheap semantic enrichment. Semantics must not drop a named declaration still in the syntax tree, and must not require full analysis.
2. Named `TopDecl` plus syntactic members:
- `fn` → Function, no children;
- `declare struct` → Struct, children: fields, `ctor`s, methods;
- `declare service` → Service, children: methods;
- `declare contract` → Contract, children: signatures as methods;
- `declare host` → Host, children: signatures;
- `declare builtin type` → Builtin type, children: projection fields and signatures;
- `declare enum` → Enum, children: cases;
- `declare error` → Error, children: labels;
- `declare callback` → Callback, no children;
- `declare global` / `const` → Global / Const, no children;
- `implements Contract for Owner` → top-level Contract named `contractName`, `detail` = owner name, children: methods.
Locals, parameters, `this`, imports, and comments stay out. Private fields stay in. Overloads stay siblings. Host and builtin type appear only if the open file declares them.
3. Recovered AST is enough. Unnamed `InvalidDecl` is omitted. Named decls survive later semantic errors. No `PbsAst.File` → empty list. Do not invent nodes from text or token pairs.
4. Keep the capability on aggregated `FrontendLanguageService.documentSymbols` (default empty) plus a hierarchical DTO (`name`, `kind`, optional `detail`, range offsets, selection offsets, `children`). No file path on the DTO. PBS builds the tree in `prometeu-frontend-pbs`. Common LSP must not import `p.studio.compiler.pbs.*`. No Document Symbol SPI.
5. Advertise `documentSymbolProvider`. Payload is hierarchical `DocumentSymbol`, not flat `SymbolInformation`. `range` is the declaration span. `selectionRange` is the name span only if the AST already has one; this wave reuses the declaration span. Do not scan tokens for identifier spans. VS Code stays a thin client.
6. First wave is the current `.pbs` only. Out: imported supplementals, `mod.barrel`, locals, parameters, imports, comments, virtual documents, workspace symbols, folding, rename, snapshot cache. Server order is AST declaration order, not alphabetical.
7. Reuse `FrontendSymbolKind` / `PbsEditorialSymbolKind`. LSP projects to the closest host `SymbolKind`. Name is the identifier. `detail` may carry the AST signature (`(a: int) -> int`). Attributes such as `[Init]` do not enter the name.
Outline does not jump to another file. The physical-file destination policy of definition/references is not the outline policy.
Spec 23 §8.3 now also states: document symbols are the named declaration tree of the requested document; recovered syntax is enough; a missing syntax tree is empty; this is not a workspace index, folding ranges, or a foreign-file navigation list.
## Final Implementation
| Layer | What landed |
|---|---|
| PBS editorial | `PbsAstDocumentSymbols` walks `PbsAst.File.topDecls` in source order. `PbsEditorialSupportService.documentSymbols` delegates to that walk. |
| Generic contract | `FrontendLanguageService.documentSymbols` defaults to `List.of()`. `FrontendDocumentSymbol` is hierarchical and pathless. |
| PBS mapping | `PBSFrontendLanguageService` requires `editorialContext.syntaxTree()` to be `PbsAst.File`. Barrels and unknown trees stay empty. Kinds map with `valueOf(kind.name())`. |
| LSP | `documentSymbolProvider`, `textDocument/documentSymbol`, hierarchical `DocumentSymbol`. Nested enum/error children project to `EnumMember`. Compile-only frontends stay empty. |
| VS Code | Unchanged thin client. |
`mod.barrel` is assembled as `BarrelFile`, not `PbsAst.File`, so `astByFile` has no barrel outline. Empty is the honest first-wave result.
Error case labels have no per-label span in the AST. Children reuse the parent `ErrorDecl` span rather than scanning tokens.
## Examples
Outline of a file is the declarations in that file, not imports or locals:
```pbs
import { Log } from @sdk:log;
fn helper() -> int { return 42; }
fn frame() -> void {
let local = 1;
helper(local);
}
```
Outline: `helper`, `frame`. Not `Log`, not `local`.
Struct members and overloads as siblings:
```pbs
declare struct Vec(x: int) {
ctor make(x: int) { return; }
fn blend(dx: int, dy: int) -> int { return dx; }
}
fn helper() -> int { return 42; }
fn helper(value: int) -> int { return value; }
```
Outline: `Vec` (`x`, `make`, `blend`), then two `helper` siblings. Private field `x` is present. `detail` on `blend` is `(dx: int, dy: int) -> int`.
Recovered parse keeps the named decl:
```pbs
declare ;
fn ok() -> int { return 1; }
```
Outline: `ok`. The unnamed `InvalidDecl` is omitted.
`[Init] fn init()` still names the symbol `init`.
`implements TickLike for Point` is a top-level node named `TickLike` with detail `Point`.
## Pitfalls
Do not wait for semantic analysis to succeed before showing outline. The tree must survive broken intermediate edits.
Do not omit private fields because they are not in `mod.barrel`. Outline is for the author of this file.
Do not collapse overloads. AST identity forbids merging declaration sets.
Do not walk `File.imports` or supplemental imported `TopDecl`s. Those symbols are not declared in this document.
Do not implement outline for `mod.barrel` in this contract. Barrel AST is a different root. Empty is correct until a later decision.
Do not scan tokens to invent a tighter `selectionRange`. This wave uses the declaration span.
Do not put a file path on `FrontendDocumentSymbol`. The host already has the document URI. Outline is not go-to-definition.
Do not reuse the physical-file jump rule of `LSN-0068` as an outline destination policy. Clicking outline stays inside the buffer.
Do not overload document symbols with folding or scope guides (`LSN-0034`, `AGD-0053`).
Do not split a Document Symbol SPI “for future languages”. PBS is a real consumer of `FrontendLanguageService` (`LSN-0067`).
Do not treat this lesson as closing workspace symbols, folding, rename, document links, or hierarchy.
## References
- Agenda: `AGD-0044` (Q1 A, Q2 AST tree, Q3 A, Q4 A, Q5 A, Q6 `.pbs`-only, Q7 existing kinds)
- Decision: `DEC-0054`
- Plan: `PLN-0133`
- Spec 23 §8.3 — optional document symbols; recovered AST tree of the requested document
- `LSN-0068` — definition is a jump; outline is not
- `LSN-0069` — references invert definition identity
- `LSN-0034` — do not overload document symbols with structural anchors
- `LSN-0058` — generic `FrontendLanguageService` for LSP
- `LSN-0047` — protocol stays in `lsp-v1`; compiler owns semantics
- `LSN-0067` — no SPI without a real in-repo consumer
- Still open: `AGD-0045` workspace symbols, `AGD-0046` rename, `AGD-0051` snapshots, `AGD-0053` folding, `AGD-0055` document links, `AGD-0056` hierarchy