[ty] Add completion support for import and from ... import statements by BurntSushi · Pull Request #19883 · astral-sh/ruff (original) (raw)

@BurntSushi BurntSushi added server

Related to the LSP server

ty

Multi-file analysis & type inference

labels

Aug 12, 2025

MichaReiser

BurntSushi

BurntSushi added a commit that referenced this pull request

Aug 13, 2025

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref #19883 (comment)

BurntSushi added a commit that referenced this pull request

Aug 13, 2025

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref #19883 (comment)

BurntSushi added a commit that referenced this pull request

Aug 13, 2025

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref #19883 (comment)

BurntSushi added a commit that referenced this pull request

Aug 14, 2025

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref #19883 (comment)

BurntSushi added a commit that referenced this pull request

Aug 18, 2025

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref #19883 (comment)

BurntSushi added a commit that referenced this pull request

Aug 18, 2025

@BurntSushi

This change rejiggers how we register globs for file watching with the LSP client. Previously, we registered a few globs like **/*.py, **/pyproject.toml and more. There were two problems with this approach.

Firstly, it only watches files within the project root. Search paths may be outside the project root. Such as virtualenv directory.

Secondly, there is variation on how tools interact with virtual environments. In the case of uv, depending on its link mode, we might not get any file change notifications after running uv add foo or uv remove foo.

To remedy this, we instead just list for file change notifications on all files for all search paths. This simplifies the globs we use, but does potentially increase the number of notifications we'll get. However, given the somewhat simplistic interface supported by the LSP protocol, I think this is unavoidable (unless we used our own file watcher, which has its own considerably downsides). Moreover, this is seemingly consistent with how ty check --watch works.

This also required moving file watcher registration to after workspaces are initialized, or else we don't know what the right search paths are.

This change is in service of #19883, which in order for cache invalidation to work right, the LSP client needs to send notifications whenever a dependency is added or removed. This change should make that possible.

I tried this patch with #19883 in addition to my work to activate Salsa caching, and everything seems to work as I'd expect. That is, completions no longer show stale results after a dependency is added or removed.

BurntSushi added a commit that referenced this pull request

Aug 18, 2025

@BurntSushi

This change rejiggers how we register globs for file watching with the LSP client. Previously, we registered a few globs like **/*.py, **/pyproject.toml and more. There were two problems with this approach.

Firstly, it only watches files within the project root. Search paths may be outside the project root. Such as virtualenv directory.

Secondly, there is variation on how tools interact with virtual environments. In the case of uv, depending on its link mode, we might not get any file change notifications after running uv add foo or uv remove foo.

To remedy this, we instead just list for file change notifications on all files for all search paths. This simplifies the globs we use, but does potentially increase the number of notifications we'll get. However, given the somewhat simplistic interface supported by the LSP protocol, I think this is unavoidable (unless we used our own file watcher, which has its own considerably downsides). Moreover, this is seemingly consistent with how ty check --watch works.

This also required moving file watcher registration to after workspaces are initialized, or else we don't know what the right search paths are.

This change is in service of #19883, which in order for cache invalidation to work right, the LSP client needs to send notifications whenever a dependency is added or removed. This change should make that possible.

I tried this patch with #19883 in addition to my work to activate Salsa caching, and everything seems to work as I'd expect. That is, completions no longer show stale results after a dependency is added or removed.

BurntSushi added a commit that referenced this pull request

Aug 19, 2025

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref #19883 (comment)

BurntSushi added a commit that referenced this pull request

Aug 19, 2025

@BurntSushi

This change rejiggers how we register globs for file watching with the LSP client. Previously, we registered a few globs like **/*.py, **/pyproject.toml and more. There were two problems with this approach.

Firstly, it only watches files within the project root. Search paths may be outside the project root. Such as virtualenv directory.

Secondly, there is variation on how tools interact with virtual environments. In the case of uv, depending on its link mode, we might not get any file change notifications after running uv add foo or uv remove foo.

