Non-mutating version of namer #5498
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Motivation
We want to be able to service certain LSP requests on stale data when the live data is currently being updated.
In those cases, the LSP's typechecker thread holds exclusive write access to the
GlobalStateobject while it runs a slow path. The slow path is designed to throw away all the GlobalState in a program and recompupte it.But we keep the old GlobalState around just in case we want to cancel the running slow path (e.g., we might want to do so if a subsequent edit comes along, and the combination of the previous edit + new edit is enough to take the fast path).
Importantly, it's not possible to mutate the GlobalState that we're holding on reserve for if we can cancel an edit (being able to use it to potentially cancel a slow path requires that it not be subtly changing under the hood).
Also, we don't store the result of running namer and resolver on a tree, only the result of running the index phases. I don't quite remember the historical context here, but instead for every fast path (including every LSP query run), we re-run namer and resolver over the (cached by LSP) indexed trees. So we have a unique situation—to serve stale LSP queries, we have to be able to run namer and resolver over already-indexed trees, but not mutate GlobalState.
This PR implements the first half of that picture: it runs only the parts of namer that don't mutate GlobalState. It achieves this by aggressively throwing away subtrees. Normally the
SymbolDefinerportion of namer would guarantee that every definition that appears in the source code also gets a symbol in the SymbolTable. But since we're running namer and resolver on newly-indexed trees, there might be new definitions that don't yet have entries in the symbol table.Rather than attempt to fake it, and return something like a stub symbol for which
.exists()istrue, we've opted to just delete the subtrees with new definitions entirely. This is very defensive, but makes it easy to ensure that invariants are upheld. For example, it means thatctx.owner(used by things likemethodOwner()andenclosingClass()are still reliable. It ensures that things like the number of arguments in a method def tree matches the number of ArgInfo objects in the symbol table. Etc.This obviously comes at the downside that if you e.g., change a class's name, this will cause a blocking, slow path edit and you won't even be able to do things like hover over constant literals inside that class def until the slow path finishes.
But it does mean that if you e.g. add a
T::Structabove your class, and then you go back into your class and try to hover while the slow path edit is running, those queries will work (hypothetically—the LSP side of things will arrive in #5469).Test plan
To test this change, we've repurposed our whole testdata suite.
The idea is basically: after indexing but before running namer, make a copy of the just-indexed tree and run the best-effort namer on it, but only for the purpose of exercising the ENFORCEs.
There will still be lots of errors, but