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
from typing import Union
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float]
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]]
def func(arg: type[int] | str | type[float]) -> None: ...
def func(arg: type[int] | str | type[float]) -> None:
...
# OK
x: type[int, str, float]
@ -17,4 +18,14 @@ y: builtins.type[int, str, 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
from typing import Union
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex]
z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None: ...
# OK
@ -16,5 +14,11 @@ x: type[int, str, float]
y: builtins.type[int, str, complex]
z: Union[float, complex]
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
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();
// 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
---
PYI055.py:5: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]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]`.
PYI055.py:31:11: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
|
12 | def func(arg: type[int] | str | type[float]) -> None: ...
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
13 |
14 | # OK
29 | def func():
30 | # PYI055
31 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|

View file

@ -1,56 +1,79 @@
---
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
6 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
5 | x: type[int] | type[str] | type[float]
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]
6 | x: type[int] | type[str] | type[float]
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | 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]]
6 | y: builtins.type[int] | type[str] | builtins.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]
6 | x: type[int] | type[str] | type[float]
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | x: type[int] | type[str] | type[float]
6 | 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]]
7 | z: Union[type[float], 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]
7 | y: builtins.type[int] | type[str] | builtins.type[complex]
8 | z: Union[type[float], type[complex]]
5 | x: type[int] | type[str] | type[float]
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 | z: Union[type[float], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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]]`.
|
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]`.
PYI055.pyi:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
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
13 |
14 | # OK
11 |
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
|