-
Notifications
You must be signed in to change notification settings - Fork 882
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Prefer
itertools.pairwise()
over zip()
for successive pairs (`RUF…
…007`) (#3501)
- Loading branch information
1 parent
373a77e
commit 33d2457
Showing
9 changed files
with
273 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
input = [1, 2, 3] | ||
otherInput = [2, 3, 4] | ||
|
||
# OK | ||
zip(input, otherInput) # different inputs | ||
zip(input, otherInput[1:]) # different inputs | ||
zip(input, input[2:]) # not successive | ||
zip(input[:-1], input[2:]) # not successive | ||
list(zip(input, otherInput)) # nested call | ||
zip(input, input[1::2]) # not successive | ||
|
||
# Errors | ||
zip(input, input[1:]) | ||
zip(input, input[1::1]) | ||
zip(input[:-1], input[1:]) | ||
zip(input[1:], input[2:]) | ||
zip(input[1:-1], input[2:]) | ||
list(zip(input, input[1:])) | ||
list(zip(input[:-1], input[1:])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
use num_traits::ToPrimitive; | ||
use rustpython_parser::ast::{Constant, Expr, ExprKind, Unaryop}; | ||
|
||
use ruff_diagnostics::{Diagnostic, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
|
||
use crate::checkers::ast::Checker; | ||
use crate::Range; | ||
|
||
#[violation] | ||
pub struct PairwiseOverZipped; | ||
|
||
impl Violation for PairwiseOverZipped { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
format!("Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs") | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
struct SliceInfo { | ||
arg_name: String, | ||
slice_start: Option<i64>, | ||
} | ||
|
||
impl SliceInfo { | ||
pub fn new(arg_name: String, slice_start: Option<i64>) -> Self { | ||
Self { | ||
arg_name, | ||
slice_start, | ||
} | ||
} | ||
} | ||
|
||
/// Return the argument name, lower bound, and upper bound for an expression, if it's a slice. | ||
fn match_slice_info(expr: &Expr) -> Option<SliceInfo> { | ||
let ExprKind::Subscript { value, slice, .. } = &expr.node else { | ||
return None; | ||
}; | ||
|
||
let ExprKind::Name { id: arg_id, .. } = &value.node else { | ||
return None; | ||
}; | ||
|
||
let ExprKind::Slice { lower, step, .. } = &slice.node else { | ||
return None; | ||
}; | ||
|
||
// Avoid false positives for slices with a step. | ||
if let Some(step) = step { | ||
if let Some(step) = to_bound(step) { | ||
if step != 1 { | ||
return None; | ||
} | ||
} else { | ||
return None; | ||
} | ||
} | ||
|
||
Some(SliceInfo::new( | ||
arg_id.to_string(), | ||
lower.as_ref().and_then(|expr| to_bound(expr)), | ||
)) | ||
} | ||
|
||
fn to_bound(expr: &Expr) -> Option<i64> { | ||
match &expr.node { | ||
ExprKind::Constant { | ||
value: Constant::Int(value), | ||
.. | ||
} => value.to_i64(), | ||
ExprKind::UnaryOp { | ||
op: Unaryop::USub | Unaryop::Invert, | ||
operand, | ||
} => { | ||
if let ExprKind::Constant { | ||
value: Constant::Int(value), | ||
.. | ||
} = &operand.node | ||
{ | ||
value.to_i64().map(|v| -v) | ||
} else { | ||
None | ||
} | ||
} | ||
_ => None, | ||
} | ||
} | ||
|
||
/// RUF007 | ||
pub fn pairwise_over_zipped(checker: &mut Checker, func: &Expr, args: &[Expr]) { | ||
let ExprKind::Name { id, .. } = &func.node else { | ||
return; | ||
}; | ||
|
||
if !(args.len() > 1 && id == "zip" && checker.ctx.is_builtin(id)) { | ||
return; | ||
}; | ||
|
||
// Allow the first argument to be a `Name` or `Subscript`. | ||
let Some(first_arg_info) = ({ | ||
if let ExprKind::Name { id, .. } = &args[0].node { | ||
Some(SliceInfo::new(id.to_string(), None)) | ||
} else { | ||
match_slice_info(&args[0]) | ||
} | ||
}) else { | ||
return; | ||
}; | ||
|
||
// Require second argument to be a `Subscript`. | ||
let ExprKind::Subscript { .. } = &args[1].node else { | ||
return; | ||
}; | ||
let Some(second_arg_info) = match_slice_info(&args[1]) else { | ||
return; | ||
}; | ||
|
||
// Verify that the arguments match the same name. | ||
if first_arg_info.arg_name != second_arg_info.arg_name { | ||
return; | ||
} | ||
|
||
// Verify that the arguments are successive. | ||
if second_arg_info.slice_start.unwrap_or(0) - first_arg_info.slice_start.unwrap_or(0) != 1 { | ||
return; | ||
} | ||
|
||
checker | ||
.diagnostics | ||
.push(Diagnostic::new(PairwiseOverZipped, Range::from(func))); | ||
} |
96 changes: 96 additions & 0 deletions
96
...es/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
--- | ||
source: crates/ruff/src/rules/ruff/mod.rs | ||
expression: diagnostics | ||
--- | ||
- kind: | ||
name: PairwiseOverZipped | ||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" | ||
suggestion: ~ | ||
fixable: false | ||
location: | ||
row: 13 | ||
column: 0 | ||
end_location: | ||
row: 13 | ||
column: 3 | ||
fix: ~ | ||
parent: ~ | ||
- kind: | ||
name: PairwiseOverZipped | ||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" | ||
suggestion: ~ | ||
fixable: false | ||
location: | ||
row: 14 | ||
column: 0 | ||
end_location: | ||
row: 14 | ||
column: 3 | ||
fix: ~ | ||
parent: ~ | ||
- kind: | ||
name: PairwiseOverZipped | ||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" | ||
suggestion: ~ | ||
fixable: false | ||
location: | ||
row: 15 | ||
column: 0 | ||
end_location: | ||
row: 15 | ||
column: 3 | ||
fix: ~ | ||
parent: ~ | ||
- kind: | ||
name: PairwiseOverZipped | ||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" | ||
suggestion: ~ | ||
fixable: false | ||
location: | ||
row: 16 | ||
column: 0 | ||
end_location: | ||
row: 16 | ||
column: 3 | ||
fix: ~ | ||
parent: ~ | ||
- kind: | ||
name: PairwiseOverZipped | ||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" | ||
suggestion: ~ | ||
fixable: false | ||
location: | ||
row: 17 | ||
column: 0 | ||
end_location: | ||
row: 17 | ||
column: 3 | ||
fix: ~ | ||
parent: ~ | ||
- kind: | ||
name: PairwiseOverZipped | ||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" | ||
suggestion: ~ | ||
fixable: false | ||
location: | ||
row: 18 | ||
column: 5 | ||
end_location: | ||
row: 18 | ||
column: 8 | ||
fix: ~ | ||
parent: ~ | ||
- kind: | ||
name: PairwiseOverZipped | ||
body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" | ||
suggestion: ~ | ||
fixable: false | ||
location: | ||
row: 19 | ||
column: 5 | ||
end_location: | ||
row: 19 | ||
column: 8 | ||
fix: ~ | ||
parent: ~ | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.