-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary Implement [`no-slice-copy`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/no_slice_copy.py) as `slice-copy` (`FURB145`). Related to #1348. ## Test Plan `cargo test`
- Loading branch information
Showing
13 changed files
with
325 additions
and
40 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,21 @@ | ||
l = [1, 2, 3, 4, 5] | ||
|
||
# Errors. | ||
a = l[:] | ||
b, c = 1, l[:] | ||
d, e = l[:], 1 | ||
m = l[::] | ||
l[:] | ||
print(l[:]) | ||
|
||
# False negatives. | ||
aa = a[:] # Type inference. | ||
|
||
# OK. | ||
t = (1, 2, 3, 4, 5) | ||
f = t[:] # t.copy() is not supported. | ||
g = l[1:3] | ||
h = l[1:] | ||
i = l[:3] | ||
j = l[1:3:2] | ||
k = l[::2] |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
use ruff_python_ast as ast; | ||
use ruff_python_codegen::Generator; | ||
use ruff_text_size::TextRange; | ||
|
||
/// Format a code snippet to call `name.method()`. | ||
pub(super) fn generate_method_call(name: &str, method: &str, generator: Generator) -> String { | ||
// Construct `name`. | ||
let var = ast::ExprName { | ||
id: name.to_string(), | ||
ctx: ast::ExprContext::Load, | ||
range: TextRange::default(), | ||
}; | ||
// Construct `name.method`. | ||
let attr = ast::ExprAttribute { | ||
value: Box::new(var.into()), | ||
attr: ast::Identifier::new(method.to_string(), TextRange::default()), | ||
ctx: ast::ExprContext::Load, | ||
range: TextRange::default(), | ||
}; | ||
// Make it into a call `name.method()` | ||
let call = ast::ExprCall { | ||
func: Box::new(attr.into()), | ||
arguments: ast::Arguments { | ||
args: vec![], | ||
keywords: vec![], | ||
range: TextRange::default(), | ||
}, | ||
range: TextRange::default(), | ||
}; | ||
// And finally, turn it into a statement. | ||
let stmt = ast::StmtExpr { | ||
value: Box::new(call.into()), | ||
range: TextRange::default(), | ||
}; | ||
generator.stmt(&stmt.into()) | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
pub(crate) use check_and_remove_from_set::*; | ||
pub(crate) use delete_full_slice::*; | ||
pub(crate) use repeated_append::*; | ||
pub(crate) use slice_copy::*; | ||
|
||
mod check_and_remove_from_set; | ||
mod delete_full_slice; | ||
mod repeated_append; | ||
mod slice_copy; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::{self as ast, Expr}; | ||
use ruff_python_semantic::analyze::typing::is_list; | ||
use ruff_python_semantic::{Binding, SemanticModel}; | ||
use ruff_text_size::Ranged; | ||
|
||
use crate::checkers::ast::Checker; | ||
use crate::registry::AsRule; | ||
use crate::rules::refurb::helpers::generate_method_call; | ||
|
||
/// ## What it does | ||
/// Checks for unbounded slice expressions to copy a list. | ||
/// | ||
/// ## Why is this bad? | ||
/// The `list#copy` method is more readable and consistent with copying other | ||
/// types. | ||
/// | ||
/// ## Known problems | ||
/// This rule is prone to false negatives due to type inference limitations, | ||
/// as it will only detect lists that are instantiated as literals or annotated | ||
/// with a type annotation. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// a = [1, 2, 3] | ||
/// b = a[:] | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// a = [1, 2, 3] | ||
/// b = a.copy() | ||
/// ``` | ||
/// | ||
/// ## References | ||
/// - [Python documentation: Mutable Sequence Types](https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types) | ||
#[violation] | ||
pub struct SliceCopy; | ||
|
||
impl Violation for SliceCopy { | ||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes; | ||
|
||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
format!("Prefer `copy` method over slicing") | ||
} | ||
|
||
fn autofix_title(&self) -> Option<String> { | ||
Some("Replace with `copy()`".to_string()) | ||
} | ||
} | ||
|
||
/// FURB145 | ||
pub(crate) fn slice_copy(checker: &mut Checker, subscript: &ast::ExprSubscript) { | ||
if subscript.ctx.is_store() || subscript.ctx.is_del() { | ||
return; | ||
} | ||
|
||
let Some(name) = match_list_full_slice(subscript, checker.semantic()) else { | ||
return; | ||
}; | ||
let mut diagnostic = Diagnostic::new(SliceCopy, subscript.range()); | ||
if checker.patch(diagnostic.kind.rule()) { | ||
let replacement = generate_method_call(name, "copy", checker.generator()); | ||
diagnostic.set_fix(Fix::suggested(Edit::replacement( | ||
replacement, | ||
subscript.start(), | ||
subscript.end(), | ||
))); | ||
} | ||
checker.diagnostics.push(diagnostic); | ||
} | ||
|
||
/// Matches `obj[:]` where `obj` is a list. | ||
fn match_list_full_slice<'a>( | ||
subscript: &'a ast::ExprSubscript, | ||
semantic: &SemanticModel, | ||
) -> Option<&'a str> { | ||
// Check that it is `obj[:]`. | ||
if !matches!( | ||
subscript.slice.as_ref(), | ||
Expr::Slice(ast::ExprSlice { | ||
lower: None, | ||
upper: None, | ||
step: None, | ||
range: _, | ||
}) | ||
) { | ||
return None; | ||
} | ||
|
||
let ast::ExprName { id, .. } = subscript.value.as_name_expr()?; | ||
|
||
// Check that `obj` is a list. | ||
let scope = semantic.current_scope(); | ||
let bindings: Vec<&Binding> = scope | ||
.get_all(id) | ||
.map(|binding_id| semantic.binding(binding_id)) | ||
.collect(); | ||
let [binding] = bindings.as_slice() else { | ||
return None; | ||
}; | ||
if !is_list(binding, semantic) { | ||
return None; | ||
} | ||
|
||
Some(id) | ||
} |
Oops, something went wrong.