]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/only_used_in_recursion.rs
fix lint errors
[rust.git] / clippy_lints / src / only_used_in_recursion.rs
1 use std::collections::VecDeque;
2
3 use clippy_utils::diagnostics::span_lint_and_sugg;
4 use itertools::{izip, Itertools};
5 use rustc_ast::{walk_list, Label, Mutability};
6 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
7 use rustc_errors::Applicability;
8 use rustc_hir::def::Res;
9 use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
10 use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
11 use rustc_hir::{
12     Arm, Block, Body, Expr, ExprKind, Guard, HirId, Let, Local, Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind,
13     UnOp,
14 };
15 use rustc_lint::{LateContext, LateLintPass};
16 use rustc_middle::ty;
17 use rustc_middle::ty::{Ty, TyCtxt, TypeckResults};
18 use rustc_session::{declare_lint_pass, declare_tool_lint};
19 use rustc_span::symbol::kw;
20 use rustc_span::symbol::Ident;
21 use rustc_span::Span;
22
23 declare_clippy_lint! {
24     /// ### What it does
25     /// Checks for arguments that are only used in recursion with no side-effects.
26     ///
27     /// ### Why is this bad?
28     /// It could contain a useless calculation and can make function simpler.
29     ///
30     /// The arguments can be involved in calculations and assignments but as long as
31     /// the calculations have no side-effects (function calls or mutating dereference)
32     /// and the assigned variables are also only in recursion, it is useless.
33     ///
34     /// ### Known problems
35     /// In some cases, this would not catch all useless arguments.
36     ///
37     /// ```rust
38     /// fn foo(a: usize, b: usize) -> usize {
39     ///     let f = |x| x + 1;
40     ///
41     ///     if a == 0 {
42     ///         1
43     ///     } else {
44     ///         foo(a - 1, f(b))
45     ///     }
46     /// }
47     /// ```
48     ///
49     /// For example, the argument `b` is only used in recursion, but the lint would not catch it.
50     ///
51     /// List of some examples that can not be caught:
52     /// - binary operation of non-primitive types
53     /// - closure usage
54     /// - some `break` relative operations
55     /// - struct pattern binding
56     ///
57     /// Also, when you recurse the function name with path segments, it is not possible to detect.
58     ///
59     /// ### Example
60     /// ```rust
61     /// fn f(a: usize, b: usize) -> usize {
62     ///     if a == 0 {
63     ///         1
64     ///     } else {
65     ///         f(a - 1, b + 1)
66     ///     }
67     /// }
68     /// # fn main() {
69     /// #     print!("{}", f(1, 1));
70     /// # }
71     /// ```
72     /// Use instead:
73     /// ```rust
74     /// fn f(a: usize) -> usize {
75     ///     if a == 0 {
76     ///         1
77     ///     } else {
78     ///         f(a - 1)
79     ///     }
80     /// }
81     /// # fn main() {
82     /// #     print!("{}", f(1));
83     /// # }
84     /// ```
85     #[clippy::version = "1.60.0"]
86     pub ONLY_USED_IN_RECURSION,
87     complexity,
88     "arguments that is only used in recursion can be removed"
89 }
90 declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
91
92 impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
93     fn check_fn(
94         &mut self,
95         cx: &LateContext<'tcx>,
96         kind: FnKind<'tcx>,
97         _: &'tcx rustc_hir::FnDecl<'tcx>,
98         body: &'tcx Body<'tcx>,
99         _: Span,
100         id: HirId,
101     ) {
102         if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind {
103             let data = cx.tcx.def_path(cx.tcx.hir().local_def_id(id).to_def_id()).data;
104             if data.len() > 1 {
105                 match data.get(data.len() - 2) {
106                     Some(DisambiguatedDefPathData {
107                         data: DefPathData::Impl,
108                         disambiguator,
109                     }) if *disambiguator != 0 => return,
110                     _ => {},
111                 }
112             }
113
114             let ty_res = cx.typeck_results();
115             let param_span = body
116                 .params
117                 .iter()
118                 .flat_map(|param| {
119                     let mut v = Vec::new();
120                     param.pat.each_binding(|_, hir_id, span, ident| {
121                         v.push((hir_id, span, ident));
122                     });
123                     v
124                 })
125                 .skip(match kind {
126                     FnKind::Method(..) => 1,
127                     _ => 0,
128                 })
129                 .filter(|(_, _, ident)| !ident.name.as_str().starts_with('_'))
130                 .collect_vec();
131
132             let params = body.params.iter().map(|param| param.pat).collect();
133
134             let mut visitor = SideEffectVisit {
135                 graph: FxHashMap::default(),
136                 has_side_effect: FxHashSet::default(),
137                 ret_vars: Vec::new(),
138                 contains_side_effect: false,
139                 break_vars: FxHashMap::default(),
140                 params,
141                 fn_ident: ident,
142                 is_method: matches!(kind, FnKind::Method(..)),
143                 ty_res,
144                 ty_ctx: cx.tcx,
145             };
146
147             visitor.visit_expr(&body.value);
148             let vars = std::mem::take(&mut visitor.ret_vars);
149             // this would set the return variables to side effect
150             visitor.add_side_effect(vars);
151
152             let mut queue = visitor.has_side_effect.iter().copied().collect::<VecDeque<_>>();
153
154             // a simple BFS to check all the variables that have side effect
155             while let Some(id) = queue.pop_front() {
156                 if let Some(next) = visitor.graph.get(&id) {
157                     for i in next {
158                         if !visitor.has_side_effect.contains(i) {
159                             visitor.has_side_effect.insert(*i);
160                             queue.push_back(*i);
161                         }
162                     }
163                 }
164             }
165
166             for (id, span, ident) in param_span {
167                 // if the variable is not used in recursion, it would be marked as unused
168                 if !visitor.has_side_effect.contains(&id) {
169                     let mut queue = VecDeque::new();
170                     let mut visited = FxHashSet::default();
171
172                     queue.push_back(id);
173
174                     // a simple BFS to check the graph can reach to itself
175                     // if it can't, it means the variable is never used in recursion
176                     while let Some(id) = queue.pop_front() {
177                         if let Some(next) = visitor.graph.get(&id) {
178                             for i in next {
179                                 if !visited.contains(i) {
180                                     visited.insert(id);
181                                     queue.push_back(*i);
182                                 }
183                             }
184                         }
185                     }
186
187                     if visited.contains(&id) {
188                         span_lint_and_sugg(
189                             cx,
190                             ONLY_USED_IN_RECURSION,
191                             span,
192                             "parameter is only used in recursion",
193                             "if this is intentional, prefix with an underscore",
194                             format!("_{}", ident.name.as_str()),
195                             Applicability::MaybeIncorrect,
196                         );
197                     }
198                 }
199             }
200         }
201     }
202 }
203
204 pub fn is_primitive(ty: Ty<'_>) -> bool {
205     match ty.kind() {
206         ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
207         ty::Ref(_, t, _) => is_primitive(*t),
208         _ => false,
209     }
210 }
211
212 pub fn is_array(ty: Ty<'_>) -> bool {
213     match ty.kind() {
214         ty::Array(..) | ty::Slice(..) => true,
215         ty::Ref(_, t, _) => is_array(*t),
216         _ => false,
217     }
218 }
219
220 /// This builds the graph of side effect.
221 /// The edge `a -> b` means if `a` has side effect, `b` will have side effect.
222 ///
223 /// There are some exmaple in following code:
224 /// ```rust, ignore
225 /// let b = 1;
226 /// let a = b; // a -> b
227 /// let (c, d) = (a, b); // c -> b, d -> b
228 ///
229 /// let e = if a == 0 { // e -> a
230 ///     c // e -> c
231 /// } else {
232 ///     d // e -> d
233 /// };
234 /// ```
235 pub struct SideEffectVisit<'tcx> {
236     graph: FxHashMap<HirId, FxHashSet<HirId>>,
237     has_side_effect: FxHashSet<HirId>,
238     // bool for if the variable was dereferenced from mutable reference
239     ret_vars: Vec<(HirId, bool)>,
240     contains_side_effect: bool,
241     // break label
242     break_vars: FxHashMap<Ident, Vec<(HirId, bool)>>,
243     params: Vec<&'tcx Pat<'tcx>>,
244     fn_ident: Ident,
245     is_method: bool,
246     ty_res: &'tcx TypeckResults<'tcx>,
247     ty_ctx: TyCtxt<'tcx>,
248 }
249
250 impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> {
251     fn visit_block(&mut self, b: &'tcx Block<'tcx>) {
252         b.stmts.iter().for_each(|stmt| {
253             self.visit_stmt(stmt);
254             self.ret_vars.clear();
255         });
256         walk_list!(self, visit_expr, b.expr);
257     }
258
259     fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) {
260         match s.kind {
261             StmtKind::Local(Local {
262                 pat, init: Some(init), ..
263             }) => {
264                 self.visit_pat_expr(pat, init, false);
265             },
266             StmtKind::Item(i) => {
267                 let item = self.ty_ctx.hir().item(i);
268                 self.visit_item(item);
269             },
270             StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
271             StmtKind::Local(_) => {},
272         }
273     }
274
275     fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
276         debug_assert!(self.ret_vars.is_empty());
277         match ex.kind {
278             ExprKind::Array(exprs) | ExprKind::Tup(exprs) => {
279                 self.ret_vars = exprs
280                     .iter()
281                     .flat_map(|expr| {
282                         self.visit_expr(expr);
283                         std::mem::take(&mut self.ret_vars)
284                     })
285                     .collect();
286             },
287             ExprKind::Call(callee, args) => self.visit_fn(callee, args),
288             ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args),
289             ExprKind::Binary(_, lhs, rhs) => {
290                 self.visit_bin_op(lhs, rhs);
291             },
292             ExprKind::Unary(op, expr) => self.visit_un_op(op, expr),
293             ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false),
294             ExprKind::If(bind, then_expr, else_expr) => {
295                 self.visit_if(bind, then_expr, else_expr);
296             },
297             ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms),
298             // since analysing the closure is not easy, just set all variables in it to side-effect
299             ExprKind::Closure(_, _, body_id, _, _) => {
300                 let body = self.ty_ctx.hir().body(body_id);
301                 self.visit_body(body);
302                 let vars = std::mem::take(&mut self.ret_vars);
303                 self.add_side_effect(vars);
304             },
305             ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => {
306                 self.visit_block_label(block, label);
307             },
308             ExprKind::Assign(bind, expr, _) => {
309                 self.visit_assign(bind, expr);
310             },
311             ExprKind::AssignOp(_, bind, expr) => {
312                 self.visit_assign(bind, expr);
313                 self.visit_bin_op(bind, expr);
314             },
315             ExprKind::Field(expr, _) => {
316                 self.visit_expr(expr);
317                 if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
318                     self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
319                 }
320             },
321             ExprKind::Index(expr, index) => {
322                 self.visit_expr(expr);
323                 let mut vars = std::mem::take(&mut self.ret_vars);
324                 self.visit_expr(index);
325                 self.ret_vars.append(&mut vars);
326
327                 if !is_array(self.ty_res.expr_ty(expr)) {
328                     self.add_side_effect(self.ret_vars.clone());
329                 } else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
330                     self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
331                 }
332             },
333             ExprKind::Break(dest, Some(expr)) => {
334                 self.visit_expr(expr);
335                 if let Some(label) = dest.label {
336                     self.break_vars
337                         .entry(label.ident)
338                         .or_insert(Vec::new())
339                         .append(&mut self.ret_vars);
340                 }
341                 self.contains_side_effect = true;
342             },
343             ExprKind::Ret(Some(expr)) => {
344                 self.visit_expr(expr);
345                 let vars = std::mem::take(&mut self.ret_vars);
346                 self.add_side_effect(vars);
347                 self.contains_side_effect = true;
348             },
349             ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => {
350                 self.contains_side_effect = true;
351             },
352             ExprKind::Struct(_, exprs, expr) => {
353                 let mut ret_vars = exprs
354                     .iter()
355                     .flat_map(|field| {
356                         self.visit_expr(field.expr);
357                         std::mem::take(&mut self.ret_vars)
358                     })
359                     .collect();
360
361                 walk_list!(self, visit_expr, expr);
362                 self.ret_vars.append(&mut ret_vars);
363             },
364             _ => walk_expr(self, ex),
365         }
366     }
367
368     fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) {
369         if let Res::Local(id) = path.res {
370             self.ret_vars.push((id, false));
371         }
372     }
373 }
374
375 impl<'tcx> SideEffectVisit<'tcx> {
376     fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
377         // Just support array and tuple unwrapping for now.
378         //
379         // ex) `(a, b) = (c, d);`
380         // The graph would look like this:
381         //   a -> c
382         //   b -> d
383         //
384         // This would minimize the connection of the side-effect graph.
385         match (&lhs.kind, &rhs.kind) {
386             (ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => {
387                 // if not, it is a compile error
388                 debug_assert!(lhs.len() == rhs.len());
389                 izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs));
390             },
391             // in other assigns, we have to connect all each other
392             // because they can be connected somehow
393             _ => {
394                 self.visit_expr(lhs);
395                 let lhs_vars = std::mem::take(&mut self.ret_vars);
396                 self.visit_expr(rhs);
397                 let rhs_vars = std::mem::take(&mut self.ret_vars);
398                 self.connect_assign(&lhs_vars, &rhs_vars, false);
399             },
400         }
401     }
402
403     fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option<Label>) {
404         self.visit_block(block);
405         let _ = label.and_then(|label| {
406             self.break_vars
407                 .remove(&label.ident)
408                 .map(|mut break_vars| self.ret_vars.append(&mut break_vars))
409         });
410     }
411
412     fn visit_bin_op(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
413         self.visit_expr(lhs);
414         let mut ret_vars = std::mem::take(&mut self.ret_vars);
415         self.visit_expr(rhs);
416         self.ret_vars.append(&mut ret_vars);
417
418         // the binary operation between non primitive values are overloaded operators
419         // so they can have side-effects
420         if !is_primitive(self.ty_res.expr_ty(lhs)) || !is_primitive(self.ty_res.expr_ty(rhs)) {
421             self.ret_vars.iter().for_each(|id| {
422                 self.has_side_effect.insert(id.0);
423             });
424             self.contains_side_effect = true;
425         }
426     }
427
428     fn visit_un_op(&mut self, op: UnOp, expr: &'tcx Expr<'tcx>) {
429         self.visit_expr(expr);
430         let ty = self.ty_res.expr_ty(expr);
431         // dereferencing a reference has no side-effect
432         if !is_primitive(ty) && !matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(..))) {
433             self.add_side_effect(self.ret_vars.clone());
434         }
435
436         if matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(_, _, Mutability::Mut))) {
437             self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
438         }
439     }
440
441     fn visit_pat_expr(&mut self, pat: &'tcx Pat<'tcx>, expr: &'tcx Expr<'tcx>, connect_self: bool) {
442         match (&pat.kind, &expr.kind) {
443             (PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) => {
444                 self.ret_vars = izip!(*pats, *exprs)
445                     .flat_map(|(pat, expr)| {
446                         self.visit_pat_expr(pat, expr, connect_self);
447                         std::mem::take(&mut self.ret_vars)
448                     })
449                     .collect();
450             },
451             (PatKind::Slice(front_exprs, _, back_exprs), ExprKind::Array(exprs)) => {
452                 let mut vars = izip!(*front_exprs, *exprs)
453                     .flat_map(|(pat, expr)| {
454                         self.visit_pat_expr(pat, expr, connect_self);
455                         std::mem::take(&mut self.ret_vars)
456                     })
457                     .collect();
458                 self.ret_vars = izip!(back_exprs.iter().rev(), exprs.iter().rev())
459                     .flat_map(|(pat, expr)| {
460                         self.visit_pat_expr(pat, expr, connect_self);
461                         std::mem::take(&mut self.ret_vars)
462                     })
463                     .collect();
464                 self.ret_vars.append(&mut vars);
465             },
466             _ => {
467                 let mut lhs_vars = Vec::new();
468                 pat.each_binding(|_, id, _, _| lhs_vars.push((id, false)));
469                 self.visit_expr(expr);
470                 let rhs_vars = std::mem::take(&mut self.ret_vars);
471                 self.connect_assign(&lhs_vars, &rhs_vars, connect_self);
472                 self.ret_vars = rhs_vars;
473             },
474         }
475     }
476
477     fn visit_fn(&mut self, callee: &'tcx Expr<'tcx>, args: &'tcx [Expr<'tcx>]) {
478         self.visit_expr(callee);
479         let mut ret_vars = std::mem::take(&mut self.ret_vars);
480         self.add_side_effect(ret_vars.clone());
481
482         if_chain! {
483             if !self.is_method;
484             if let ExprKind::Path(QPath::Resolved(_, path)) = callee.kind;
485             if let Res::Def(..) = path.res;
486             if path.segments.len() == 1;
487             let ident = path.segments.last().unwrap().ident;
488             if ident == self.fn_ident;
489             then {
490                 izip!(self.params.clone(), args)
491                     .for_each(|(pat, expr)| {
492                         self.visit_pat_expr(pat, expr, true);
493                         self.ret_vars.clear();
494                     });
495             } else {
496                 // This would set arguments used in closure that does not have side-effect.
497                 // Closure itself can be detected whether there is a side-effect, but the
498                 // value of variable that is holding closure can change.
499                 // So, we just check the variables.
500                 self.ret_vars = args
501                     .iter()
502                     .flat_map(|expr| {
503                         self.visit_expr(expr);
504                         std::mem::take(&mut self.ret_vars)
505                     })
506                     .collect_vec()
507                     .into_iter()
508                     .map(|id| {
509                         self.has_side_effect.insert(id.0);
510                         id
511                     })
512                     .collect();
513                 self.contains_side_effect = true;
514             }
515         }
516
517         self.ret_vars.append(&mut ret_vars);
518     }
519
520     fn visit_method_call(&mut self, path: &'tcx PathSegment<'tcx>, args: &'tcx [Expr<'tcx>]) {
521         if_chain! {
522             if self.is_method;
523             if path.ident == self.fn_ident;
524             if let ExprKind::Path(QPath::Resolved(_, path)) = args.first().unwrap().kind;
525             if let Res::Local(..) = path.res;
526             let ident = path.segments.last().unwrap().ident;
527             if ident.name == kw::SelfLower;
528             then {
529                 izip!(self.params.clone(), args.iter())
530                     .for_each(|(pat, expr)| {
531                         self.visit_pat_expr(pat, expr, true);
532                         self.ret_vars.clear();
533                     });
534             } else {
535                 self.ret_vars = args
536                     .iter()
537                     .flat_map(|expr| {
538                         self.visit_expr(expr);
539                         std::mem::take(&mut self.ret_vars)
540                     })
541                     .collect_vec()
542                     .into_iter()
543                     .map(|a| {
544                         self.has_side_effect.insert(a.0);
545                         a
546                     })
547                     .collect();
548                 self.contains_side_effect = true;
549             }
550         }
551     }
552
553     fn visit_if(&mut self, bind: &'tcx Expr<'tcx>, then_expr: &'tcx Expr<'tcx>, else_expr: Option<&'tcx Expr<'tcx>>) {
554         let contains_side_effect = self.contains_side_effect;
555         self.contains_side_effect = false;
556         self.visit_expr(bind);
557         let mut vars = std::mem::take(&mut self.ret_vars);
558         self.visit_expr(then_expr);
559         let mut then_vars = std::mem::take(&mut self.ret_vars);
560         walk_list!(self, visit_expr, else_expr);
561         if self.contains_side_effect {
562             self.add_side_effect(vars.clone());
563         }
564         self.contains_side_effect |= contains_side_effect;
565         self.ret_vars.append(&mut vars);
566         self.ret_vars.append(&mut then_vars);
567     }
568
569     fn visit_match(&mut self, expr: &'tcx Expr<'tcx>, arms: &'tcx [Arm<'tcx>]) {
570         self.visit_expr(expr);
571         let mut expr_vars = std::mem::take(&mut self.ret_vars);
572         self.ret_vars = arms
573             .iter()
574             .flat_map(|arm| {
575                 let contains_side_effect = self.contains_side_effect;
576                 self.contains_side_effect = false;
577                 // this would visit `expr` multiple times
578                 // but couldn't think of a better way
579                 self.visit_pat_expr(arm.pat, expr, false);
580                 let mut vars = std::mem::take(&mut self.ret_vars);
581                 let _ = arm.guard.as_ref().map(|guard| {
582                     self.visit_expr(match guard {
583                         Guard::If(expr) | Guard::IfLet(_, expr) => expr,
584                     });
585                     vars.append(&mut self.ret_vars);
586                 });
587                 self.visit_expr(arm.body);
588                 if self.contains_side_effect {
589                     self.add_side_effect(vars.clone());
590                     self.add_side_effect(expr_vars.clone());
591                 }
592                 self.contains_side_effect |= contains_side_effect;
593                 vars.append(&mut self.ret_vars);
594                 vars
595             })
596             .collect();
597         self.ret_vars.append(&mut expr_vars);
598     }
599
600     fn connect_assign(&mut self, lhs: &[(HirId, bool)], rhs: &[(HirId, bool)], connect_self: bool) {
601         // if mutable dereference is on assignment it can have side-effect
602         // (this can lead to parameter mutable dereference and change the original value)
603         // too hard to detect whether this value is from parameter, so this would all
604         // check mutable dereference assignment to side effect
605         lhs.iter().filter(|(_, b)| *b).for_each(|(id, _)| {
606             self.has_side_effect.insert(*id);
607             self.contains_side_effect = true;
608         });
609
610         // there is no connection
611         if lhs.is_empty() || rhs.is_empty() {
612             return;
613         }
614
615         // by connected rhs in cycle, the connections would decrease
616         // from `n * m` to `n + m`
617         // where `n` and `m` are length of `lhs` and `rhs`.
618
619         // unwrap is possible since rhs is not empty
620         let rhs_first = rhs.first().unwrap();
621         for (id, _) in lhs.iter() {
622             if connect_self || *id != rhs_first.0 {
623                 self.graph
624                     .entry(*id)
625                     .or_insert_with(FxHashSet::default)
626                     .insert(rhs_first.0);
627             }
628         }
629
630         let rhs = rhs.iter();
631         izip!(rhs.clone().cycle().skip(1), rhs).for_each(|(from, to)| {
632             if connect_self || from.0 != to.0 {
633                 self.graph.entry(from.0).or_insert_with(FxHashSet::default).insert(to.0);
634             }
635         });
636     }
637
638     fn add_side_effect(&mut self, v: Vec<(HirId, bool)>) {
639         for (id, _) in v {
640             self.has_side_effect.insert(id);
641             self.contains_side_effect = true;
642         }
643     }
644 }