Skip to content

Commit 2a9c254

Browse files
committed
Add collection_is_never_read
1 parent ba7fd68 commit 2a9c254

File tree

6 files changed

+283
-0
lines changed

6 files changed

+283
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4307,6 +4307,7 @@ Released 2018-09-13
43074307
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
43084308
[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
43094309
[`collapsible_str_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_str_replace
4310+
[`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read
43104311
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
43114312
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
43124313
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::get_enclosing_block;
3+
use clippy_utils::get_parent_node;
4+
use clippy_utils::path_to_local_id;
5+
use clippy_utils::ty::is_type_diagnostic_item;
6+
use clippy_utils::visitors::for_each_expr_with_closures;
7+
use core::ops::ControlFlow;
8+
use rustc_hir::Block;
9+
use rustc_hir::ExprKind;
10+
use rustc_hir::HirId;
11+
use rustc_hir::Local;
12+
use rustc_hir::Node;
13+
use rustc_hir::PatKind;
14+
use rustc_lint::LateContext;
15+
use rustc_lint::LateLintPass;
16+
use rustc_session::declare_lint_pass;
17+
use rustc_session::declare_tool_lint;
18+
use rustc_span::symbol::sym;
19+
use rustc_span::Symbol;
20+
21+
declare_clippy_lint! {
22+
/// ### What it does
23+
/// Checks for collections that are never queried.
24+
///
25+
/// ### Why is this bad?
26+
/// Putting effort into constructing a collection but then never querying it might indicate that
27+
/// the author forgot to do whatever they intended to do with the collection. Example: Clone
28+
/// a vector, sort it for iteration, but then mistakenly iterate the original vector
29+
/// instead.
30+
///
31+
/// ### Example
32+
/// ```rust
33+
/// let mut sorted_samples = samples.clone();
34+
/// sorted_samples.sort();
35+
/// for sample in &samples { // Oops, meant to use `sorted_samples`.
36+
/// println!("{sample}");
37+
/// }
38+
/// ```
39+
/// Use instead:
40+
/// ```rust
41+
/// let mut sorted_samples = samples.clone();
42+
/// sorted_samples.sort();
43+
/// for sample in &sorted_samples {
44+
/// println!("{sample}");
45+
/// }
46+
/// ```
47+
#[clippy::version = "1.69.0"]
48+
pub COLLECTION_IS_NEVER_READ,
49+
nursery,
50+
"a collection is never queried"
51+
}
52+
declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]);
53+
54+
static COLLECTIONS: [Symbol; 10] = [
55+
sym::BTreeMap,
56+
sym::BTreeSet,
57+
sym::BinaryHeap,
58+
sym::HashMap,
59+
sym::HashSet,
60+
sym::LinkedList,
61+
sym::Option,
62+
sym::String,
63+
sym::Vec,
64+
sym::VecDeque,
65+
];
66+
67+
impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
68+
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
69+
// Look for local variables whose type is a container. Search surrounding bock for read access.
70+
let ty = cx.typeck_results().pat_ty(local.pat);
71+
if COLLECTIONS.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
72+
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
73+
&& let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id)
74+
&& has_no_read_access(cx, local_id, enclosing_block)
75+
{
76+
span_lint(cx, COLLECTION_IS_NEVER_READ, local.span, "collection is never read");
77+
}
78+
}
79+
}
80+
81+
fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool {
82+
let mut has_access = false;
83+
let mut has_read_access = false;
84+
85+
// Inspect all expressions and sub-expressions in the block.
86+
for_each_expr_with_closures(cx, block, |expr| {
87+
// Ignore expressions that are not simply `id`.
88+
if !path_to_local_id(expr, id) {
89+
return ControlFlow::Continue(());
90+
}
91+
92+
// `id` is being accessed. Investigate if it's a read access.
93+
has_access = true;
94+
95+
// `id` appearing in the left-hand side of an assignment is not a read access:
96+
//
97+
// id = ...; // Not reading `id`.
98+
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
99+
&& let ExprKind::Assign(lhs, ..) = parent.kind
100+
&& path_to_local_id(lhs, id)
101+
{
102+
return ControlFlow::Continue(());
103+
}
104+
105+
// Method call on `id` in a statement ignores any return value, so it's not a read access:
106+
//
107+
// id.foo(...); // Not reading `id`.
108+
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
109+
&& let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
110+
&& path_to_local_id(receiver, id)
111+
&& let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id)
112+
{
113+
return ControlFlow::Continue(());
114+
}
115+
116+
// Any other access to `id` is a read access. Stop searching.
117+
has_read_access = true;
118+
ControlFlow::Break(())
119+
});
120+
121+
// Ignore collections that have no access at all. Other lints should catch them.
122+
has_access && !has_read_access
123+
}

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
9292
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
9393
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
9494
crate::collapsible_if::COLLAPSIBLE_IF_INFO,
95+
crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO,
9596
crate::comparison_chain::COMPARISON_CHAIN_INFO,
9697
crate::copies::BRANCHES_SHARING_CODE_INFO,
9798
crate::copies::IFS_SAME_COND_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ mod casts;
8787
mod checked_conversions;
8888
mod cognitive_complexity;
8989
mod collapsible_if;
90+
mod collection_is_never_read;
9091
mod comparison_chain;
9192
mod copies;
9293
mod copy_iterator;
@@ -924,6 +925,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
924925
))
925926
});
926927
store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi));
928+
store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead));
927929
// add lints here, do not remove this comment, it's used in `new_lint`
928930
}
929931

