Skip to content

Commit ecd37f9

Browse files
committed
new lint: unnecessary_reserve
1 parent e02c885 commit ecd37f9

File tree

9 files changed

+430
-1
lines changed

9 files changed

+430
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6173,6 +6173,7 @@ Released 2018-09-13
61736173
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
61746174
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
61756175
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
6176+
[`unnecessary_reserve`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_reserve
61766177
[`unnecessary_result_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_result_map_or_else
61776178
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
61786179
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
624624
crate::pathbuf_init_then_push::PATHBUF_INIT_THEN_PUSH_INFO,
625625
crate::pattern_type_mismatch::PATTERN_TYPE_MISMATCH_INFO,
626626
crate::permissions_set_readonly_false::PERMISSIONS_SET_READONLY_FALSE_INFO,
627+
crate::unnecessary_reserve::UNNECESSARY_RESERVE_INFO,
627628
crate::pointers_in_nomem_asm_block::POINTERS_IN_NOMEM_ASM_BLOCK_INFO,
628629
crate::precedence::PRECEDENCE_INFO,
629630
crate::ptr::CMP_NULL_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ mod unnecessary_box_returns;
372372
mod unnecessary_literal_bound;
373373
mod unnecessary_map_on_constructor;
374374
mod unnecessary_owned_empty_strings;
375+
mod unnecessary_reserve;
375376
mod unnecessary_self_imports;
376377
mod unnecessary_semicolon;
377378
mod unnecessary_struct_initialization;
@@ -956,6 +957,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
956957
store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf)));
957958
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
958959
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
960+
store.register_late_pass(move |_| Box::new(unnecessary_reserve::UnnecessaryReserve::new(conf)));
959961
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
960962
store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf)));
961963
store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf)));
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::msrvs::{self, Msrv};
4+
use clippy_utils::visitors::for_each_expr;
5+
use clippy_utils::{SpanlessEq, get_enclosing_block, match_def_path, paths};
6+
use core::ops::ControlFlow;
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{Block, Expr, ExprKind, PathSegment};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::ty;
11+
use rustc_session::impl_lint_pass;
12+
use rustc_span::sym;
13+
14+
declare_clippy_lint! {
15+
/// ### What it does
16+
///
17+
/// This lint checks for a call to `reserve` before `extend` on a `Vec` or `VecDeque`.
18+
/// ### Why is this bad?
19+
/// Since Rust 1.62, `extend` implicitly calls `reserve`
20+
///
21+
/// ### Example
22+
/// ```rust
23+
/// let mut vec: Vec<usize> = vec![];
24+
/// let array: &[usize] = &[1, 2];
25+
/// vec.reserve(array.len());
26+
/// vec.extend(array);
27+
/// ```
28+
/// Use instead:
29+
/// ```rust
30+
/// let mut vec: Vec<usize> = vec![];
31+
/// let array: &[usize] = &[1, 2];
32+
/// vec.extend(array);
33+
/// ```
34+
#[clippy::version = "1.64.0"]
35+
pub UNNECESSARY_RESERVE,
36+
pedantic,
37+
"calling `reserve` before `extend` on a `Vec` or `VecDeque`, when it will be called implicitly"
38+
}
39+
40+
impl_lint_pass!(UnnecessaryReserve => [UNNECESSARY_RESERVE]);
41+
42+
pub struct UnnecessaryReserve {
43+
msrv: Msrv,
44+
}
45+
impl UnnecessaryReserve {
46+
pub fn new(conf: &'static Conf) -> Self {
47+
Self {
48+
msrv: conf.msrv.clone(),
49+
}
50+
}
51+
}
52+
53+
impl<'tcx> LateLintPass<'tcx> for UnnecessaryReserve {
54+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
55+
if !self.msrv.meets(msrvs::EXTEND_IMPLICIT_RESERVE) {
56+
return;
57+
}
58+
59+
if let ExprKind::MethodCall(PathSegment { ident: method, .. }, struct_calling_on, args_a, _) = expr.kind
60+
&& method.name.as_str() == "reserve"
61+
&& acceptable_type(cx, struct_calling_on)
62+
&& let Some(block) = get_enclosing_block(cx, expr.hir_id)
63+
&& let Some(next_stmt_span) = check_extend_method(cx, block, struct_calling_on, &args_a[0])
64+
&& !next_stmt_span.from_expansion()
65+
{
66+
span_lint_and_then(
67+
cx,
68+
UNNECESSARY_RESERVE,
69+
next_stmt_span,
70+
"unnecessary call to `reserve`",
71+
|diag| {
72+
diag.span_suggestion(
73+
expr.span,
74+
"remove this line",
75+
String::new(),
76+
Applicability::MaybeIncorrect,
77+
);
78+
},
79+
);
80+
}
81+
}
82+
83+
extract_msrv_attr!(LateContext);
84+
}
85+
86+
#[must_use]
87+
fn acceptable_type<'tcx>(cx: &LateContext<'tcx>, struct_calling_on: &Expr<'_>) -> bool {
88+
let acceptable_types = [sym::Vec, sym::VecDeque];
89+
acceptable_types.iter().any(|&acceptable_ty| {
90+
match cx.typeck_results().expr_ty(struct_calling_on).peel_refs().kind() {
91+
ty::Adt(def, _) => cx.tcx.is_diagnostic_item(acceptable_ty, def.did()),
92+
_ => false,
93+
}
94+
})
95+
}
96+
97+
#[must_use]
98+
fn check_extend_method<'tcx>(
99+
cx: &LateContext<'tcx>,
100+
block: &'tcx Block<'tcx>,
101+
struct_expr: &Expr<'tcx>,
102+
args_a: &Expr<'tcx>,
103+
) -> Option<rustc_span::Span> {
104+
let args_a_kind = &args_a.kind;
105+
let mut read_found = false;
106+
let mut spanless_eq = SpanlessEq::new(cx);
107+
108+
let _: Option<!> = for_each_expr(cx, block, |expr: &Expr<'tcx>| {
109+
if let ExprKind::MethodCall(_, struct_calling_on, _, _) = expr.kind
110+
&& let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
111+
&& let ExprKind::MethodCall(
112+
PathSegment {
113+
ident: method_call_a, ..
114+
},
115+
..,
116+
) = args_a_kind
117+
&& method_call_a.name == sym::len
118+
&& match_def_path(cx, expr_def_id, &paths::ITER_EXTEND)
119+
&& acceptable_type(cx, struct_calling_on)
120+
&& spanless_eq.eq_expr(struct_calling_on, struct_expr)
121+
{
122+
read_found = true;
123+
}
124+
let _: bool = !read_found;
125+
ControlFlow::Continue(())
126+
});
127+
128+
if read_found { Some(block.span) } else { None }
129+
}

