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: 63.56% (19159/30145)
* Branch Coverage: 54.14% (7612/14060)
* Lines of Code: 30145
* Cyclomatic Complexity: 12214
#### Quality Gates Summary
Output truncated.
Test / Build skipped: 15, passed: 678
Intrepid/Prometeu/Studio/pipeline/head This commit looks good
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.
129 lines
7.7 KiB
Markdown
129 lines
7.7 KiB
Markdown
---
|
||
id: LSN-0072
|
||
ticket: pbs-lsp-rename-symbol
|
||
title: PBS rename is a validated physical workspace edit
|
||
created: 2026-09-22
|
||
tags: [studio, lsp, vscode, compiler-pbs, editor, rename]
|
||
---
|
||
|
||
# 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):
|
||
|
||
1. Identity is the physical declaration-location set from definition. Do not text-search. Do not add `SymbolId` or a persistent `RefIndex`.
|
||
2. 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.
|
||
3. Walk regular `fileTable` files of the current `analyze()` surface. Omit `/virtual/stdlib` and other non-regular handles. Every edit location must be a compiler-known physical source file.
|
||
4. Refuse when physical identity is empty: builtins, virtual stdlib/SDK, unresolved names, keywords, comments, strings, `this`, `Self`. Refuse `mod.barrel`. Refuse `declare error` case labels that have no per-label span.
|
||
5. 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.
|
||
6. Refuse when the cursor lexeme is an import alias (`item.alias()` distinct from `item.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 change `as Alias` tokens or uses that spell a differing alias.
|
||
7. `prepareRename` validates the cursor and returns the identifier range, or null. `rename` also validates the new name: PBS identifier, not a keyword, no collision in the same binding space (top-level `fn`/`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 empty `WorkspaceEdit`. The server does not write files and does not rename paths.
|
||
8. Keep the capability on aggregated `FrontendLanguageService`. `prepareRename` defaults empty; `rename` defaults unsupported. PBS maps in `prometeu-frontend-pbs`. Common LSP must not import `p.studio.compiler.pbs.*`. No Rename SPI. Advertise `renameProvider` with `prepareProvider`. 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:
|
||
|
||
```pbs
|
||
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:
|
||
|
||
```pbs
|
||
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:
|
||
|
||
```pbs
|
||
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:
|
||
|
||
```pbs
|
||
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 writes
|
||
- `LSN-0069` — physical inverse that supplies usage sites
|
||
- `LSN-0051` — wave 1 deferred rename
|
||
- `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-0051` snapshots, `AGD-0053` folding, `AGD-0055` document links, `AGD-0056` hierarchy
|