]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/usage.rs
Rollup merge of #81588 - xfix:delete-doc-alias, r=Mark-Simulacrum
[rust.git] / src / tools / clippy / clippy_lints / src / utils / usage.rs
1 use crate::utils;
2 use crate::utils::match_var;
3 use rustc_data_structures::fx::FxHashSet;
4 use rustc_hir as hir;
5 use rustc_hir::def::Res;
6 use rustc_hir::intravisit;
7 use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
8 use rustc_hir::{Expr, ExprKind, HirId, Path};
9 use rustc_infer::infer::TyCtxtInferExt;
10 use rustc_lint::LateContext;
11 use rustc_middle::hir::map::Map;
12 use rustc_middle::ty;
13 use rustc_span::symbol::{Ident, Symbol};
14 use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
15
16 /// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
17 pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<FxHashSet<HirId>> {
18     let mut delegate = MutVarsDelegate {
19         used_mutably: FxHashSet::default(),
20         skip: false,
21     };
22     cx.tcx.infer_ctxt().enter(|infcx| {
23         ExprUseVisitor::new(
24             &mut delegate,
25             &infcx,
26             expr.hir_id.owner,
27             cx.param_env,
28             cx.typeck_results(),
29         )
30         .walk_expr(expr);
31     });
32
33     if delegate.skip {
34         return None;
35     }
36     Some(delegate.used_mutably)
37 }
38
39 pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
40     if let Res::Local(id) = variable.res {
41         mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id))
42     } else {
43         true
44     }
45 }
46
47 struct MutVarsDelegate {
48     used_mutably: FxHashSet<HirId>,
49     skip: bool,
50 }
51
52 impl<'tcx> MutVarsDelegate {
53     #[allow(clippy::similar_names)]
54     fn update(&mut self, cat: &PlaceWithHirId<'tcx>) {
55         match cat.place.base {
56             PlaceBase::Local(id) => {
57                 self.used_mutably.insert(id);
58             },
59             PlaceBase::Upvar(_) => {
60                 //FIXME: This causes false negatives. We can't get the `NodeId` from
61                 //`Categorization::Upvar(_)`. So we search for any `Upvar`s in the
62                 //`while`-body, not just the ones in the condition.
63                 self.skip = true
64             },
65             _ => {},
66         }
67     }
68 }
69
70 impl<'tcx> Delegate<'tcx> for MutVarsDelegate {
71     fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {}
72
73     fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, bk: ty::BorrowKind) {
74         if let ty::BorrowKind::MutBorrow = bk {
75             self.update(&cmt)
76         }
77     }
78
79     fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) {
80         self.update(&cmt)
81     }
82 }
83
84 pub struct UsedVisitor {
85     pub var: Symbol, // var to look for
86     pub used: bool,  // has the var been used otherwise?
87 }
88
89 impl<'tcx> Visitor<'tcx> for UsedVisitor {
90     type Map = Map<'tcx>;
91
92     fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
93         if match_var(expr, self.var) {
94             self.used = true;
95         } else {
96             walk_expr(self, expr);
97         }
98     }
99
100     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
101         NestedVisitorMap::None
102     }
103 }
104
105 pub fn is_unused<'tcx>(ident: &'tcx Ident, body: &'tcx Expr<'_>) -> bool {
106     let mut visitor = UsedVisitor {
107         var: ident.name,
108         used: false,
109     };
110     walk_expr(&mut visitor, body);
111     !visitor.used
112 }
113
114 pub struct ParamBindingIdCollector {
115     binding_hir_ids: Vec<hir::HirId>,
116 }
117 impl<'tcx> ParamBindingIdCollector {
118     fn collect_binding_hir_ids(body: &'tcx hir::Body<'tcx>) -> Vec<hir::HirId> {
119         let mut hir_ids: Vec<hir::HirId> = Vec::new();
120         for param in body.params.iter() {
121             let mut finder = ParamBindingIdCollector {
122                 binding_hir_ids: Vec::new(),
123             };
124             finder.visit_param(param);
125             for hir_id in &finder.binding_hir_ids {
126                 hir_ids.push(*hir_id);
127             }
128         }
129         hir_ids
130     }
131 }
132 impl<'tcx> intravisit::Visitor<'tcx> for ParamBindingIdCollector {
133     type Map = Map<'tcx>;
134
135     fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
136         if let hir::PatKind::Binding(_, hir_id, ..) = pat.kind {
137             self.binding_hir_ids.push(hir_id);
138         }
139         intravisit::walk_pat(self, pat);
140     }
141
142     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
143         intravisit::NestedVisitorMap::None
144     }
145 }
146
147 pub struct BindingUsageFinder<'a, 'tcx> {
148     cx: &'a LateContext<'tcx>,
149     binding_ids: Vec<hir::HirId>,
150     usage_found: bool,
151 }
152 impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> {
153     pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool {
154         let mut finder = BindingUsageFinder {
155             cx,
156             binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body),
157             usage_found: false,
158         };
159         finder.visit_body(body);
160         finder.usage_found
161     }
162 }
163 impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
164     type Map = Map<'tcx>;
165
166     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
167         if !self.usage_found {
168             intravisit::walk_expr(self, expr);
169         }
170     }
171
172     fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, _: hir::HirId) {
173         if let hir::def::Res::Local(id) = path.res {
174             if self.binding_ids.contains(&id) {
175                 self.usage_found = true;
176             }
177         }
178     }
179
180     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
181         intravisit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
182     }
183 }
184
185 struct ReturnBreakContinueMacroVisitor {
186     seen_return_break_continue: bool,
187 }
188
189 impl ReturnBreakContinueMacroVisitor {
190     fn new() -> ReturnBreakContinueMacroVisitor {
191         ReturnBreakContinueMacroVisitor {
192             seen_return_break_continue: false,
193         }
194     }
195 }
196
197 impl<'tcx> Visitor<'tcx> for ReturnBreakContinueMacroVisitor {
198     type Map = Map<'tcx>;
199     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
200         NestedVisitorMap::None
201     }
202
203     fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
204         if self.seen_return_break_continue {
205             // No need to look farther if we've already seen one of them
206             return;
207         }
208         match &ex.kind {
209             ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
210                 self.seen_return_break_continue = true;
211             },
212             // Something special could be done here to handle while or for loop
213             // desugaring, as this will detect a break if there's a while loop
214             // or a for loop inside the expression.
215             _ => {
216                 if utils::in_macro(ex.span) {
217                     self.seen_return_break_continue = true;
218                 } else {
219                     rustc_hir::intravisit::walk_expr(self, ex);
220                 }
221             },
222         }
223     }
224 }
225
226 pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
227     let mut recursive_visitor = ReturnBreakContinueMacroVisitor::new();
228     recursive_visitor.visit_expr(expression);
229     recursive_visitor.seen_return_break_continue
230 }