]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/loops/same_item_push.rs
Rollup merge of #82826 - pierwill:fix-IPv, r=JohnTitor
[rust.git] / src / tools / clippy / clippy_lints / src / loops / same_item_push.rs
1 use super::SAME_ITEM_PUSH;
2 use crate::utils::{implements_trait, is_type_diagnostic_item, snippet_with_macro_callsite, span_lint_and_help};
3 use if_chain::if_chain;
4 use rustc_hir::def::{DefKind, Res};
5 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
6 use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Node, Pat, PatKind, Stmt, StmtKind};
7 use rustc_lint::LateContext;
8 use rustc_middle::hir::map::Map;
9 use rustc_span::symbol::sym;
10 use std::iter::Iterator;
11
12 /// Detects for loop pushing the same item into a Vec
13 pub(super) fn check<'tcx>(
14     cx: &LateContext<'tcx>,
15     pat: &'tcx Pat<'_>,
16     _: &'tcx Expr<'_>,
17     body: &'tcx Expr<'_>,
18     _: &'tcx Expr<'_>,
19 ) {
20     fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
21         let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
22         let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
23
24         span_lint_and_help(
25             cx,
26             SAME_ITEM_PUSH,
27             vec.span,
28             "it looks like the same item is being pushed into this Vec",
29             None,
30             &format!(
31                 "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})",
32                 item_str, vec_str, item_str
33             ),
34         )
35     }
36
37     if !matches!(pat.kind, PatKind::Wild) {
38         return;
39     }
40
41     // Determine whether it is safe to lint the body
42     let mut same_item_push_visitor = SameItemPushVisitor {
43         should_lint: true,
44         vec_push: None,
45         cx,
46     };
47     walk_expr(&mut same_item_push_visitor, body);
48     if same_item_push_visitor.should_lint {
49         if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push {
50             let vec_ty = cx.typeck_results().expr_ty(vec);
51             let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
52             if cx
53                 .tcx
54                 .lang_items()
55                 .clone_trait()
56                 .map_or(false, |id| implements_trait(cx, ty, id, &[]))
57             {
58                 // Make sure that the push does not involve possibly mutating values
59                 match pushed_item.kind {
60                     ExprKind::Path(ref qpath) => {
61                         match cx.qpath_res(qpath, pushed_item.hir_id) {
62                             // immutable bindings that are initialized with literal or constant
63                             Res::Local(hir_id) => {
64                                 if_chain! {
65                                     let node = cx.tcx.hir().get(hir_id);
66                                     if let Node::Binding(pat) = node;
67                                     if let PatKind::Binding(bind_ann, ..) = pat.kind;
68                                     if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
69                                     let parent_node = cx.tcx.hir().get_parent_node(hir_id);
70                                     if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node);
71                                     if let Some(init) = parent_let_expr.init;
72                                     then {
73                                         match init.kind {
74                                             // immutable bindings that are initialized with literal
75                                             ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
76                                             // immutable bindings that are initialized with constant
77                                             ExprKind::Path(ref path) => {
78                                                 if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
79                                                     emit_lint(cx, vec, pushed_item);
80                                                 }
81                                             }
82                                             _ => {},
83                                         }
84                                     }
85                                 }
86                             },
87                             // constant
88                             Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
89                             _ => {},
90                         }
91                     },
92                     ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
93                     _ => {},
94                 }
95             }
96         }
97     }
98 }
99
100 // Scans the body of the for loop and determines whether lint should be given
101 struct SameItemPushVisitor<'a, 'tcx> {
102     should_lint: bool,
103     // this field holds the last vec push operation visited, which should be the only push seen
104     vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
105     cx: &'a LateContext<'tcx>,
106 }
107
108 impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
109     type Map = Map<'tcx>;
110
111     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
112         match &expr.kind {
113             // Non-determinism may occur ... don't give a lint
114             ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false,
115             ExprKind::Block(block, _) => self.visit_block(block),
116             _ => {},
117         }
118     }
119
120     fn visit_block(&mut self, b: &'tcx Block<'_>) {
121         for stmt in b.stmts.iter() {
122             self.visit_stmt(stmt);
123         }
124     }
125
126     fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) {
127         let vec_push_option = get_vec_push(self.cx, s);
128         if vec_push_option.is_none() {
129             // Current statement is not a push so visit inside
130             match &s.kind {
131                 StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr),
132                 _ => {},
133             }
134         } else {
135             // Current statement is a push ...check whether another
136             // push had been previously done
137             if self.vec_push.is_none() {
138                 self.vec_push = vec_push_option;
139             } else {
140                 // There are multiple pushes ... don't lint
141                 self.should_lint = false;
142             }
143         }
144     }
145
146     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
147         NestedVisitorMap::None
148     }
149 }
150
151 // Given some statement, determine if that statement is a push on a Vec. If it is, return
152 // the Vec being pushed into and the item being pushed
153 fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
154     if_chain! {
155             // Extract method being called
156             if let StmtKind::Semi(semi_stmt) = &stmt.kind;
157             if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind;
158             // Figure out the parameters for the method call
159             if let Some(self_expr) = args.get(0);
160             if let Some(pushed_item) = args.get(1);
161             // Check that the method being called is push() on a Vec
162             if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type);
163             if path.ident.name.as_str() == "push";
164             then {
165                 return Some((self_expr, pushed_item))
166             }
167     }
168     None
169 }