Files
bfren f5affecddc Optimize module reloading check (#19228)
Check each module once when deciding whether to reload a cached module

## Description

When a module that has already been parsed is imported again,
`parse_module_file` calls `module_needs_reloading` to check whether any
file in the module's tree has changed on disk. That walk recurses
through `submodules` and `imported_modules` without tracking which
modules it has visited, so a module reachable through several paths is
re-read once per *path*.

That's common in practice: a `mod.nu` that `export use`s its sibling
files, where most siblings also `use` a few shared helpers. In the repro
from the issue, every extra `use nested` of a 9-file module re-reads its
files ~133 times; with a real 22-file library it's ~412 reads per
import. Linux mostly hides this because file opens are cheap, but on
Windows it dominates parse time, and the LSP pays it on every keystroke.

This PR adds a `seen` set for the duration of the walk, so each
reachable module's file is read at most once per check. Skipping an
already-seen module is safe: `any()` short-circuits on the first change,
so a module seen again has already been found unchanged. I also
extracted the read-and-compare into a separate `module_file_changed`
function.

Measured on Windows with the repro script from the issue (`nu -n
repro.nu`):

| importers | before | after |
| --------: | -----: | ----: |
| 1 | 34.9 ms | 28.4 ms |
| 2 | 51.2 ms | 29.2 ms |
| 5 | 97.5 ms | 32.4 ms |
| 10 | 179.0 ms | 38.3 ms |

Per extra importer: **16.0 ms → 1.1 ms**. In my real-world case (a
13-file module importing a 22-file library from most of its files),
parsing on Windows went from ~3.1 s to the point where the LSP is
responsive again.

## User-facing changes (Release notes)

Improved performance when the same module is imported from many places.
Nushell no longer re-reads a module's files once for every import path
when checking whether a cached module has changed. This especially
speeds up parsing and the language server on Windows for libraries whose
files import each other.

## Additional notes

- fixes #19226
- `cargo test -p nu-parser` and `cargo test --test tests -- modules::`
pass; the second runs the module reload tests, including the new
`reload_module_reached_through_several_imports`.
- Behaviour is unchanged: a modified file anywhere in the tree still
triggers a reload. Only repeat visits of the same module within one
check are skipped.
2026-10-14 16:16:01 -05:00
..