tests/ui/collection_is_never_read.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#![allow(unused)]
2+
#![warn(clippy::collection_is_never_read)]
3+
4+
use std::collections::HashMap;
5+
6+
fn main() {}
7+
8+
fn not_a_collection() {
9+
// TODO: Expand `collection_is_never_read` beyond collections?
10+
let mut x = 10; // Ok
11+
x += 1;
12+
}
13+
14+
fn no_access_at_all() {
15+
// Other lints should catch this.
16+
let x = vec![1, 2, 3]; // Ok
17+
}
18+
19+
fn write_without_read() {
20+
// The main use case for `collection_is_never_read`.
21+
let mut x = HashMap::new(); // WARNING
22+
x.insert(1, 2);
23+
}
24+
25+
fn read_without_write() {
26+
let mut x = vec![1, 2, 3]; // Ok
27+
let _ = x.len();
28+
}
29+
30+
fn write_and_read() {
31+
let mut x = vec![1, 2, 3]; // Ok
32+
x.push(4);
33+
let _ = x.len();
34+
}
35+
36+
fn write_after_read() {
37+
// TODO: Warn here, but this requires more extensive data flow analysis.
38+
let mut x = vec![1, 2, 3]; // Ok
39+
let _ = x.len();
40+
x.push(4); // Pointless
41+
}
42+
43+
fn write_before_reassign() {
44+
// TODO: Warn here, but this requires more extensive data flow analysis.
45+
let mut x = HashMap::new(); // Ok
46+
x.insert(1, 2); // Pointless
47+
x = HashMap::new();
48+
let _ = x.len();
49+
}
50+
51+
fn read_in_closure() {
52+
let mut x = HashMap::new(); // Ok
53+
x.insert(1, 2);
54+
let _ = || {
55+
let _ = x.len();
56+
};
57+
}
58+
59+
fn write_in_closure() {
60+
let mut x = vec![1, 2, 3]; // WARNING
61+
let _ = || {
62+
x.push(4);
63+
};
64+
}
65+
66+
fn read_in_format() {
67+
let mut x = HashMap::new(); // Ok
68+
x.insert(1, 2);
69+
format!("{x:?}");
70+
}
71+
72+
fn shadowing_1() {
73+
let x = HashMap::<usize, usize>::new(); // Ok
74+
let _ = x.len();
75+
let mut x = HashMap::new(); // WARNING
76+
x.insert(1, 2);
77+
}
78+
79+
fn shadowing_2() {
80+
let mut x = HashMap::new(); // WARNING
81+
x.insert(1, 2);
82+
let x = HashMap::<usize, usize>::new(); // Ok
83+
let _ = x.len();
84+
}
85+
86+
#[allow(clippy::let_unit_value)]
87+
fn fake_read() {
88+
let mut x = vec![1, 2, 3]; // Ok
89+
x.reverse();
90+
// `collection_is_never_read` gets fooled, but other lints should catch this.
91+
let _: () = x.clear();
92+
}
93+
94+
fn assignment() {
95+
let mut x = vec![1, 2, 3]; // WARNING
96+
let y = vec![4, 5, 6]; // Ok
97+
x = y;
98+
}
99+
100+
#[allow(clippy::self_assignment)]
101+
fn self_assignment() {
102+
let mut x = vec![1, 2, 3]; // WARNING
103+
x = x;
104+
}
105+
106+
fn method_argument_but_not_target() {
107+
struct MyStruct;
108+
impl MyStruct {
109+
fn my_method(&self, _argument: &[usize]) {}
110+
}
111+
let my_struct = MyStruct;
112+
113+
let mut x = vec![1, 2, 3]; // Ok
114+
x.reverse();
115+
my_struct.my_method(&x);
116+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
error: collection is never read
2+
--> $DIR/collection_is_never_read.rs:21:5
3+
|
4+
LL | let mut x = HashMap::new(); // WARNING
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::collection-is-never-read` implied by `-D warnings`
8+
9+
error: collection is never read
10+
--> $DIR/collection_is_never_read.rs:60:5
11+
|
12+
LL | let mut x = vec![1, 2, 3]; // WARNING
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
15+
error: collection is never read
16+
--> $DIR/collection_is_never_read.rs:75:5
17+
|
18+
LL | let mut x = HashMap::new(); // WARNING
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
20+
21+
error: collection is never read
22+
--> $DIR/collection_is_never_read.rs:80:5
23+
|
24+
LL | let mut x = HashMap::new(); // WARNING
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
27+
error: collection is never read
28+
--> $DIR/collection_is_never_read.rs:95:5
29+
|
30+
LL | let mut x = vec![1, 2, 3]; // WARNING
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
32+
33+
error: collection is never read
34+
--> $DIR/collection_is_never_read.rs:102:5
35+
|
36+
LL | let mut x = vec![1, 2, 3]; // WARNING
37+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
38+
39+
error: aborting due to 6 previous errors
40+

0 commit comments

Comments
 (0)