Nimblesite / Nimblesite/Basilisk

Rename Symbol does not update importers: the definition is renamed, callers are silently left broken

Open
#368 0 comments 0 reactions 0 assignees View on GitHub

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)
  1. Open the folder in VS Code (src/demo/consumer.py need not be open).
  2. Put the cursor on calculate in def calculate(...) in exports.py.
  3. F2compute → 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"], with helper untouched.
  • Comment/string masking works. Five prose mentions of calculate in exports.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(&current_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:

  1. consumer.py is not in idx.files — it was never opened; does the initial workspace scan index unopened files under include?
  2. No reverse edgefrom demo.exports import calculate yielded no resolved_path, so importers_of() returned empty. Note the import resolves through extraPaths = ["src"]; the CLI clearly resolves it (its imports_missing_name diagnostic names demo.exports), so the question is whether the LSP's index applies extraPaths the same way.
  3. imported_symbols lacks the key — the map is not populated for from X import Y form.

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.