Skip to content

Commit 70db226

Browse files
committed
Auto merge of #10528 - bluthej:clear-with-drain, r=llogiq
Clear with drain changelog: [`clear_with_drain`]: Add new lint Fixes #9339
2 parents 5698f43 + df65d21 commit 70db226

File tree

9 files changed

+346
-25
lines changed

9 files changed

+346
-25
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -4441,6 +4441,7 @@ Released 2018-09-13
44414441
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
44424442
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
44434443
[`checked_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#checked_conversions
4444+
[`clear_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#clear_with_drain
44444445
[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
44454446
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
44464447
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
307307
crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO,
308308
crate::methods::CHARS_LAST_CMP_INFO,
309309
crate::methods::CHARS_NEXT_CMP_INFO,
310+
crate::methods::CLEAR_WITH_DRAIN_INFO,
310311
crate::methods::CLONED_INSTEAD_OF_COPIED_INFO,
311312
crate::methods::CLONE_DOUBLE_REF_INFO,
312313
crate::methods::CLONE_ON_COPY_INFO,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::is_range_full;
3+
use clippy_utils::ty::is_type_diagnostic_item;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{Expr, ExprKind, QPath};
6+
use rustc_lint::LateContext;
7+
use rustc_span::symbol::sym;
8+
use rustc_span::Span;
9+
10+
use super::CLEAR_WITH_DRAIN;
11+
12+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
13+
let ty = cx.typeck_results().expr_ty(recv);
14+
if is_type_diagnostic_item(cx, ty, sym::Vec)
15+
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
16+
&& is_range_full(cx, arg, Some(container_path))
17+
{
18+
span_lint_and_sugg(
19+
cx,
20+
CLEAR_WITH_DRAIN,
21+
span.with_hi(expr.span.hi()),
22+
"`drain` used to clear a `Vec`",
23+
"try",
24+
"clear()".to_string(),
25+
Applicability::MachineApplicable,
26+
);
27+
}
28+
}
+3-21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::higher::Range;
3-
use clippy_utils::is_integer_const;
4-
use rustc_ast::ast::RangeLimits;
2+
use clippy_utils::is_range_full;
53
use rustc_errors::Applicability;
64
use rustc_hir::{Expr, ExprKind, QPath};
75
use rustc_lint::LateContext;
@@ -15,8 +13,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
1513
&& let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
1614
&& let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did())
1715
&& matches!(ty_name, sym::Vec | sym::VecDeque)
18-
&& let Some(range) = Range::hir(arg)
19-
&& is_full_range(cx, recv, range)
16+
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
17+
&& is_range_full(cx, arg, Some(container_path))
2018
{
2119
span_lint_and_sugg(
2220
cx,
@@ -29,19 +27,3 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
2927
);
3028
};
3129
}
32-
33-
fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool {
34-
range.start.map_or(true, |e| is_integer_const(cx, e, 0))
35-
&& range.end.map_or(true, |e| {
36-
if range.limits == RangeLimits::HalfOpen
37-
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind
38-
&& let ExprKind::MethodCall(name, self_arg, [], _) = e.kind
39-
&& name.ident.name == sym::len
40-
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
41-
{
42-
container_path.res == path.res
43-
} else {
44-
false
45-
}
46-
})
47-
}

clippy_lints/src/methods/mod.rs

+35-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod chars_last_cmp;
99
mod chars_last_cmp_with_unwrap;
1010
mod chars_next_cmp;
1111
mod chars_next_cmp_with_unwrap;
12+
mod clear_with_drain;
1213
mod clone_on_copy;
1314
mod clone_on_ref_ptr;
1415
mod cloned_instead_of_copied;
@@ -110,7 +111,7 @@ use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_
110111
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty};
111112
use if_chain::if_chain;
112113
use rustc_hir as hir;
113-
use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind};
114+
use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
114115
use rustc_hir_analysis::hir_ty_to_ty;
115116
use rustc_lint::{LateContext, LateLintPass, LintContext};
116117
use rustc_middle::lint::in_external_macro;
@@ -3190,6 +3191,31 @@ declare_clippy_lint! {
31903191
"single command line argument that looks like it should be multiple arguments"
31913192
}
31923193

