Add PBS rename as a validated WorkspaceEdit of physical identifier sites on FrontendLanguageService. Identity is definition; locals and params rename from their name span. Refuse aliases, virtual stdlib, builtins, error labels, keywords, and collisions. Advertise renameProvider with prepareProvider; refused cursors are LSP errors, not empty success. Housekeep DSC-0043 with LSN-0072.
7.7 KiB
| id | ticket | title | created | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| LSN-0072 | pbs-lsp-rename-symbol | PBS rename is a validated physical workspace edit | 2026-09-22 |
|
PBS rename is a validated physical workspace edit
Original Problem
PBS already had compiler-backed completion, hover, signature help, go-to-definition (LSN-0068), find-references (LSN-0069), outline (LSN-0070), and workspace symbols (LSN-0071). Users still could not rename a symbol through the editor. Manual text edits miss uses or rewrite unrelated overloads, comments, strings, and same-name declarations.
The risk was a file-local rename, a lexeme replace, a new SymbolId / RefIndex, snapshot cache (AGD-0051) as a prerequisite, virtual stdlib documents, or a successful empty WorkspaceEdit that pretends the symbol changed.
Consolidated Decision
PBS Rename Symbol is the LSP projection of a validated WorkspaceEdit over physical identifier spans of the same identity definition already returns.
Durable locks from DEC-0056 (AGD-0046 Q1–Q6 = A):
- Identity is the physical declaration-location set from definition. Do not text-search. Do not add
SymbolIdor a persistentRefIndex. - Sites are identifier tokens whose definition intersects that identity, plus identifier-sized declaration name spans. Overloads of the same name in the same owner rewrite together. Members rewrite only that member. Comments, strings, and homonyms of another identity stay.
- Walk regular
fileTablefiles of the currentanalyze()surface. Omit/virtual/stdliband other non-regular handles. Every edit location must be a compiler-known physical source file. - Refuse when physical identity is empty: builtins, virtual stdlib/SDK, unresolved names, keywords, comments, strings,
this,Self. Refusemod.barrel. Refusedeclare errorcase labels that have no per-label span. - Exception: a local or parameter name span is renameable even when definition at that offset is empty. Identity becomes that name span; uses are identifiers whose definition intersects it.
- Refuse when the cursor lexeme is an import alias (
item.alias()distinct fromitem.name()), including uses of that alias. Do not follow alias definition and rewrite the export. Export rename happens only when the cursor writes the original name. Export rewrite must not changeas Aliastokens or uses that spell a differing alias. prepareRenamevalidates the cursor and returns the identifier range, or null.renamealso validates the new name: PBS identifier, not a keyword, no collision in the same binding space (top-levelfn/service/global/const, local/param in the same scope, field in the same struct, method in the same owner, case in the same enum), ignoring rewritten sites and the overload set. Failure is an LSP error. Never return a successful emptyWorkspaceEdit. The server does not write files and does not rename paths.- Keep the capability on aggregated
FrontendLanguageService.prepareRenamedefaults empty;renamedefaults unsupported. PBS maps inprometeu-frontend-pbs. Common LSP must not importp.studio.compiler.pbs.*. No Rename SPI. AdvertiserenameProviderwithprepareProvider. Compile-only stays unsupported. VS Code stays a thin client.
Spec 23 §8.3 now also states: rename is a validated workspace edit of identifier spans in compiler-known physical source files; identity is definition; local/param name spans remain renameable; alias, virtual, and error-label cursors refuse; invalid names and collisions are protocol errors, not empty success; the server does not write files; prepareProvider is advertised when rename is exposed.
Final Implementation
| Layer | What landed |
|---|---|
| PBS editorial | PbsEditorialSupportService.planRename builds identity from definition or local/param name span, refuses aliases and error labels, walks IDENTIFIER tokens whose definition intersects identity and whose lexeme is the original name, then validates identifier/keyword/collision. |
| Generic contract | FrontendLanguageService.prepareRename defaults empty. rename defaults FrontendRenameResult.unsupported(). FrontendRenameEdit is path + offsets + newText. Applied results reject an empty edit list. |
| PBS mapping | PBSFrontendLanguageService reuses projectDocuments and toFrontendLocation. Empty physical identity is refuse, even if editorial sites exist on virtual handles. |
| LSP | renameSupported, prepareRename, rename. Mapper advertises RenameOptions.prepareProvider. Refused rename is ResponseErrorException, not an empty WorkspaceEdit. Compile-only does not advertise. |
| VS Code | Unchanged thin client. |
Stdlib and SDK modules remain virtual (/virtual/stdlib/...). F2 on Gfx.clear is refused, matching empty F12 / Shift+F12. Hover is unchanged.
let total at the binding itself still has empty definition. Rename uses the name span as identity so F2 on the binding works.
Examples
Same-file function, including overloads and the declaring identifier. Comment and string stay:
fn helper() -> int { return 1; }
fn helper(value: int) -> int { return value; }
fn frame() -> void {
helper();
helper(2);
// helper
let text = "helper";
}
F2 on helper → aid rewrites both declarations and both calls.
Local name span:
fn frame() -> void {
let total = 1;
return total;
}
F2 on let total or on the use rewrites both identifier sites.
Alias is refused. Export rename keeps the local alias:
import { Log as Logger } from @sdk:log;
Logger.info("a");
F2 on Logger or Logger.info is refused. F2 on a physical Log declaration rewrites the original name and import { Log as Logger }, and leaves Logger / Logger.info.
Builtins and virtual stdlib stay hover-only:
let n: int = 1;
Gfx.clear(5);
Invalid new names (fn, 1ab) and collisions (helper → existing frame) fail with an LSP error.
Pitfalls
Do not implement rename as workspace text search in lsp-v1 or in the VS Code extension. It will rewrite comments, strings, overloads of another identity, and import aliases.
Do not follow definition through an import alias. Logger currently resolves to Log's span; a blind inverse-of-definition rename would rewrite the export.
Do not return a successful empty WorkspaceEdit for a refused cursor. The editor will treat that as “renamed”.
Do not skip the local/param name-span exception. F2 on let total is the common case, and definition there is empty.
Do not rewrite declare error case labels by using the parent ErrorDecl span. Those labels have no per-label span.
Do not wait for snapshot cache. Rename reuses the request-local walk already used by references.
Do not treat empty/refused stdlib rename as a missing feature. Virtual paths are not regular files.
Do not split a Rename SPI “for future languages”. PBS is a real consumer of FrontendLanguageService (LSN-0067).
Do not treat this lesson as closing snapshots, folding, document links, hierarchy, code actions, formatting, file rename, or local-alias rename.
References
- Agenda:
AGD-0046(Q1–Q6 = A) - Decision:
DEC-0056 - Plan:
PLN-0135 - Spec 23 §8.3 — optional rename; physical-file-or-empty; alias refuse; error instead of empty success;
prepareProvider - Spec:
docs/specs/compiler-languages/pbs/3. Core Syntax Specification.md§4.3–4.4 LSN-0068— definition identity this feature writesLSN-0069— physical inverse that supplies usage sitesLSN-0051— wave 1 deferred renameLSN-0058— genericFrontendLanguageServicefor LSPLSN-0047— protocol stays inlsp-v1; compiler owns semanticsLSN-0067— no SPI without a real in-repo consumer- Still open:
AGD-0051snapshots,AGD-0053folding,AGD-0055document links,AGD-0056hierarchy