Scope the Require::Everything escalation to the requests that read ASTs, and add the #3636 regression test (#4643)

Summary:
Follow-up to https://github.com/facebook/pyrefly/issues/3637 (landed as `3c288a2`), measured against `origin/main` @ `8ec5070d2`.

Two things: the regression test https://github.com/facebook/pyrefly/issues/3636 still lacks, and a narrowing of where the
`Require::Everything` escalation is paid.

## 1. The regression test

`3c288a2` is correct — I verified `test_incoming_call_hierarchy_unopened_caller_file` passes
against unmodified `main`. But it landed as 4 additions / 3 deletions in one file with no test,
so nothing holds the behaviour. `test_incoming_call_hierarchy_basic` opens *both* files, so it
cannot catch https://github.com/facebook/pyrefly/issues/3636.

The new test opens only `callee.py` and runs with `IndexingMode::LazyBlocking`, so `caller.py`
is reached through the index. Verified to fail against the code as it stood before `3c288a2`.

d34db3ff — this is the test we discussed on https://github.com/facebook/pyrefly/issues/3637; the PR merged before I could add it there.

## 2. Who pays for the escalation

`3c288a2` moved the escalation into `compute_transitive_rdeps_for_definition_impl`, which is
shared by four features. Only two of them ever read an AST:

| consumer | reads an AST? |
| --- | --- |
| `callHierarchy/incomingCalls` | **yes** — `get_ast`, to attribute a call site to its enclosing function |
| `typeHierarchy` subtypes | **yes** — `get_ast` + `get_solutions` + `get_bindings` |
| `textDocument/references` | no — `local_references_from_definition` and `find_child_implementations_impl` are both index-backed |
| `find_global_implementations_from_definition` | no — `solutions_index` only |

So `references`, much the hottest of the four, now runs a full check over every transitive rdep
of the definition's module and retains their ASTs for data it never reads.

This PR moves the escalation to the two consumers that need it:

- **call hierarchy** asks the index which rdeps actually reference the target, then escalates
  only those. A file that never mentions the target costs nothing beyond the lookup. Ordering
  matters: the pre-`3c288a2` code fetched the AST *before* checking for references. The
  escalation is batched into one `run` — doing it per file from inside the walk leaves every
  file after the first still without an AST.
- **type hierarchy** escalates its own deduped candidates, so its behaviour is exactly what
  `main` does today.

`references` and `implementations` stop paying entirely.

## Measurements

This answers the perf question yangdanny97 raised on https://github.com/facebook/pyrefly/issues/3637 in June.

Release builds, a 209-file Python project (the same one I profiled on https://github.com/facebook/pyrefly/issues/3637, since grown from
189), LSP driven programmatically opening only the file it queries. **A fresh server per cold rep**, median of 3; the timed request is the first one that
server receives. RSS delta is sampled immediately before and after that single request, so it is
what the request itself retained. Result counts in parentheses are ground truth and were
identical on both builds. The three positions are the same ones I profiled on https://github.com/facebook/pyrefly/issues/3637.

| | refs (82) | refs (36) | `incomingCalls` (32) |
| --- | --- | --- | --- |
| `main` @ `8ec5070d2` — cold | 65.4 ms | 31.5 ms | 31.6 ms |
| this PR — cold | **3.4 ms** | **1.9 ms** | **26.8 ms** |
| `main` — RSS retained by the one request | 81 MB | 15 MB | 16 MB |
| this PR — RSS retained | **2 MB** | **1 MB** | **7 MB** |
| `main` — server peak RSS | 396 MB | 329 MB | 316 MB |
| this PR — server peak RSS | **281 MB** | **281 MB** | **291 MB** |

The whole table was run twice on a quiet machine; every figure reproduced within 1%
(e.g. refs (82) cold 65.4 / 65.5 ms on `main`, 3.4 / 3.4 ms here).

A note on methodology, because it changes the answer: the profile I posted on https://github.com/facebook/pyrefly/issues/3637 used a
**median of 5 warm reps**. Escalation to `Require::Everything` is a one-time state transition
that the transaction retains, so reps 2..5 measure the already-escalated steady state and a warm
median structurally hides the cost being argued about. That profile was not evidence that the
escalation is cheap. Hence the fresh-server-per-rep harness here.

