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