mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-18 11:41:21 +00:00
[ty] Add narrowing for isinstance() and issubclass() checks that use PEP-604 unions (#21334)
Some checks failed
CI / Determine changes (push) Has been cancelled
CI / cargo fmt (push) Has been cancelled
CI / python package (push) Has been cancelled
CI / pre-commit (push) Has been cancelled
CI / mkdocs (push) Has been cancelled
[ty Playground] Release / publish (push) Has been cancelled
CI / cargo clippy (push) Has been cancelled
CI / cargo test (linux) (push) Has been cancelled
CI / cargo test (linux, release) (push) Has been cancelled
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Has been cancelled
CI / cargo test (macos-latest) (push) Has been cancelled
CI / test ruff-lsp (push) Has been cancelled
CI / cargo test (wasm) (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / cargo fuzz build (push) Has been cancelled
CI / fuzz parser (push) Has been cancelled
CI / test scripts (push) Has been cancelled
CI / ecosystem (push) Has been cancelled
CI / Fuzz for new ty panics (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / ty completion evaluation (push) Has been cancelled
CI / formatter instabilities and black similarity (push) Has been cancelled
CI / check playground (push) Has been cancelled
CI / benchmarks instrumented (ruff) (push) Has been cancelled
CI / benchmarks instrumented (ty) (push) Has been cancelled
CI / benchmarks walltime (medium|multithreaded) (push) Has been cancelled
CI / benchmarks walltime (small|large) (push) Has been cancelled
Some checks failed
CI / Determine changes (push) Has been cancelled
CI / cargo fmt (push) Has been cancelled
CI / python package (push) Has been cancelled
CI / pre-commit (push) Has been cancelled
CI / mkdocs (push) Has been cancelled
[ty Playground] Release / publish (push) Has been cancelled
CI / cargo clippy (push) Has been cancelled
CI / cargo test (linux) (push) Has been cancelled
CI / cargo test (linux, release) (push) Has been cancelled
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Has been cancelled
CI / cargo test (macos-latest) (push) Has been cancelled
CI / test ruff-lsp (push) Has been cancelled
CI / cargo test (wasm) (push) Has been cancelled
CI / cargo build (msrv) (push) Has been cancelled
CI / cargo fuzz build (push) Has been cancelled
CI / fuzz parser (push) Has been cancelled
CI / test scripts (push) Has been cancelled
CI / ecosystem (push) Has been cancelled
CI / Fuzz for new ty panics (push) Has been cancelled
CI / cargo shear (push) Has been cancelled
CI / ty completion evaluation (push) Has been cancelled
CI / formatter instabilities and black similarity (push) Has been cancelled
CI / check playground (push) Has been cancelled
CI / benchmarks instrumented (ruff) (push) Has been cancelled
CI / benchmarks instrumented (ty) (push) Has been cancelled
CI / benchmarks walltime (medium|multithreaded) (push) Has been cancelled
CI / benchmarks walltime (small|large) (push) Has been cancelled
This commit is contained in:
parent
09e6af16c8
commit
020ff1723b
3 changed files with 156 additions and 3 deletions
|
|
@ -70,6 +70,74 @@ def _(flag: bool):
|
|||
reveal_type(x) # revealed: Literal["a"]
|
||||
```
|
||||
|
||||
## `classinfo` is a PEP-604 union of types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
def _(x: int | str | bytes | memoryview | range):
|
||||
if isinstance(x, int | str):
|
||||
reveal_type(x) # revealed: int | str
|
||||
elif isinstance(x, bytes | memoryview):
|
||||
reveal_type(x) # revealed: bytes | memoryview[Unknown]
|
||||
else:
|
||||
reveal_type(x) # revealed: range
|
||||
```
|
||||
|
||||
Although `isinstance()` usually only works if all elements in the `UnionType` are class objects, at
|
||||
runtime a special exception is made for `None` so that `isinstance(x, int | None)` can work:
|
||||
|
||||
```py
|
||||
def _(x: int | str | bytes | range | None):
|
||||
if isinstance(x, int | str | None):
|
||||
reveal_type(x) # revealed: int | str | None
|
||||
else:
|
||||
reveal_type(x) # revealed: bytes | range
|
||||
```
|
||||
|
||||
## `classinfo` is an invalid PEP-604 union of types
|
||||
|
||||
Except for the `None` special case mentioned above, narrowing can only take place if all elements in
|
||||
the PEP-604 union are class literals. If any elements are generic aliases or other types, the
|
||||
`isinstance()` call may fail at runtime, so no narrowing can take place:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
def _(x: int | list[int] | bytes):
|
||||
# TODO: this fails at runtime; we should emit a diagnostic
|
||||
# (requires special-casing of the `isinstance()` signature)
|
||||
if isinstance(x, int | list[int]):
|
||||
reveal_type(x) # revealed: int | list[int] | bytes
|
||||
else:
|
||||
reveal_type(x) # revealed: int | list[int] | bytes
|
||||
```
|
||||
|
||||
## PEP-604 unions on Python \<3.10
|
||||
|
||||
PEP-604 unions were added in Python 3.10, so attempting to use them on Python 3.9 does not lead to
|
||||
any type narrowing.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
def _(x: int | str | bytes):
|
||||
# error: [unsupported-operator]
|
||||
if isinstance(x, int | str):
|
||||
reveal_type(x) # revealed: (int & Unknown) | (str & Unknown) | (bytes & Unknown)
|
||||
else:
|
||||
reveal_type(x) # revealed: (int & Unknown) | (str & Unknown) | (bytes & Unknown)
|
||||
```
|
||||
|
||||
## Class types
|
||||
|
||||
```py
|
||||
|
|
|
|||
|
|
@ -131,6 +131,74 @@ def _(flag1: bool, flag2: bool):
|
|||
reveal_type(t) # revealed: <class 'str'>
|
||||
```
|
||||
|
||||
## `classinfo` is a PEP-604 union of types
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
def f(x: type[int | str | bytes | range]):
|
||||
if issubclass(x, int | str):
|
||||
reveal_type(x) # revealed: type[int] | type[str]
|
||||
elif issubclass(x, bytes | memoryview):
|
||||
reveal_type(x) # revealed: type[bytes]
|
||||
else:
|
||||
reveal_type(x) # revealed: <class 'range'>
|
||||
```
|
||||
|
||||
Although `issubclass()` usually only works if all elements in the `UnionType` are class objects, at
|
||||
runtime a special exception is made for `None` so that `issubclass(x, int | None)` can work:
|
||||
|
||||
```py
|
||||
def _(x: type):
|
||||
if issubclass(x, int | str | None):
|
||||
reveal_type(x) # revealed: type[int] | type[str] | <class 'NoneType'>
|
||||
else:
|
||||
reveal_type(x) # revealed: type & ~type[int] & ~type[str] & ~<class 'NoneType'>
|
||||
```
|
||||
|
||||
## `classinfo` is an invalid PEP-604 union of types
|
||||
|
||||
Except for the `None` special case mentioned above, narrowing can only take place if all elements in
|
||||
the PEP-604 union are class literals. If any elements are generic aliases or other types, the
|
||||
`issubclass()` call may fail at runtime, so no narrowing can take place:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
def _(x: type[int | list | bytes]):
|
||||
# TODO: this fails at runtime; we should emit a diagnostic
|
||||
# (requires special-casing of the `issubclass()` signature)
|
||||
if issubclass(x, int | list[int]):
|
||||
reveal_type(x) # revealed: type[int] | type[list[Unknown]] | type[bytes]
|
||||
else:
|
||||
reveal_type(x) # revealed: type[int] | type[list[Unknown]] | type[bytes]
|
||||
```
|
||||
|
||||
## PEP-604 unions on Python \<3.10
|
||||
|
||||
PEP-604 unions were added in Python 3.10, so attempting to use them on Python 3.9 does not lead to
|
||||
any type narrowing.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
def _(x: type[int | str | bytes]):
|
||||
# error: [unsupported-operator]
|
||||
if issubclass(x, int | str):
|
||||
reveal_type(x) # revealed: (type[int] & Unknown) | (type[str] & Unknown) | (type[bytes] & Unknown)
|
||||
else:
|
||||
reveal_type(x) # revealed: (type[int] & Unknown) | (type[str] & Unknown) | (type[bytes] & Unknown)
|
||||
```
|
||||
|
||||
## Special cases
|
||||
|
||||
### Emit a diagnostic if the first argument is of wrong type
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue