Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/import/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,18 @@ python-version = "3.13"
import aifc # error: [unresolved-import]
from distutils import sysconfig # error: [unresolved-import]
```

## Cannot shadow core standard library modules

`types.py`:

```py
x: int
```

```py
# error: [unresolved-import]
from types import x

from types import FunctionType
```
16 changes: 13 additions & 3 deletions crates/ty_python_semantic/src/module_resolver/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,14 +535,23 @@ struct ModuleNameIngredient<'db> {
pub(super) name: ModuleName,
}

/// Returns `true` if the module name refers to a standard library module which can't be shadowed
/// by a first-party module.
///
/// This includes "builtin" modules, which can never be shadowed at runtime either, as well as the
/// `types` module, which tends to be imported early in Python startup, so can't be consistently
/// shadowed, and is important to type checking.
fn is_non_shadowable(minor_version: u8, module_name: &str) -> bool {
module_name == "types" || ruff_python_stdlib::sys::is_builtin_module(minor_version, module_name)
}

/// Given a module name and a list of search paths in which to lookup modules,
/// attempt to resolve the module name
fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<ResolvedName> {
let program = Program::get(db);
let python_version = program.python_version(db);
let resolver_state = ResolverContext::new(db, python_version);
let is_builtin_module =
ruff_python_stdlib::sys::is_builtin_module(python_version.minor, name.as_str());
let is_non_shadowable = is_non_shadowable(python_version.minor, name.as_str());

let name = RelaxedModuleName::new(name);
let stub_name = name.to_stub_package();
Expand All @@ -553,7 +562,8 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<ResolvedName> {
// the module name always resolves to the stdlib module,
// even if there's a module of the same name in the first-party root
// (which would normally result in the stdlib module being overridden).
if is_builtin_module && !search_path.is_standard_library() {
// TODO: offer a diagnostic if there is a first-party module of the same name
if is_non_shadowable && !search_path.is_standard_library() {
continue;
}

Expand Down
7 changes: 0 additions & 7 deletions crates/ty_python_semantic/tests/corpus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,6 @@ fn run_corpus_tests(pattern: &str) -> anyhow::Result<()> {
.with_context(|| format!("Failed to read test file: {path}"))?;

let mut check_with_file_name = |path: &SystemPath| {
if relative_path.file_name() == Some("types.pyi") {
println!(
"Skipping {relative_path:?}: paths with `types.pyi` as their final segment cause a stack overflow"
);
return;
}

db.memory_file_system().write_file_all(path, &code).unwrap();
File::sync_path(&mut db, path);

Expand Down
Loading