1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
12 * A classic liveness analysis based on dataflow over the AST. Computes,
13 * for each local variable in a function, whether that variable is live
14 * at a given point. Program execution points are identified by their
19 * The basic model is that each local variable is assigned an index. We
20 * represent sets of local variables using a vector indexed by this
21 * index. The value in the vector is either 0, indicating the variable
22 * is dead, or the id of an expression that uses the variable.
24 * We conceptually walk over the AST in reverse execution order. If we
25 * find a use of a variable, we add it to the set of live variables. If
26 * we find an assignment to a variable, we remove it from the set of live
27 * variables. When we have to merge two flows, we take the union of
28 * those two flows---if the variable is live on both paths, we simply
29 * pick one id. In the event of loops, we continue doing this until a
30 * fixed point is reached.
32 * ## Checking initialization
34 * At the function entry point, all variables must be dead. If this is
35 * not the case, we can report an error using the id found in the set of
36 * live variables, which identifies a use of the variable which is not
37 * dominated by an assignment.
41 * After each explicit move, the variable must be dead.
43 * ## Computing last uses
45 * Any use of the variable where the variable is dead afterwards is a
48 * # Implementation details
50 * The actual implementation contains two (nested) walks over the AST.
51 * The outer walk has the job of building up the ir_maps instance for the
52 * enclosing function. On the way down the tree, it identifies those AST
53 * nodes and variable IDs that will be needed for the liveness analysis
54 * and assigns them contiguous IDs. The liveness id for an AST node is
55 * called a `live_node` (it's a newtype'd uint) and the id for a variable
56 * is called a `variable` (another newtype'd uint).
58 * On the way back up the tree, as we are about to exit from a function
59 * declaration we allocate a `liveness` instance. Now that we know
60 * precisely how many nodes and variables we need, we can allocate all
61 * the various arrays that we will need to precisely the right size. We then
62 * perform the actual propagation on the `liveness` instance.
64 * This propagation is encoded in the various `propagate_through_*()`
65 * methods. It effectively does a reverse walk of the AST; whenever we
66 * reach a loop node, we iterate until a fixed point is reached.
68 * ## The `Users` struct
70 * At each live node `N`, we track three pieces of information for each
71 * variable `V` (these are encapsulated in the `Users` struct):
73 * - `reader`: the `LiveNode` ID of some node which will read the value
74 * that `V` holds on entry to `N`. Formally: a node `M` such
75 * that there exists a path `P` from `N` to `M` where `P` does not
76 * write `V`. If the `reader` is `invalid_node()`, then the current
77 * value will never be read (the variable is dead, essentially).
79 * - `writer`: the `LiveNode` ID of some node which will write the
80 * variable `V` and which is reachable from `N`. Formally: a node `M`
81 * such that there exists a path `P` from `N` to `M` and `M` writes
82 * `V`. If the `writer` is `invalid_node()`, then there is no writer
83 * of `V` that follows `N`.
85 * - `used`: a boolean value indicating whether `V` is *used*. We
86 * distinguish a *read* from a *use* in that a *use* is some read that
87 * is not just used to generate a new value. For example, `x += 1` is
88 * a read but not a use. This is used to generate better warnings.
90 * ## Special Variables
92 * We generate various special variables for various, well, special purposes.
93 * These are described in the `specials` struct:
95 * - `exit_ln`: a live node that is generated to represent every 'exit' from
96 * the function, whether it be by explicit return, fail, or other means.
98 * - `fallthrough_ln`: a live node that represents a fallthrough
100 * - `no_ret_var`: a synthetic variable that is only 'read' from, the
101 * fallthrough node. This allows us to detect functions where we fail
102 * to return explicitly.
106 use middle::lint::{UnusedVariable, DeadAssignment};
107 use middle::pat_util;
112 use std::cast::transmute;
113 use std::cell::{Cell, RefCell};
119 use collections::HashMap;
121 use syntax::codemap::Span;
122 use syntax::parse::token::special_idents;
123 use syntax::parse::token;
124 use syntax::print::pprust::{expr_to_str, block_to_str};
125 use syntax::{visit, ast_util};
126 use syntax::visit::{Visitor, FnKind};
129 struct Variable(uint);
131 struct LiveNode(uint);
134 fn get(&self) -> uint { let Variable(v) = *self; v }
138 fn get(&self) -> uint { let LiveNode(v) = *self; v }
141 impl Clone for LiveNode {
142 fn clone(&self) -> LiveNode {
155 fn live_node_kind_to_str(lnk: LiveNodeKind, cx: ty::ctxt) -> ~str {
156 let cm = cx.sess.codemap;
158 FreeVarNode(s) => format!("Free var node [{}]", cm.span_to_str(s)),
159 ExprNode(s) => format!("Expr node [{}]", cm.span_to_str(s)),
160 VarDefNode(s) => format!("Var def node [{}]", cm.span_to_str(s)),
161 ExitNode => ~"Exit node"
165 struct LivenessVisitor;
167 impl Visitor<@IrMaps> for LivenessVisitor {
168 fn visit_fn(&mut self, fk: &FnKind, fd: &FnDecl, b: &Block, s: Span, n: NodeId, e: @IrMaps) {
169 visit_fn(self, fk, fd, b, s, n, e);
171 fn visit_local(&mut self, l: &Local, e: @IrMaps) { visit_local(self, l, e); }
172 fn visit_expr(&mut self, ex: &Expr, e: @IrMaps) { visit_expr(self, ex, e); }
173 fn visit_arm(&mut self, a: &Arm, e: @IrMaps) { visit_arm(self, a, e); }
176 pub fn check_crate(tcx: ty::ctxt,
177 method_map: typeck::MethodMap,
178 capture_map: moves::CaptureMap,
180 let mut visitor = LivenessVisitor;
182 let initial_maps = @IrMaps(tcx, method_map, capture_map);
183 visit::walk_crate(&mut visitor, krate, initial_maps);
184 tcx.sess.abort_if_errors();
187 impl fmt::Show for LiveNode {
188 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189 write!(f.buf, "ln({})", self.get())
193 impl fmt::Show for Variable {
194 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
195 write!(f.buf, "v({})", self.get())
199 // ______________________________________________________________________
202 // This is the first pass and the one that drives the main
203 // computation. It walks up and down the IR once. On the way down,
204 // we count for each function the number of variables as well as
205 // liveness nodes. A liveness node is basically an expression or
206 // capture clause that does something of interest: either it has
207 // interesting control flow or it uses/defines a local variable.
209 // On the way back up, at each function node we create liveness sets
210 // (we now know precisely how big to make our various vectors and so
211 // forth) and then do the data-flow propagation to compute the set
212 // of live variables at each program point.
214 // Finally, we run back over the IR one last time and, using the
215 // computed liveness, check various safety conditions. For example,
216 // there must be no live nodes at the definition site for a variable
217 // unless it has an initializer. Similarly, each non-mutable local
218 // variable must not be assigned if there is some successor
219 // assignment. And so forth.
222 pub fn is_valid(&self) -> bool {
223 self.get() != uint::MAX
227 fn invalid_node() -> LiveNode { LiveNode(uint::MAX) }
236 FromMatch(BindingMode),
237 FromLetWithInitializer,
256 method_map: typeck::MethodMap,
257 capture_map: moves::CaptureMap,
259 num_live_nodes: Cell<uint>,
260 num_vars: Cell<uint>,
261 live_node_map: RefCell<HashMap<NodeId, LiveNode>>,
262 variable_map: RefCell<HashMap<NodeId, Variable>>,
263 capture_info_map: RefCell<HashMap<NodeId, @~[CaptureInfo]>>,
264 var_kinds: RefCell<~[VarKind]>,
265 lnks: RefCell<~[LiveNodeKind]>,
268 fn IrMaps(tcx: ty::ctxt,
269 method_map: typeck::MethodMap,
270 capture_map: moves::CaptureMap)
274 method_map: method_map,
275 capture_map: capture_map,
276 num_live_nodes: Cell::new(0),
277 num_vars: Cell::new(0),
278 live_node_map: RefCell::new(HashMap::new()),
279 variable_map: RefCell::new(HashMap::new()),
280 capture_info_map: RefCell::new(HashMap::new()),
281 var_kinds: RefCell::new(~[]),
282 lnks: RefCell::new(~[]),
287 pub fn add_live_node(&self, lnk: LiveNodeKind) -> LiveNode {
288 let num_live_nodes = self.num_live_nodes.get();
289 let ln = LiveNode(num_live_nodes);
290 let mut lnks = self.lnks.borrow_mut();
291 lnks.get().push(lnk);
292 self.num_live_nodes.set(num_live_nodes + 1);
294 debug!("{} is of kind {}", ln.to_str(),
295 live_node_kind_to_str(lnk, self.tcx));
300 pub fn add_live_node_for_node(&self, node_id: NodeId, lnk: LiveNodeKind) {
301 let ln = self.add_live_node(lnk);
302 let mut live_node_map = self.live_node_map.borrow_mut();
303 live_node_map.get().insert(node_id, ln);
305 debug!("{} is node {}", ln.to_str(), node_id);
308 pub fn add_variable(&self, vk: VarKind) -> Variable {
309 let v = Variable(self.num_vars.get());
311 let mut var_kinds = self.var_kinds.borrow_mut();
312 var_kinds.get().push(vk);
314 self.num_vars.set(self.num_vars.get() + 1);
317 Local(LocalInfo { id: node_id, .. }) | Arg(node_id, _) => {
318 let mut variable_map = self.variable_map.borrow_mut();
319 variable_map.get().insert(node_id, v);
324 debug!("{} is {:?}", v.to_str(), vk);
329 pub fn variable(&self, node_id: NodeId, span: Span) -> Variable {
330 let variable_map = self.variable_map.borrow();
331 match variable_map.get().find(&node_id) {
334 self.tcx.sess.span_bug(
335 span, format!("no variable registered for id {}", node_id));
340 pub fn variable_name(&self, var: Variable) -> ~str {
341 let var_kinds = self.var_kinds.borrow();
342 match var_kinds.get()[var.get()] {
343 Local(LocalInfo { ident: nm, .. }) | Arg(_, nm) => {
344 token::get_ident(nm).get().to_str()
346 ImplicitRet => ~"<implicit-ret>"
350 pub fn set_captures(&self, node_id: NodeId, cs: ~[CaptureInfo]) {
351 let mut capture_info_map = self.capture_info_map.borrow_mut();
352 capture_info_map.get().insert(node_id, @cs);
355 pub fn captures(&self, expr: &Expr) -> @~[CaptureInfo] {
356 let capture_info_map = self.capture_info_map.borrow();
357 match capture_info_map.get().find(&expr.id) {
360 self.tcx.sess.span_bug(expr.span, "no registered caps");
365 pub fn lnk(&self, ln: LiveNode) -> LiveNodeKind {
366 let lnks = self.lnks.borrow();
371 impl Visitor<()> for Liveness {
372 fn visit_fn(&mut self, fk: &FnKind, fd: &FnDecl, b: &Block, s: Span, n: NodeId, _: ()) {
373 check_fn(self, fk, fd, b, s, n);
375 fn visit_local(&mut self, l: &Local, _: ()) {
376 check_local(self, l);
378 fn visit_expr(&mut self, ex: &Expr, _: ()) {
379 check_expr(self, ex);
381 fn visit_arm(&mut self, a: &Arm, _: ()) {
386 fn visit_fn(v: &mut LivenessVisitor,
393 debug!("visit_fn: id={}", id);
394 let _i = ::util::common::indenter();
396 // swap in a new set of IR maps for this function body:
397 let fn_maps = @IrMaps(this.tcx, this.method_map, this.capture_map);
400 debug!("creating fn_maps: {}", transmute::<&IrMaps, *IrMaps>(fn_maps));
403 for arg in decl.inputs.iter() {
404 pat_util::pat_bindings(this.tcx.def_map,
406 |_bm, arg_id, _x, path| {
407 debug!("adding argument {}", arg_id);
408 let ident = ast_util::path_to_ident(path);
409 fn_maps.add_variable(Arg(arg_id, ident));
413 // gather up the various local variables, significant expressions,
415 visit::walk_fn(v, fk, decl, body, sp, id, fn_maps);
417 // Special nodes and variables:
418 // - exit_ln represents the end of the fn, either by return or fail
419 // - implicit_ret_var is a pseudo-variable that represents
420 // an implicit return
421 let specials = Specials {
422 exit_ln: fn_maps.add_live_node(ExitNode),
423 fallthrough_ln: fn_maps.add_live_node(ExitNode),
424 no_ret_var: fn_maps.add_variable(ImplicitRet)
428 let mut lsets = Liveness(fn_maps, specials);
429 let entry_ln = lsets.compute(decl, body);
431 // check for various error conditions
432 lsets.visit_block(body, ());
433 lsets.check_ret(id, sp, fk, entry_ln, body);
434 lsets.warn_about_unused_args(decl, entry_ln);
437 fn visit_local(v: &mut LivenessVisitor, local: &Local, this: @IrMaps) {
438 let def_map = this.tcx.def_map;
439 pat_util::pat_bindings(def_map, local.pat, |bm, p_id, sp, path| {
440 debug!("adding local variable {}", p_id);
441 let name = ast_util::path_to_ident(path);
442 this.add_live_node_for_node(p_id, VarDefNode(sp));
443 let kind = match local.init {
444 Some(_) => FromLetWithInitializer,
445 None => FromLetNoInitializer
447 let mutbl = match bm {
448 BindByValue(MutMutable) => true,
451 this.add_variable(Local(LocalInfo {
458 visit::walk_local(v, local, this);
461 fn visit_arm(v: &mut LivenessVisitor, arm: &Arm, this: @IrMaps) {
462 let def_map = this.tcx.def_map;
463 for pat in arm.pats.iter() {
464 pat_util::pat_bindings(def_map, *pat, |bm, p_id, sp, path| {
465 debug!("adding local variable {} from match with bm {:?}",
467 let name = ast_util::path_to_ident(path);
468 let mutbl = match bm {
469 BindByValue(MutMutable) => true,
472 this.add_live_node_for_node(p_id, VarDefNode(sp));
473 this.add_variable(Local(LocalInfo {
481 visit::walk_arm(v, arm, this);
484 fn visit_expr(v: &mut LivenessVisitor, expr: &Expr, this: @IrMaps) {
486 // live nodes required for uses or definitions of variables:
488 let def_map = this.tcx.def_map.borrow();
489 let def = def_map.get().get_copy(&expr.id);
490 debug!("expr {}: path that leads to {:?}", expr.id, def);
491 if moves::moved_variable_node_id_from_def(def).is_some() {
492 this.add_live_node_for_node(expr.id, ExprNode(expr.span));
494 visit::walk_expr(v, expr, this);
496 ExprFnBlock(..) | ExprProc(..) => {
497 // Interesting control flow (for loops can contain labeled
498 // breaks or continues)
499 this.add_live_node_for_node(expr.id, ExprNode(expr.span));
501 // Make a live_node for each captured variable, with the span
502 // being the location that the variable is used. This results
503 // in better error messages than just pointing at the closure
504 // construction site.
505 let capture_map = this.capture_map.borrow();
506 let cvs = capture_map.get().get(&expr.id);
507 let mut call_caps = ~[];
508 for cv in cvs.borrow().iter() {
509 match moves::moved_variable_node_id_from_def(cv.def) {
511 let cv_ln = this.add_live_node(FreeVarNode(cv.span));
512 let is_move = match cv.mode {
513 // var must be dead afterwards
514 moves::CapMove => true,
516 // var can stil be used
517 moves::CapCopy | moves::CapRef => false
519 call_caps.push(CaptureInfo {ln: cv_ln,
526 this.set_captures(expr.id, call_caps);
528 visit::walk_expr(v, expr, this);
531 // live nodes required for interesting control flow:
532 ExprIf(..) | ExprMatch(..) | ExprWhile(..) | ExprLoop(..) => {
533 this.add_live_node_for_node(expr.id, ExprNode(expr.span));
534 visit::walk_expr(v, expr, this);
536 ExprForLoop(..) => fail!("non-desugared expr_for_loop"),
537 ExprBinary(op, _, _) if ast_util::lazy_binop(op) => {
538 this.add_live_node_for_node(expr.id, ExprNode(expr.span));
539 visit::walk_expr(v, expr, this);
542 // otherwise, live nodes are not required:
543 ExprIndex(..) | ExprField(..) | ExprVstore(..) | ExprVec(..) |
544 ExprCall(..) | ExprMethodCall(..) | ExprTup(..) | ExprLogLevel |
545 ExprBinary(..) | ExprAddrOf(..) |
546 ExprCast(..) | ExprUnary(..) | ExprBreak(_) |
547 ExprAgain(_) | ExprLit(_) | ExprRet(..) | ExprBlock(..) |
548 ExprAssign(..) | ExprAssignOp(..) | ExprMac(..) |
549 ExprStruct(..) | ExprRepeat(..) | ExprParen(..) |
550 ExprInlineAsm(..) | ExprBox(..) => {
551 visit::walk_expr(v, expr, this);
556 // ______________________________________________________________________
557 // Computing liveness sets
559 // Actually we compute just a bit more than just liveness, but we use
560 // the same basic propagation framework in all cases.
569 fn invalid_users() -> Users {
571 reader: invalid_node(),
572 writer: invalid_node(),
579 fallthrough_ln: LiveNode,
583 static ACC_READ: uint = 1u;
584 static ACC_WRITE: uint = 2u;
585 static ACC_USE: uint = 4u;
587 type LiveNodeMap = @RefCell<HashMap<NodeId, LiveNode>>;
589 pub struct Liveness {
593 successors: @RefCell<~[LiveNode]>,
594 users: @RefCell<~[Users]>,
595 // The list of node IDs for the nested loop scopes
597 loop_scope: @RefCell<~[NodeId]>,
598 // mappings from loop node ID to LiveNode
599 // ("break" label should map to loop node ID,
600 // it probably doesn't now)
601 break_ln: LiveNodeMap,
605 fn Liveness(ir: @IrMaps, specials: Specials) -> Liveness {
610 successors: @RefCell::new(vec::from_elem(ir.num_live_nodes.get(),
612 users: @RefCell::new(vec::from_elem(ir.num_live_nodes.get() *
615 loop_scope: @RefCell::new(~[]),
616 break_ln: @RefCell::new(HashMap::new()),
617 cont_ln: @RefCell::new(HashMap::new()),
622 pub fn live_node(&self, node_id: NodeId, span: Span) -> LiveNode {
623 let ir: &IrMaps = self.ir;
624 let live_node_map = ir.live_node_map.borrow();
625 match live_node_map.get().find(&node_id) {
628 // This must be a mismatch between the ir_map construction
629 // above and the propagation code below; the two sets of
630 // code have to agree about which AST nodes are worth
631 // creating liveness nodes for.
632 self.tcx.sess.span_bug(
633 span, format!("no live node registered for node {}",
639 pub fn variable(&self, node_id: NodeId, span: Span) -> Variable {
640 self.ir.variable(node_id, span)
643 pub fn pat_bindings(&self,
645 f: |LiveNode, Variable, Span, NodeId|) {
646 let def_map = self.tcx.def_map;
647 pat_util::pat_bindings(def_map, pat, |_bm, p_id, sp, _n| {
648 let ln = self.live_node(p_id, sp);
649 let var = self.variable(p_id, sp);
650 f(ln, var, sp, p_id);
654 pub fn arm_pats_bindings(&self,
656 f: |LiveNode, Variable, Span, NodeId|) {
657 // only consider the first pattern; any later patterns must have
658 // the same bindings, and we also consider the first pattern to be
659 // the "authoratative" set of ids
660 if !pats.is_empty() {
661 self.pat_bindings(pats[0], f)
665 pub fn define_bindings_in_pat(&self, pat: @Pat, succ: LiveNode)
667 self.define_bindings_in_arm_pats([pat], succ)
670 pub fn define_bindings_in_arm_pats(&self, pats: &[@Pat], succ: LiveNode)
673 self.arm_pats_bindings(pats, |ln, var, _sp, _id| {
674 self.init_from_succ(ln, succ);
675 self.define(ln, var);
681 pub fn idx(&self, ln: LiveNode, var: Variable) -> uint {
682 ln.get() * self.ir.num_vars.get() + var.get()
685 pub fn live_on_entry(&self, ln: LiveNode, var: Variable)
686 -> Option<LiveNodeKind> {
687 assert!(ln.is_valid());
688 let users = self.users.borrow();
689 let reader = users.get()[self.idx(ln, var)].reader;
690 if reader.is_valid() {Some(self.ir.lnk(reader))} else {None}
694 Is this variable live on entry to any of its successor nodes?
696 pub fn live_on_exit(&self, ln: LiveNode, var: Variable)
697 -> Option<LiveNodeKind> {
699 let successors = self.successors.borrow();
700 successors.get()[ln.get()]
702 self.live_on_entry(successor, var)
705 pub fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
706 assert!(ln.is_valid());
707 let users = self.users.borrow();
708 users.get()[self.idx(ln, var)].used
711 pub fn assigned_on_entry(&self, ln: LiveNode, var: Variable)
712 -> Option<LiveNodeKind> {
713 assert!(ln.is_valid());
714 let users = self.users.borrow();
715 let writer = users.get()[self.idx(ln, var)].writer;
716 if writer.is_valid() {Some(self.ir.lnk(writer))} else {None}
719 pub fn assigned_on_exit(&self, ln: LiveNode, var: Variable)
720 -> Option<LiveNodeKind> {
722 let successors = self.successors.borrow();
723 successors.get()[ln.get()]
725 self.assigned_on_entry(successor, var)
728 pub fn indices2(&self,
732 let node_base_idx = self.idx(ln, Variable(0u));
733 let succ_base_idx = self.idx(succ_ln, Variable(0u));
734 for var_idx in range(0u, self.ir.num_vars.get()) {
735 op(node_base_idx + var_idx, succ_base_idx + var_idx);
739 pub fn write_vars(&self,
742 test: |uint| -> LiveNode) -> io::IoResult<()> {
743 let node_base_idx = self.idx(ln, Variable(0));
744 for var_idx in range(0u, self.ir.num_vars.get()) {
745 let idx = node_base_idx + var_idx;
746 if test(idx).is_valid() {
747 try!(write!(wr, " {}", Variable(var_idx).to_str()));
753 pub fn find_loop_scope(&self,
754 opt_label: Option<Ident>,
760 // Refers to a labeled loop. Use the results of resolve
762 let def_map = self.tcx.def_map.borrow();
763 match def_map.get().find(&id) {
764 Some(&DefLabel(loop_id)) => loop_id,
765 _ => self.tcx.sess.span_bug(sp, "label on break/loop \
766 doesn't refer to a loop")
770 // Vanilla 'break' or 'loop', so use the enclosing
772 let loop_scope = self.loop_scope.borrow();
773 if loop_scope.get().len() == 0 {
774 self.tcx.sess.span_bug(sp, "break outside loop");
776 // FIXME(#5275): this shouldn't have to be a method...
777 self.last_loop_scope()
783 pub fn last_loop_scope(&self) -> NodeId {
784 let loop_scope = self.loop_scope.borrow();
785 *loop_scope.get().last().unwrap()
788 #[allow(unused_must_use)]
789 pub fn ln_str(&self, ln: LiveNode) -> ~str {
790 let mut wr = io::MemWriter::new();
792 let wr = &mut wr as &mut io::Writer;
794 let lnks = self.ir.lnks.try_borrow();
796 "[ln({}) of kind {:?} reads",
798 lnks.and_then(|lnks| Some(lnks.get()[ln.get()])));
800 let users = self.users.try_borrow();
803 self.write_vars(wr, ln, |idx| users.get()[idx].reader);
804 write!(wr, " writes");
805 self.write_vars(wr, ln, |idx| users.get()[idx].writer);
808 write!(wr, " (users borrowed)");
811 let successors = self.successors.try_borrow();
813 Some(successors) => {
814 write!(wr, " precedes {}]", successors.get()[ln.get()].to_str());
817 write!(wr, " precedes (successors borrowed)]");
821 str::from_utf8_owned(wr.unwrap()).unwrap()
824 pub fn init_empty(&self, ln: LiveNode, succ_ln: LiveNode) {
826 let mut successors = self.successors.borrow_mut();
827 successors.get()[ln.get()] = succ_ln;
830 // It is not necessary to initialize the
831 // values to empty because this is the value
832 // they have when they are created, and the sets
833 // only grow during iterations.
835 // self.indices(ln) { |idx|
836 // self.users[idx] = invalid_users();
840 pub fn init_from_succ(&self, ln: LiveNode, succ_ln: LiveNode) {
841 // more efficient version of init_empty() / merge_from_succ()
843 let mut successors = self.successors.borrow_mut();
844 successors.get()[ln.get()] = succ_ln;
847 self.indices2(ln, succ_ln, |idx, succ_idx| {
848 let mut users = self.users.borrow_mut();
849 users.get()[idx] = users.get()[succ_idx]
851 debug!("init_from_succ(ln={}, succ={})",
852 self.ln_str(ln), self.ln_str(succ_ln));
855 pub fn merge_from_succ(&self,
860 if ln == succ_ln { return false; }
862 let mut changed = false;
863 self.indices2(ln, succ_ln, |idx, succ_idx| {
864 let mut users = self.users.borrow_mut();
865 changed |= copy_if_invalid(users.get()[succ_idx].reader,
866 &mut users.get()[idx].reader);
867 changed |= copy_if_invalid(users.get()[succ_idx].writer,
868 &mut users.get()[idx].writer);
869 if users.get()[succ_idx].used && !users.get()[idx].used {
870 users.get()[idx].used = true;
875 debug!("merge_from_succ(ln={}, succ={}, first_merge={}, changed={})",
876 ln.to_str(), self.ln_str(succ_ln), first_merge, changed);
879 fn copy_if_invalid(src: LiveNode, dst: &mut LiveNode) -> bool {
890 // Indicates that a local variable was *defined*; we know that no
891 // uses of the variable can precede the definition (resolve checks
892 // this) so we just clear out all the data.
893 pub fn define(&self, writer: LiveNode, var: Variable) {
894 let idx = self.idx(writer, var);
895 let mut users = self.users.borrow_mut();
896 users.get()[idx].reader = invalid_node();
897 users.get()[idx].writer = invalid_node();
899 debug!("{} defines {} (idx={}): {}", writer.to_str(), var.to_str(),
900 idx, self.ln_str(writer));
903 // Either read, write, or both depending on the acc bitset
904 pub fn acc(&self, ln: LiveNode, var: Variable, acc: uint) {
905 let idx = self.idx(ln, var);
906 let mut users = self.users.borrow_mut();
907 let user = &mut users.get()[idx];
909 if (acc & ACC_WRITE) != 0 {
910 user.reader = invalid_node();
914 // Important: if we both read/write, must do read second
915 // or else the write will override.
916 if (acc & ACC_READ) != 0 {
920 if (acc & ACC_USE) != 0 {
924 debug!("{} accesses[{:x}] {}: {}",
925 ln.to_str(), acc, var.to_str(), self.ln_str(ln));
928 // _______________________________________________________________________
930 pub fn compute(&self, decl: &FnDecl, body: &Block) -> LiveNode {
931 // if there is a `break` or `again` at the top level, then it's
932 // effectively a return---this only occurs in `for` loops,
933 // where the body is really a closure.
935 debug!("compute: using id for block, {}", block_to_str(body));
937 let entry_ln: LiveNode =
938 self.with_loop_nodes(body.id, self.s.exit_ln, self.s.exit_ln,
939 || { self.propagate_through_fn_block(decl, body) });
941 // hack to skip the loop unless debug! is enabled:
942 debug!("^^ liveness computation results for body {} (entry={})",
944 for ln_idx in range(0u, self.ir.num_live_nodes.get()) {
945 debug!("{}", self.ln_str(LiveNode(ln_idx)));
954 pub fn propagate_through_fn_block(&self, _: &FnDecl, blk: &Block)
956 // the fallthrough exit is only for those cases where we do not
957 // explicitly return:
958 self.init_from_succ(self.s.fallthrough_ln, self.s.exit_ln);
959 if blk.expr.is_none() {
960 self.acc(self.s.fallthrough_ln, self.s.no_ret_var, ACC_READ)
963 self.propagate_through_block(blk, self.s.fallthrough_ln)
966 pub fn propagate_through_block(&self, blk: &Block, succ: LiveNode)
968 let succ = self.propagate_through_opt_expr(blk.expr, succ);
969 blk.stmts.rev_iter().fold(succ, |succ, stmt| {
970 self.propagate_through_stmt(*stmt, succ)
974 pub fn propagate_through_stmt(&self, stmt: &Stmt, succ: LiveNode)
977 StmtDecl(decl, _) => {
978 return self.propagate_through_decl(decl, succ);
981 StmtExpr(expr, _) | StmtSemi(expr, _) => {
982 return self.propagate_through_expr(expr, succ);
986 self.tcx.sess.span_bug(stmt.span, "unexpanded macro");
991 pub fn propagate_through_decl(&self, decl: &Decl, succ: LiveNode)
994 DeclLocal(ref local) => {
995 self.propagate_through_local(*local, succ)
1001 pub fn propagate_through_local(&self, local: &Local, succ: LiveNode)
1003 // Note: we mark the variable as defined regardless of whether
1004 // there is an initializer. Initially I had thought to only mark
1005 // the live variable as defined if it was initialized, and then we
1006 // could check for uninit variables just by scanning what is live
1007 // at the start of the function. But that doesn't work so well for
1008 // immutable variables defined in a loop:
1009 // loop { let x; x = 5; }
1010 // because the "assignment" loops back around and generates an error.
1012 // So now we just check that variables defined w/o an
1013 // initializer are not live at the point of their
1014 // initialization, which is mildly more complex than checking
1015 // once at the func header but otherwise equivalent.
1017 let succ = self.propagate_through_opt_expr(local.init, succ);
1018 self.define_bindings_in_pat(local.pat, succ)
1021 pub fn propagate_through_exprs(&self, exprs: &[@Expr], succ: LiveNode)
1023 exprs.rev_iter().fold(succ, |succ, expr| {
1024 self.propagate_through_expr(*expr, succ)
1028 pub fn propagate_through_opt_expr(&self,
1029 opt_expr: Option<@Expr>,
1032 opt_expr.iter().fold(succ, |succ, expr| {
1033 self.propagate_through_expr(*expr, succ)
1037 pub fn propagate_through_expr(&self, expr: @Expr, succ: LiveNode)
1039 debug!("propagate_through_expr: {}", expr_to_str(expr));
1042 // Interesting cases with control flow or which gen/kill
1045 self.access_path(expr, succ, ACC_READ | ACC_USE)
1048 ExprField(e, _, _) => {
1049 self.propagate_through_expr(e, succ)
1052 ExprFnBlock(_, blk) | ExprProc(_, blk) => {
1053 debug!("{} is an ExprFnBlock or ExprProc", expr_to_str(expr));
1056 The next-node for a break is the successor of the entire
1057 loop. The next-node for a continue is the top of this loop.
1059 self.with_loop_nodes(blk.id, succ,
1060 self.live_node(expr.id, expr.span), || {
1062 // the construction of a closure itself is not important,
1063 // but we have to consider the closed over variables.
1064 let caps = self.ir.captures(expr);
1065 caps.rev_iter().fold(succ, |succ, cap| {
1066 self.init_from_succ(cap.ln, succ);
1067 let var = self.variable(cap.var_nid, expr.span);
1068 self.acc(cap.ln, var, ACC_READ | ACC_USE);
1074 ExprIf(cond, then, els) => {
1088 let else_ln = self.propagate_through_opt_expr(els, succ);
1089 let then_ln = self.propagate_through_block(then, succ);
1090 let ln = self.live_node(expr.id, expr.span);
1091 self.init_from_succ(ln, else_ln);
1092 self.merge_from_succ(ln, then_ln, false);
1093 self.propagate_through_expr(cond, ln)
1096 ExprWhile(cond, blk) => {
1097 self.propagate_through_loop(expr, Some(cond), blk, succ)
1100 ExprForLoop(..) => fail!("non-desugared expr_for_loop"),
1102 // Note that labels have been resolved, so we don't need to look
1103 // at the label ident
1104 ExprLoop(blk, _) => {
1105 self.propagate_through_loop(expr, None, blk, succ)
1108 ExprMatch(e, ref arms) => {
1123 let ln = self.live_node(expr.id, expr.span);
1124 self.init_empty(ln, succ);
1125 let mut first_merge = true;
1126 for arm in arms.iter() {
1128 self.propagate_through_block(arm.body, succ);
1130 self.propagate_through_opt_expr(arm.guard, body_succ);
1132 self.define_bindings_in_arm_pats(arm.pats, guard_succ);
1133 self.merge_from_succ(ln, arm_succ, first_merge);
1134 first_merge = false;
1136 self.propagate_through_expr(e, ln)
1140 // ignore succ and subst exit_ln:
1141 self.propagate_through_opt_expr(o_e, self.s.exit_ln)
1144 ExprBreak(opt_label) => {
1145 // Find which label this break jumps to
1146 let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
1148 // Now that we know the label we're going to,
1149 // look it up in the break loop nodes table
1151 let break_ln = self.break_ln.borrow();
1152 match break_ln.get().find(&sc) {
1154 None => self.tcx.sess.span_bug(expr.span,
1155 "break to unknown label")
1159 ExprAgain(opt_label) => {
1160 // Find which label this expr continues to
1161 let sc = self.find_loop_scope(opt_label, expr.id, expr.span);
1163 // Now that we know the label we're going to,
1164 // look it up in the continue loop nodes table
1166 let cont_ln = self.cont_ln.borrow();
1167 match cont_ln.get().find(&sc) {
1169 None => self.tcx.sess.span_bug(expr.span,
1170 "loop to unknown label")
1174 ExprAssign(l, r) => {
1175 // see comment on lvalues in
1176 // propagate_through_lvalue_components()
1177 let succ = self.write_lvalue(l, succ, ACC_WRITE);
1178 let succ = self.propagate_through_lvalue_components(l, succ);
1179 self.propagate_through_expr(r, succ)
1182 ExprAssignOp(_, l, r) => {
1183 // see comment on lvalues in
1184 // propagate_through_lvalue_components()
1185 let succ = self.write_lvalue(l, succ, ACC_WRITE|ACC_READ);
1186 let succ = self.propagate_through_expr(r, succ);
1187 self.propagate_through_lvalue_components(l, succ)
1190 // Uninteresting cases: just propagate in rev exec order
1192 ExprVstore(expr, _) => {
1193 self.propagate_through_expr(expr, succ)
1196 ExprVec(ref exprs, _) => {
1197 self.propagate_through_exprs(*exprs, succ)
1200 ExprRepeat(element, count, _) => {
1201 let succ = self.propagate_through_expr(count, succ);
1202 self.propagate_through_expr(element, succ)
1205 ExprStruct(_, ref fields, with_expr) => {
1206 let succ = self.propagate_through_opt_expr(with_expr, succ);
1207 fields.rev_iter().fold(succ, |succ, field| {
1208 self.propagate_through_expr(field.expr, succ)
1212 ExprCall(f, ref args) => {
1213 // calling a fn with bot return type means that the fn
1214 // will fail, and hence the successors can be ignored
1215 let t_ret = ty::ty_fn_ret(ty::expr_ty(self.tcx, f));
1216 let succ = if ty::type_is_bot(t_ret) {self.s.exit_ln}
1218 let succ = self.propagate_through_exprs(*args, succ);
1219 self.propagate_through_expr(f, succ)
1222 ExprMethodCall(_, _, ref args) => {
1223 // calling a method with bot return type means that the method
1224 // will fail, and hence the successors can be ignored
1225 let t_ret = ty::node_id_to_type(self.tcx, expr.id);
1226 let succ = if ty::type_is_bot(t_ret) {self.s.exit_ln}
1228 self.propagate_through_exprs(*args, succ)
1231 ExprTup(ref exprs) => {
1232 self.propagate_through_exprs(*exprs, succ)
1235 ExprBinary(op, l, r) if ast_util::lazy_binop(op) => {
1236 let r_succ = self.propagate_through_expr(r, succ);
1238 let ln = self.live_node(expr.id, expr.span);
1239 self.init_from_succ(ln, succ);
1240 self.merge_from_succ(ln, r_succ, false);
1242 self.propagate_through_expr(l, ln)
1246 ExprBinary(_, l, r) |
1248 self.propagate_through_exprs([l, r], succ)
1255 self.propagate_through_expr(e, succ)
1258 ExprInlineAsm(ref ia) => {
1259 let succ = ia.inputs.rev_iter().fold(succ, |succ, &(_, expr)| {
1260 self.propagate_through_expr(expr, succ)
1262 ia.outputs.rev_iter().fold(succ, |succ, &(_, expr)| {
1263 // see comment on lvalues in
1264 // propagate_through_lvalue_components()
1265 let succ = self.write_lvalue(expr, succ, ACC_WRITE);
1266 self.propagate_through_lvalue_components(expr, succ)
1276 self.propagate_through_block(blk, succ)
1280 self.tcx.sess.span_bug(expr.span, "unexpanded macro");
1285 pub fn propagate_through_lvalue_components(&self,
1291 // In general, the full flow graph structure for an
1292 // assignment/move/etc can be handled in one of two ways,
1293 // depending on whether what is being assigned is a "tracked
1294 // value" or not. A tracked value is basically a local
1295 // variable or argument.
1297 // The two kinds of graphs are:
1299 // Tracked lvalue Untracked lvalue
1300 // ----------------------++-----------------------
1304 // (rvalue) || (rvalue)
1307 // (write of lvalue) || (lvalue components)
1312 // ----------------------++-----------------------
1314 // I will cover the two cases in turn:
1316 // # Tracked lvalues
1318 // A tracked lvalue is a local variable/argument `x`. In
1319 // these cases, the link_node where the write occurs is linked
1320 // to node id of `x`. The `write_lvalue()` routine generates
1321 // the contents of this node. There are no subcomponents to
1324 // # Non-tracked lvalues
1326 // These are lvalues like `x[5]` or `x.f`. In that case, we
1327 // basically ignore the value which is written to but generate
1328 // reads for the components---`x` in these two examples. The
1329 // components reads are generated by
1330 // `propagate_through_lvalue_components()` (this fn).
1332 // # Illegal lvalues
1334 // It is still possible to observe assignments to non-lvalues;
1335 // these errors are detected in the later pass borrowck. We
1336 // just ignore such cases and treat them as reads.
1339 ExprPath(_) => succ,
1340 ExprField(e, _, _) => self.propagate_through_expr(e, succ),
1341 _ => self.propagate_through_expr(expr, succ)
1345 // see comment on propagate_through_lvalue()
1346 pub fn write_lvalue(&self, expr: &Expr, succ: LiveNode, acc: uint)
1349 ExprPath(_) => self.access_path(expr, succ, acc),
1351 // We do not track other lvalues, so just propagate through
1352 // to their subcomponents. Also, it may happen that
1353 // non-lvalues occur here, because those are detected in the
1354 // later pass borrowck.
1359 pub fn access_path(&self, expr: &Expr, succ: LiveNode, acc: uint)
1361 let def_map = self.tcx.def_map.borrow();
1362 let def = def_map.get().get_copy(&expr.id);
1363 match moves::moved_variable_node_id_from_def(def) {
1365 let ln = self.live_node(expr.id, expr.span);
1367 self.init_from_succ(ln, succ);
1368 let var = self.variable(nid, expr.span);
1369 self.acc(ln, var, acc);
1377 pub fn propagate_through_loop(&self,
1379 cond: Option<@Expr>,
1386 We model control flow like this:
1404 let mut first_merge = true;
1405 let ln = self.live_node(expr.id, expr.span);
1406 self.init_empty(ln, succ);
1408 // if there is a condition, then it's possible we bypass
1409 // the body altogether. otherwise, the only way is via a
1410 // break in the loop body.
1411 self.merge_from_succ(ln, succ, first_merge);
1412 first_merge = false;
1414 debug!("propagate_through_loop: using id for loop body {} {}",
1415 expr.id, block_to_str(body));
1417 let cond_ln = self.propagate_through_opt_expr(cond, ln);
1418 let body_ln = self.with_loop_nodes(expr.id, succ, ln, || {
1419 self.propagate_through_block(body, cond_ln)
1422 // repeat until fixed point is reached:
1423 while self.merge_from_succ(ln, body_ln, first_merge) {
1424 first_merge = false;
1425 assert!(cond_ln == self.propagate_through_opt_expr(cond,
1427 assert!(body_ln == self.with_loop_nodes(expr.id, succ, ln,
1429 self.propagate_through_block(body, cond_ln)
1436 pub fn with_loop_nodes<R>(
1438 loop_node_id: NodeId,
1443 debug!("with_loop_nodes: {} {}", loop_node_id, break_ln.get());
1445 let mut loop_scope = self.loop_scope.borrow_mut();
1446 loop_scope.get().push(loop_node_id);
1449 let mut this_break_ln = self.break_ln.borrow_mut();
1450 let mut this_cont_ln = self.cont_ln.borrow_mut();
1451 this_break_ln.get().insert(loop_node_id, break_ln);
1452 this_cont_ln.get().insert(loop_node_id, cont_ln);
1456 let mut loop_scope = self.loop_scope.borrow_mut();
1457 loop_scope.get().pop();
1463 // _______________________________________________________________________
1464 // Checking for error conditions
1466 fn check_local(this: &mut Liveness, local: &Local) {
1469 this.warn_about_unused_or_dead_vars_in_pat(local.pat);
1473 // No initializer: the variable might be unused; if not, it
1474 // should not be live at this point.
1476 debug!("check_local() with no initializer");
1477 this.pat_bindings(local.pat, |ln, var, sp, id| {
1478 if !this.warn_about_unused(sp, id, ln, var) {
1479 match this.live_on_exit(ln, var) {
1480 None => { /* not live: good */ }
1482 this.report_illegal_read(
1483 local.span, lnk, var,
1484 PossiblyUninitializedVariable);
1492 visit::walk_local(this, local, ());
1495 fn check_arm(this: &mut Liveness, arm: &Arm) {
1496 this.arm_pats_bindings(arm.pats, |ln, var, sp, id| {
1497 this.warn_about_unused(sp, id, ln, var);
1499 visit::walk_arm(this, arm, ());
1502 fn check_expr(this: &mut Liveness, expr: &Expr) {
1504 ExprAssign(l, r) => {
1505 this.check_lvalue(l);
1506 this.visit_expr(r, ());
1508 visit::walk_expr(this, expr, ());
1511 ExprAssignOp(_, l, _) => {
1512 this.check_lvalue(l);
1514 visit::walk_expr(this, expr, ());
1517 ExprInlineAsm(ref ia) => {
1518 for &(_, input) in ia.inputs.iter() {
1519 this.visit_expr(input, ());
1522 // Output operands must be lvalues
1523 for &(_, out) in ia.outputs.iter() {
1524 this.check_lvalue(out);
1525 this.visit_expr(out, ());
1528 visit::walk_expr(this, expr, ());
1531 // no correctness conditions related to liveness
1532 ExprCall(..) | ExprMethodCall(..) | ExprIf(..) | ExprMatch(..) |
1533 ExprWhile(..) | ExprLoop(..) | ExprIndex(..) | ExprField(..) |
1534 ExprVstore(..) | ExprVec(..) | ExprTup(..) | ExprLogLevel |
1536 ExprCast(..) | ExprUnary(..) | ExprRet(..) | ExprBreak(..) |
1537 ExprAgain(..) | ExprLit(_) | ExprBlock(..) |
1538 ExprMac(..) | ExprAddrOf(..) | ExprStruct(..) | ExprRepeat(..) |
1539 ExprParen(..) | ExprFnBlock(..) | ExprProc(..) | ExprPath(..) |
1541 visit::walk_expr(this, expr, ());
1543 ExprForLoop(..) => fail!("non-desugared expr_for_loop")
1547 fn check_fn(_v: &Liveness,
1553 // do not check contents of nested fns
1557 PossiblyUninitializedVariable,
1558 PossiblyUninitializedField,
1564 pub fn check_ret(&self,
1570 if self.live_on_entry(entry_ln, self.s.no_ret_var).is_some() {
1571 // if no_ret_var is live, then we fall off the end of the
1572 // function without any kind of return expression:
1574 let t_ret = ty::ty_fn_ret(ty::node_id_to_type(self.tcx, id));
1575 if ty::type_is_nil(t_ret) {
1576 // for nil return types, it is ok to not return a value expl.
1577 } else if ty::type_is_bot(t_ret) {
1578 // for bot return types, not ok. Function should fail.
1579 self.tcx.sess.span_err(
1580 sp, "some control paths may return");
1582 let ends_with_stmt = match body.expr {
1583 None if body.stmts.len() > 0 =>
1584 match body.stmts.last().unwrap().node {
1586 let t_stmt = ty::expr_ty(self.tcx, e);
1587 ty::get(t_stmt).sty == ty::get(t_ret).sty
1594 let last_stmt = body.stmts.last().unwrap();
1595 let span_semicolon = Span {
1596 lo: last_stmt.span.hi,
1597 hi: last_stmt.span.hi,
1598 expn_info: last_stmt.span.expn_info
1600 self.tcx.sess.span_note(
1601 span_semicolon, "consider removing this semicolon:");
1603 self.tcx.sess.span_err(
1604 sp, "not all control paths return a value");
1609 pub fn check_lvalue(&mut self, expr: @Expr) {
1612 let def_map = self.tcx.def_map.borrow();
1613 match def_map.get().get_copy(&expr.id) {
1614 DefLocal(nid, _) => {
1615 // Assignment to an immutable variable or argument: only legal
1616 // if there is no later assignment. If this local is actually
1617 // mutable, then check for a reassignment to flag the mutability
1619 let ln = self.live_node(expr.id, expr.span);
1620 let var = self.variable(nid, expr.span);
1621 self.warn_about_dead_assign(expr.span, expr.id, ln, var);
1624 match moves::moved_variable_node_id_from_def(def) {
1626 let ln = self.live_node(expr.id, expr.span);
1627 let var = self.variable(nid, expr.span);
1628 self.warn_about_dead_assign(expr.span, expr.id, ln, var);
1637 // For other kinds of lvalues, no checks are required,
1638 // and any embedded expressions are actually rvalues
1639 visit::walk_expr(self, expr, ());
1644 pub fn report_illegal_read(&self,
1649 let msg = match rk {
1650 PossiblyUninitializedVariable => "possibly uninitialized \
1652 PossiblyUninitializedField => "possibly uninitialized field",
1653 MovedValue => "moved value",
1654 PartiallyMovedValue => "partially moved value"
1656 let name = self.ir.variable_name(var);
1658 FreeVarNode(span) => {
1659 self.tcx.sess.span_err(
1661 format!("capture of {}: `{}`", msg, name));
1664 self.tcx.sess.span_err(
1666 format!("use of {}: `{}`", msg, name));
1668 ExitNode | VarDefNode(_) => {
1669 self.tcx.sess.span_bug(
1671 format!("illegal reader: {:?}", lnk));
1676 pub fn should_warn(&self, var: Variable) -> Option<~str> {
1677 let name = self.ir.variable_name(var);
1678 if name.len() == 0 || name[0] == ('_' as u8) { None } else { Some(name) }
1681 pub fn warn_about_unused_args(&self, decl: &FnDecl, entry_ln: LiveNode) {
1682 for arg in decl.inputs.iter() {
1683 pat_util::pat_bindings(self.tcx.def_map,
1685 |_bm, p_id, sp, path| {
1686 let var = self.variable(p_id, sp);
1687 // Ignore unused self.
1688 let ident = ast_util::path_to_ident(path);
1689 if ident.name != special_idents::self_.name {
1690 self.warn_about_unused(sp, p_id, entry_ln, var);
1696 pub fn warn_about_unused_or_dead_vars_in_pat(&self, pat: @Pat) {
1697 self.pat_bindings(pat, |ln, var, sp, id| {
1698 if !self.warn_about_unused(sp, id, ln, var) {
1699 self.warn_about_dead_assign(sp, id, ln, var);
1704 pub fn warn_about_unused(&self,
1710 if !self.used_on_entry(ln, var) {
1711 let r = self.should_warn(var);
1712 for name in r.iter() {
1714 // annoying: for parameters in funcs like `fn(x: int)
1715 // {ret}`, there is only one node, so asking about
1716 // assigned_on_exit() is not meaningful.
1717 let is_assigned = if ln == self.s.exit_ln {
1720 self.assigned_on_exit(ln, var).is_some()
1724 self.tcx.sess.add_lint(UnusedVariable, id, sp,
1725 format!("variable `{}` is assigned to, \
1726 but never used", *name));
1728 self.tcx.sess.add_lint(UnusedVariable, id, sp,
1729 format!("unused variable: `{}`", *name));
1738 pub fn warn_about_dead_assign(&self,
1743 if self.live_on_exit(ln, var).is_none() {
1744 let r = self.should_warn(var);
1745 for name in r.iter() {
1746 self.tcx.sess.add_lint(DeadAssignment, id, sp,
1747 format!("value assigned to `{}` is never read", *name));