diff --git a/crates/ty_python_semantic/resources/mdtest/import/basic.md b/crates/ty_python_semantic/resources/mdtest/import/basic.md index 45abcf5018..1220ddc770 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/basic.md +++ b/crates/ty_python_semantic/resources/mdtest/import/basic.md @@ -192,6 +192,26 @@ from string.templatelib import Template # error: [unresolved-import] from importlib.resources import abc # error: [unresolved-import] ``` +## Attempting to import a stdlib submodule when both parts haven't yet been added + +`compression` and `compression.zstd` were both added in 3.14 so there is a typeshed `VERSIONS` entry +for `compression` but not `compression.zstd`. We can't be confident `compression.zstd` exists but we +do know `compression` does and can still give good diagnostics about it. + + + +```toml +[environment] +python-version = "3.10" +``` + +```py +import compression.zstd # error: [unresolved-import] +from compression import zstd # error: [unresolved-import] +import compression.fakebutwhocansay # error: [unresolved-import] +from compression import fakebutwhocansay # error: [unresolved-import] +``` + ## Attempting to import a stdlib module that was previously removed diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import…_(dba22bd97137ee38).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import…_(dba22bd97137ee38).snap new file mode 100644 index 0000000000..cc712bb06e --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/basic.md_-_Structures_-_Attempting_to_import…_(dba22bd97137ee38).snap @@ -0,0 +1,83 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: basic.md - Structures - Attempting to import a stdlib submodule when both parts haven't yet been added +mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | import compression.zstd # error: [unresolved-import] +2 | from compression import zstd # error: [unresolved-import] +3 | import compression.fakebutwhocansay # error: [unresolved-import] +4 | from compression import fakebutwhocansay # error: [unresolved-import] +``` + +# Diagnostics + +``` +error[unresolved-import]: Cannot resolve imported module `compression.zstd` + --> src/mdtest_snippet.py:1:8 + | +1 | import compression.zstd # error: [unresolved-import] + | ^^^^^^^^^^^^^^^^ +2 | from compression import zstd # error: [unresolved-import] +3 | import compression.fakebutwhocansay # error: [unresolved-import] + | +info: The stdlib module `compression` is only available on Python 3.14+ +info: Python 3.10 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Cannot resolve imported module `compression` + --> src/mdtest_snippet.py:2:6 + | +1 | import compression.zstd # error: [unresolved-import] +2 | from compression import zstd # error: [unresolved-import] + | ^^^^^^^^^^^ +3 | import compression.fakebutwhocansay # error: [unresolved-import] +4 | from compression import fakebutwhocansay # error: [unresolved-import] + | +info: The stdlib module `compression` is only available on Python 3.14+ +info: Python 3.10 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Cannot resolve imported module `compression.fakebutwhocansay` + --> src/mdtest_snippet.py:3:8 + | +1 | import compression.zstd # error: [unresolved-import] +2 | from compression import zstd # error: [unresolved-import] +3 | import compression.fakebutwhocansay # error: [unresolved-import] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +4 | from compression import fakebutwhocansay # error: [unresolved-import] + | +info: The stdlib module `compression` is only available on Python 3.14+ +info: Python 3.10 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` + +``` +error[unresolved-import]: Cannot resolve imported module `compression` + --> src/mdtest_snippet.py:4:6 + | +2 | from compression import zstd # error: [unresolved-import] +3 | import compression.fakebutwhocansay # error: [unresolved-import] +4 | from compression import fakebutwhocansay # error: [unresolved-import] + | ^^^^^^^^^^^ + | +info: The stdlib module `compression` is only available on Python 3.14+ +info: Python 3.10 was assumed when resolving modules because it was specified on the command line +info: rule `unresolved-import` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 7155e1f7a1..4b2c7cdc44 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -4790,21 +4790,26 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let program = Program::get(self.db()); let typeshed_versions = program.search_paths(self.db()).typeshed_versions(); - if let Some(version_range) = typeshed_versions.exact(&module_name) { - // We know it is a stdlib module on *some* Python versions... - let python_version = program.python_version(self.db()); - if !version_range.contains(python_version) { - // ...But not on *this* Python version. - diagnostic.info(format_args!( - "The stdlib module `{module_name}` is only available on Python {version_range}", - version_range = version_range.diagnostic_display(), - )); - add_inferred_python_version_hint_to_diagnostic( - self.db(), - &mut diagnostic, - "resolving modules", - ); - return; + // Loop over ancestors in case we have info on the parent module but not submodule + for module_name in module_name.ancestors() { + if let Some(version_range) = typeshed_versions.exact(&module_name) { + // We know it is a stdlib module on *some* Python versions... + let python_version = program.python_version(self.db()); + if !version_range.contains(python_version) { + // ...But not on *this* Python version. + diagnostic.info(format_args!( + "The stdlib module `{module_name}` is only available on Python {version_range}", + version_range = version_range.diagnostic_display(), + )); + add_inferred_python_version_hint_to_diagnostic( + self.db(), + &mut diagnostic, + "resolving modules", + ); + return; + } + // We found the most precise answer we could, stop searching + break; } } }