-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add collection_is_never_read
#10415
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
Merged
Merged
Add collection_is_never_read
#10415
Changes from 2 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2a9c254
Add `collection_is_never_read`
schubart 0870819
Fix lint documentation
schubart 5770d40
More compact `use` statements
schubart cb3bb8a
Add tests that show: insert() can be a read or not
schubart fbb7fd5
Add test for an interesting edge case
schubart 85ad8a6
Avoid false positives from extension traits
schubart 4ee6553
Add test where container is passed to a function
schubart File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
use clippy_utils::diagnostics::span_lint; | ||
use clippy_utils::get_enclosing_block; | ||
use clippy_utils::get_parent_node; | ||
use clippy_utils::path_to_local_id; | ||
use clippy_utils::ty::is_type_diagnostic_item; | ||
use clippy_utils::visitors::for_each_expr_with_closures; | ||
use core::ops::ControlFlow; | ||
use rustc_hir::Block; | ||
use rustc_hir::ExprKind; | ||
use rustc_hir::HirId; | ||
use rustc_hir::Local; | ||
use rustc_hir::Node; | ||
use rustc_hir::PatKind; | ||
use rustc_lint::LateContext; | ||
use rustc_lint::LateLintPass; | ||
use rustc_session::declare_lint_pass; | ||
use rustc_session::declare_tool_lint; | ||
use rustc_span::symbol::sym; | ||
use rustc_span::Symbol; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Checks for collections that are never queried. | ||
/// | ||
/// ### Why is this bad? | ||
/// Putting effort into constructing a collection but then never querying it might indicate that | ||
/// the author forgot to do whatever they intended to do with the collection. Example: Clone | ||
/// a vector, sort it for iteration, but then mistakenly iterate the original vector | ||
/// instead. | ||
/// | ||
/// ### Example | ||
/// ```rust | ||
/// # let samples = vec![3, 1, 2]; | ||
/// let mut sorted_samples = samples.clone(); | ||
/// sorted_samples.sort(); | ||
/// for sample in &samples { // Oops, meant to use `sorted_samples`. | ||
/// println!("{sample}"); | ||
/// } | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// # let samples = vec![3, 1, 2]; | ||
/// let mut sorted_samples = samples.clone(); | ||
/// sorted_samples.sort(); | ||
/// for sample in &sorted_samples { | ||
/// println!("{sample}"); | ||
/// } | ||
/// ``` | ||
#[clippy::version = "1.69.0"] | ||
pub COLLECTION_IS_NEVER_READ, | ||
nursery, | ||
"a collection is never queried" | ||
} | ||
declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]); | ||
|
||
static COLLECTIONS: [Symbol; 10] = [ | ||
sym::BTreeMap, | ||
sym::BTreeSet, | ||
sym::BinaryHeap, | ||
sym::HashMap, | ||
sym::HashSet, | ||
sym::LinkedList, | ||
sym::Option, | ||
sym::String, | ||
sym::Vec, | ||
sym::VecDeque, | ||
]; | ||
|
||
impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead { | ||
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { | ||
// Look for local variables whose type is a container. Search surrounding bock for read access. | ||
let ty = cx.typeck_results().pat_ty(local.pat); | ||
if COLLECTIONS.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym)) | ||
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind | ||
&& let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id) | ||
&& has_no_read_access(cx, local_id, enclosing_block) | ||
{ | ||
span_lint(cx, COLLECTION_IS_NEVER_READ, local.span, "collection is never read"); | ||
} | ||
} | ||
} | ||
|
||
fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool { | ||
let mut has_access = false; | ||
let mut has_read_access = false; | ||
|
||
// Inspect all expressions and sub-expressions in the block. | ||
for_each_expr_with_closures(cx, block, |expr| { | ||
// Ignore expressions that are not simply `id`. | ||
if !path_to_local_id(expr, id) { | ||
return ControlFlow::Continue(()); | ||
} | ||
|
||
// `id` is being accessed. Investigate if it's a read access. | ||
has_access = true; | ||
|
||
// `id` appearing in the left-hand side of an assignment is not a read access: | ||
// | ||
// id = ...; // Not reading `id`. | ||
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) | ||
&& let ExprKind::Assign(lhs, ..) = parent.kind | ||
&& path_to_local_id(lhs, id) | ||
{ | ||
return ControlFlow::Continue(()); | ||
} | ||
|
||
// Method call on `id` in a statement ignores any return value, so it's not a read access: | ||
// | ||
// id.foo(...); // Not reading `id`. | ||
if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id) | ||
&& let ExprKind::MethodCall(_, receiver, _, _) = parent.kind | ||
&& path_to_local_id(receiver, id) | ||
&& let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) | ||
{ | ||
return ControlFlow::Continue(()); | ||
} | ||
|
||
// Any other access to `id` is a read access. Stop searching. | ||
has_read_access = true; | ||
ControlFlow::Break(()) | ||
}); | ||
|
||
// Ignore collections that have no access at all. Other lints should catch them. | ||
has_access && !has_read_access | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
#![allow(unused)] | ||
#![warn(clippy::collection_is_never_read)] | ||
|
||
use std::collections::HashMap; | ||
|
||
fn main() {} | ||
schubart marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
fn not_a_collection() { | ||
// TODO: Expand `collection_is_never_read` beyond collections? | ||
let mut x = 10; // Ok | ||
x += 1; | ||
} | ||
|
||
fn no_access_at_all() { | ||
// Other lints should catch this. | ||
let x = vec![1, 2, 3]; // Ok | ||
} | ||
|
||
fn write_without_read() { | ||
// The main use case for `collection_is_never_read`. | ||
let mut x = HashMap::new(); // WARNING | ||
x.insert(1, 2); | ||
} | ||
|
||
fn read_without_write() { | ||
let mut x = vec![1, 2, 3]; // Ok | ||
let _ = x.len(); | ||
} | ||
|
||
fn write_and_read() { | ||
let mut x = vec![1, 2, 3]; // Ok | ||
x.push(4); | ||
let _ = x.len(); | ||
} | ||
|
||
fn write_after_read() { | ||
// TODO: Warn here, but this requires more extensive data flow analysis. | ||
let mut x = vec![1, 2, 3]; // Ok | ||
let _ = x.len(); | ||
x.push(4); // Pointless | ||
} | ||
|
||
fn write_before_reassign() { | ||
// TODO: Warn here, but this requires more extensive data flow analysis. | ||
let mut x = HashMap::new(); // Ok | ||
x.insert(1, 2); // Pointless | ||
x = HashMap::new(); | ||
let _ = x.len(); | ||
} | ||
|
||
fn read_in_closure() { | ||
let mut x = HashMap::new(); // Ok | ||
x.insert(1, 2); | ||
let _ = || { | ||
let _ = x.len(); | ||
}; | ||
} | ||
|
||
fn write_in_closure() { | ||
let mut x = vec![1, 2, 3]; // WARNING | ||
let _ = || { | ||
x.push(4); | ||
}; | ||
} | ||
|
||
fn read_in_format() { | ||
let mut x = HashMap::new(); // Ok | ||
x.insert(1, 2); | ||
format!("{x:?}"); | ||
} | ||
|
||
fn shadowing_1() { | ||
let x = HashMap::<usize, usize>::new(); // Ok | ||
let _ = x.len(); | ||
let mut x = HashMap::new(); // WARNING | ||
x.insert(1, 2); | ||
} | ||
|
||
fn shadowing_2() { | ||
let mut x = HashMap::new(); // WARNING | ||
x.insert(1, 2); | ||
let x = HashMap::<usize, usize>::new(); // Ok | ||
let _ = x.len(); | ||
} | ||
|
||
#[allow(clippy::let_unit_value)] | ||
fn fake_read() { | ||
let mut x = vec![1, 2, 3]; // Ok | ||
x.reverse(); | ||
// `collection_is_never_read` gets fooled, but other lints should catch this. | ||
let _: () = x.clear(); | ||
} | ||
|
||
fn assignment() { | ||
let mut x = vec![1, 2, 3]; // WARNING | ||
let y = vec![4, 5, 6]; // Ok | ||
x = y; | ||
} | ||
|
||
#[allow(clippy::self_assignment)] | ||
fn self_assignment() { | ||
let mut x = vec![1, 2, 3]; // WARNING | ||
x = x; | ||
} | ||
|
||
fn method_argument_but_not_target() { | ||
struct MyStruct; | ||
impl MyStruct { | ||
fn my_method(&self, _argument: &[usize]) {} | ||
} | ||
let my_struct = MyStruct; | ||
|
||
let mut x = vec![1, 2, 3]; // Ok | ||
x.reverse(); | ||
my_struct.my_method(&x); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
error: collection is never read | ||
--> $DIR/collection_is_never_read.rs:21:5 | ||
| | ||
LL | let mut x = HashMap::new(); // WARNING | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | ||
= note: `-D clippy::collection-is-never-read` implied by `-D warnings` | ||
|
||
error: collection is never read | ||
--> $DIR/collection_is_never_read.rs:60:5 | ||
| | ||
LL | let mut x = vec![1, 2, 3]; // WARNING | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: collection is never read | ||
--> $DIR/collection_is_never_read.rs:75:5 | ||
| | ||
LL | let mut x = HashMap::new(); // WARNING | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: collection is never read | ||
--> $DIR/collection_is_never_read.rs:80:5 | ||
| | ||
LL | let mut x = HashMap::new(); // WARNING | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: collection is never read | ||
--> $DIR/collection_is_never_read.rs:95:5 | ||
| | ||
LL | let mut x = vec![1, 2, 3]; // WARNING | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: collection is never read | ||
--> $DIR/collection_is_never_read.rs:102:5 | ||
| | ||
LL | let mut x = vec![1, 2, 3]; // WARNING | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: aborting due to 6 previous errors | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.