blob: 520febca938f098dcba9d4fd89f259602fe2f415 [file] [log] [blame]
// 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 syntax_pos::Span;
use rustc::middle::region::ScopeTree;
use rustc::mir::{BorrowKind, Field, Local, LocalKind, Location, Operand};
use rustc::mir::{Place, ProjectionElem, Rvalue, Statement, StatementKind};
use rustc::ty::{self, RegionKind};
use rustc_data_structures::indexed_vec::Idx;
use std::rc::Rc;
use super::{MirBorrowckCtxt, Context};
use super::{InitializationRequiringAction, PrefixSet};
use dataflow::{ActiveBorrows, BorrowData, FlowAtLocation, MovingOutStatements};
use dataflow::move_paths::MovePathIndex;
use util::borrowck_errors::{BorrowckErrors, Origin};
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
pub(super) fn report_use_of_moved_or_uninitialized(
&mut self,
_context: Context,
desired_action: InitializationRequiringAction,
(place, span): (&Place<'tcx>, Span),
mpi: MovePathIndex,
curr_move_out: &FlowAtLocation<MovingOutStatements<'_, 'gcx, 'tcx>>,
) {
let mois = self.move_data.path_map[mpi]
.iter()
.filter(|moi| curr_move_out.contains(moi))
.collect::<Vec<_>>();
if mois.is_empty() {
let item_msg = match self.describe_place(place) {
Some(name) => format!("`{}`", name),
None => "value".to_owned(),
};
self.tcx
.cannot_act_on_uninitialized_variable(
span,
desired_action.as_noun(),
&self.describe_place(place).unwrap_or("_".to_owned()),
Origin::Mir,
)
.span_label(span, format!("use of possibly uninitialized {}", item_msg))
.emit();
} else {
let msg = ""; //FIXME: add "partially " or "collaterally "
let mut err = self.tcx.cannot_act_on_moved_value(
span,
desired_action.as_noun(),
msg,
&self.describe_place(place).unwrap_or("_".to_owned()),
Origin::Mir,
);
let mut is_loop_move = false;
for moi in mois {
let move_msg = ""; //FIXME: add " (into closure)"
let move_span = self.mir.source_info(self.move_data.moves[*moi].source).span;
if span == move_span {
err.span_label(
span,
format!("value moved{} here in previous iteration of loop", move_msg),
);
is_loop_move = true;
} else {
err.span_label(move_span, format!("value moved{} here", move_msg));
};
}
if !is_loop_move {
err.span_label(
span,
format!(
"value {} here after move",
desired_action.as_verb_in_past_tense()
),
);
}
if let Some(ty) = self.retrieve_type_for_place(place) {
let needs_note = match ty.sty {
ty::TypeVariants::TyClosure(id, _) => {
let tables = self.tcx.typeck_tables_of(id);
let node_id = self.tcx.hir.as_local_node_id(id).unwrap();
let hir_id = self.tcx.hir.node_to_hir_id(node_id);
if let Some(_) = tables.closure_kind_origins().get(hir_id) {
false
} else {
true
}
},
_ => true,
};
if needs_note {
let note_msg = match self.describe_place(place) {
Some(name) => format!("`{}`", name),
None => "value".to_owned(),
};
err.note(&format!("move occurs because {} has type `{}`, \
which does not implement the `Copy` trait",
note_msg, ty));
}
}
err.emit();
}
}
pub(super) fn report_move_out_while_borrowed(
&mut self,
context: Context,
(place, span): (&Place<'tcx>, Span),
borrow: &BorrowData<'tcx>,
) {
let value_msg = match self.describe_place(place) {
Some(name) => format!("`{}`", name),
None => "value".to_owned(),
};
let borrow_msg = match self.describe_place(&borrow.borrowed_place) {
Some(name) => format!("`{}`", name),
None => "value".to_owned(),
};
let mut err = self.tcx.cannot_move_when_borrowed(
span,
&self.describe_place(place).unwrap_or("_".to_owned()),
Origin::Mir,
);
err.span_label(
self.retrieve_borrow_span(borrow),
format!("borrow of {} occurs here", borrow_msg),
);
err.span_label(span, format!("move out of {} occurs here", value_msg));
self.explain_why_borrow_contains_point(context, borrow, &mut err);
err.emit();
}
pub(super) fn report_use_while_mutably_borrowed(
&mut self,
context: Context,
(place, span): (&Place<'tcx>, Span),
borrow: &BorrowData<'tcx>,
) {
let mut err = self.tcx.cannot_use_when_mutably_borrowed(
span,
&self.describe_place(place).unwrap_or("_".to_owned()),
self.retrieve_borrow_span(borrow),
&self.describe_place(&borrow.borrowed_place).unwrap_or("_".to_owned()),
Origin::Mir,
);
self.explain_why_borrow_contains_point(context, borrow, &mut err);
err.emit();
}
/// Finds the span of arguments of a closure (within `maybe_closure_span`) and its usage of
/// the local assigned at `location`.
/// This is done by searching in statements succeeding `location`
/// and originating from `maybe_closure_span`.
fn find_closure_span(
&self,
maybe_closure_span: Span,
location: Location,
) -> Option<(Span, Span)> {
use rustc::hir::ExprClosure;
use rustc::mir::AggregateKind;
let local = match self.mir[location.block].statements.get(location.statement_index) {
Some(&Statement { kind: StatementKind::Assign(Place::Local(local), _), .. }) => local,
_ => return None,
};
for stmt in &self.mir[location.block].statements[location.statement_index + 1..] {
if maybe_closure_span != stmt.source_info.span {
break;
}
if let StatementKind::Assign(_, Rvalue::Aggregate(ref kind, ref places)) = stmt.kind {
if let AggregateKind::Closure(def_id, _) = **kind {
debug!("find_closure_span: found closure {:?}", places);
return if let Some(node_id) = self.tcx.hir.as_local_node_id(def_id) {
let args_span = if let ExprClosure(_, _, _, span, _) =
self.tcx.hir.expect_expr(node_id).node
{
span
} else {
return None;
};
self.tcx
.with_freevars(node_id, |freevars| {
for (v, place) in freevars.iter().zip(places) {
match *place {
Operand::Copy(Place::Local(l)) |
Operand::Move(Place::Local(l)) if local == l =>
{
debug!(
"find_closure_span: found captured local {:?}",
l
);
return Some(v.span);
}
_ => {}
}
}
None
})
.map(|var_span| (args_span, var_span))
} else {
None
};
}
}
}
None
}
pub(super) fn report_conflicting_borrow(
&mut self,
context: Context,
(place, span): (&Place<'tcx>, Span),
gen_borrow_kind: BorrowKind,
issued_borrow: &BorrowData,
end_issued_loan_span: Option<Span>,
) {
let issued_span = self.retrieve_borrow_span(issued_borrow);
let new_closure_span = self.find_closure_span(span, context.loc);
let span = new_closure_span.map(|(args, _)| args).unwrap_or(span);
let old_closure_span = self.find_closure_span(issued_span, issued_borrow.location);
let issued_span = old_closure_span
.map(|(args, _)| args)
.unwrap_or(issued_span);
let desc_place = self.describe_place(place).unwrap_or("_".to_owned());
// FIXME: supply non-"" `opt_via` when appropriate
let mut err = match (
gen_borrow_kind,
"immutable",
"mutable",
issued_borrow.kind,
"immutable",
"mutable",
) {
(BorrowKind::Shared, lft, _, BorrowKind::Mut, _, rgt) |
(BorrowKind::Mut, _, lft, BorrowKind::Shared, rgt, _) => self.tcx
.cannot_reborrow_already_borrowed(
span,
&desc_place,
"",
lft,
issued_span,
"it",
rgt,
"",
end_issued_loan_span,
Origin::Mir,
),
(BorrowKind::Mut, _, _, BorrowKind::Mut, _, _) => self.tcx
.cannot_mutably_borrow_multiply(
span,
&desc_place,
"",
issued_span,
"",
end_issued_loan_span,
Origin::Mir,
),
(BorrowKind::Unique, _, _, BorrowKind::Unique, _, _) => self.tcx
.cannot_uniquely_borrow_by_two_closures(
span,
&desc_place,
issued_span,
end_issued_loan_span,
Origin::Mir,
),
(BorrowKind::Unique, _, _, _, _, _) => self.tcx.cannot_uniquely_borrow_by_one_closure(
span,
&desc_place,
"",
issued_span,
"it",
"",
end_issued_loan_span,
Origin::Mir,
),
(BorrowKind::Shared, lft, _, BorrowKind::Unique, _, _) => self.tcx
.cannot_reborrow_already_uniquely_borrowed(
span,
&desc_place,
"",
lft,
issued_span,
"",
end_issued_loan_span,
Origin::Mir,
),
(BorrowKind::Mut, _, lft, BorrowKind::Unique, _, _) => self.tcx
.cannot_reborrow_already_uniquely_borrowed(
span,
&desc_place,
"",
lft,
issued_span,
"",
end_issued_loan_span,
Origin::Mir,
),
(BorrowKind::Shared, _, _, BorrowKind::Shared, _, _) => unreachable!(),
};
if let Some((_, var_span)) = old_closure_span {
err.span_label(
var_span,
format!(
"previous borrow occurs due to use of `{}` in closure",
desc_place
),
);
}
if let Some((_, var_span)) = new_closure_span {
err.span_label(
var_span,
format!("borrow occurs due to use of `{}` in closure", desc_place),
);
}
self.explain_why_borrow_contains_point(context, issued_borrow, &mut err);
err.emit();
}
pub(super) fn report_borrowed_value_does_not_live_long_enough(
&mut self,
context: Context,
borrow: &BorrowData<'tcx>,
drop_span: Span,
borrows: &ActiveBorrows<'cx, 'gcx, 'tcx>
) {
let end_span = borrows.opt_region_end_span(&borrow.region);
let scope_tree = borrows.0.scope_tree();
let root_place = self.prefixes(&borrow.borrowed_place, PrefixSet::All).last().unwrap();
match root_place {
&Place::Local(local) => {
if let Some(_) = self.storage_dead_or_drop_error_reported_l.replace(local) {
debug!("report_does_not_live_long_enough({:?}): <suppressed>",
(borrow, drop_span));
return
}
}
&Place::Static(ref statik) => {
if let Some(_) = self.storage_dead_or_drop_error_reported_s
.replace(statik.def_id)
{
debug!("report_does_not_live_long_enough({:?}): <suppressed>",
(borrow, drop_span));
return
}
},
&Place::Projection(_) =>
unreachable!("root_place is an unreachable???")
};
let borrow_span = self.mir.source_info(borrow.location).span;
let proper_span = match *root_place {
Place::Local(local) => self.mir.local_decls[local].source_info.span,
_ => drop_span,
};
match (borrow.region, &self.describe_place(&borrow.borrowed_place)) {
(RegionKind::ReScope(_), Some(name)) => {
self.report_scoped_local_value_does_not_live_long_enough(
context,
name,
&scope_tree,
&borrow,
drop_span,
borrow_span,
proper_span,
end_span
);
},
(RegionKind::ReScope(_), None) => {
self.report_scoped_temporary_value_does_not_live_long_enough(
context,
&scope_tree,
&borrow,
drop_span,
borrow_span,
proper_span,
end_span
);
},
(RegionKind::ReEarlyBound(_), Some(name)) |
(RegionKind::ReFree(_), Some(name)) |
(RegionKind::ReStatic, Some(name)) |
(RegionKind::ReEmpty, Some(name)) |
(RegionKind::ReVar(_), Some(name)) => {
self.report_unscoped_local_value_does_not_live_long_enough(
context,
name,
&scope_tree,
&borrow,
drop_span,
borrow_span,
proper_span,
end_span,
);
},
(RegionKind::ReEarlyBound(_), None) |
(RegionKind::ReFree(_), None) |
(RegionKind::ReStatic, None) |
(RegionKind::ReEmpty, None) |
(RegionKind::ReVar(_), None) => {
self.report_unscoped_temporary_value_does_not_live_long_enough(
context,
&scope_tree,
&borrow,
drop_span,
borrow_span,
proper_span,
end_span,
);
},
(RegionKind::ReLateBound(_, _), _) |
(RegionKind::ReSkolemized(_, _), _) |
(RegionKind::ReClosureBound(_), _) |
(RegionKind::ReErased, _) => {
span_bug!(drop_span, "region does not make sense in this context");
},
}
}
fn report_scoped_local_value_does_not_live_long_enough(
&mut self,
context: Context,
name: &String,
_scope_tree: &Rc<ScopeTree>,
borrow: &BorrowData<'tcx>,
drop_span: Span,
borrow_span: Span,
_proper_span: Span,
end_span: Option<Span>,
) {
let mut err = self.tcx.path_does_not_live_long_enough(borrow_span,
&format!("`{}`", name),
Origin::Mir);
err.span_label(borrow_span, "borrowed value does not live long enough");
err.span_label(drop_span, format!("`{}` dropped here while still borrowed", name));
if let Some(end) = end_span {
err.span_label(end, "borrowed value needs to live until here");
}
self.explain_why_borrow_contains_point(context, borrow, &mut err);
err.emit();
}
fn report_scoped_temporary_value_does_not_live_long_enough(
&mut self,
context: Context,
_scope_tree: &Rc<ScopeTree>,
borrow: &BorrowData<'tcx>,
drop_span: Span,
_borrow_span: Span,
proper_span: Span,
end_span: Option<Span>,
) {
let mut err = self.tcx.path_does_not_live_long_enough(proper_span,
"borrowed value",
Origin::Mir);
err.span_label(proper_span, "temporary value does not live long enough");
err.span_label(drop_span, "temporary value dropped here while still borrowed");
err.note("consider using a `let` binding to increase its lifetime");
if let Some(end) = end_span {
err.span_label(end, "temporary value needs to live until here");
}
self.explain_why_borrow_contains_point(context, borrow, &mut err);
err.emit();
}
fn report_unscoped_local_value_does_not_live_long_enough(
&mut self,
context: Context,
name: &String,
scope_tree: &Rc<ScopeTree>,
borrow: &BorrowData<'tcx>,
drop_span: Span,
borrow_span: Span,
_proper_span: Span,
_end_span: Option<Span>,
) {
let mut err = self.tcx.path_does_not_live_long_enough(borrow_span,
&format!("`{}`", name),
Origin::Mir);
err.span_label(borrow_span, "borrowed value does not live long enough");
err.span_label(drop_span, "borrowed value only lives until here");
self.tcx.note_and_explain_region(scope_tree, &mut err,
"borrowed value must be valid for ",
borrow.region, "...");
self.explain_why_borrow_contains_point(context, borrow, &mut err);
err.emit();
}
fn report_unscoped_temporary_value_does_not_live_long_enough(
&mut self,
context: Context,
scope_tree: &Rc<ScopeTree>,
borrow: &BorrowData<'tcx>,
drop_span: Span,
_borrow_span: Span,
proper_span: Span,
_end_span: Option<Span>
) {
let mut err = self.tcx.path_does_not_live_long_enough(proper_span,
"borrowed value",
Origin::Mir);
err.span_label(proper_span, "temporary value does not live long enough");
err.span_label(drop_span, "temporary value only lives until here");
self.tcx.note_and_explain_region(scope_tree, &mut err,
"borrowed value must be valid for ",
borrow.region, "...");
self.explain_why_borrow_contains_point(context, borrow, &mut err);
err.emit();
}
pub(super) fn report_illegal_mutation_of_borrowed(
&mut self,
context: Context,
(place, span): (&Place<'tcx>, Span),
loan: &BorrowData,
) {
let mut err = self.tcx.cannot_assign_to_borrowed(
span,
self.retrieve_borrow_span(loan),
&self.describe_place(place).unwrap_or("_".to_owned()),
Origin::Mir,
);
self.explain_why_borrow_contains_point(context, loan, &mut err);
err.emit();
}
pub(super) fn report_illegal_reassignment(
&mut self,
_context: Context,
(place, span): (&Place<'tcx>, Span),
assigned_span: Span,
) {
let is_arg = if let Place::Local(local) = place {
if let LocalKind::Arg = self.mir.local_kind(*local) {
true
} else {
false
}
} else {
false
};
let mut err = self.tcx.cannot_reassign_immutable(
span,
&self.describe_place(place).unwrap_or("_".to_owned()),
is_arg,
Origin::Mir,
);
let msg = if is_arg {
"cannot assign to immutable argument"
} else {
"cannot assign twice to immutable variable"
};
if span != assigned_span {
if is_arg {
err.span_label(assigned_span, "argument not declared as `mut`");
} else {
let value_msg = match self.describe_place(place) {
Some(name) => format!("`{}`", name),
None => "value".to_owned(),
};
err.span_label(assigned_span, format!("first assignment to {}", value_msg));
}
}
err.span_label(span, msg);
err.emit();
}
}
impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
// End-user visible description of `place` if one can be found. If the
// place is a temporary for instance, None will be returned.
pub(super) fn describe_place(&self, place: &Place<'tcx>) -> Option<String> {
let mut buf = String::new();
match self.append_place_to_string(place, &mut buf, false) {
Ok(()) => Some(buf),
Err(()) => None,
}
}
// Appends end-user visible description of `place` to `buf`.
fn append_place_to_string(
&self,
place: &Place<'tcx>,
buf: &mut String,
mut autoderef: bool,
) -> Result<(), ()> {
match *place {
Place::Local(local) => {
self.append_local_to_string(local, buf)?;
}
Place::Static(ref static_) => {
buf.push_str(&format!("{}", &self.tcx.item_name(static_.def_id)));
}
Place::Projection(ref proj) => {
match proj.elem {
ProjectionElem::Deref => {
if let Some(field) = self.is_upvar_field_projection(&proj.base) {
let var_index = field.index();
let name = self.mir.upvar_decls[var_index].debug_name.to_string();
if self.mir.upvar_decls[var_index].by_ref {
buf.push_str(&name);
} else {
buf.push_str(&format!("*{}", &name));
}
} else {
if autoderef {
self.append_place_to_string(&proj.base, buf, autoderef)?;
} else {
buf.push_str(&"*");
self.append_place_to_string(&proj.base, buf, autoderef)?;
}
}
}
ProjectionElem::Downcast(..) => {
self.append_place_to_string(&proj.base, buf, autoderef)?;
}
ProjectionElem::Field(field, _ty) => {
autoderef = true;
if let Some(field) = self.is_upvar_field_projection(place) {
let var_index = field.index();
let name = self.mir.upvar_decls[var_index].debug_name.to_string();
buf.push_str(&name);
} else {
let field_name = self.describe_field(&proj.base, field);
self.append_place_to_string(&proj.base, buf, autoderef)?;
buf.push_str(&format!(".{}", field_name));
}
}
ProjectionElem::Index(index) => {
autoderef = true;
self.append_place_to_string(&proj.base, buf, autoderef)?;
buf.push_str("[");
if let Err(_) = self.append_local_to_string(index, buf) {
buf.push_str("..");
}
buf.push_str("]");
}
ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {
autoderef = true;
// Since it isn't possible to borrow an element on a particular index and
// then use another while the borrow is held, don't output indices details
// to avoid confusing the end-user
self.append_place_to_string(&proj.base, buf, autoderef)?;
buf.push_str(&"[..]");
}
};
}
}
Ok(())
}
// Appends end-user visible description of the `local` place to `buf`. If `local` doesn't have
// a name, then `Err` is returned
fn append_local_to_string(&self, local_index: Local, buf: &mut String) -> Result<(), ()> {
let local = &self.mir.local_decls[local_index];
match local.name {
Some(name) => {
buf.push_str(&format!("{}", name));
Ok(())
}
None => Err(()),
}
}
// End-user visible description of the `field`nth field of `base`
fn describe_field(&self, base: &Place, field: Field) -> String {
match *base {
Place::Local(local) => {
let local = &self.mir.local_decls[local];
self.describe_field_from_ty(&local.ty, field)
}
Place::Static(ref static_) => self.describe_field_from_ty(&static_.ty, field),
Place::Projection(ref proj) => match proj.elem {
ProjectionElem::Deref => self.describe_field(&proj.base, field),
ProjectionElem::Downcast(def, variant_index) => {
format!("{}", def.variants[variant_index].fields[field.index()].name)
}
ProjectionElem::Field(_, field_type) => {
self.describe_field_from_ty(&field_type, field)
}
ProjectionElem::Index(..) |
ProjectionElem::ConstantIndex { .. } |
ProjectionElem::Subslice { .. } => {
format!("{}", self.describe_field(&proj.base, field))
}
},
}
}
// End-user visible description of the `field_index`nth field of `ty`
fn describe_field_from_ty(&self, ty: &ty::Ty, field: Field) -> String {
if ty.is_box() {
// If the type is a box, the field is described from the boxed type
self.describe_field_from_ty(&ty.boxed_ty(), field)
} else {
match ty.sty {
ty::TyAdt(def, _) => if def.is_enum() {
format!("{}", field.index())
} else {
format!("{}", def.non_enum_variant().fields[field.index()].name)
},
ty::TyTuple(_, _) => format!("{}", field.index()),
ty::TyRef(_, tnm) | ty::TyRawPtr(tnm) => {
self.describe_field_from_ty(&tnm.ty, field)
}
ty::TyArray(ty, _) | ty::TySlice(ty) => self.describe_field_from_ty(&ty, field),
ty::TyClosure(closure_def_id, _) => {
// Convert the def-id into a node-id. node-ids are only valid for
// the local code in the current crate, so this returns an `Option` in case
// the closure comes from another crate. But in that case we wouldn't
// be borrowck'ing it, so we can just unwrap:
let node_id = self.tcx.hir.as_local_node_id(closure_def_id).unwrap();
let freevar = self.tcx.with_freevars(node_id, |fv| fv[field.index()]);
self.tcx.hir.name(freevar.var_id()).to_string()
}
_ => {
// Might need a revision when the fields in trait RFC is implemented
// (https://github.com/rust-lang/rfcs/pull/1546)
bug!(
"End-user description not implemented for field access on `{:?}`",
ty.sty
);
}
}
}
}
// Retrieve span of given borrow from the current MIR representation
fn retrieve_borrow_span(&self, borrow: &BorrowData) -> Span {
self.mir.source_info(borrow.location).span
}
// Retrieve type of a place for the current MIR representation
fn retrieve_type_for_place(&self, place: &Place<'tcx>) -> Option<ty::Ty> {
match place {
Place::Local(local) => {
let local = &self.mir.local_decls[*local];
Some(local.ty)
},
Place::Static(ref st) => Some(st.ty),
Place::Projection(ref proj) => {
match proj.elem {
ProjectionElem::Field(_, ty) => Some(ty),
_ => None,
}
},
}
}
}