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.
7.7 KiB
| id | ticket | title | created | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| LSN-0070 | pbs-lsp-document-symbols-outline | PBS outline is an AST-backed document-symbol tree | 2026-09-21 |
|
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):
- Presence, hierarchy, and ranges come from the document AST. Kind and
detailmay use cheap semantic enrichment. Semantics must not drop a named declaration still in the syntax tree, and must not require full analysis. - Named
TopDeclplus syntactic members:fn→ Function, no children;declare struct→ Struct, children: fields,ctors, 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 namedcontractName,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.
- Recovered AST is enough. Unnamed
InvalidDeclis omitted. Named decls survive later semantic errors. NoPbsAst.File→ empty list. Do not invent nodes from text or token pairs. - Keep the capability on aggregated
FrontendLanguageService.documentSymbols(default empty) plus a hierarchical DTO (name,kind, optionaldetail, range offsets, selection offsets,children). No file path on the DTO. PBS builds the tree inprometeu-frontend-pbs. Common LSP must not importp.studio.compiler.pbs.*. No Document Symbol SPI. - Advertise
documentSymbolProvider. Payload is hierarchicalDocumentSymbol, not flatSymbolInformation.rangeis the declaration span.selectionRangeis 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. - First wave is the current
.pbsonly. 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. - Reuse
FrontendSymbolKind/PbsEditorialSymbolKind. LSP projects to the closest hostSymbolKind. Name is the identifier.detailmay 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:
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:
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:
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 TopDecls. 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 notLSN-0069— references invert definition identityLSN-0034— do not overload document symbols with structural anchorsLSN-0058— genericFrontendLanguageServicefor LSPLSN-0047— protocol stays inlsp-v1; compiler owns semanticsLSN-0067— no SPI without a real in-repo consumer- Still open:
AGD-0045workspace symbols,AGD-0046rename,AGD-0051snapshots,AGD-0053folding,AGD-0055document links,AGD-0056hierarchy