-
Notifications
You must be signed in to change notification settings - Fork 897
/
unnecessary_double_cast_or_process.rs
143 lines (135 loc) · 4.43 KB
/
unnecessary_double_cast_or_process.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableKeyword;
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::rules::flake8_comprehensions::fixes;
/// ## What it does
/// Checks for unnecessary `list`, `reversed`, `set`, `sorted`, and `tuple`
/// call within `list`, `set`, `sorted`, and `tuple` calls.
///
/// ## Why is this bad?
/// It's unnecessary to double-cast or double-process iterables by wrapping
/// the listed functions within an additional `list`, `set`, `sorted`, or
/// `tuple` call. Doing so is redundant and can be confusing for readers.
///
/// ## Examples
/// ```python
/// list(tuple(iterable))
/// ```
///
/// Use instead:
/// ```python
/// list(iterable)
/// ```
///
/// This rule applies to a variety of functions, including `list`, `reversed`,
/// `set`, `sorted`, and `tuple`. For example:
///
/// - Instead of `list(list(iterable))`, use `list(iterable)`.
/// - Instead of `list(tuple(iterable))`, use `list(iterable)`.
/// - Instead of `tuple(list(iterable))`, use `tuple(iterable)`.
/// - Instead of `tuple(tuple(iterable))`, use `tuple(iterable)`.
/// - Instead of `set(set(iterable))`, use `set(iterable)`.
/// - Instead of `set(list(iterable))`, use `set(iterable)`.
/// - Instead of `set(tuple(iterable))`, use `set(iterable)`.
/// - Instead of `set(sorted(iterable))`, use `set(iterable)`.
/// - Instead of `set(reversed(iterable))`, use `set(iterable)`.
/// - Instead of `sorted(list(iterable))`, use `sorted(iterable)`.
/// - Instead of `sorted(tuple(iterable))`, use `sorted(iterable)`.
/// - Instead of `sorted(sorted(iterable))`, use `sorted(iterable)`.
/// - Instead of `sorted(reversed(iterable))`, use `sorted(iterable)`.
#[violation]
pub struct UnnecessaryDoubleCastOrProcess {
inner: String,
outer: String,
}
impl AlwaysFixableViolation for UnnecessaryDoubleCastOrProcess {
#[derive_message_formats]
fn message(&self) -> String {
let UnnecessaryDoubleCastOrProcess { inner, outer } = self;
format!("Unnecessary `{inner}` call within `{outer}()`")
}
fn fix_title(&self) -> String {
let UnnecessaryDoubleCastOrProcess { inner, .. } = self;
format!("Remove the inner `{inner}` call")
}
}
/// C414
pub(crate) fn unnecessary_double_cast_or_process(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
outer_kw: &[Keyword],
) {
let Some(outer) = func.as_name_expr() else {
return;
};
if !matches!(
outer.id.as_str(),
"list" | "tuple" | "set" | "reversed" | "sorted"
) {
return;
}
let Some(arg) = args.first() else {
return;
};
let Expr::Call(ast::ExprCall {
func,
arguments: Arguments {
keywords: inner_kw, ..
},
..
}) = arg
else {
return;
};
let Some(inner) = func.as_name_expr() else {
return;
};
if !checker.semantic().is_builtin(&inner.id) || !checker.semantic().is_builtin(&outer.id) {
return;
}
// Avoid collapsing nested `sorted` calls with non-identical keyword arguments
// (i.e., `key`, `reverse`).
if inner.id.as_str() == "sorted" && outer.id.as_str() == "sorted" {
if inner_kw.len() != outer_kw.len() {
return;
}
if !inner_kw.iter().all(|inner| {
outer_kw
.iter()
.any(|outer| ComparableKeyword::from(inner) == ComparableKeyword::from(outer))
}) {
return;
}
}
// Ex) `set(tuple(...))`
// Ex) `list(tuple(...))`
// Ex) `set(set(...))`
if matches!(
(outer.id.as_str(), inner.id.as_str()),
("set" | "sorted", "list" | "tuple" | "reversed" | "sorted")
| ("set", "set")
| ("list" | "tuple", "list" | "tuple")
) {
let mut diagnostic = Diagnostic::new(
UnnecessaryDoubleCastOrProcess {
inner: inner.id.to_string(),
outer: outer.id.to_string(),
},
expr.range(),
);
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_double_cast_or_process(
expr,
checker.locator(),
checker.stylist(),
)
.map(Fix::unsafe_edit)
});
checker.diagnostics.push(diagnostic);
}
}