]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/unnecessary_iter_cloned.rs
Split out `infalliable_detructuring_match`
[rust.git] / clippy_lints / src / methods / unnecessary_iter_cloned.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::higher::ForLoop;
3 use clippy_utils::source::snippet_opt;
4 use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait};
5 use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage};
6 use rustc_errors::Applicability;
7 use rustc_hir::intravisit::{walk_expr, Visitor};
8 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat};
9 use rustc_lint::LateContext;
10 use rustc_middle::hir::nested_filter;
11 use rustc_middle::ty;
12 use rustc_span::{sym, Symbol};
13
14 use super::UNNECESSARY_TO_OWNED;
15
16 pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
17     if_chain! {
18         if let Some(parent) = get_parent_expr(cx, expr);
19         if let Some(callee_def_id) = fn_def_id(cx, parent);
20         if is_into_iter(cx, callee_def_id);
21         then {
22             check_for_loop_iter(cx, parent, method_name, receiver, false)
23         } else {
24             false
25         }
26     }
27 }
28
29 /// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the
30 /// iterated-over items could be iterated over by reference. The reason why `check` above does not
31 /// include this code directly is so that it can be called from
32 /// `unnecessary_into_owned::check_into_iter_call_arg`.
33 pub fn check_for_loop_iter(
34     cx: &LateContext<'_>,
35     expr: &Expr<'_>,
36     method_name: Symbol,
37     receiver: &Expr<'_>,
38     cloned_before_iter: bool,
39 ) -> bool {
40     if_chain! {
41         if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent));
42         if let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent);
43         let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body);
44         if !clone_or_copy_needed;
45         if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
46         then {
47             let snippet = if_chain! {
48                 if let ExprKind::MethodCall(maybe_iter_method_name, [collection], _) = receiver.kind;
49                 if maybe_iter_method_name.ident.name == sym::iter;
50
51                 if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
52                 let receiver_ty = cx.typeck_results().expr_ty(receiver);
53                 if implements_trait(cx, receiver_ty, iterator_trait_id, &[]);
54                 if let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty);
55
56                 if let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator);
57                 let collection_ty = cx.typeck_results().expr_ty(collection);
58                 if implements_trait(cx, collection_ty, into_iterator_trait_id, &[]);
59                 if let Some(into_iter_item_ty) = get_associated_type(cx, collection_ty, into_iterator_trait_id, "Item");
60
61                 if iter_item_ty == into_iter_item_ty;
62                 if let Some(collection_snippet) = snippet_opt(cx, collection.span);
63                 then {
64                     collection_snippet
65                 } else {
66                     receiver_snippet
67                 }
68             };
69             span_lint_and_then(
70                 cx,
71                 UNNECESSARY_TO_OWNED,
72                 expr.span,
73                 &format!("unnecessary use of `{}`", method_name),
74                 |diag| {
75                     // If `check_into_iter_call_arg` called `check_for_loop_iter` because a call to
76                     // a `to_owned`-like function was removed, then the next suggestion may be
77                     // incorrect. This is because the iterator that results from the call's removal
78                     // could hold a reference to a resource that is used mutably. See
79                     // https://github.com/rust-lang/rust-clippy/issues/8148.
80                     let applicability = if cloned_before_iter {
81                         Applicability::MaybeIncorrect
82                     } else {
83                         Applicability::MachineApplicable
84                     };
85                     diag.span_suggestion(expr.span, "use", snippet, applicability);
86                     for addr_of_expr in addr_of_exprs {
87                         match addr_of_expr.kind {
88                             ExprKind::AddrOf(_, _, referent) => {
89                                 let span = addr_of_expr.span.with_hi(referent.span.lo());
90                                 diag.span_suggestion(span, "remove this `&`", String::new(), applicability);
91                             }
92                             _ => unreachable!(),
93                         }
94                     }
95                 }
96             );
97             return true;
98         }
99     }
100     false
101 }
102
103 /// The core logic of `check_for_loop_iter` above, this function wraps a use of
104 /// `CloneOrCopyVisitor`.
105 fn clone_or_copy_needed<'tcx>(
106     cx: &LateContext<'tcx>,
107     pat: &Pat<'tcx>,
108     body: &'tcx Expr<'tcx>,
109 ) -> (bool, Vec<&'tcx Expr<'tcx>>) {
110     let mut visitor = CloneOrCopyVisitor {
111         cx,
112         binding_hir_ids: pat_bindings(pat),
113         clone_or_copy_needed: false,
114         addr_of_exprs: Vec::new(),
115     };
116     visitor.visit_expr(body);
117     (visitor.clone_or_copy_needed, visitor.addr_of_exprs)
118 }
119
120 /// Returns a vector of all `HirId`s bound by the pattern.
121 fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
122     let mut collector = usage::ParamBindingIdCollector {
123         binding_hir_ids: Vec::new(),
124     };
125     collector.visit_pat(pat);
126     collector.binding_hir_ids
127 }
128
129 /// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
130 /// operations performed on `binding_hir_ids` are:
131 /// * to take non-mutable references to them
132 /// * to use them as non-mutable `&self` in method calls
133 /// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
134 /// when `CloneOrCopyVisitor` is done visiting.
135 struct CloneOrCopyVisitor<'cx, 'tcx> {
136     cx: &'cx LateContext<'tcx>,
137     binding_hir_ids: Vec<HirId>,
138     clone_or_copy_needed: bool,
139     addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
140 }
141
142 impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
143     type NestedFilter = nested_filter::OnlyBodies;
144
145     fn nested_visit_map(&mut self) -> Self::Map {
146         self.cx.tcx.hir()
147     }
148
149     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
150         walk_expr(self, expr);
151         if self.is_binding(expr) {
152             if let Some(parent) = get_parent_expr(self.cx, expr) {
153                 match parent.kind {
154                     ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
155                         self.addr_of_exprs.push(parent);
156                         return;
157                     },
158                     ExprKind::MethodCall(_, args, _) => {
159                         if_chain! {
160                             if args.iter().skip(1).all(|arg| !self.is_binding(arg));
161                             if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
162                             let method_ty = self.cx.tcx.type_of(method_def_id);
163                             let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
164                             if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
165                             then {
166                                 return;
167                             }
168                         }
169                     },
170                     _ => {},
171                 }
172             }
173             self.clone_or_copy_needed = true;
174         }
175     }
176 }
177
178 impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
179     fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
180         self.binding_hir_ids
181             .iter()
182             .any(|hir_id| path_to_local_id(expr, *hir_id))
183     }
184 }
185
186 /// Returns true if the named method is `IntoIterator::into_iter`.
187 pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool {
188     cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id)
189 }