Nimblesite / Nimblesite/Basilisk
Rename Symbol does not update importers: the definition is renamed, callers are silently left broken
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 54
- Forks
- 3
- PR merge metrics
- No merged PRs in 30d
Description
Summary
Renaming a module-level function via Rename Symbol (F2) updates the definition, its same-file references, and its __all__ entry — but does not update importers. The importing file is left referring to a name that no longer exists, so a rename that looks successful silently breaks working code.
Cross-file rename is implemented (navigation.rs:409-423), walking import_graph.importers_of(). This is that path failing to fire, not a missing feature.
Cross-file scope is also the intended behaviour, settled in Refs #229 — the spec was updated to document rename as workspace-wide precisely because the code already was. So this is a defect in a supported feature, not a request to extend one.
Reproduction
pyproject.toml:
[project]
name = "rename-repro"
version = "0.1.0"
[tool.basilisk]
include = ["src"]
extraPaths = ["src"]
src/demo/exports.py:
__all__ = ["calculate", "helper"]
# This comment mentions calculate on purpose - a rename must NOT touch it.
NOTE = "calculate appears in this string literal - a rename must NOT touch it"
def calculate(value: int) -> int:
return value * 2
def helper() -> int:
return calculate(1)
src/demo/consumer.py:
from demo.exports import calculate
def use() -> int:
return calculate(3)
- Open the folder in VS Code (
src/demo/consumer.pyneed not be open). - Put the cursor on
calculateindef calculate(...)inexports.py. - F2 →
compute→ Enter.
Actual
exports.py is fully updated. consumer.py is untouched:
from demo.exports import calculate # <- still the old name; module no longer defines it
return calculate(3) # <- still the old name
The project no longer type-checks — caught, fittingly, by imports_missing_name:
$ basilisk check
error[imports_missing_name]: Cannot import name `calculate` from `demo.exports` — the module defines no such name
--> ./src/demo/consumer.py:3:26
Found 3 diagnostics (3 errors).
Expected
consumer.py's import and call site are renamed in the same WorkspaceEdit, leaving the project checking clean.
What does work — this is narrowly scoped
Both behaviours added in #363 are correct, and this report is not about them:
__all__entries update.__all__ = ["calculate", "helper"]→["compute", "helper"], withhelperuntouched.- Comment/string masking works. Five prose mentions of
calculateinexports.py(module docstring, a comment, a string constant, the function's own docstring, and a backticked mention in a class docstring) were all correctly left alone.
The single-file core is doing its job. The cross-file half is not running.
Where to look
references.rs:71 documents the split:
// Implements [LSPARCH-FEATURES-RENAME] and [REFACTOR-RENAME] (single-file core;
// the cross-file handler half lives in server/handlers/navigation.rs under
// [ANALYSIS-CROSSLSP-RENAME]).
The handler gates each importer on two conditions:
for importer_path in graph.importers_of(¤t_path) {
if let Some(entry) = idx.files.get(&importer_path) {
if let Some(ref res) = entry.resolved {
if res.imported_symbols.contains_key(&name) {
and import_graph.rs:74 builds the reverse edges from ImportInfo.resolved_path for every indexed file. So a failure at any of these produces exactly the observed silence:
consumer.pyis not inidx.files— it was never opened; does the initial workspace scan index unopened files underinclude?- No reverse edge —
from demo.exports import calculateyielded noresolved_path, soimporters_of()returned empty. Note the import resolves throughextraPaths = ["src"]; the CLI clearly resolves it (itsimports_missing_namediagnostic namesdemo.exports), so the question is whether the LSP's index appliesextraPathsthe same way. imported_symbolslacks the key — the map is not populated forfrom X import Yform.
Hypothesis 2 seems the most likely to me given extraPaths is in play, but I have not confirmed which of the three it is.
Why this matters
A rename that refuses to run is a nuisance. A rename that appears to succeed while breaking every importer is worse: nothing in the UI signals partial application, and on a real codebase the damage lands in files the user never opened and will not think to check. The breakage is only visible on the next full check.
Possibly related: the CodeLens above def calculate read "2 references", counting only the two same-file call sites and ignoring consumer.py's two. If the lens and rename share a reference-search path, one fix may address both.
Environment
- Basilisk built from
main@47b71ee4 - macOS (darwin 25.5.0), VS Code extension from the same tree,
basilisk.useLsp: true(default)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in crates/basilisk-lsp/src/server/handlers/navigation.rs at the cross-file rename path, then inspect import_graph.rs and references.rs. Reproduce the issue with the provided pyproject.toml and two source files, checking whether the unopened importer is indexed, its reverse edge resolves through extraPaths, and imported_symbols contains the name. Done means the importer’s import and call are included in the WorkspaceEdit and basilisk check passes cleanly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, rust
- Domain
- developer-experience, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 64/100