-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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 [unnecessary_reserve]
lint
#9073
Changes from all commits
9c9cbe9
76c615d
6e10e45
404883b
a30e82a
1ec4304
380ef8c
d0d8a77
42c6bcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::{match_def_path, meets_msrv, msrvs, paths, visitors::expr_visitor_no_bodies}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{intravisit::Visitor, Block, ExprKind, QPath, StmtKind}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::ty; | ||
use rustc_semver::RustcVersion; | ||
use rustc_session::{declare_tool_lint, impl_lint_pass}; | ||
use rustc_span::sym; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// This lint checks for a call to `reserve` before `extend` on a `Vec` or `VecDeque`. | ||
/// ### Why is this bad? | ||
/// Since Rust 1.62, `extend` implicitly calls `reserve` | ||
/// ### Example | ||
/// ```rust | ||
/// let mut vec: Vec<usize> = vec![]; | ||
/// let array: &[usize] = &[1, 2]; | ||
/// vec.reserve(array.len()); | ||
/// vec.extend(array); | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// let mut vec: Vec<usize> = vec![]; | ||
/// let array: &[usize] = &[1, 2]; | ||
/// vec.extend(array); | ||
/// ``` | ||
#[clippy::version = "1.64.0"] | ||
pub UNNECESSARY_RESERVE, | ||
pedantic, | ||
"calling `reserve` before `extend` on a `Vec` or `VecDeque`, when it will be called implicitly" | ||
} | ||
|
||
pub struct UnnecessaryReserve { | ||
msrv: Option<RustcVersion>, | ||
} | ||
|
||
impl UnnecessaryReserve { | ||
#[must_use] | ||
pub fn new(msrv: Option<RustcVersion>) -> Self { | ||
Self { msrv } | ||
} | ||
} | ||
|
||
impl_lint_pass!(UnnecessaryReserve => [UNNECESSARY_RESERVE]); | ||
|
||
impl<'tcx> LateLintPass<'tcx> for UnnecessaryReserve { | ||
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure you don't have to check the whole block, but rather just the expression and if you find a |
||
if !meets_msrv(self.msrv, msrvs::UNNECESSARY_RESERVE) { | ||
return; | ||
} | ||
|
||
for (idx, stmt) in block.stmts.iter().enumerate() { | ||
if let StmtKind::Semi(semi_expr) = stmt.kind | ||
&& let ExprKind::MethodCall(_, [struct_calling_on, _], _) = semi_expr.kind | ||
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(semi_expr.hir_id) | ||
&& (match_def_path(cx, expr_def_id, &paths::VEC_RESERVE) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These new EDIT: Just saw, that you're already doing this in |
||
match_def_path(cx, expr_def_id, &paths::VEC_DEQUE_RESERVE)) | ||
&& acceptable_type(cx, struct_calling_on) | ||
&& let Some(next_stmt_span) = check_extend_method(cx, block, idx, struct_calling_on) | ||
&& !next_stmt_span.from_expansion() | ||
{ | ||
span_lint_and_then( | ||
cx, | ||
UNNECESSARY_RESERVE, | ||
next_stmt_span, | ||
"unnecessary call to `reserve`", | ||
|diag| { | ||
diag.span_suggestion( | ||
semi_expr.span, | ||
"remove this line", | ||
String::new(), | ||
Applicability::MaybeIncorrect, | ||
); | ||
} | ||
); | ||
} | ||
} | ||
} | ||
|
||
extract_msrv_attr!(LateContext); | ||
} | ||
|
||
#[must_use] | ||
fn acceptable_type(cx: &LateContext<'_>, struct_calling_on: &rustc_hir::Expr<'_>) -> bool { | ||
let acceptable_types = [sym::Vec, sym::VecDeque]; | ||
acceptable_types.iter().any(|&acceptable_ty| { | ||
match cx.typeck_results().expr_ty(struct_calling_on).peel_refs().kind() { | ||
ty::Adt(def, _) => cx.tcx.is_diagnostic_item(acceptable_ty, def.did()), | ||
_ => false, | ||
} | ||
}) | ||
} | ||
|
||
#[must_use] | ||
fn check_extend_method( | ||
cx: &LateContext<'_>, | ||
block: &Block<'_>, | ||
idx: usize, | ||
struct_expr: &rustc_hir::Expr<'_>, | ||
) -> Option<rustc_span::Span> { | ||
let mut read_found = false; | ||
let next_stmt_span; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would not pre-declare this, but rather use a more Rust like style below with let next_stmt_span = if idx == block.stmts.len() - 1 { |
||
|
||
let mut visitor = expr_visitor_no_bodies(|expr| { | ||
if let ExprKind::MethodCall(_, [struct_calling_on, _], _) = expr.kind | ||
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) | ||
&& match_def_path(cx, expr_def_id, &paths::ITER_EXTEND) | ||
&& acceptable_type(cx, struct_calling_on) | ||
&& equal_ident(struct_calling_on, struct_expr) | ||
{ | ||
read_found = true; | ||
} | ||
!read_found | ||
}); | ||
|
||
if idx == block.stmts.len() - 1 { | ||
if let Some(e) = block.expr { | ||
visitor.visit_expr(e); | ||
next_stmt_span = e.span; | ||
} else { | ||
return None; | ||
} | ||
} else { | ||
let next_stmt = &block.stmts[idx + 1]; | ||
visitor.visit_stmt(next_stmt); | ||
next_stmt_span = next_stmt.span; | ||
} | ||
drop(visitor); | ||
flip1995 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if read_found { | ||
return Some(next_stmt_span); | ||
} | ||
|
||
None | ||
} | ||
|
||
#[must_use] | ||
fn equal_ident(left: &rustc_hir::Expr<'_>, right: &rustc_hir::Expr<'_>) -> bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure, you can use our |
||
fn ident_name(expr: &rustc_hir::Expr<'_>) -> Option<rustc_span::Symbol> { | ||
if let ExprKind::Path(QPath::Resolved(None, inner_path)) = expr.kind | ||
&& let [inner_seg] = inner_path.segments | ||
{ | ||
return Some(inner_seg.ident.name); | ||
} | ||
None | ||
} | ||
|
||
ident_name(left) == ident_name(right) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ macro_rules! msrv_aliases { | |
|
||
// names may refer to stabilized feature flags or library items | ||
msrv_aliases! { | ||
1,62,0 { BOOL_THEN_SOME } | ||
1,62,0 { BOOL_THEN_SOME, UNNECESSARY_RESERVE } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be named after the feature that was introduced rather than after the lint. |
||
1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN } | ||
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } | ||
1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
#![warn(clippy::unnecessary_reserve)] | ||
#![feature(custom_inner_attributes)] | ||
|
||
use std::collections::HashMap; | ||
use std::collections::VecDeque; | ||
|
||
fn main() { | ||
vec_reserve(); | ||
vec_deque_reserve(); | ||
hash_map_reserve(); | ||
msrv_1_62(); | ||
} | ||
|
||
Comment on lines
+7
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: This should be at the end of the test file. |
||
fn vec_reserve() { | ||
let mut vec: Vec<usize> = vec![]; | ||
let array: &[usize] = &[1, 2]; | ||
|
||
// do lint | ||
vec.reserve(1); | ||
vec.extend([1]); | ||
Comment on lines
+19
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm unsure if this should lint. I think this should only lint for vec.reserve(expr.len());
vec.extend(expr); where Or am I missing something? |
||
|
||
// do lint | ||
vec.reserve(array.len()); | ||
vec.extend(array); | ||
|
||
// do lint | ||
{ | ||
vec.reserve(1); | ||
vec.extend([1]) | ||
}; | ||
|
||
// do not lint | ||
vec.reserve(array.len()); | ||
vec.push(1); | ||
vec.extend(array); | ||
|
||
// do not lint | ||
let mut other_vec: Vec<usize> = vec![]; | ||
other_vec.reserve(1); | ||
vec.extend([1]) | ||
} | ||
|
||
fn vec_deque_reserve() { | ||
let mut vec_deque: VecDeque<usize> = [1].into(); | ||
let array: &[usize] = &[1, 2]; | ||
|
||
// do lint | ||
vec_deque.reserve(1); | ||
vec_deque.extend([1]); | ||
|
||
// do lint | ||
vec_deque.reserve(array.len()); | ||
vec_deque.extend(array); | ||
|
||
// do lint | ||
{ | ||
vec_deque.reserve(1); | ||
vec_deque.extend([1]) | ||
}; | ||
|
||
// do not lint | ||
vec_deque.reserve(array.len() + 1); | ||
vec_deque.push_back(1); | ||
vec_deque.extend(array); | ||
|
||
// do not lint | ||
let mut other_vec_deque: VecDeque<usize> = [1].into(); | ||
other_vec_deque.reserve(1); | ||
vec_deque.extend([1]) | ||
} | ||
|
||
fn hash_map_reserve() { | ||
let mut map: HashMap<usize, usize> = HashMap::new(); | ||
let mut other_map: HashMap<usize, usize> = HashMap::new(); | ||
// do not lint | ||
map.reserve(other_map.len()); | ||
map.extend(other_map); | ||
} | ||
|
||
fn msrv_1_62() { | ||
#![clippy::msrv = "1.61"] | ||
let mut vec: Vec<usize> = vec![]; | ||
let array: &[usize] = &[1, 2]; | ||
|
||
// do not lint | ||
vec.reserve(1); | ||
vec.extend([1]); | ||
|
||
let mut vec_deque: VecDeque<usize> = [1].into(); | ||
let array: &[usize] = &[1, 2]; | ||
|
||
// do not lint | ||
vec_deque.reserve(1); | ||
vec_deque.extend([1]); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:20:5 | ||
| | ||
LL | vec.reserve(1); | ||
| -------------- help: remove this line | ||
LL | vec.extend([1]); | ||
| ^^^^^^^^^^^^^^^^ | ||
| | ||
= note: `-D clippy::unnecessary-reserve` implied by `-D warnings` | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:24:5 | ||
| | ||
LL | vec.reserve(array.len()); | ||
| ------------------------ help: remove this line | ||
LL | vec.extend(array); | ||
| ^^^^^^^^^^^^^^^^^^ | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:29:9 | ||
| | ||
LL | vec.reserve(1); | ||
| -------------- help: remove this line | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the span of the suggestion is wrong? Shouldn't it include the |
||
LL | vec.extend([1]) | ||
| ^^^^^^^^^^^^^^^ | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:49:5 | ||
| | ||
LL | vec_deque.reserve(1); | ||
| -------------------- help: remove this line | ||
LL | vec_deque.extend([1]); | ||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:53:5 | ||
| | ||
LL | vec_deque.reserve(array.len()); | ||
| ------------------------------ help: remove this line | ||
LL | vec_deque.extend(array); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:58:9 | ||
| | ||
LL | vec_deque.reserve(1); | ||
| -------------------- help: remove this line | ||
LL | vec_deque.extend([1]) | ||
| ^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: aborting due to 6 previous errors | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: readability