## Test suite

`cargo test --lib --no-fail-fast` on this branch: **8,093 passed / 0 failed / 4 ignored** in the
`pyrefly` crate, 8,828 passed and 0 failed across all 13 test binaries. The 4 ignored are
pre-existing. Result counts from the probes above were identical to `main` on every case
(82 / 36 / 32), so this is a cost change, not a behaviour change.

## What this does not do

Nothing tests the *cost* property. If the escalation is re-broadened later, the tests stay green
and only the numbers change. I did not find a good way to assert it at this layer; suggestions
welcome.

Pull Request resolved: https://github.com/facebook/pyrefly/pull/4643

Reviewed By: stroxler

Differential Revision: D117389890

fbshipit-source-id: e1ef862413d9efe7b926b545b3f9b396713a6c85
4 files changed
tree: 6a5780d65751b6a69784846627cee1c724a0858e
  1. .cargo/
  2. .claude/
  3. .github/
  4. .llms/
  5. .vscode/
  6. conformance/
  7. crates/
  8. lsp/
  9. pyrefly/
  10. pyrefly_wasm/
  11. release_notes/
  12. schemas/
  13. scripts/
  14. tensor-shapes/
  15. test/
  16. website/
  17. .gitattributes
  18. .gitignore
  19. action.yml
  20. AGENTS.md
  21. ARCHITECTURE.md
  22. Cargo.lock
  23. Cargo.toml
  24. CODE_OF_CONDUCT.md
  25. CONTRIBUTING.md
  26. debug.html
  27. empty.py
  28. LICENSE
  29. package-lock.json
  30. README.md
  31. REVIEW.md
  32. rust-toolchain.toml
  33. rustfmt.toml
  34. TENSOR_SHAPES_CONTRIBUTING.md
  35. test.py
  36. version.bzl
  37. yarn.lock
README.md

Pyrefly: A fast type checker and language server for Python with powerful IDE features

pyrefly PyPI VS Code Open VSX Discord License: MIT

Pyrefly is a type checker and language server for Python, which provides lightning-fast type checking along with IDE features such as code navigation, semantic highlighting, and code completion. It is available as a command-line tool and an extension for popular IDEs and editors such as VSCode, Neovim, Zed, and more.

See the Pyrefly website for full documentation and how to add Pyrefly to your editor of choice.

Pyrefly's current development status is stable.

Key Features

  • Fast. Pyrefly checks over 1.85 million lines of code per second, type checking projects like PyTorch 15x faster than Mypy and Pyright. In the IDE, rechecks typically complete in under 10 milliseconds after saving a file.
  • Production-proven at scale. Pyrefly is the default type checker for Instagram's 20-million-line Python codebase at Meta, and has been adopted by large open source projects including PyTorch and JAX.
  • Full-featured language server. Code navigation, autocomplete, hover information, inlay hints, semantic highlighting, and more, with consistent results across the CLI and your editor of choice.
  • Understands real-world Python. Built-in support for frameworks and tools like Pydantic, Django, and pytest, with model validation, field types, fixture navigation, and autocomplete that work out of the box.
  • Adoption-ready. Migrate from Mypy or Pyright with pyrefly init, silence existing errors with pyrefly suppress, and generate type annotations with pyrefly infer. Start with one file and expand at your own pace.

Getting Started

Version Policy

Pyrefly releases new minor versions (1.x.0) monthly and patch versions in between as-needed for critical fixes. Pyrefly does not follow strict semantic versioning: minor versions contain more significant changes than patch versions, but any version may introduce new type errors and other breaking changes. The pyrefly suppress command can be used to easily silence errors when upgrading to a new version.

Getting Involved

If you have questions or would like to report a bug, please create an issue.

See our contributing guide and architecture overview for information on how to contribute to Pyrefly.

Join our Discord to chat about Pyrefly and types. This is also where we hold biweekly office hours.