Skip to content

Commit 5ec2e19

Browse files
committed
Auto merge of rust-lang#10614 - bluthej:clear-with-drain, r=Manishearth
Clear with drain Fixes rust-lang#10572: both the original intent of the issue (extending `clear_with_drain`) and the false negative for `collection_is_never_read` I found in the process are fixed by this PR. changelog: [`clear_with_drain`]: extend to 5 other types of containers. [`collection_is_never_read`]: fix false negative for `String`s.
2 parents e22019d + d8f0a96 commit 5ec2e19

8 files changed

+787
-89
lines changed

clippy_lints/src/collection_is_never_read.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use clippy_utils::diagnostics::span_lint;
2-
use clippy_utils::ty::is_type_diagnostic_item;
2+
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
33
use clippy_utils::visitors::for_each_expr_with_closures;
44
use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id};
55
use core::ops::ControlFlow;
6-
use rustc_hir::{Block, ExprKind, HirId, Local, Node, PatKind};
6+
use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_session::{declare_lint_pass, declare_tool_lint};
99
use rustc_span::symbol::sym;
@@ -44,24 +44,23 @@ declare_clippy_lint! {
4444
}
4545
declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]);
4646

47-
static COLLECTIONS: [Symbol; 10] = [
47+
// Add `String` here when it is added to diagnostic items
48+
static COLLECTIONS: [Symbol; 9] = [
4849
sym::BTreeMap,
4950
sym::BTreeSet,
5051
sym::BinaryHeap,
5152
sym::HashMap,
5253
sym::HashSet,
5354
sym::LinkedList,
5455
sym::Option,
55-
sym::String,
5656
sym::Vec,
5757
sym::VecDeque,
5858
];
5959

6060
impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
6161
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
6262
// Look for local variables whose type is a container. Search surrounding bock for read access.
63-
let ty = cx.typeck_results().pat_ty(local.pat);
64-
if COLLECTIONS.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
63+
if match_acceptable_type(cx, local, &COLLECTIONS)
6564
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
6665
&& let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id)
6766
&& has_no_read_access(cx, local_id, enclosing_block)
@@ -71,6 +70,13 @@ impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
7170
}
7271
}
7372

73+
fn match_acceptable_type(cx: &LateContext<'_>, local: &Local<'_>, collections: &[rustc_span::Symbol]) -> bool {
74+
let ty = cx.typeck_results().pat_ty(local.pat);
75+
collections.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
76+
// String type is a lang item but not a diagnostic item for now so we need a separate check
77+
|| is_type_lang_item(cx, ty, LangItem::String)
78+
}
79+
7480
fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool {
7581
let mut has_access = false;
7682
let mut has_read_access = false;

clippy_lints/src/methods/clear_with_drain.rs

+33-8
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,50 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::is_range_full;
3-
use clippy_utils::ty::is_type_diagnostic_item;
3+
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
44
use rustc_errors::Applicability;
5-
use rustc_hir::{Expr, ExprKind, QPath};
5+
use rustc_hir as hir;
6+
use rustc_hir::{Expr, ExprKind, LangItem, QPath};
67
use rustc_lint::LateContext;
78
use rustc_span::symbol::sym;
89
use rustc_span::Span;
910

1011
use super::CLEAR_WITH_DRAIN;
1112

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))
13+
// Add `String` here when it is added to diagnostic items
14+
const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 2] = [sym::Vec, sym::VecDeque];
15+
16+
const ACCEPTABLE_TYPES_WITHOUT_ARG: [rustc_span::Symbol; 3] = [sym::BinaryHeap, sym::HashMap, sym::HashSet];
17+
18+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: Option<&Expr<'_>>) {
19+
if let Some(arg) = arg {
20+
if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITH_ARG)
21+
&& let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
22+
&& is_range_full(cx, arg, Some(container_path))
23+
{
24+
suggest(cx, expr, recv, span);
25+
}
26+
} else if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITHOUT_ARG) {
27+
suggest(cx, expr, recv, span);
28+
}
29+
}
30+
31+
fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, types: &[rustc_span::Symbol]) -> bool {
32+
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
33+
types.iter().any(|&ty| is_type_diagnostic_item(cx, expr_ty, ty))
34+
// String type is a lang item but not a diagnostic item for now so we need a separate check
35+
|| is_type_lang_item(cx, expr_ty, LangItem::String)
36+
}
37+
38+
fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) {
39+
if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
40+
// Use `opt_item_name` while `String` is not a diagnostic item
41+
&& let Some(ty_name) = cx.tcx.opt_item_name(adt.did())
1742
{
1843
span_lint_and_sugg(
1944
cx,
2045
CLEAR_WITH_DRAIN,
2146
span.with_hi(expr.span.hi()),
22-
"`drain` used to clear a `Vec`",
47+
&format!("`drain` used to clear a `{ty_name}`"),
2348
"try",
2449
"clear()".to_string(),
2550
Applicability::MachineApplicable,

clippy_lints/src/methods/mod.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -3193,7 +3193,7 @@ declare_clippy_lint! {
31933193

31943194
declare_clippy_lint! {
31953195
/// ### What it does
3196-
/// Checks for usage of `.drain(..)` for the sole purpose of clearing a `Vec`.
3196+
/// Checks for usage of `.drain(..)` for the sole purpose of clearing a container.
31973197
///
31983198
/// ### Why is this bad?
31993199
/// This creates an unnecessary iterator that is dropped immediately.
@@ -3213,7 +3213,7 @@ declare_clippy_lint! {
32133213
#[clippy::version = "1.69.0"]
32143214
pub CLEAR_WITH_DRAIN,
32153215
nursery,
3216-
"calling `drain` in order to `clear` a `Vec`"
3216+
"calling `drain` in order to `clear` a container"
32173217
}
32183218

32193219
pub struct Methods {
@@ -3589,12 +3589,13 @@ impl Methods {
35893589
Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
35903590
_ => {},
35913591
},
3592-
("drain", [arg]) => {
3593-
if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id)
3594-
&& matches!(kind, StmtKind::Semi(_))
3592+
("drain", ..) => {
3593+
if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id)
3594+
&& matches!(kind, StmtKind::Semi(_))
3595+
&& args.len() <= 1
35953596
{
3596-
clear_with_drain::check(cx, expr, recv, span, arg);
3597-
} else {
3597+
clear_with_drain::check(cx, expr, recv, span, args.first());
3598+
} else if let [arg] = args {
35983599
iter_with_drain::check(cx, expr, recv, span, arg);
35993600
}
36003601
},

0 commit comments

Comments
 (0)