[ty] Add search paths info to unresolved import diagnostics (#20040)

Fixes https://github.com/astral-sh/ty/issues/457

---------

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
Renkai Ge 2025-08-26 23:01:16 +08:00 committed by GitHub
parent 136abace92
commit 73720c73be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 111 additions and 7 deletions

View file

@ -26,6 +26,9 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist`
2 | from does_not_exist import foo, bar, baz
| ^^^^^^^^^^^^^^
|
info: Searched in the following paths during module resolution:
info: 1. /src (first-party code)
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

View file

@ -24,6 +24,9 @@ error[unresolved-import]: Cannot resolve imported module `zqzqzqzqzqzqzq`
1 | import zqzqzqzqzqzqzq # error: [unresolved-import] "Cannot resolve imported module `zqzqzqzqzqzqzq`"
| ^^^^^^^^^^^^^^
|
info: Searched in the following paths during module resolution:
info: 1. /src (first-party code)
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

View file

@ -36,6 +36,9 @@ error[unresolved-import]: Cannot resolve imported module `a.foo`
3 |
4 | # Topmost component unresolvable:
|
info: Searched in the following paths during module resolution:
info: 1. /src (first-party code)
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default
@ -49,6 +52,9 @@ error[unresolved-import]: Cannot resolve imported module `b.foo`
5 | import b.foo # error: [unresolved-import] "Cannot resolve imported module `b.foo`"
| ^^^^^
|
info: Searched in the following paths during module resolution:
info: 1. /src (first-party code)
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

View file

@ -28,6 +28,9 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist`
2 |
3 | x = does_not_exist.foo
|
info: Searched in the following paths during module resolution:
info: 1. /src (first-party code)
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

View file

@ -28,6 +28,9 @@ error[unresolved-import]: Cannot resolve imported module `does_not_exist`
2 |
3 | stat = add(10, 15)
|
info: Searched in the following paths during module resolution:
info: 1. /src (first-party code)
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

View file

@ -10,8 +10,7 @@ pub use resolver::{resolve_module, resolve_real_module};
use ruff_db::system::SystemPath;
use crate::Db;
use crate::module_resolver::resolver::{ModuleResolveMode, search_paths};
use resolver::SearchPathIterator;
pub(crate) use resolver::{ModuleResolveMode, SearchPathIterator, search_paths};
mod list;
mod module;

View file

@ -700,6 +700,25 @@ impl SearchPath {
SearchPathInner::StandardLibraryVendored(_) => "std-vendored",
}
}
/// Returns a string suitable for describing what kind of search path this is
/// in user-facing diagnostics.
#[must_use]
pub(crate) fn describe_kind(&self) -> &'static str {
match *self.0 {
SearchPathInner::Extra(_) => {
"extra search path specified on the CLI or in your config file"
}
SearchPathInner::FirstParty(_) => "first-party code",
SearchPathInner::StandardLibraryCustom(_) => {
"custom stdlib stubs specified on the CLI or in your config file"
}
SearchPathInner::StandardLibraryReal(_) => "runtime stdlib source code",
SearchPathInner::SitePackages(_) => "site-packages",
SearchPathInner::Editable(_) => "editable install",
SearchPathInner::StandardLibraryVendored(_) => "stdlib typeshed stubs vendored by ty",
}
}
}
impl PartialEq<SystemPath> for SearchPath {

View file

@ -64,7 +64,9 @@ use super::string_annotation::{
use super::subclass_of::SubclassOfInner;
use super::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
use crate::module_name::{ModuleName, ModuleNameResolutionError};
use crate::module_resolver::{KnownModule, file_to_module, resolve_module};
use crate::module_resolver::{
KnownModule, ModuleResolveMode, file_to_module, resolve_module, search_paths,
};
use crate::node_key::NodeKey;
use crate::place::{
Boundness, ConsideredDefinitions, LookupError, Place, PlaceAndQualifiers,
@ -4983,6 +4985,26 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
// Add search paths information to the diagnostic
// Use the same search paths function that is used in actual module resolution
let mut search_paths =
search_paths(self.db(), ModuleResolveMode::StubsAllowed).peekable();
if search_paths.peek().is_some() {
diagnostic.info(format_args!(
"Searched in the following paths during module resolution:"
));
for (index, path) in search_paths.enumerate() {
diagnostic.info(format_args!(
" {}. {} ({})",
index + 1,
path,
path.describe_kind()
));
}
}
diagnostic.info(
"make sure your Python environment is properly configured: \
https://docs.astral.sh/ty/modules/#python-environment",