clippy_utils/src/msrvs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ msrv_aliases! {
3131
1,68,0 { PATH_MAIN_SEPARATOR_STR }
3232
1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS }
3333
1,63,0 { CLONE_INTO }
34-
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN }
34+
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_C_FN, EXTEND_IMPLICIT_RESERVE }
3535
1,59,0 { THREAD_LOCAL_CONST_INIT }
3636
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
3737
1,56,0 { CONST_FN_UNION }

clippy_utils/src/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
1515
pub const DIAG: [&str; 2] = ["rustc_errors", "Diag"];
1616
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
1717
pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
18+
pub const ITER_EXTEND: [&str; 6] = ["core", "iter", "traits", "collect", "Extend", "extend"];
1819
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
1920
pub const IDENT_AS_STR: [&str; 4] = ["rustc_span", "symbol", "Ident", "as_str"];
2021
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];

tests/ui/unnecessary_reserve.fixed

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#![warn(clippy::unnecessary_reserve)]
2+
#![feature(custom_inner_attributes)]
3+
4+
#![allow(clippy::unnecessary_operation)]
5+
use std::collections::{HashMap, VecDeque};
6+
7+
fn main() {
8+
vec_reserve();
9+
vec_deque_reserve();
10+
hash_map_reserve();
11+
msrv_1_62();
12+
}
13+
14+
fn vec_reserve() {
15+
let mut vec: Vec<usize> = vec![];
16+
let array1: &[usize] = &[1, 2];
17+
let array2: &[usize] = &[3, 4];
18+
19+
// do not lint - different arrays
20+
;
21+
vec.extend(array2);
22+
23+
// do not lint
24+
vec.reserve(1);
25+
vec.extend([1]);
26+
27+
//// do lint
28+
;
29+
vec.extend(array1);
30+
31+
// do lint
32+
{
33+
;
34+
vec.extend(array1)
35+
};
36+
37+
// do not lint
38+
;
39+
vec.push(1);
40+
vec.extend(array1);
41+
42+
// do not lint
43+
let mut other_vec: Vec<usize> = Vec::with_capacity(1);
44+
vec.extend([1])
45+
}
46+
47+
fn vec_deque_reserve() {
48+
let mut vec_deque: VecDeque<usize> = [1].into();
49+
let array: &[usize] = &[1, 2];
50+
51+
// do not lint
52+
vec_deque.reserve(1);
53+
vec_deque.extend([1]);
54+
55+
// do lint
56+
;
57+
vec_deque.extend(array);
58+
59+
// do not lint
60+
{
61+
vec_deque.reserve(1);
62+
vec_deque.extend([1])
63+
};
64+
65+
// do not lint
66+
vec_deque.reserve(array.len() + 1);
67+
vec_deque.push_back(1);
68+
vec_deque.extend(array);
69+
70+
// do not lint
71+
let mut other_vec_deque: VecDeque<usize> = [1].into();
72+
other_vec_deque.reserve(1);
73+
vec_deque.extend([1])
74+
}
75+
76+
fn hash_map_reserve() {
77+
let mut map: HashMap<usize, usize> = HashMap::new();
78+
let mut other_map: HashMap<usize, usize> = HashMap::new();
79+
// do not lint
80+
map.reserve(other_map.len());
81+
map.extend(other_map);
82+
}
83+
84+
fn msrv_1_62() {
85+
#![clippy::msrv = "1.61"]
86+
let mut vec: Vec<usize> = vec![];
87+
let array: &[usize] = &[1, 2];
88+
89+
// do not lint
90+
vec.reserve(1);
91+
vec.extend([1]);
92+
93+
let mut vec_deque: VecDeque<usize> = [1].into();
94+
let array: &[usize] = &[1, 2];
95+
96+
// do not lint
97+
vec_deque.reserve(1);
98+
vec_deque.extend([1]);
99+
}

