Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add autofix for PYI055 #7886

Merged
merged 5 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ def func(arg: type[int, float] | str) -> None:

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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ast::{ExprContext, Operator};
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
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};
use crate::{checkers::ast::Checker, registry::AsRule, rules::flake8_pyi::helpers::traverse_union};

/// ## What it does
/// Checks for the presence of multiple `type`s in a union.
Expand All @@ -27,7 +28,7 @@ pub struct UnnecessaryTypeUnion {
is_pep604_union: bool,
}

impl Violation for UnnecessaryTypeUnion {
impl AlwaysFixableViolation for UnnecessaryTypeUnion {
#[derive_message_formats]
fn message(&self) -> String {
let union_str = if self.is_pep604_union {
Expand All @@ -40,6 +41,23 @@ impl Violation for UnnecessaryTypeUnion {
"Multiple `type` members in a union. Combine them into one, e.g., `type[{union_str}]`."
)
}

fn fix_title(&self) -> String {
format!("Combine multiple `type` members into one union")
}
}

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
Expand Down Expand Up @@ -74,15 +92,83 @@ 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(),
members: type_members.clone(),
is_pep604_union,
},
union.range(),
));
);

if checker.patch(diagnostic.kind.rule()) {
let union_str = if is_pep604_union {
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(),
}))
} 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(Expr::Subscript(ast::ExprSubscript {
value: Box::new(Expr::Name(ast::ExprName {
id: "Union".into(),
ctx: ExprContext::Load,
range: TextRange::default(),
})),
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(),
}))
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a shorthand for any of this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's bad 😞

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is NOT what I wanted to hear! 🤣 Thanks anyways.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully clear that I mean the abstractions are bad, and not your code :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤣 of course.

...but why not both?


diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
union_str,
union.range(),
)));
}

checker.diagnostics.push(diagnostic);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
---
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:11: 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 | item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
32 | item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
|
= help: Combine multiple `type` members into one union

ℹ Fix
28 28 |
29 29 | def func():
30 30 | # PYI055
31 |- item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
31 |+ item: type[requests_mock.Mocker | httpretty | str] = requests_mock.Mocker
32 32 | item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker

PYI055.py:32:12: PYI055 [*] Multiple `type` members in a union. Combine them into one, e.g., `type[Union[requests_mock.Mocker, httpretty, str]]`.
|
30 | # PYI055
31 | item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
32 | item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
|
= help: Combine multiple `type` members into one union

ℹ Fix
29 29 | def func():
30 30 | # PYI055
31 31 | item: type[requests_mock.Mocker] | type[httpretty] | type[str] = requests_mock.Mocker
32 |- item2: Union[type[requests_mock.Mocker], type[httpretty], type[str]] = requests_mock.Mocker
32 |+ item2: type[Union[requests_mock.Mocker, httpretty, str]] = requests_mock.Mocker


Original file line number Diff line number Diff line change
@@ -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 |
Expand All @@ -10,17 +10,39 @@ 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 into one union

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]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
6 | y: builtins.type[int] | type[str] | builtins.type[complex]
7 | z: Union[type[float], type[complex]]
|
= help: Combine multiple `type` members into one union

ℹ 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]`.
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]
Expand All @@ -29,17 +51,39 @@ 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 into one union

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]
7 | z: Union[type[float], type[complex]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI055
8 | z: Union[type[float, int], type[complex]]
|
= help: Combine multiple `type` members into one union

ℹ 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]]`.
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]]
Expand All @@ -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 into one union

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 |
Expand All @@ -58,22 +113,70 @@ PYI055.pyi:10:15: PYI055 Multiple `type` members in a union. Combine them into o
11 |
12 | # OK
|
= help: Combine multiple `type` members into one union

ℹ 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]`.
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():
|
= help: Combine multiple `type` members into one union

ℹ 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]`.
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 into one union

ℹ 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 into one union

ℹ 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


Loading