• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use clippy_utils::diagnostics::span_lint;
2 use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
3 use clippy_utils::visitors::for_each_expr_with_closures;
4 use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id};
5 use core::ops::ControlFlow;
6 use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind};
7 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_session::{declare_lint_pass, declare_tool_lint};
9 use rustc_span::symbol::sym;
10 use rustc_span::Symbol;
11 
12 declare_clippy_lint! {
13     /// ### What it does
14     /// Checks for collections that are never queried.
15     ///
16     /// ### Why is this bad?
17     /// Putting effort into constructing a collection but then never querying it might indicate that
18     /// the author forgot to do whatever they intended to do with the collection. Example: Clone
19     /// a vector, sort it for iteration, but then mistakenly iterate the original vector
20     /// instead.
21     ///
22     /// ### Example
23     /// ```rust
24     /// # let samples = vec![3, 1, 2];
25     /// let mut sorted_samples = samples.clone();
26     /// sorted_samples.sort();
27     /// for sample in &samples { // Oops, meant to use `sorted_samples`.
28     ///     println!("{sample}");
29     /// }
30     /// ```
31     /// Use instead:
32     /// ```rust
33     /// # let samples = vec![3, 1, 2];
34     /// let mut sorted_samples = samples.clone();
35     /// sorted_samples.sort();
36     /// for sample in &sorted_samples {
37     ///     println!("{sample}");
38     /// }
39     /// ```
40     #[clippy::version = "1.70.0"]
41     pub COLLECTION_IS_NEVER_READ,
42     nursery,
43     "a collection is never queried"
44 }
45 declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]);
46 
47 // Add `String` here when it is added to diagnostic items
48 static COLLECTIONS: [Symbol; 9] = [
49     sym::BTreeMap,
50     sym::BTreeSet,
51     sym::BinaryHeap,
52     sym::HashMap,
53     sym::HashSet,
54     sym::LinkedList,
55     sym::Option,
56     sym::Vec,
57     sym::VecDeque,
58 ];
59 
60 impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>)61     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
62         // Look for local variables whose type is a container. Search surrounding bock for read access.
63         if match_acceptable_type(cx, local, &COLLECTIONS)
64             && let PatKind::Binding(_, local_id, _, _) = local.pat.kind
65             && let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id)
66             && has_no_read_access(cx, local_id, enclosing_block)
67         {
68             span_lint(cx, COLLECTION_IS_NEVER_READ, local.span, "collection is never read");
69         }
70     }
71 }
72 
match_acceptable_type(cx: &LateContext<'_>, local: &Local<'_>, collections: &[rustc_span::Symbol]) -> bool73 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 
has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool80 fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool {
81     let mut has_access = false;
82     let mut has_read_access = false;
83 
84     // Inspect all expressions and sub-expressions in the block.
85     for_each_expr_with_closures(cx, block, |expr| {
86         // Ignore expressions that are not simply `id`.
87         if !path_to_local_id(expr, id) {
88             return ControlFlow::Continue(());
89         }
90 
91         // `id` is being accessed. Investigate if it's a read access.
92         has_access = true;
93 
94         // `id` appearing in the left-hand side of an assignment is not a read access:
95         //
96         // id = ...; // Not reading `id`.
97         if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
98             && let ExprKind::Assign(lhs, ..) = parent.kind
99             && path_to_local_id(lhs, id)
100         {
101             return ControlFlow::Continue(());
102         }
103 
104         // Look for method call with receiver `id`. It might be a non-read access:
105         //
106         // id.foo(args)
107         //
108         // Only assuming this for "official" methods defined on the type. For methods defined in extension
109         // traits (identified as local, based on the orphan rule), pessimistically assume that they might
110         // have side effects, so consider them a read.
111         if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
112             && let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
113             && path_to_local_id(receiver, id)
114             && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
115             && !method_def_id.is_local()
116         {
117             // The method call is a statement, so the return value is not used. That's not a read access:
118             //
119             // id.foo(args);
120             if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) {
121                 return ControlFlow::Continue(());
122             }
123 
124             // The method call is not a statement, so its return value is used somehow but its type is the
125             // unit type, so this is not a real read access. Examples:
126             //
127             // let y = x.clear();
128             // println!("{:?}", x.clear());
129             if cx.typeck_results().expr_ty(parent).is_unit() {
130                 return ControlFlow::Continue(());
131             }
132         }
133 
134         // Any other access to `id` is a read access. Stop searching.
135         has_read_access = true;
136         ControlFlow::Break(())
137     });
138 
139     // Ignore collections that have no access at all. Other lints should catch them.
140     has_access && !has_read_access
141 }
142