tests/ui/unnecessary_reserve.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#![warn(clippy::unnecessary_reserve)]
2+
#![feature(custom_inner_attributes)]
3+
4+
#[allow(clippy::unnecessary_operation)]
5+
use std::collections::{HashMap, VecDeque};
6+
7+
fn main() {
8+
vec_reserve();
9+
vec_deque_reserve();
10+
hash_map_reserve();
11+
msrv_1_62();
12+
}
13+
14+
fn vec_reserve() {
15+
let mut vec: Vec<usize> = vec![];
16+
let array1: &[usize] = &[1, 2];
17+
let array2: &[usize] = &[3, 4];
18+
19+
// do not lint - different arrays
20+
vec.reserve(array1.len());
21+
vec.extend(array2);
22+
23+
// do not lint
24+
vec.reserve(1);
25+
vec.extend([1]);
26+
27+
//// do lint
28+
vec.reserve(array1.len());
29+
vec.extend(array1);
30+
31+
// do lint
32+
{
33+
vec.reserve(array1.len());
34+
vec.extend(array1)
35+
};
36+
37+
// do not lint
38+
vec.reserve(array1.len());
39+
vec.push(1);
40+
vec.extend(array1);
41+
42+
// do not lint
43+
let mut other_vec: Vec<usize> = vec![];
44+
other_vec.reserve(1);
45+
vec.extend([1])
46+
}
47+
48+
fn vec_deque_reserve() {
49+
let mut vec_deque: VecDeque<usize> = [1].into();
50+
let array: &[usize] = &[1, 2];
51+
52+
// do not lint
53+
vec_deque.reserve(1);
54+
vec_deque.extend([1]);
55+
56+
// do lint
57+
vec_deque.reserve(array.len());
58+
vec_deque.extend(array);
59+
60+
// do not lint
61+
{
62+
vec_deque.reserve(1);
63+
vec_deque.extend([1])
64+
};
65+
66+
// do not lint
67+
vec_deque.reserve(array.len() + 1);
68+
vec_deque.push_back(1);
69+
vec_deque.extend(array);
70+
71+
// do not lint
72+
let mut other_vec_deque: VecDeque<usize> = [1].into();
73+
other_vec_deque.reserve(1);
74+
vec_deque.extend([1])
75+
}
76+
77+
fn hash_map_reserve() {
78+
let mut map: HashMap<usize, usize> = HashMap::new();
79+
let mut other_map: HashMap<usize, usize> = HashMap::new();
80+
// do not lint
81+
map.reserve(other_map.len());
82+
map.extend(other_map);
83+
}
84+
85+
fn msrv_1_62() {
86+
#![clippy::msrv = "1.61"]
87+
let mut vec: Vec<usize> = vec![];
88+
let array: &[usize] = &[1, 2];
89+
90+
// do not lint
91+
vec.reserve(1);
92+
vec.extend([1]);
93+
94+
let mut vec_deque: VecDeque<usize> = [1].into();
95+
let array: &[usize] = &[1, 2];
96+
97+
// do not lint
98+
vec_deque.reserve(1);
99+
vec_deque.extend([1]);
100+
}

0 commit comments

Comments
 (0)