blob: fb93c41ce4f7619de08f6fcdeb43c7569a688267 [file] [log] [blame]
// Copyright 2018 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 core::unicode::property::Pattern_White_Space;
use std::fmt::{self, Display};
use rustc::mir::*;
use rustc::ty;
use rustc_errors::{DiagnosticBuilder,Applicability};
use syntax_pos::Span;
use borrow_check::MirBorrowckCtxt;
use borrow_check::prefixes::PrefixSet;
use dataflow::move_paths::{
IllegalMoveOrigin, IllegalMoveOriginKind, InitLocation,
LookupResult, MoveError, MovePathIndex,
};
use util::borrowck_errors::{BorrowckErrors, Origin};
// Often when desugaring a pattern match we may have many individual moves in
// MIR that are all part of one operation from the user's point-of-view. For
// example:
//
// let (x, y) = foo()
//
// would move x from the 0 field of some temporary, and y from the 1 field. We
// group such errors together for cleaner error reporting.
//
// Errors are kept separate if they are from places with different parent move
// paths. For example, this generates two errors:
//
// let (&x, &y) = (&String::new(), &String::new());
#[derive(Debug)]
enum GroupedMoveError<'tcx> {
// Place expression can't be moved from,
// e.g., match x[0] { s => (), } where x: &[String]
MovesFromPlace {
original_path: Place<'tcx>,
span: Span,
move_from: Place<'tcx>,
kind: IllegalMoveOriginKind<'tcx>,
binds_to: Vec<Local>,
},
// Part of a value expression can't be moved from,
// e.g., match &String::new() { &x => (), }
MovesFromValue {
original_path: Place<'tcx>,
span: Span,
move_from: MovePathIndex,
kind: IllegalMoveOriginKind<'tcx>,
binds_to: Vec<Local>,
},
// Everything that isn't from pattern matching.
OtherIllegalMove {
original_path: Place<'tcx>,
span: Span,
kind: IllegalMoveOriginKind<'tcx>,
},
}
enum BorrowedContentSource {
Arc,
Rc,
DerefRawPointer,
Other,
}
impl Display for BorrowedContentSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
BorrowedContentSource::Arc => write!(f, "an `Arc`"),
BorrowedContentSource::Rc => write!(f, "an `Rc`"),
BorrowedContentSource::DerefRawPointer => write!(f, "dereference of raw pointer"),
BorrowedContentSource::Other => write!(f, "borrowed content"),
}
}
}
impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) {
let grouped_errors = self.group_move_errors(move_errors);
for error in grouped_errors {
self.report(error);
}
}
fn group_move_errors(
&self,
errors: Vec<(Place<'tcx>, MoveError<'tcx>)>
) -> Vec<GroupedMoveError<'tcx>> {
let mut grouped_errors = Vec::new();
for (original_path, error) in errors {
self.append_to_grouped_errors(&mut grouped_errors, original_path, error);
}
grouped_errors
}
fn append_to_grouped_errors(
&self,
grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
original_path: Place<'tcx>,
error: MoveError<'tcx>,
) {
match error {
MoveError::UnionMove { .. } => {
unimplemented!("don't know how to report union move errors yet.")
}
MoveError::IllegalMove {
cannot_move_out_of: IllegalMoveOrigin { location, kind },
} => {
let stmt_source_info = self.mir.source_info(location);
// Note: that the only time we assign a place isn't a temporary
// to a user variable is when initializing it.
// If that ever stops being the case, then the ever initialized
// flow could be used.
if let Some(StatementKind::Assign(
Place::Local(local),
box Rvalue::Use(Operand::Move(move_from)),
)) = self.mir.basic_blocks()[location.block]
.statements
.get(location.statement_index)
.map(|stmt| &stmt.kind)
{
let local_decl = &self.mir.local_decls[*local];
// opt_match_place is the
// match_span is the span of the expression being matched on
// match *x.y { ... } match_place is Some(*x.y)
// ^^^^ match_span is the span of *x.y
//
// opt_match_place is None for let [mut] x = ... statements,
// whether or not the right-hand side is a place expression
if let Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
opt_match_place: Some((ref opt_match_place, match_span)),
binding_mode: _,
opt_ty_info: _,
pat_span: _,
}))) = local_decl.is_user_variable
{
self.append_binding_error(
grouped_errors,
kind,
original_path,
move_from,
*local,
opt_match_place,
match_span,
stmt_source_info.span,
);
return;
}
}
grouped_errors.push(GroupedMoveError::OtherIllegalMove {
span: stmt_source_info.span,
original_path,
kind,
});
}
}
}
fn append_binding_error(
&self,
grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
kind: IllegalMoveOriginKind<'tcx>,
original_path: Place<'tcx>,
move_from: &Place<'tcx>,
bind_to: Local,
match_place: &Option<Place<'tcx>>,
match_span: Span,
statement_span: Span,
) {
debug!(
"append_binding_error(match_place={:?}, match_span={:?})",
match_place, match_span
);
let from_simple_let = match_place.is_none();
let match_place = match_place.as_ref().unwrap_or(move_from);
match self.move_data.rev_lookup.find(match_place) {
// Error with the match place
LookupResult::Parent(_) => {
for ge in &mut *grouped_errors {
if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge {
if match_span == *span {
debug!("appending local({:?}) to list", bind_to);
if !binds_to.is_empty() {
binds_to.push(bind_to);
}
return;
}
}
}
debug!("found a new move error location");
// Don't need to point to x in let x = ... .
let (binds_to, span) = if from_simple_let {
(vec![], statement_span)
} else {
(vec![bind_to], match_span)
};
grouped_errors.push(GroupedMoveError::MovesFromPlace {
span,
move_from: match_place.clone(),
original_path,
kind,
binds_to,
});
}
// Error with the pattern
LookupResult::Exact(_) => {
let mpi = match self.move_data.rev_lookup.find(move_from) {
LookupResult::Parent(Some(mpi)) => mpi,
// move_from should be a projection from match_place.
_ => unreachable!("Probably not unreachable..."),
};
for ge in &mut *grouped_errors {
if let GroupedMoveError::MovesFromValue {
span,
move_from: other_mpi,
binds_to,
..
} = ge
{
if match_span == *span && mpi == *other_mpi {
debug!("appending local({:?}) to list", bind_to);
binds_to.push(bind_to);
return;
}
}
}
debug!("found a new move error location");
grouped_errors.push(GroupedMoveError::MovesFromValue {
span: match_span,
move_from: mpi,
original_path,
kind,
binds_to: vec![bind_to],
});
}
};
}
fn report(&mut self, error: GroupedMoveError<'tcx>) {
let (mut err, err_span) = {
let (span, original_path, kind): (Span, &Place<'tcx>, &IllegalMoveOriginKind) =
match error {
GroupedMoveError::MovesFromPlace {
span,
ref original_path,
ref kind,
..
} |
GroupedMoveError::MovesFromValue { span, ref original_path, ref kind, .. } |
GroupedMoveError::OtherIllegalMove { span, ref original_path, ref kind } => {
(span, original_path, kind)
},
};
let origin = Origin::Mir;
debug!("report: original_path={:?} span={:?}, kind={:?} \
original_path.is_upvar_field_projection={:?}", original_path, span, kind,
original_path.is_upvar_field_projection(self.mir, &self.infcx.tcx));
(
match kind {
IllegalMoveOriginKind::Static => {
self.infcx.tcx.cannot_move_out_of(span, "static item", origin)
}
IllegalMoveOriginKind::BorrowedContent { target_place: place } => {
// Inspect the type of the content behind the
// borrow to provide feedback about why this
// was a move rather than a copy.
let ty = place.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx);
let is_upvar_field_projection =
self.prefixes(&original_path, PrefixSet::All)
.any(|p| p.is_upvar_field_projection(self.mir, &self.infcx.tcx)
.is_some());
debug!("report: ty={:?}", ty);
match ty.sty {
ty::Array(..) | ty::Slice(..) =>
self.infcx.tcx.cannot_move_out_of_interior_noncopy(
span, ty, None, origin
),
ty::Closure(def_id, closure_substs)
if !self.mir.upvar_decls.is_empty() && is_upvar_field_projection
=> {
let closure_kind_ty =
closure_substs.closure_kind_ty(def_id, self.infcx.tcx);
let closure_kind = closure_kind_ty.to_opt_closure_kind();
let place_description = match closure_kind {
Some(ty::ClosureKind::Fn) => {
"captured variable in an `Fn` closure"
}
Some(ty::ClosureKind::FnMut) => {
"captured variable in an `FnMut` closure"
}
Some(ty::ClosureKind::FnOnce) => {
bug!("closure kind does not match first argument type")
}
None => bug!("closure kind not inferred by borrowck"),
};
debug!("report: closure_kind_ty={:?} closure_kind={:?} \
place_description={:?}", closure_kind_ty, closure_kind,
place_description);
let mut diag = self.infcx.tcx.cannot_move_out_of(
span, place_description, origin);
for prefix in self.prefixes(&original_path, PrefixSet::All) {
if let Some(field) = prefix.is_upvar_field_projection(
self.mir, &self.infcx.tcx) {
let upvar_decl = &self.mir.upvar_decls[field.index()];
let upvar_hir_id =
upvar_decl.var_hir_id.assert_crate_local();
let upvar_node_id =
self.infcx.tcx.hir().hir_to_node_id(upvar_hir_id);
let upvar_span = self.infcx.tcx.hir().span(upvar_node_id);
diag.span_label(upvar_span, "captured outer variable");
break;
}
}
diag
}
_ => {
let source = self.borrowed_content_source(place);
self.infcx.tcx.cannot_move_out_of(
span, &source.to_string(), origin
)
},
}
}
IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
self.infcx.tcx
.cannot_move_out_of_interior_of_drop(span, ty, origin)
}
IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } =>
self.infcx.tcx.cannot_move_out_of_interior_noncopy(
span, ty, Some(*is_index), origin
),
},
span,
)
};
self.add_move_hints(error, &mut err, err_span);
err.buffer(&mut self.errors_buffer);
}
fn add_move_hints(
&self,
error: GroupedMoveError<'tcx>,
err: &mut DiagnosticBuilder<'a>,
span: Span,
) {
let snippet = self.infcx.tcx.sess.source_map().span_to_snippet(span).unwrap();
match error {
GroupedMoveError::MovesFromPlace {
mut binds_to,
move_from,
..
} => {
let try_remove_deref = match move_from {
Place::Projection(box PlaceProjection {
elem: ProjectionElem::Deref,
..
}) => true,
_ => false,
};
if try_remove_deref && snippet.starts_with('*') {
// The snippet doesn't start with `*` in (e.g.) index
// expressions `a[b]`, which roughly desugar to
// `*Index::index(&a, b)` or
// `*IndexMut::index_mut(&mut a, b)`.
err.span_suggestion_with_applicability(
span,
"consider removing the `*`",
snippet[1..].to_owned(),
Applicability::Unspecified,
);
} else {
err.span_suggestion_with_applicability(
span,
"consider borrowing here",
format!("&{}", snippet),
Applicability::Unspecified,
);
}
binds_to.sort();
binds_to.dedup();
self.add_move_error_details(err, &binds_to);
}
GroupedMoveError::MovesFromValue { mut binds_to, .. } => {
binds_to.sort();
binds_to.dedup();
self.add_move_error_suggestions(err, &binds_to);
self.add_move_error_details(err, &binds_to);
}
// No binding. Nothing to suggest.
GroupedMoveError::OtherIllegalMove { .. } => (),
}
}
fn add_move_error_suggestions(
&self,
err: &mut DiagnosticBuilder<'a>,
binds_to: &[Local],
) {
let mut suggestions: Vec<(Span, &str, String)> = Vec::new();
for local in binds_to {
let bind_to = &self.mir.local_decls[*local];
if let Some(
ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
pat_span,
..
}))
) = bind_to.is_user_variable {
let pat_snippet = self.infcx.tcx.sess.source_map()
.span_to_snippet(pat_span)
.unwrap();
if pat_snippet.starts_with('&') {
let pat_snippet = pat_snippet[1..].trim_start();
let suggestion;
let to_remove;
if pat_snippet.starts_with("mut")
&& pat_snippet["mut".len()..].starts_with(Pattern_White_Space)
{
suggestion = pat_snippet["mut".len()..].trim_start();
to_remove = "&mut";
} else {
suggestion = pat_snippet;
to_remove = "&";
}
suggestions.push((
pat_span,
to_remove,
suggestion.to_owned(),
));
}
}
}
suggestions.sort_unstable_by_key(|&(span, _, _)| span);
suggestions.dedup_by_key(|&mut (span, _, _)| span);
for (span, to_remove, suggestion) in suggestions {
err.span_suggestion_with_applicability(
span,
&format!("consider removing the `{}`", to_remove),
suggestion,
Applicability::MachineApplicable,
);
}
}
fn add_move_error_details(
&self,
err: &mut DiagnosticBuilder<'a>,
binds_to: &[Local],
) {
let mut noncopy_var_spans = Vec::new();
for (j, local) in binds_to.into_iter().enumerate() {
let bind_to = &self.mir.local_decls[*local];
let binding_span = bind_to.source_info.span;
if j == 0 {
err.span_label(binding_span, "data moved here");
} else {
err.span_label(binding_span, "...and here");
}
if binds_to.len() == 1 {
err.span_note(
binding_span,
&format!(
"move occurs because `{}` has type `{}`, \
which does not implement the `Copy` trait",
bind_to.name.unwrap(),
bind_to.ty
),
);
} else {
noncopy_var_spans.push(binding_span);
}
}
if binds_to.len() > 1 {
err.span_note(
noncopy_var_spans,
"move occurs because these variables have types that \
don't implement the `Copy` trait",
);
}
}
fn borrowed_content_source(&self, place: &Place<'tcx>) -> BorrowedContentSource {
// Look up the provided place and work out the move path index for it,
// we'll use this to work back through where this value came from and check whether it
// was originally part of an `Rc` or `Arc`.
let initial_mpi = match self.move_data.rev_lookup.find(place) {
LookupResult::Exact(mpi) | LookupResult::Parent(Some(mpi)) => mpi,
_ => return BorrowedContentSource::Other,
};
let mut queue = vec![initial_mpi];
let mut visited = Vec::new();
debug!("borrowed_content_source: queue={:?}", queue);
while let Some(mpi) = queue.pop() {
debug!(
"borrowed_content_source: mpi={:?} queue={:?} visited={:?}",
mpi, queue, visited
);
// Don't visit the same path twice.
if visited.contains(&mpi) {
continue;
}
visited.push(mpi);
for i in &self.move_data.init_path_map[mpi] {
let init = &self.move_data.inits[*i];
debug!("borrowed_content_source: init={:?}", init);
// We're only interested in statements that initialized a value, not the
// initializations from arguments.
let loc = match init.location {
InitLocation::Statement(stmt) => stmt,
_ => continue,
};
let bbd = &self.mir[loc.block];
let is_terminator = bbd.statements.len() == loc.statement_index;
debug!("borrowed_content_source: loc={:?} is_terminator={:?}", loc, is_terminator);
if !is_terminator {
let stmt = &bbd.statements[loc.statement_index];
debug!("borrowed_content_source: stmt={:?}", stmt);
// We're only interested in assignments (in particular, where the
// assignment came from - was it an `Rc` or `Arc`?).
if let StatementKind::Assign(_, box Rvalue::Ref(_, _, source)) = &stmt.kind {
let ty = source.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx);
let ty = match ty.sty {
ty::TyKind::Ref(_, ty, _) => ty,
_ => ty,
};
debug!("borrowed_content_source: ty={:?}", ty);
if ty.is_arc() {
return BorrowedContentSource::Arc;
} else if ty.is_rc() {
return BorrowedContentSource::Rc;
} else {
queue.push(init.path);
}
}
} else if let Some(Terminator {
kind: TerminatorKind::Call { args, .. },
..
}) = &bbd.terminator {
for arg in args {
let source = match arg {
Operand::Copy(place) | Operand::Move(place) => place,
_ => continue,
};
let ty = source.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx);
let ty = match ty.sty {
ty::TyKind::Ref(_, ty, _) => ty,
_ => ty,
};
debug!("borrowed_content_source: ty={:?}", ty);
if ty.is_arc() {
return BorrowedContentSource::Arc;
} else if ty.is_rc() {
return BorrowedContentSource::Rc;
} else {
queue.push(init.path);
}
}
}
}
}
// If we didn't find an `Arc` or an `Rc`, then check specifically for
// a dereference of a place that has the type of a raw pointer.
// We can't use `place.ty(..).to_ty(..)` here as that strips away the raw pointer.
if let Place::Projection(box Projection {
base,
elem: ProjectionElem::Deref,
}) = place {
if base.ty(self.mir, self.infcx.tcx).to_ty(self.infcx.tcx).is_unsafe_ptr() {
return BorrowedContentSource::DerefRawPointer;
}
}
BorrowedContentSource::Other
}
}