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