3194+
declare_clippy_lint! {
3195+
/// ### What it does
3196+
/// Checks for usage of `.drain(..)` for the sole purpose of clearing a `Vec`.
3197+
///
3198+
/// ### Why is this bad?
3199+
/// This creates an unnecessary iterator that is dropped immediately.
3200+
///
3201+
/// Calling `.clear()` also makes the intent clearer.
3202+
///
3203+
/// ### Example
3204+
/// ```rust
3205+
/// let mut v = vec![1, 2, 3];
3206+
/// v.drain(..);
3207+
/// ```
3208+
/// Use instead:
3209+
/// ```rust
3210+
/// let mut v = vec![1, 2, 3];
3211+
/// v.clear();
3212+
/// ```
3213+
#[clippy::version = "1.69.0"]
3214+
pub CLEAR_WITH_DRAIN,
3215+
nursery,
3216+
"calling `drain` in order to `clear` a `Vec`"
3217+
}
3218+
31933219
pub struct Methods {
31943220
avoid_breaking_exported_api: bool,
31953221
msrv: Msrv,
@@ -3318,6 +3344,7 @@ impl_lint_pass!(Methods => [
33183344
SEEK_TO_START_INSTEAD_OF_REWIND,
33193345
NEEDLESS_COLLECT,
33203346
SUSPICIOUS_COMMAND_ARG_SPACE,
3347+
CLEAR_WITH_DRAIN,
33213348
]);
33223349

33233350
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3563,7 +3590,13 @@ impl Methods {
35633590
_ => {},
35643591
},
35653592
("drain", [arg]) => {
3566-
iter_with_drain::check(cx, expr, recv, span, arg);
3593+
if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id)
3594+
&& matches!(kind, StmtKind::Semi(_))
3595+
{
3596+
clear_with_drain::check(cx, expr, recv, span, arg);
3597+
} else {
3598+
iter_with_drain::check(cx, expr, recv, span, arg);
3599+
}
35673600
},
35683601
("ends_with", [arg]) => {
35693602
if let ExprKind::MethodCall(.., span) = expr.kind {

clippy_utils/src/lib.rs

+66-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ use std::sync::OnceLock;
7878
use std::sync::{Mutex, MutexGuard};
7979

8080
use if_chain::if_chain;
81-
use rustc_ast::ast::{self, LitKind};
81+
use rustc_ast::ast::{self, LitKind, RangeLimits};
8282
use rustc_ast::Attribute;
8383
use rustc_data_structures::fx::FxHashMap;
8484
use rustc_data_structures::unhash::UnhashMap;
@@ -96,6 +96,7 @@ use rustc_hir::{
9696
use rustc_lexer::{tokenize, TokenKind};
9797
use rustc_lint::{LateContext, Level, Lint, LintContext};
9898
use rustc_middle::hir::place::PlaceBase;
99+
use rustc_middle::mir::ConstantKind;
99100
use rustc_middle::ty as rustc_ty;
100101
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
101102
use rustc_middle::ty::binding::BindingMode;
@@ -114,7 +115,8 @@ use rustc_span::symbol::{kw, Ident, Symbol};
114115
use rustc_span::Span;
115116
use rustc_target::abi::Integer;
116117

117-
use crate::consts::{constant, Constant};
118+
use crate::consts::{constant, miri_to_const, Constant};
119+
use crate::higher::Range;
118120
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
119121
use crate::visitors::for_each_expr;
120122

@@ -1491,6 +1493,68 @@ pub fn is_else_clause(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
14911493
}
14921494
}
14931495

1496+
/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`.
1497+
/// For the lower bound, this means that:
1498+
/// - either there is none
1499+
/// - or it is the smallest value that can be represented by the range's integer type
1500+
/// For the upper bound, this means that:
1501+
/// - either there is none
1502+
/// - or it is the largest value that can be represented by the range's integer type and is
1503+
/// inclusive
1504+
/// - or it is a call to some container's `len` method and is exclusive, and the range is passed to
1505+
/// a method call on that same container (e.g. `v.drain(..v.len())`)
1506+
/// If the given `Expr` is not some kind of range, the function returns `false`.
1507+
pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool {
1508+
let ty = cx.typeck_results().expr_ty(expr);
1509+
if let Some(Range { start, end, limits }) = Range::hir(expr) {
1510+
let start_is_none_or_min = start.map_or(true, |start| {
1511+
if let rustc_ty::Adt(_, subst) = ty.kind()
1512+
&& let bnd_ty = subst.type_at(0)
1513+
&& let Some(min_val) = bnd_ty.numeric_min_val(cx.tcx)
1514+
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, min_val.to_valtree()))
1515+
&& let min_const_kind = ConstantKind::from_value(const_val, bnd_ty)
1516+
&& let Some(min_const) = miri_to_const(cx.tcx, min_const_kind)
1517+
&& let Some((start_const, _)) = constant(cx, cx.typeck_results(), start)
1518+
{
1519+
start_const == min_const
1520+
} else {
1521+
false
1522+
}
1523+
});
1524+
let end_is_none_or_max = end.map_or(true, |end| {
1525+
match limits {
1526+
RangeLimits::Closed => {
1527+
if let rustc_ty::Adt(_, subst) = ty.kind()
1528+
&& let bnd_ty = subst.type_at(0)
1529+
&& let Some(max_val) = bnd_ty.numeric_max_val(cx.tcx)
1530+
&& let const_val = cx.tcx.valtree_to_const_val((bnd_ty, max_val.to_valtree()))
1531+
&& let max_const_kind = ConstantKind::from_value(const_val, bnd_ty)
1532+
&& let Some(max_const) = miri_to_const(cx.tcx, max_const_kind)
1533+
&& let Some((end_const, _)) = constant(cx, cx.typeck_results(), end)
1534+
{
1535+
end_const == max_const
1536+
} else {
1537+
false
1538+
}
1539+
},
1540+
RangeLimits::HalfOpen => {
1541+
if let Some(container_path) = container_path
1542+
&& let ExprKind::MethodCall(name, self_arg, [], _) = end.kind
1543+
&& name.ident.name == sym::len
1544+
&& let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
1545+
{
1546+
container_path.res == path.res
1547+
} else {
1548+
false
1549+
}
1550+
},
1551+
}
1552+
});
1553+
return start_is_none_or_min && end_is_none_or_max;
1554+
}
1555+
false
1556+
}
1557+
14941558
/// Checks whether the given expression is a constant integer of the given value.
14951559
/// unlike `is_integer_literal`, this version does const folding
14961560
pub fn is_integer_const(cx: &LateContext<'_>, e: &Expr<'_>, value: u128) -> bool {

tests/ui/clear_with_drain.fixed

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// run-rustfix
2+
#![allow(unused)]
3+
#![warn(clippy::clear_with_drain)]
4+
5+
fn range() {
6+
let mut v = vec![1, 2, 3];
7+
let iter = v.drain(0..v.len()); // Yay
8+
9+
let mut v = vec![1, 2, 3];
10+
let n = v.drain(0..v.len()).count(); // Yay
11+
12+
let mut v = vec![1, 2, 3];
13+
let iter = v.drain(usize::MIN..v.len()); // Yay
14+
let n = iter.count();
15+
16+
let mut v = vec![1, 2, 3];
17+
v.clear(); // Nay
18+
19+
let mut v = vec![1, 2, 3];
20+
v.clear(); // Nay
21+
}
22+
23+
fn range_from() {
24+
let mut v = vec![1, 2, 3];
25+
let iter = v.drain(0..); // Yay
26+
27+
let mut v = vec![1, 2, 3];
28+
let mut iter = v.drain(0..); // Yay
29+
let next = iter.next();
30+
31+
let mut v = vec![1, 2, 3];
32+
let next = v.drain(usize::MIN..).next(); // Yay
33+
34+
let mut v = vec![1, 2, 3];
35+
v.clear(); // Nay
36+
37+
let mut v = vec![1, 2, 3];
38+
v.clear(); // Nay
39+
}
40+
41+
fn range_full() {
42+
let mut v = vec![1, 2, 3];
43+
let iter = v.drain(..); // Yay
44+
45+
let mut v = vec![1, 2, 3];
46+
// Yay
47+
for x in v.drain(..) {
48+
let y = format!("x = {x}");
49+
}
50+
51+
let mut v = vec![1, 2, 3];
52+
v.clear(); // Nay
53+
}
54+
55+
fn range_to() {
56+
let mut v = vec![1, 2, 3];
57+
let iter = v.drain(..v.len()); // Yay
58+
59+
let mut v = vec![1, 2, 3];
60+
let iter = v.drain(..v.len()); // Yay
61+
for x in iter {
62+
let y = format!("x = {x}");
63+
}
64+
65+
let mut v = vec![1, 2, 3];
66+
v.clear(); // Nay
67+
}
68+
69+
fn partial_drains() {
70+
let mut v = vec![1, 2, 3];
71+
v.drain(1..); // Yay
72+
let mut v = vec![1, 2, 3];
73+
v.drain(1..).max(); // Yay
74+
75+
let mut v = vec![1, 2, 3];
76+
v.drain(..v.len() - 1); // Yay
77+
let mut v = vec![1, 2, 3];
78+
v.drain(..v.len() - 1).min(); // Yay
79+
80+
let mut v = vec![1, 2, 3];
81+
v.drain(1..v.len() - 1); // Yay
82+
let mut v = vec![1, 2, 3];
83+
let w: Vec<i8> = v.drain(1..v.len() - 1).collect(); // Yay
84+
}
85+
86+
fn main() {}

0 commit comments

Comments
 (0)