Syntax.Rename
Rename as a join against the resolver, not as a tree walk.
The resolver has already decided what every name in a program means. A rename tool that walks the syntax and works scoping out again is a second, weaker answer to a question already answered, and the two disagree at exactly the cases that matter. So nothing here computes scope: it queries the prism-resolved-syntax-v1 document, which carries a node id, a form kind, and an exact byte span for every node, and emits span-addressed edits for Syntax.Edit to apply.
What that seam supports, and what it does not, decides this module’s shape. Every variable reference is a var node whose span covers exactly its identifier, so references are addressable and countable. No binding occurrence is: a let node’s span starts at the let keyword, a parameter is a bare name with no span at all, and there is no edge from a reference to the binder it resolved to. A use-only rewrite of a name whose binder lives in the same file therefore produces a file where the uses no longer name their binder, which is a corrupt program dressed up as a refactor.
Rather than guess, rn_plan refuses whenever the seam cannot prove the rewrite is complete. The proof it demands is cheap and exact: lex the source, take every identifier token spelling the old name, and require that each one is a var occurrence the resolver recorded. If some mention is not (a binder, a declaration head, a parameter list, an import), the plan is refused. What survives is the case the seam really does cover: renaming every reference to a name bound outside this document.
The refusals are the useful part of the result, and they are the gap to report: when the seam grows a binder-identity fact, this module tightens without an interface change.
Types
RnUse
type RnUse = RnUse { id: Int, span: Span, name: String }
One variable reference: the node id the checker facts join on, the exact span of the identifier, and the identifier text sliced from the embedded source.
RenameRefusal
type RenameRefusal
= RnNotAnIdent(String)
| RnTaken(String)
| RnUnknown(String)
| RnUnaddressed(Span)
| RnBinderInDocument(String, Span)
| RnSourceUnreadable(LexError)
deriving (Eq, Show)
Why a rename was refused. Every case is a fact about the document, not a guess about the programmer’s intent.
RenameError
type RenameError
= RnRefused(RenameRefusal)
| RnEdit(EditError)
deriving (Eq, Show)
The two ways a rename can fail: the seam refused to prove it, or the edits did not survive being applied.
Functions and Values
rn_message
rn_message : (Syntax.Rename.RenameRefusal) -> String
A human-readable account of a refusal.
rn_occurrences
rn_occurrences : (Syntax.Resolved.ResolvedDoc) -> List(Syntax.Rename.RnUse)
Every variable reference in the document, in source order: the occurrence table an editor’s find-references is, and the join key column a checker uses to pull prism-tc-facts-v1 rows.
rn_unaddressed
rn_unaddressed : (Syntax.Resolved.ResolvedDoc) -> List(Syntax.Source.Span)
Every reference the embedded source cannot address. Non-empty means the document and its source disagree, and every query here should be distrusted.
rn_uses_of
rn_uses_of : (Syntax.Resolved.ResolvedDoc, String) -> List(Syntax.Rename.RnUse)
Every reference to one name, in source order.
rn_use_count
rn_use_count : (Syntax.Resolved.ResolvedDoc, String) -> Int
How many times a name is referenced.
rn_is_ident
rn_is_ident : (String) -> Bool
Whether a string is a single identifier, decided by the real lexer rather than a hand-rolled character predicate. A keyword is not an identifier, so a rename to one is refused here rather than discovered after the edit.
rn_mentions
rn_mentions : (String, String) -> Result(List(Syntax.Source.Span), Syntax.Lex.LexError)
The span of every identifier token spelling name, anywhere in the text. This is the completeness check: a mention the resolver did not record as a reference is a binder, a declaration head, or a parameter, and any of those makes a use-only rewrite wrong.
rn_plan
rn_plan : (Syntax.Resolved.ResolvedDoc, String, String) -> Result(List(Syntax.Edit.Edit), Syntax.Rename.RenameRefusal)
The edit set that renames every reference to from into to, or the reason the seam cannot prove that rewrite is complete.
The checks run in a fixed order, so the refusal a caller sees is always the first thing wrong: the source must lex, every reference must be addressable, to must be a single identifier that occurs nowhere in the document, from must be referenced at least once, and every mention of from must be one of those references.
rn_rename
rn_rename : (Syntax.Resolved.ResolvedDoc, String, String) -> Result(String, Syntax.Rename.RenameError)
Rename and apply, in one step: the plan, then the edits, then the re-lex Syntax.Edit insists on. Either refusal comes back as itself.