To remedy this, we instead just list for file change notifications on all files for all search paths. This simplifies the globs we use, but does potentially increase the number of notifications we'll get. However, given the somewhat simplistic interface supported by the LSP protocol, I think this is unavoidable (unless we used our own file watcher, which has its own considerably downsides). Moreover, this is seemingly consistent with how ty check --watch works.

This also required moving file watcher registration to after workspaces are initialized, or else we don't know what the right search paths are.

This change is in service of #19883, which in order for cache invalidation to work right, the LSP client needs to send notifications whenever a dependency is added or removed. This change should make that possible.

I tried this patch with #19883 in addition to my work to activate Salsa caching, and everything seems to work as I'd expect. That is, completions no longer show stale results after a dependency is added or removed.

BurntSushi added a commit that referenced this pull request

Aug 19, 2025

@BurntSushi

This change rejiggers how we register globs for file watching with the LSP client. Previously, we registered a few globs like **/*.py, **/pyproject.toml and more. There were two problems with this approach.

Firstly, it only watches files within the project root. Search paths may be outside the project root. Such as virtualenv directory.

Secondly, there is variation on how tools interact with virtual environments. In the case of uv, depending on its link mode, we might not get any file change notifications after running uv add foo or uv remove foo.

To remedy this, we instead just list for file change notifications on all files for all search paths. This simplifies the globs we use, but does potentially increase the number of notifications we'll get. However, given the somewhat simplistic interface supported by the LSP protocol, I think this is unavoidable (unless we used our own file watcher, which has its own considerably downsides). Moreover, this is seemingly consistent with how ty check --watch works.

This also required moving file watcher registration to after workspaces are initialized, or else we don't know what the right search paths are.

This change is in service of #19883, which in order for cache invalidation to work right, the LSP client needs to send notifications whenever a dependency is added or removed. This change should make that possible.

I tried this patch with #19883 in addition to my work to activate Salsa caching, and everything seems to work as I'd expect. That is, completions no longer show stale results after a dependency is added or removed.

@BurntSushi

This makes it easier to do exhaustive case analysis on a SearchPath depending on whether it is a vendored or system path.

@BurntSushi

We'll want to use these when implementing the "list modules" API.

@BurntSushi

These tests capture existing behavior.

I added these when I stumbled upon what I thought was an oddity: we prioritize foo.pyi over foo.py, but prioritize foo/__init__.py over foo.pyi.

(I plan to investigate this more closely in follow-up work. Particularly, to look at other type checkers. It seems like we may want to change this to always prioritize stubs.)

@BurntSushi

This makes it a little more flexible to call. For example, we might have a StmtImport and not a StmtImportFrom.

@BurntSushi

Previously, if the module was just foo-stubs, we'd skip over stripping the -stubs suffix which would lead to us returning None.

This function is now a little convoluted and could be simpler if we did an intermediate allocation. But I kept the iterative approach and added a special case to handle foo-stubs.

@BurntSushi

@BurntSushi

This will make it easier to emit this info into snapshots for testing.

@BurntSushi

The actual implementation wasn't too bad. It's not long but pretty fiddly. I copied over the tests from the existing module resolver and adapted them to work with this API. Then I added a number of my own tests as well.

@BurntSushi

These tests were added as a regression check that a panic didn't occur. So we were asserting a bit more than necessary. In particular, these will soon return completions for modules, which creates large snapshots that we don't need.

So modify these to just check there is sensible output that doesn't panic.

@BurntSushi

This makes import <CURSOR> and from <CURSOR> completions work.

This also makes import os.<CURSOR> and from os.<CURSOR> completions work. In this case, we are careful to only offer submodule completions.

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref #19883 (comment)

KotlinIsland pushed a commit to KotlinIsland/basedpython that referenced this pull request

May 1, 2026

@BurntSushi

This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on Module for good error messages. This also motivated a switch to an interned struct instead of a tracked struct. This ensures that list_modules and resolve_modules reuse the same Module values when the inputs are the same.

Ref astral-sh/ruff#19883 (comment)

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters

[ Show hidden characters]({{ revealButtonHref }})