| // Copyright 2017 The Rust Project Developers. See the COPYRIGHT |
| // file at the top-level directory of this distribution and at |
| // http://rust-lang.org/COPYRIGHT. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| use rustc::hir::intravisit::{Visitor, NestedVisitorMap}; |
| use rustc::hir::{self, HirId}; |
| use rustc::lint::builtin::UNUSED_MUT; |
| use rustc::ty; |
| use rustc::util::nodemap::{FxHashMap, FxHashSet}; |
| use errors::Applicability; |
| use std::slice; |
| use syntax::ptr::P; |
| |
| use borrowck::BorrowckCtxt; |
| |
| pub fn check<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>, body: &'tcx hir::Body) { |
| let mut used_mut = bccx.used_mut_nodes.borrow().clone(); |
| UsedMutFinder { |
| bccx, |
| set: &mut used_mut, |
| }.visit_expr(&body.value); |
| let mut cx = UnusedMutCx { bccx, used_mut }; |
| for arg in body.arguments.iter() { |
| cx.check_unused_mut_pat(slice::from_ref(&arg.pat)); |
| } |
| cx.visit_expr(&body.value); |
| } |
| |
| struct UsedMutFinder<'a, 'tcx: 'a> { |
| bccx: &'a BorrowckCtxt<'a, 'tcx>, |
| set: &'a mut FxHashSet<HirId>, |
| } |
| |
| struct UnusedMutCx<'a, 'tcx: 'a> { |
| bccx: &'a BorrowckCtxt<'a, 'tcx>, |
| used_mut: FxHashSet<HirId>, |
| } |
| |
| impl<'a, 'tcx> UnusedMutCx<'a, 'tcx> { |
| fn check_unused_mut_pat(&self, pats: &[P<hir::Pat>]) { |
| let tcx = self.bccx.tcx; |
| let mut mutables: FxHashMap<_, Vec<_>> = Default::default(); |
| for p in pats { |
| p.each_binding(|_, hir_id, span, ident| { |
| // Skip anything that looks like `_foo` |
| if ident.as_str().starts_with("_") { |
| return; |
| } |
| |
| // Skip anything that looks like `&foo` or `&mut foo`, only look |
| // for by-value bindings |
| if let Some(&bm) = self.bccx.tables.pat_binding_modes().get(hir_id) { |
| match bm { |
| ty::BindByValue(hir::MutMutable) => {} |
| _ => return, |
| } |
| |
| mutables.entry(ident.name).or_default().push((hir_id, span)); |
| } else { |
| tcx.sess.delay_span_bug(span, "missing binding mode"); |
| } |
| }); |
| } |
| |
| for (_name, ids) in mutables { |
| // If any id for this name was used mutably then consider them all |
| // ok, so move on to the next |
| if ids.iter().any(|&(ref hir_id, _)| self.used_mut.contains(hir_id)) { |
| continue; |
| } |
| |
| let (hir_id, span) = ids[0]; |
| if span.compiler_desugaring_kind().is_some() { |
| // If the `mut` arises as part of a desugaring, we should ignore it. |
| continue; |
| } |
| |
| // Ok, every name wasn't used mutably, so issue a warning that this |
| // didn't need to be mutable. |
| let mut_span = tcx.sess.source_map().span_until_non_whitespace(span); |
| tcx.struct_span_lint_hir(UNUSED_MUT, |
| hir_id, |
| span, |
| "variable does not need to be mutable") |
| .span_suggestion_short_with_applicability( |
| mut_span, |
| "remove this `mut`", |
| String::new(), |
| Applicability::MachineApplicable) |
| .emit(); |
| } |
| } |
| } |
| |
| impl<'a, 'tcx> Visitor<'tcx> for UnusedMutCx<'a, 'tcx> { |
| fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { |
| NestedVisitorMap::OnlyBodies(&self.bccx.tcx.hir) |
| } |
| |
| fn visit_arm(&mut self, arm: &hir::Arm) { |
| self.check_unused_mut_pat(&arm.pats) |
| } |
| |
| fn visit_local(&mut self, local: &hir::Local) { |
| self.check_unused_mut_pat(slice::from_ref(&local.pat)); |
| } |
| } |
| |
| impl<'a, 'tcx> Visitor<'tcx> for UsedMutFinder<'a, 'tcx> { |
| fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { |
| NestedVisitorMap::OnlyBodies(&self.bccx.tcx.hir) |
| } |
| |
| fn visit_nested_body(&mut self, id: hir::BodyId) { |
| let def_id = self.bccx.tcx.hir.body_owner_def_id(id); |
| self.set.extend(self.bccx.tcx.borrowck(def_id).used_mut_nodes.iter().cloned()); |
| self.visit_body(self.bccx.tcx.hir.body(id)); |
| } |
| } |