From 73720c73be981df6f71ce837a67ca1167da0265e Mon Sep 17 00:00:00 2001 From: Renkai Ge Date: Tue, 26 Aug 2025 23:01:16 +0800 Subject: [PATCH] [ty] Add search paths info to unresolved import diagnostics (#20040) Fixes https://github.com/astral-sh/ty/issues/457 --------- Co-authored-by: Alex Waygood --- crates/ty/tests/cli/main.rs | 19 ++++++++++++-- crates/ty/tests/cli/python_environment.rs | 25 +++++++++++++++++++ crates/ty/tests/cli/rule_selection.rs | 10 ++++++-- ...tiple_objects_imp…_(cbfbf5ff94e6e104).snap | 3 +++ ...esolvable_module_…_(846453deaca1071c).snap | 3 +++ ...esolvable_submodu…_(4fad4be9778578b7).snap | 6 +++++ ...n_unresolvable_impo…_(72d090df51ea97b8).snap | 3 +++ ...sing_`from`_with_an…_(9fa713dfa17cc404).snap | 3 +++ .../src/module_resolver/mod.rs | 3 +-- .../src/module_resolver/path.rs | 19 ++++++++++++++ crates/ty_python_semantic/src/types/infer.rs | 24 +++++++++++++++++- 11 files changed, 111 insertions(+), 7 deletions(-) diff --git a/crates/ty/tests/cli/main.rs b/crates/ty/tests/cli/main.rs index 82edcda8c7..f85f294842 100644 --- a/crates/ty/tests/cli/main.rs +++ b/crates/ty/tests/cli/main.rs @@ -263,6 +263,9 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> { 3 | 4 | stat = add(10, 15) | + info: Searched in the following paths during module resolution: + info: 1. / (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 @@ -477,7 +480,7 @@ fn check_specific_paths() -> anyhow::Result<()> { assert_cmd_snapshot!( case.command(), - @r###" + @r" success: false exit_code: 1 ----- stdout ----- @@ -489,6 +492,9 @@ fn check_specific_paths() -> anyhow::Result<()> { 3 | 4 | print(z) | + info: Searched in the following paths during module resolution: + info: 1. / (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 @@ -498,6 +504,9 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | + info: Searched in the following paths during module resolution: + info: 1. / (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 @@ -505,7 +514,7 @@ fn check_specific_paths() -> anyhow::Result<()> { ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "### + " ); // Now check only the `tests` and `other.py` files. @@ -524,6 +533,9 @@ fn check_specific_paths() -> anyhow::Result<()> { 3 | 4 | print(z) | + info: Searched in the following paths during module resolution: + info: 1. / (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 @@ -533,6 +545,9 @@ fn check_specific_paths() -> anyhow::Result<()> { 2 | import does_not_exist # error: unresolved-import | ^^^^^^^^^^^^^^ | + info: Searched in the following paths during module resolution: + info: 1. / (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 diff --git a/crates/ty/tests/cli/python_environment.rs b/crates/ty/tests/cli/python_environment.rs index 3c9714d00e..d20a9c8add 100644 --- a/crates/ty/tests/cli/python_environment.rs +++ b/crates/ty/tests/cli/python_environment.rs @@ -333,6 +333,10 @@ import bar", | ^^^ 2 | import bar | + info: Searched in the following paths during module resolution: + info: 1. / (first-party code) + info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) + info: 3. /strange-venv-location/lib/python3.13/site-packages (site-packages) 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 @@ -378,6 +382,11 @@ fn lib64_site_packages_directory_on_unix() -> anyhow::Result<()> { 1 | import foo, bar, baz | ^^^ | + info: Searched in the following paths during module resolution: + info: 1. / (first-party code) + info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty) + info: 3. /.venv/lib/python3.13/site-packages (site-packages) + info: 4. /.venv/lib64/python3.13/site-packages (site-packages) 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 @@ -1273,6 +1282,9 @@ home = ./ 3 | from package1 import ChildConda 4 | from package1 import WorkingVenv | + info: Searched in the following paths during module resolution: + info: 1. /project (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 @@ -1285,6 +1297,9 @@ home = ./ 4 | from package1 import WorkingVenv 5 | from package1 import BaseConda | + info: Searched in the following paths during module resolution: + info: 1. /project (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 @@ -1297,6 +1312,9 @@ home = ./ | ^^^^^^^^ 5 | from package1 import BaseConda | + info: Searched in the following paths during module resolution: + info: 1. /project (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 @@ -1308,6 +1326,9 @@ home = ./ 5 | from package1 import BaseConda | ^^^^^^^^ | + info: Searched in the following paths during module resolution: + info: 1. /project (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 @@ -1716,6 +1737,10 @@ fn default_root_tests_package() -> anyhow::Result<()> { 4 | 5 | print(f"{foo} {bar}") | + info: Searched in the following paths during module resolution: + info: 1. / (first-party code) + info: 2. /src (first-party code) + info: 3. 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 diff --git a/crates/ty/tests/cli/rule_selection.rs b/crates/ty/tests/cli/rule_selection.rs index 16a1d4eb74..8b5382f564 100644 --- a/crates/ty/tests/cli/rule_selection.rs +++ b/crates/ty/tests/cli/rule_selection.rs @@ -89,7 +89,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { // Assert that there's an `unresolved-reference` diagnostic (error) // and an unresolved-import (error) diagnostic by default. - assert_cmd_snapshot!(case.command(), @r###" + assert_cmd_snapshot!(case.command(), @r" success: false exit_code: 1 ----- stdout ----- @@ -101,6 +101,9 @@ fn cli_rule_severity() -> anyhow::Result<()> { 3 | 4 | y = 4 / 0 | + info: Searched in the following paths during module resolution: + info: 1. / (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 @@ -118,7 +121,7 @@ fn cli_rule_severity() -> anyhow::Result<()> { ----- stderr ----- WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors. - "###); + "); assert_cmd_snapshot!( case @@ -141,6 +144,9 @@ fn cli_rule_severity() -> anyhow::Result<()> { 3 | 4 | y = 4 / 0 | + info: Searched in the following paths during module resolution: + info: 1. / (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` was selected on the command line diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imp…_(cbfbf5ff94e6e104).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imp…_(cbfbf5ff94e6e104).snap index 13b63a659f..79600310ea 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imp…_(cbfbf5ff94e6e104).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Multiple_objects_imp…_(cbfbf5ff94e6e104).snap @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_…_(846453deaca1071c).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_…_(846453deaca1071c).snap index faa9a6155f..af7b769b1f 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_…_(846453deaca1071c).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_module_…_(846453deaca1071c).snap @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodu…_(4fad4be9778578b7).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodu…_(4fad4be9778578b7).snap index 1bbe221bb7..f6ecd692f3 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodu…_(4fad4be9778578b7).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Unresolvable_submodu…_(4fad4be9778578b7).snap @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_An_unresolvable_impo…_(72d090df51ea97b8).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_An_unresolvable_impo…_(72d090df51ea97b8).snap index 633cb50b16..fbaa1abc00 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_An_unresolvable_impo…_(72d090df51ea97b8).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_An_unresolvable_impo…_(72d090df51ea97b8).snap @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_Using_`from`_with_an…_(9fa713dfa17cc404).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_Using_`from`_with_an…_(9fa713dfa17cc404).snap index 0d687bf11c..83f03b7929 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_Using_`from`_with_an…_(9fa713dfa17cc404).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/unresolved_import.md_-_Unresolved_import_di…_-_Using_`from`_with_an…_(9fa713dfa17cc404).snap @@ -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 diff --git a/crates/ty_python_semantic/src/module_resolver/mod.rs b/crates/ty_python_semantic/src/module_resolver/mod.rs index 901adbf4ef..549ffa2c96 100644 --- a/crates/ty_python_semantic/src/module_resolver/mod.rs +++ b/crates/ty_python_semantic/src/module_resolver/mod.rs @@ -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; diff --git a/crates/ty_python_semantic/src/module_resolver/path.rs b/crates/ty_python_semantic/src/module_resolver/path.rs index 9a697f27f1..cb524cb4ac 100644 --- a/crates/ty_python_semantic/src/module_resolver/path.rs +++ b/crates/ty_python_semantic/src/module_resolver/path.rs @@ -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 for SearchPath { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index abb032644e..d76bb6b5c7 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -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",