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

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
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, 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 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:

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