Add autofix for `PYI055` (#7886)

This commit is contained in:
Steve C 2023-10-12 20:56:34 -04:00 committed by GitHub
parent f08a5f67eb
commit 1e184e69f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 277 additions and 32 deletions

View File

@ -28,4 +28,12 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
def func():
from typing import Union as U
# PYI055
x: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker

View File

@ -21,4 +21,5 @@ item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
def func():
# PYI055
item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker

View File

@ -1,7 +1,8 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ast::{ExprContext, Operator};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Expr;
use ruff_text_size::Ranged;
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::{Ranged, TextRange};
use crate::{checkers::ast::Checker, rules::flake8_pyi::helpers::traverse_union};
@ -9,8 +10,8 @@ use crate::{checkers::ast::Checker, rules::flake8_pyi::helpers::traverse_union};
/// Checks for the presence of multiple `type`s in a union.
///
/// ## Why is this bad?
/// The `type` built-in function accepts unions, and it is
/// clearer to explicitly specify them as a single `type`.
/// The `type` built-in function accepts unions, and it is clearer to
/// explicitly specify them as a single `type`.
///
/// ## Example
/// ```python
@ -28,6 +29,8 @@ pub struct UnnecessaryTypeUnion {
}
impl Violation for UnnecessaryTypeUnion {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let union_str = if self.is_pep604_union {
@ -40,6 +43,23 @@ impl Violation for UnnecessaryTypeUnion {
"Multiple `type` members in a union. Combine them into one, e.g., `type[{union_str}]`."
)
}
fn fix_title(&self) -> Option<String> {
Some(format!("Combine multiple `type` members"))
}
}
fn concatenate_bin_ors(exprs: Vec<&Expr>) -> Expr {
let mut exprs = exprs.into_iter();
let first = exprs.next().unwrap();
exprs.fold((*first).clone(), |acc, expr| {
Expr::BinOp(ast::ExprBinOp {
left: Box::new(acc),
op: Operator::BitOr,
right: Box::new((*expr).clone()),
range: TextRange::default(),
})
})
}
/// PYI055
@ -49,14 +69,17 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
return;
}
let mut type_exprs = Vec::new();
// Check if `union` is a PEP604 union (e.g. `float | int`) or a `typing.Union[float, int]`
let is_pep604_union = !union.as_subscript_expr().is_some_and(|subscript| {
checker
let subscript = union.as_subscript_expr();
if subscript.is_some_and(|subscript| {
!checker
.semantic()
.match_typing_expr(&subscript.value, "Union")
});
}) {
return;
}
let mut type_exprs = Vec::new();
let mut collect_type_exprs = |expr: &'a Expr, _| {
let Some(subscript) = expr.as_subscript_expr() else {
@ -74,15 +97,79 @@ pub(crate) fn unnecessary_type_union<'a>(checker: &mut Checker, union: &'a Expr)
traverse_union(&mut collect_type_exprs, checker.semantic(), union, None);
if type_exprs.len() > 1 {
checker.diagnostics.push(Diagnostic::new(
let type_members: Vec<String> = type_exprs
.clone()
.into_iter()
.map(|type_expr| checker.locator().slice(type_expr.as_ref()).to_string())
.collect();
let mut diagnostic = Diagnostic::new(
UnnecessaryTypeUnion {
members: type_exprs
.into_iter()
.map(|type_expr| checker.locator().slice(type_expr.as_ref()).to_string())
.collect(),
is_pep604_union,
members: type_members.clone(),
is_pep604_union: subscript.is_none(),
},
union.range(),
));
);
if checker.semantic().is_builtin("type") {
let content = if let Some(subscript) = subscript {
checker
.generator()
.expr(&Expr::Subscript(ast::ExprSubscript {
value: Box::new(Expr::Name(ast::ExprName {
id: "type".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
})),
slice: Box::new(Expr::Subscript(ast::ExprSubscript {
value: subscript.value.clone(),
slice: Box::new(Expr::Tuple(ast::ExprTuple {
elts: type_members
.into_iter()
.map(|type_member| {
Expr::Name(ast::ExprName {
id: type_member,
ctx: ExprContext::Load,
range: TextRange::default(),
})
})
.collect(),
ctx: ExprContext::Load,
range: TextRange::default(),
})),
ctx: ExprContext::Load,
range: TextRange::default(),
})),
ctx: ExprContext::Load,
range: TextRange::default(),
}))
} else {
checker
.generator()
.expr(&Expr::Subscript(ast::ExprSubscript {
value: Box::new(Expr::Name(ast::ExprName {
id: "type".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
})),
slice: Box::new(concatenate_bin_ors(
type_exprs
.clone()
.into_iter()
.map(std::convert::AsRef::as_ref)
.collect(),
)),
ctx: ExprContext::Load,
range: TextRange::default(),
}))
};
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
content,
union.range(),
)));
}
checker.diagnostics.push(diagnostic);
}
}

