[ty] Add special-cased inference for __import__(name) and importlib.import_module(name) (#19008)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / mkdocs (push) Waiting to run
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This commit is contained in:
InSync 2025-06-29 17:49:23 +07:00 committed by GitHub
parent de1f8177be
commit e7aadfc28b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 185 additions and 51 deletions

View file

@ -0,0 +1,91 @@
# `__import__`
The global function `__import__()` allows for dynamic imports.
A few of its call patterns are recognized and resolved to literal module types instead of the
general `ModuleType`, which is used as the fallback for unrecognized call patterns and unresolvable
names.
## Basic
```py
reveal_type(__import__("sys")) # revealed: <module 'sys'>
reveal_type(__import__(name="shutil")) # revealed: <module 'shutil'>
reveal_type(__import__("nonexistent")) # revealed: ModuleType
reveal_type(__import__("collections.abc")) # revealed: ModuleType
reveal_type(__import__("fnmatch", globals())) # revealed: ModuleType
reveal_type(__import__("shelve", fromlist=[""])) # revealed: ModuleType
```
## Unions
The specified name must be a string literal. Different modules must be imported explicitly.
```py
def _(flag: bool):
if flag:
name = "sys"
else:
name = "os"
reveal_type(name) # revealed: Literal["sys", "os"]
reveal_type(__import__(name)) # revealed: ModuleType
if flag:
module = __import__("heapq")
else:
module = __import__("curses")
reveal_type(module) # revealed: <module 'heapq'> | <module 'curses'>
```
## Nested modules
`main.py`:
```py
# TODO: Should be `<module 'a'>`
a = reveal_type(__import__("a.b.c")) # revealed: ModuleType
# TODO: Should be `int`, `str`, `bytes`
# error: [unresolved-attribute]
reveal_type(a.a) # revealed: Unknown
# error: [unresolved-attribute]
reveal_type(a.b.b) # revealed: Unknown
# error: [unresolved-attribute]
reveal_type(a.b.c.c) # revealed: Unknown
```
`a/__init__.py`:
```py
a: int = 1
```
`a/b/__init__.py`:
```py
b: str = ""
```
`a/b/c.py`:
```py
c: bytes = b""
```
## `importlib.import_module()`
`importlib.import_module()` has similar semantics, but returns the submodule.
```py
import importlib
reveal_type(importlib.import_module("bisect")) # revealed: <module 'bisect'>
reveal_type(importlib.import_module("os.path")) # revealed: <module 'os.path'>
reveal_type(importlib.import_module(name="tempfile")) # revealed: <module 'tempfile'>
reveal_type(importlib.import_module("nonexistent")) # revealed: ModuleType
reveal_type(importlib.import_module("config", "logging")) # revealed: ModuleType
```