-
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
#10157
Add unnecessary_reserve
lint
#10157
Changes from all commits
8e73e2a
ab1a1b3
a980ee8
df42bbd
c772ac3
6100e83
9a961d5
a02df99
47e916f
c332c48
390d702
6cd1b64
3ae7b12
37fd6b3
37eb8c3
1f08765
2da64a9
4df0eba
452717b
696c11e
f3dda97
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,121 @@ | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::msrvs::{self, Msrv}; | ||
use clippy_utils::{get_enclosing_block, match_def_path, paths, visitors::for_each_expr, SpanlessEq}; | ||
use core::ops::ControlFlow; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Block, Expr, ExprKind, PathSegment}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::ty; | ||
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" | ||
} | ||
|
||
impl_lint_pass!(UnnecessaryReserve => [UNNECESSARY_RESERVE]); | ||
|
||
pub struct UnnecessaryReserve { | ||
msrv: Msrv, | ||
} | ||
impl UnnecessaryReserve { | ||
pub fn new(msrv: Msrv) -> Self { | ||
Self { msrv } | ||
} | ||
} | ||
|
||
impl<'tcx> LateLintPass<'tcx> for UnnecessaryReserve { | ||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { | ||
if !self.msrv.meets(msrvs::EXTEND_IMPLICIT_RESERVE) { | ||
return; | ||
} | ||
|
||
if let ExprKind::MethodCall(PathSegment { ident: method, .. }, struct_calling_on, args_a, _) = expr.kind | ||
&& method.name.as_str() == "reserve" | ||
&& acceptable_type(cx, struct_calling_on) | ||
&& let Some(block) = get_enclosing_block(cx, expr.hir_id) | ||
&& let Some(next_stmt_span) = check_extend_method(cx, block, struct_calling_on, &args_a[0]) | ||
&& !next_stmt_span.from_expansion() | ||
{ | ||
span_lint_and_then( | ||
cx, | ||
UNNECESSARY_RESERVE, | ||
next_stmt_span, | ||
"unnecessary call to `reserve`", | ||
|diag| { | ||
diag.span_suggestion( | ||
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<'_>, | ||
struct_expr: &rustc_hir::Expr<'_>, | ||
args_a: &rustc_hir::Expr<'_>, | ||
) -> Option<rustc_span::Span> { | ||
let args_a_kind = &args_a.kind; | ||
let mut read_found = false; | ||
let mut spanless_eq = SpanlessEq::new(cx); | ||
|
||
let _: Option<!> = for_each_expr(block, |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) | ||
&& let ExprKind::MethodCall(PathSegment { ident: method_call_a, .. },..) = args_a_kind | ||
&& method_call_a.name == rustc_span::sym::len | ||
&& match_def_path(cx, expr_def_id, &paths::ITER_EXTEND) | ||
&& acceptable_type(cx, struct_calling_on) | ||
// Check that both expr are equal | ||
&& spanless_eq.eq_expr(struct_calling_on, struct_expr) | ||
{ | ||
read_found = true; | ||
} | ||
let _: bool = !read_found; | ||
ControlFlow::Continue(()) | ||
}); | ||
|
||
if read_found { Some(block.span) } else { None } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,7 @@ pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"]; | |
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"]; | ||
pub const INSERT_STR: [&str; 4] = ["alloc", "string", "String", "insert_str"]; | ||
pub const ITER_EMPTY: [&str; 5] = ["core", "iter", "sources", "empty", "Empty"]; | ||
pub const ITER_EXTEND: [&str; 6] = ["core", "iter", "traits", "collect", "Extend", "extend"]; | ||
pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"]; | ||
#[cfg(feature = "internal")] | ||
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; | ||
|
@@ -154,6 +155,8 @@ pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "Vec | |
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"]; | ||
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; | ||
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"]; | ||
pub const VEC_RESERVE: [&str; 4] = ["alloc", "vec", "Vec", "reserve"]; | ||
pub const VEC_DEQUE_RESERVE: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "reserve"]; | ||
Comment on lines
+158
to
+159
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. Those are a lot of paths added here. It may be worth opening a 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. @flip1995 Thank you so much for your review and suggestion. I will fix these. |
||
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; | ||
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; | ||
pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"]; | ||
|
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(); | ||
} | ||
|
||
fn vec_reserve() { | ||
let mut vec: Vec<usize> = vec![]; | ||
let array: &[usize] = &[1, 2]; | ||
|
||
// do not lint | ||
vec.reserve(1); | ||
vec.extend([1]); | ||
Comment on lines
+18
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 think one test that is missing is something like
This might be covered by this, but I'm not 100% sure. This should not be linted. Doesn't make much sense to write something like this, but it should be an easy FP to avoid. 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. Ok. |
||
|
||
//// do lint | ||
vec.reserve(array.len()); | ||
vec.extend(array); | ||
|
||
// do lint | ||
{ | ||
vec.reserve(array.len()); | ||
vec.extend(array) | ||
}; | ||
|
||
// 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 not lint | ||
vec_deque.reserve(1); | ||
vec_deque.extend([1]); | ||
|
||
// do lint | ||
vec_deque.reserve(array.len()); | ||
vec_deque.extend(array); | ||
|
||
// do not 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,62 @@ | ||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:14:18 | ||
| | ||
LL | fn vec_reserve() { | ||
| __________________^ | ||
LL | | let mut vec: Vec<usize> = vec![]; | ||
LL | | let array: &[usize] = &[1, 2]; | ||
LL | | | ||
... | | ||
LL | | vec.reserve(array.len()); | ||
| | ------------------------ help: remove this line | ||
... | | ||
LL | | vec.extend([1]) | ||
LL | | } | ||
| |_^ | ||
| | ||
= note: `-D clippy::unnecessary-reserve` implied by `-D warnings` | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:27:5 | ||
| | ||
LL | / { | ||
LL | | vec.reserve(array.len()); | ||
| | ------------------------ help: remove this line | ||
LL | | vec.extend(array) | ||
LL | | }; | ||
| |_____^ | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:14:18 | ||
| | ||
LL | fn vec_reserve() { | ||
| __________________^ | ||
LL | | let mut vec: Vec<usize> = vec![]; | ||
LL | | let array: &[usize] = &[1, 2]; | ||
LL | | | ||
... | | ||
LL | | vec.reserve(array.len()); | ||
| | ------------------------ help: remove this line | ||
... | | ||
LL | | vec.extend([1]) | ||
LL | | } | ||
| |_^ | ||
|
||
error: unnecessary call to `reserve` | ||
--> $DIR/unnecessary_reserve.rs:43:24 | ||
| | ||
LL | fn vec_deque_reserve() { | ||
| ________________________^ | ||
LL | | let mut vec_deque: VecDeque<usize> = [1].into(); | ||
LL | | let array: &[usize] = &[1, 2]; | ||
LL | | | ||
... | | ||
LL | | vec_deque.reserve(array.len()); | ||
| | ------------------------------ help: remove this line | ||
... | | ||
LL | | vec_deque.extend([1]) | ||
LL | | } | ||
| |_^ | ||
|
||
error: aborting due to 4 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.
Shouldn't this be
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.
@flip1995 Thank you so much for your review.
UNNECESSARY_RESERVE
is defined inclippy_lints/src/unnecessary_reserve.rs
asimpl_lint_pass!(UnnecessaryReserve => [UNNECESSARY_RESERVE]);
which is aLintPass
and thought that need to declare as an another constants.