View File

@ -1,12 +1,58 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI055.py:31:11: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
PYI055.py:31:8: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty | str]`.
|
29 | def func():
30 | # PYI055
31 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
31 | x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
32 | y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
= help: Combine multiple `type` members
Fix
28 28 |
29 29 | def func():
30 30 | # PYI055
31 |- x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
31 |+ x: type[requests_mock.Mocker | httpretty | str] = requests_mock.Mocker
32 32 | y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
33 33 |
34 34 |
PYI055.py:32:8: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[requests_mock.Mocker, httpretty, str]]`.
|
30 | # PYI055
31 | x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
32 | y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
= help: Combine multiple `type` members
Fix
29 29 | def func():
30 30 | # PYI055
31 31 | x: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
32 |- y: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
32 |+ y: type[Union[requests_mock.Mocker, httpretty, str]] = requests_mock.Mocker
33 33 |
34 34 |
35 35 | def func():
PYI055.py:39:8: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[requests_mock.Mocker, httpretty, str]]`.
|
38 | # PYI055
39 | x: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
= help: Combine multiple `type` members
Fix
36 36 | from typing import Union as U
37 37 |
38 38 | # PYI055
39 |- x: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
39 |+ x: type[Union[requests_mock.Mocker, httpretty, str]] = requests_mock.Mocker

View File

@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI055.pyi:4: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]`.
|
2 | from typing import Union
3 |
@ -10,8 +10,19 @@ PYI055.pyi:4:4: PYI055 Multiple `type` members in a union. Combine them into one
5 | x: type[int] | type[str] | type[float]
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
|
= help: Combine multiple `type` members
PYI055.pyi:5:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
Fix
1 1 | import builtins
2 2 | from typing import Union
3 3 |
4 |-w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
4 |+w: type[int | str | complex]
5 5 | x: type[int] | type[str] | type[float]
6 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 7 | z: Union[type[float], type[complex]]
PYI055.pyi:5:4: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | float]`.
|
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | x: type[int] | type[str] | type[float]
@ -19,8 +30,19 @@ PYI055.pyi:5:4: PYI055 Multiple `type` members in a union. Combine them into one
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 | z: Union[type[float], type[complex]]
|
= help: Combine multiple `type` members
PYI055.pyi:6:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
Fix
2 2 | from typing import Union
3 3 |
4 4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 |-x: type[int] | type[str] | type[float]
5 |+x: type[int | str | float]
6 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 7 | z: Union[type[float], type[complex]]
8 8 | z: Union[type[float, int], type[complex]]
PYI055.pyi:6:4: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[int | str | complex]`.
|
4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 | x: type[int] | type[str] | type[float]
@ -29,8 +51,19 @@ PYI055.pyi:6:4: PYI055 Multiple `type` members in a union. Combine them into one
7 | z: Union[type[float], type[complex]]
8 | z: Union[type[float, int], type[complex]]
|
= help: Combine multiple `type` members
PYI055.pyi:7:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
Fix
3 3 |
4 4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 5 | x: type[int] | type[str] | type[float]
6 |-y: builtins.type[int] | type[str] | builtins.type[complex]
6 |+y: type[int | str | complex]
7 7 | z: Union[type[float], type[complex]]
8 8 | z: Union[type[float, int], type[complex]]
9 9 |
PYI055.pyi:7:4: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, complex]]`.
|
5 | x: type[int] | type[str] | type[float]
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
@ -38,8 +71,19 @@ PYI055.pyi:7:4: PYI055 Multiple `type` members in a union. Combine them into one
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
8 | z: Union[type[float, int], type[complex]]
|
= help: Combine multiple `type` members
PYI055.pyi:8:4: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
Fix
4 4 | w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
5 5 | x: type[int] | type[str] | type[float]
6 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 |-z: Union[type[float], type[complex]]
7 |+z: type[Union[float, complex]]
8 8 | z: Union[type[float, int], type[complex]]
9 9 |
10 10 | def func(arg: type[int] | str | type[float]) -> None: ...
PYI055.pyi:8:4: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[float, int, complex]]`.
|
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 | z: Union[type[float], type[complex]]
@ -48,8 +92,19 @@ PYI055.pyi:8:4: PYI055 Multiple `type` members in a union. Combine them into one
9 |
10 | def func(arg: type[int] | str | type[float]) -> None: ...
|
= help: Combine multiple `type` members
PYI055.pyi:10:15: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[int | float]`.
Fix
5 5 | x: type[int] | type[str] | type[float]
6 6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 7 | z: Union[type[float], type[complex]]
8 |-z: Union[type[float, int], type[complex]]
8 |+z: type[Union[float, int, complex]]
9 9 |
10 10 | def func(arg: type[int] | str | type[float]) -> None: ...
11 11 |
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 |
@ -58,8 +113,19 @@ PYI055.pyi:10:15: PYI055 Multiple `type` members in a union. Combine them into o
11 |
12 | # OK
|
= help: Combine multiple `type` members
PYI055.pyi:20:7: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
Fix
7 7 | z: Union[type[float], type[complex]]
8 8 | z: Union[type[float, int], type[complex]]
9 9 |
10 |-def func(arg: type[int] | str | type[float]) -> None: ...
10 |+def func(arg: type[int | float]) -> None: ...
11 11 |
12 12 | # OK
13 13 | x: type[int, str, float]
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
@ -67,13 +133,50 @@ PYI055.pyi:20:7: PYI055 Multiple `type` members in a union. Combine them into on
21 |
22 | def func():
|
= help: Combine multiple `type` members
PYI055.pyi:24:11: PYI055 Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty]`.
Fix
17 17 | def func(arg: type[int, float] | str) -> None: ...
18 18 |
19 19 | # OK
20 |-item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
20 |+item: type[requests_mock.Mocker | httpretty] = requests_mock.Mocker
21 21 |
22 22 | def func():
23 23 | # PYI055
PYI055.pyi:24:11: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[requests_mock.Mocker | httpretty | str]`.
|
22 | def func():
23 | # PYI055
24 | item: type[requests_mock.Mocker] | type[httpretty] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
24 | item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
25 | item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
= help: Combine multiple `type` members
Fix
21 21 |
22 22 | def func():
23 23 | # PYI055
24 |- item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
24 |+ item: type[requests_mock.Mocker | httpretty | str] = requests_mock.Mocker
25 25 | item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
PYI055.pyi:25:12: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[requests_mock.Mocker, httpretty, str]]`.
|
23 | # PYI055
24 | item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
25 | item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
= help: Combine multiple `type` members
Fix
22 22 | def func():
23 23 | # PYI055
24 24 | item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
25 |- item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
25 |+ item2: type[Union[requests_mock.Mocker, httpretty, str]] = requests_mock.Mocker