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