Avoid applying `PYI055` to runtime-evaluated annotations (#6457)

## Summary

The use of `|` as a union operator is not always safe, if a type
annotation is evaluated in a runtime context. For example, this code
errors at runtime:

```python
import httpretty
import requests_mock

item: type[requests_mock.Mocker | httpretty] = requests_mock.Mocker
```

However, it's fine in a `.pyi` file, with `__future__` annotations`, or
if the annotation is in a non-evaluated context, like:

```python
def func():
    item: type[requests_mock.Mocker | httpretty] = requests_mock.Mocker
```

This PR modifies the rule to avoid enforcing in those invalid,
runtime-evaluated contexts.

Closes https://github.com/astral-sh/ruff/issues/6455.
This commit is contained in:
Charlie Marsh 2023-08-09 16:46:41 -04:00 committed by GitHub
parent 395bb31247
commit 627f475b91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 87 deletions

View File

@ -1,7 +1,6 @@
import builtins import builtins
from typing import Union from typing import Union
w: builtins.type[int] | builtins.type[str] | builtins.type[complex] w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float] x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex] y: builtins.type[int] | type[str] | builtins.type[complex]
@ -9,7 +8,9 @@ z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]] z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None: ... def func(arg: type[int] | str | type[float]) -> None:
...
# OK # OK
x: type[int, str, float] x: type[int, str, float]
@ -17,4 +18,14 @@ y: builtins.type[int, str, complex]
z: Union[float, complex] z: Union[float, complex]
def func(arg: type[int, float] | str) -> None: ... def func(arg: type[int, float] | str) -> None:
...
# OK
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker

View File

@ -1,14 +1,12 @@
import builtins import builtins
from typing import Union from typing import Union
w: builtins.type[int] | builtins.type[str] | builtins.type[complex] w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float] x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex] y: builtins.type[int] | type[str] | builtins.type[complex]
z: Union[type[float], type[complex]] z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]] z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None: ... def func(arg: type[int] | str | type[float]) -> None: ...
# OK # OK
@ -16,5 +14,11 @@ x: type[int, str, float]
y: builtins.type[int, str, complex] y: builtins.type[int, str, complex]
z: Union[float, complex] z: Union[float, complex]
def func(arg: type[int, float] | str) -> None: ... def func(arg: type[int, float] | str) -> None: ...
# OK
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker

View File

@ -43,6 +43,11 @@ impl Violation for UnnecessaryTypeUnion {
/// PYI055 /// PYI055
pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr) { pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr) {
// The `|` operator isn't always safe to allow to runtime-evaluated annotations.
if checker.semantic().execution_context().is_runtime() {
return;
}
let mut type_exprs = Vec::new(); let mut type_exprs = Vec::new();
// Check if `union` is a PEP604 union (e.g. `float | int`) or a `typing.Union[float, int]` // Check if `union` is a PEP604 union (e.g. `float | int`) or a `typing.Union[float, int]`

View File

@ -1,56 +1,12 @@
--- ---
source: crates/ruff/src/rules/flake8_pyi/mod.rs source: crates/ruff/src/rules/flake8_pyi/mod.rs
--- ---
PYI055.py:5:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`. PYI055.py:31:11: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
|
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
6 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
PYI055.py:6:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
|
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
6 | x: type[int] | type[str] | type[float]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
8 | z: Union[type[float], type[complex]]
|
PYI055.py:7:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
6 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
8 | z: Union[type[float], type[complex]]
9 | z: Union[type[float, int], type[complex]]
|
PYI055.py:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
|
6 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
8 | z: Union[type[float], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
9 | z: Union[type[float, int], type[complex]]
|
PYI055.py:9:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
8 | z: Union[type[float], type[complex]]
9 | z: Union[type[float, int], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
PYI055.py:12:15: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
| |
12 | def func(arg: type[int] | str | type[float]) -> None: ... 29 | def func():
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 30 | # PYI055
13 | 31 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
14 | # OK | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
| |

View File

@ -1,56 +1,79 @@
--- ---
source: crates/ruff/src/rules/flake8_pyi/mod.rs source: crates/ruff/src/rules/flake8_pyi/mod.rs
--- ---
PYI055.pyi:5:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`. PYI055.pyi:4:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
| |
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex] 2 | from typing import Union
3 |
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
6 | x: type[int] | type[str] | type[float] 5 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex] 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
| |
PYI055.pyi:6:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`. PYI055.pyi:5:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
| |
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex] 4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
6 | x: type[int] | type[str] | type[float] 5 | x: type[int] | type[str] | type[float]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
7 | y: builtins.type[int] | type[str] | builtins.type[complex] 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
8 | z: Union[type[float], type[complex]] 7 | z: Union[type[float], type[complex]]
| |
PYI055.pyi:7:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`. PYI055.pyi:6:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
| |
5 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex] 4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
6 | x: type[int] | type[str] | type[float] 5 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex] 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
8 | z: Union[type[float], type[complex]] 7 | z: Union[type[float], type[complex]]
9 | z: Union[type[float, int], type[complex]] 8 | z: Union[type[float, int], type[complex]]
| |
PYI055.pyi:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`. PYI055.pyi:7:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
| |
6 | x: type[int] | type[str] | type[float] 5 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex] 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
8 | z: Union[type[float], type[complex]] 7 | z: Union[type[float], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
9 | z: Union[type[float, int], type[complex]] 8 | z: Union[type[float, int], type[complex]]
| |
PYI055.pyi:9:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`. PYI055.pyi:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
8 | z: Union[type[float], type[complex]]
9 | z: Union[type[float, int], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
PYI055.pyi:12:15: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
| |
12 | def func(arg: type[int] | str | type[float]) -> None: ... 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 | z: Union[type[float], type[complex]]
8 | z: Union[type[float, int], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
9 |
10 | def func(arg: type[int] | str | type[float]) -> None: ...
|
PYI055.pyi:10:15: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
|
8 | z: Union[type[float, int], type[complex]]
9 |
10 | def func(arg: type[int] | str | type[float]) -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
13 | 11 |
14 | # OK 12 | # OK
|
PYI055.pyi:20:7: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
|
19 | # OK
20 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
21 |
22 | def func():
|
PYI055.pyi:24:11: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
|
22 | def func():
23 | # PYI055
24 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
| |