Group move errors before reporting, add suggestions
This commit is contained in:
parent
43fce075d3
commit
0193d1f736
9 changed files with 502 additions and 66 deletions
|
@ -497,8 +497,8 @@ pub enum LocalKind {
|
||||||
ReturnPointer,
|
ReturnPointer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)]
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)]
|
||||||
pub struct VarBindingForm {
|
pub struct VarBindingForm<'tcx> {
|
||||||
/// Is variable bound via `x`, `mut x`, `ref x`, or `ref mut x`?
|
/// Is variable bound via `x`, `mut x`, `ref x`, or `ref mut x`?
|
||||||
pub binding_mode: ty::BindingMode,
|
pub binding_mode: ty::BindingMode,
|
||||||
/// If an explicit type was provided for this variable binding,
|
/// If an explicit type was provided for this variable binding,
|
||||||
|
@ -508,21 +508,49 @@ pub struct VarBindingForm {
|
||||||
/// doing so breaks incremental compilation (as of this writing),
|
/// doing so breaks incremental compilation (as of this writing),
|
||||||
/// while a `Span` does not cause our tests to fail.
|
/// while a `Span` does not cause our tests to fail.
|
||||||
pub opt_ty_info: Option<Span>,
|
pub opt_ty_info: Option<Span>,
|
||||||
|
/// Place of the RHS of the =, or the subject of the `match` where this
|
||||||
|
/// variable is initialized. None in the case of `let PATTERN;`.
|
||||||
|
/// Some((None, ..)) in the case of and `let [mut] x = ...` because
|
||||||
|
/// (a) the right-hand side isn't evaluated as a place expression.
|
||||||
|
/// (b) it gives a way to separate this case from the remaining cases
|
||||||
|
/// for diagnostics.
|
||||||
|
pub opt_match_place: Option<(Option<Place<'tcx>>, Span)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)]
|
#[derive(Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)]
|
||||||
pub enum BindingForm {
|
pub enum BindingForm<'tcx> {
|
||||||
/// This is a binding for a non-`self` binding, or a `self` that has an explicit type.
|
/// This is a binding for a non-`self` binding, or a `self` that has an explicit type.
|
||||||
Var(VarBindingForm),
|
Var(VarBindingForm<'tcx>),
|
||||||
/// Binding for a `self`/`&self`/`&mut self` binding where the type is implicit.
|
/// Binding for a `self`/`&self`/`&mut self` binding where the type is implicit.
|
||||||
ImplicitSelf,
|
ImplicitSelf,
|
||||||
}
|
}
|
||||||
|
|
||||||
CloneTypeFoldableAndLiftImpls! { BindingForm, }
|
CloneTypeFoldableAndLiftImpls! { BindingForm<'tcx>, }
|
||||||
|
|
||||||
impl_stable_hash_for!(struct self::VarBindingForm { binding_mode, opt_ty_info });
|
impl_stable_hash_for!(struct self::VarBindingForm<'tcx> {
|
||||||
|
binding_mode,
|
||||||
|
opt_ty_info,
|
||||||
|
opt_match_place
|
||||||
|
});
|
||||||
|
|
||||||
impl_stable_hash_for!(enum self::BindingForm { Var(binding), ImplicitSelf, });
|
mod binding_form_impl {
|
||||||
|
use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableHasherResult};
|
||||||
|
use ich::StableHashingContext;
|
||||||
|
|
||||||
|
impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for super::BindingForm<'tcx> {
|
||||||
|
fn hash_stable<W: StableHasherResult>(&self,
|
||||||
|
hcx: &mut StableHashingContext<'a>,
|
||||||
|
hasher: &mut StableHasher<W>) {
|
||||||
|
use super::BindingForm::*;
|
||||||
|
::std::mem::discriminant(self).hash_stable(hcx, hasher);
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Var(binding) => binding.hash_stable(hcx, hasher),
|
||||||
|
ImplicitSelf => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A MIR local.
|
/// A MIR local.
|
||||||
///
|
///
|
||||||
|
@ -542,7 +570,7 @@ pub struct LocalDecl<'tcx> {
|
||||||
/// therefore it need not be visible across crates. pnkfelix
|
/// therefore it need not be visible across crates. pnkfelix
|
||||||
/// currently hypothesized we *need* to wrap this in a
|
/// currently hypothesized we *need* to wrap this in a
|
||||||
/// `ClearCrossCrate` as long as it carries as `HirId`.
|
/// `ClearCrossCrate` as long as it carries as `HirId`.
|
||||||
pub is_user_variable: Option<ClearCrossCrate<BindingForm>>,
|
pub is_user_variable: Option<ClearCrossCrate<BindingForm<'tcx>>>,
|
||||||
|
|
||||||
/// True if this is an internal local
|
/// True if this is an internal local
|
||||||
///
|
///
|
||||||
|
@ -670,6 +698,7 @@ impl<'tcx> LocalDecl<'tcx> {
|
||||||
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
|
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
|
||||||
binding_mode: ty::BindingMode::BindByValue(_),
|
binding_mode: ty::BindingMode::BindByValue(_),
|
||||||
opt_ty_info: _,
|
opt_ty_info: _,
|
||||||
|
opt_match_place: _,
|
||||||
}))) => true,
|
}))) => true,
|
||||||
|
|
||||||
// FIXME: might be able to thread the distinction between
|
// FIXME: might be able to thread the distinction between
|
||||||
|
@ -688,6 +717,7 @@ impl<'tcx> LocalDecl<'tcx> {
|
||||||
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
|
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
|
||||||
binding_mode: ty::BindingMode::BindByValue(_),
|
binding_mode: ty::BindingMode::BindByValue(_),
|
||||||
opt_ty_info: _,
|
opt_ty_info: _,
|
||||||
|
opt_match_place: _,
|
||||||
}))) => true,
|
}))) => true,
|
||||||
|
|
||||||
Some(ClearCrossCrate::Set(BindingForm::ImplicitSelf)) => true,
|
Some(ClearCrossCrate::Set(BindingForm::ImplicitSelf)) => true,
|
||||||
|
|
|
@ -35,7 +35,6 @@ use syntax_pos::Span;
|
||||||
|
|
||||||
use dataflow::indexes::BorrowIndex;
|
use dataflow::indexes::BorrowIndex;
|
||||||
use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex};
|
use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex};
|
||||||
use dataflow::move_paths::{IllegalMoveOriginKind, MoveError};
|
|
||||||
use dataflow::Borrows;
|
use dataflow::Borrows;
|
||||||
use dataflow::DataflowResultsConsumer;
|
use dataflow::DataflowResultsConsumer;
|
||||||
use dataflow::FlowAtLocation;
|
use dataflow::FlowAtLocation;
|
||||||
|
@ -62,6 +61,7 @@ mod path_utils;
|
||||||
crate mod place_ext;
|
crate mod place_ext;
|
||||||
mod prefixes;
|
mod prefixes;
|
||||||
mod used_muts;
|
mod used_muts;
|
||||||
|
mod move_errors;
|
||||||
|
|
||||||
pub(crate) mod nll;
|
pub(crate) mod nll;
|
||||||
|
|
||||||
|
@ -117,40 +117,7 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
|
||||||
let move_data: MoveData<'tcx> = match MoveData::gather_moves(mir, tcx) {
|
let move_data: MoveData<'tcx> = match MoveData::gather_moves(mir, tcx) {
|
||||||
Ok(move_data) => move_data,
|
Ok(move_data) => move_data,
|
||||||
Err((move_data, move_errors)) => {
|
Err((move_data, move_errors)) => {
|
||||||
for move_error in move_errors {
|
move_errors::report_move_errors(&mir, tcx, move_errors, &move_data);
|
||||||
let (span, kind): (Span, IllegalMoveOriginKind) = match move_error {
|
|
||||||
MoveError::UnionMove { .. } => {
|
|
||||||
unimplemented!("don't know how to report union move errors yet.")
|
|
||||||
}
|
|
||||||
MoveError::IllegalMove {
|
|
||||||
cannot_move_out_of: o,
|
|
||||||
} => (o.span, o.kind),
|
|
||||||
};
|
|
||||||
let origin = Origin::Mir;
|
|
||||||
let mut err = match kind {
|
|
||||||
IllegalMoveOriginKind::Static => {
|
|
||||||
tcx.cannot_move_out_of(span, "static item", origin)
|
|
||||||
}
|
|
||||||
IllegalMoveOriginKind::BorrowedContent { target_ty: ty } => {
|
|
||||||
// Inspect the type of the content behind the
|
|
||||||
// borrow to provide feedback about why this
|
|
||||||
// was a move rather than a copy.
|
|
||||||
match ty.sty {
|
|
||||||
ty::TyArray(..) | ty::TySlice(..) => {
|
|
||||||
tcx.cannot_move_out_of_interior_noncopy(span, ty, None, origin)
|
|
||||||
}
|
|
||||||
_ => tcx.cannot_move_out_of(span, "borrowed content", origin),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
|
|
||||||
tcx.cannot_move_out_of_interior_of_drop(span, ty, origin)
|
|
||||||
}
|
|
||||||
IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {
|
|
||||||
tcx.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index), origin)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
err.emit();
|
|
||||||
}
|
|
||||||
move_data
|
move_data
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
388
src/librustc_mir/borrow_check/move_errors.rs
Normal file
388
src/librustc_mir/borrow_check/move_errors.rs
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
// 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 rustc::hir;
|
||||||
|
use rustc::mir::*;
|
||||||
|
use rustc::ty::{self, TyCtxt};
|
||||||
|
use rustc_errors::DiagnosticBuilder;
|
||||||
|
use syntax_pos::Span;
|
||||||
|
|
||||||
|
use dataflow::move_paths::{IllegalMoveOrigin, IllegalMoveOriginKind, MoveData};
|
||||||
|
use dataflow::move_paths::{LookupResult, MoveError, MovePathIndex};
|
||||||
|
use util::borrowck_errors::{BorrowckErrors, Origin};
|
||||||
|
|
||||||
|
pub(crate) fn report_move_errors<'gcx, 'tcx>(
|
||||||
|
mir: &Mir<'tcx>,
|
||||||
|
tcx: TyCtxt<'_, 'gcx, 'tcx>,
|
||||||
|
move_errors: Vec<MoveError<'tcx>>,
|
||||||
|
move_data: &MoveData<'tcx>,
|
||||||
|
) {
|
||||||
|
MoveErrorCtxt {
|
||||||
|
mir,
|
||||||
|
tcx,
|
||||||
|
move_data,
|
||||||
|
}.report_errors(move_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct MoveErrorCtxt<'a, 'gcx: 'tcx, 'tcx: 'a> {
|
||||||
|
mir: &'a Mir<'tcx>,
|
||||||
|
tcx: TyCtxt<'a, 'gcx, 'tcx>,
|
||||||
|
move_data: &'a MoveData<'tcx>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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> {
|
||||||
|
// Match place can't be moved from
|
||||||
|
// e.g. match x[0] { s => (), } where x: &[String]
|
||||||
|
MovesFromMatchPlace {
|
||||||
|
span: Span,
|
||||||
|
move_from: Place<'tcx>,
|
||||||
|
kind: IllegalMoveOriginKind<'tcx>,
|
||||||
|
binds_to: Vec<Local>,
|
||||||
|
},
|
||||||
|
// Part of a pattern can't be moved from,
|
||||||
|
// e.g. match &String::new() { &x => (), }
|
||||||
|
MovesFromPattern {
|
||||||
|
span: Span,
|
||||||
|
move_from: MovePathIndex,
|
||||||
|
kind: IllegalMoveOriginKind<'tcx>,
|
||||||
|
binds_to: Vec<Local>,
|
||||||
|
},
|
||||||
|
// Everything that isn't from pattern matching.
|
||||||
|
OtherIllegalMove {
|
||||||
|
span: Span,
|
||||||
|
kind: IllegalMoveOriginKind<'tcx>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'gcx, 'tcx> MoveErrorCtxt<'a, 'gcx, 'tcx> {
|
||||||
|
fn report_errors(self, move_errors: Vec<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<MoveError<'tcx>>) -> Vec<GroupedMoveError<'tcx>> {
|
||||||
|
let mut grouped_errors = Vec::new();
|
||||||
|
for error in errors {
|
||||||
|
self.append_to_grouped_errors(&mut grouped_errors, error);
|
||||||
|
}
|
||||||
|
grouped_errors
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_to_grouped_errors(
|
||||||
|
self,
|
||||||
|
grouped_errors: &mut Vec<GroupedMoveError<'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);
|
||||||
|
if let Some(StatementKind::Assign(
|
||||||
|
Place::Local(local),
|
||||||
|
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];
|
||||||
|
if let Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
|
||||||
|
opt_match_place: Some((ref opt_match_place, match_span)),
|
||||||
|
binding_mode: _,
|
||||||
|
opt_ty_info: _,
|
||||||
|
}))) = local_decl.is_user_variable
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// HACK use scopes to determine if this assignment is
|
||||||
|
// the initialization of a variable.
|
||||||
|
// FIXME(matthewjasper) This would probably be more
|
||||||
|
// reliable if it used the ever initialized dataflow
|
||||||
|
// but move errors are currently reported before the
|
||||||
|
// rest of borrowck has run.
|
||||||
|
if self
|
||||||
|
.mir
|
||||||
|
.is_sub_scope(local_decl.source_info.scope, stmt_source_info.scope)
|
||||||
|
{
|
||||||
|
self.append_binding_error(
|
||||||
|
grouped_errors,
|
||||||
|
kind,
|
||||||
|
move_from,
|
||||||
|
*local,
|
||||||
|
opt_match_place,
|
||||||
|
match_span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grouped_errors.push(GroupedMoveError::OtherIllegalMove {
|
||||||
|
span: stmt_source_info.span,
|
||||||
|
kind,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_binding_error(
|
||||||
|
self,
|
||||||
|
grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
|
||||||
|
kind: IllegalMoveOriginKind<'tcx>,
|
||||||
|
move_from: &Place<'tcx>,
|
||||||
|
bind_to: Local,
|
||||||
|
match_place: &Option<Place<'tcx>>,
|
||||||
|
match_span: Span,
|
||||||
|
) {
|
||||||
|
debug!(
|
||||||
|
"append_to_grouped_errors(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::MovesFromMatchPlace { 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 = if from_simple_let {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
vec![bind_to]
|
||||||
|
};
|
||||||
|
grouped_errors.push(GroupedMoveError::MovesFromMatchPlace {
|
||||||
|
span: match_span,
|
||||||
|
move_from: match_place.clone(),
|
||||||
|
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::MovesFromPattern {
|
||||||
|
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::MovesFromPattern {
|
||||||
|
span: match_span,
|
||||||
|
move_from: mpi,
|
||||||
|
kind,
|
||||||
|
binds_to: vec![bind_to],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn report(self, error: GroupedMoveError<'tcx>) {
|
||||||
|
let (mut err, err_span) = {
|
||||||
|
let (span, kind): (Span, &IllegalMoveOriginKind) = match error {
|
||||||
|
GroupedMoveError::MovesFromMatchPlace { span, ref kind, .. }
|
||||||
|
| GroupedMoveError::MovesFromPattern { span, ref kind, .. }
|
||||||
|
| GroupedMoveError::OtherIllegalMove { span, ref kind } => (span, kind),
|
||||||
|
};
|
||||||
|
let origin = Origin::Mir;
|
||||||
|
(
|
||||||
|
match kind {
|
||||||
|
IllegalMoveOriginKind::Static => {
|
||||||
|
self.tcx.cannot_move_out_of(span, "static item", origin)
|
||||||
|
}
|
||||||
|
IllegalMoveOriginKind::BorrowedContent { target_ty: ty } => {
|
||||||
|
// Inspect the type of the content behind the
|
||||||
|
// borrow to provide feedback about why this
|
||||||
|
// was a move rather than a copy.
|
||||||
|
match ty.sty {
|
||||||
|
ty::TyArray(..) | ty::TySlice(..) => self
|
||||||
|
.tcx
|
||||||
|
.cannot_move_out_of_interior_noncopy(span, ty, None, origin),
|
||||||
|
_ => self
|
||||||
|
.tcx
|
||||||
|
.cannot_move_out_of(span, "borrowed content", origin),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
|
||||||
|
self.tcx
|
||||||
|
.cannot_move_out_of_interior_of_drop(span, ty, origin)
|
||||||
|
}
|
||||||
|
IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => self
|
||||||
|
.tcx
|
||||||
|
.cannot_move_out_of_interior_noncopy(span, ty, Some(*is_index), origin),
|
||||||
|
},
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.add_move_hints(error, &mut err, err_span);
|
||||||
|
err.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_move_hints(
|
||||||
|
self,
|
||||||
|
error: GroupedMoveError<'tcx>,
|
||||||
|
err: &mut DiagnosticBuilder<'a>,
|
||||||
|
span: Span,
|
||||||
|
) {
|
||||||
|
match error {
|
||||||
|
GroupedMoveError::MovesFromMatchPlace {
|
||||||
|
mut binds_to,
|
||||||
|
move_from,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Ok to suggest a borrow, since the target can't be moved from
|
||||||
|
// anyway.
|
||||||
|
if let Ok(snippet) = self.tcx.sess.codemap().span_to_snippet(span) {
|
||||||
|
match move_from {
|
||||||
|
Place::Projection(ref proj)
|
||||||
|
if self.suitable_to_remove_deref(proj, &snippet) =>
|
||||||
|
{
|
||||||
|
err.span_suggestion(
|
||||||
|
span,
|
||||||
|
"consider removing this dereference operator",
|
||||||
|
format!("{}", &snippet[1..]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
err.span_suggestion(
|
||||||
|
span,
|
||||||
|
"consider using a reference instead",
|
||||||
|
format!("&{}", snippet),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binds_to.sort();
|
||||||
|
binds_to.dedup();
|
||||||
|
for local in binds_to {
|
||||||
|
let bind_to = &self.mir.local_decls[local];
|
||||||
|
let binding_span = bind_to.source_info.span;
|
||||||
|
err.span_label(
|
||||||
|
binding_span,
|
||||||
|
format!(
|
||||||
|
"move occurs because {} has type `{}`, \
|
||||||
|
which does not implement the `Copy` trait",
|
||||||
|
bind_to.name.unwrap(),
|
||||||
|
bind_to.ty
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GroupedMoveError::MovesFromPattern { mut binds_to, .. } => {
|
||||||
|
// Suggest ref, since there might be a move in
|
||||||
|
// another match arm
|
||||||
|
binds_to.sort();
|
||||||
|
binds_to.dedup();
|
||||||
|
for local in binds_to {
|
||||||
|
let bind_to = &self.mir.local_decls[local];
|
||||||
|
let binding_span = bind_to.source_info.span;
|
||||||
|
|
||||||
|
// Suggest ref mut when the user has already written mut.
|
||||||
|
let ref_kind = match bind_to.mutability {
|
||||||
|
Mutability::Not => "ref",
|
||||||
|
Mutability::Mut => "ref mut",
|
||||||
|
};
|
||||||
|
match bind_to.name {
|
||||||
|
Some(name) => {
|
||||||
|
err.span_suggestion(
|
||||||
|
binding_span,
|
||||||
|
"to prevent move, use ref or ref mut",
|
||||||
|
format!("{} {:?}", ref_kind, name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
err.span_label(
|
||||||
|
span,
|
||||||
|
format!("Local {:?} is not suitable for ref", bind_to),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Nothing to suggest.
|
||||||
|
GroupedMoveError::OtherIllegalMove { .. } => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suitable_to_remove_deref(self, proj: &PlaceProjection<'tcx>, snippet: &str) -> bool {
|
||||||
|
let is_shared_ref = |ty: ty::Ty| match ty.sty {
|
||||||
|
ty::TypeVariants::TyRef(.., hir::Mutability::MutImmutable) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
proj.elem == ProjectionElem::Deref && snippet.starts_with('*') && match proj.base {
|
||||||
|
Place::Local(local) => {
|
||||||
|
let local_decl = &self.mir.local_decls[local];
|
||||||
|
// If this is a temporary, then this could be from an
|
||||||
|
// overloaded * operator.
|
||||||
|
local_decl.is_user_variable.is_some() && is_shared_ref(local_decl.ty)
|
||||||
|
}
|
||||||
|
Place::Static(ref st) => is_shared_ref(st.ty),
|
||||||
|
Place::Projection(ref proj) => match proj.elem {
|
||||||
|
ProjectionElem::Field(_, ty) => is_shared_ref(ty),
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -115,11 +115,21 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
// Declare the bindings, which may create a source scope.
|
// Declare the bindings, which may create a source scope.
|
||||||
let remainder_span = remainder_scope.span(this.hir.tcx(),
|
let remainder_span = remainder_scope.span(this.hir.tcx(),
|
||||||
&this.hir.region_scope_tree);
|
&this.hir.region_scope_tree);
|
||||||
let scope = this.declare_bindings(None, remainder_span, lint_level, &pattern,
|
|
||||||
ArmHasGuard(false));
|
let scope;
|
||||||
|
|
||||||
// Evaluate the initializer, if present.
|
// Evaluate the initializer, if present.
|
||||||
if let Some(init) = initializer {
|
if let Some(init) = initializer {
|
||||||
|
let initializer_span = init.span();
|
||||||
|
|
||||||
|
scope = this.declare_bindings(
|
||||||
|
None,
|
||||||
|
remainder_span,
|
||||||
|
lint_level,
|
||||||
|
&pattern,
|
||||||
|
ArmHasGuard(false),
|
||||||
|
Some((None, initializer_span)),
|
||||||
|
);
|
||||||
unpack!(block = this.in_opt_scope(
|
unpack!(block = this.in_opt_scope(
|
||||||
opt_destruction_scope.map(|de|(de, source_info)), block, |this| {
|
opt_destruction_scope.map(|de|(de, source_info)), block, |this| {
|
||||||
let scope = (init_scope, source_info);
|
let scope = (init_scope, source_info);
|
||||||
|
@ -128,6 +138,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
|
scope = this.declare_bindings(None, remainder_span, lint_level, &pattern,
|
||||||
|
ArmHasGuard(false), None);
|
||||||
|
|
||||||
// FIXME(#47184): We currently only insert `UserAssertTy` statements for
|
// FIXME(#47184): We currently only insert `UserAssertTy` statements for
|
||||||
// patterns that are bindings, this is as we do not want to deconstruct
|
// patterns that are bindings, this is as we do not want to deconstruct
|
||||||
// the type being assertion to match the pattern.
|
// the type being assertion to match the pattern.
|
||||||
|
|
|
@ -44,6 +44,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
arms: Vec<Arm<'tcx>>)
|
arms: Vec<Arm<'tcx>>)
|
||||||
-> BlockAnd<()> {
|
-> BlockAnd<()> {
|
||||||
let tcx = self.hir.tcx();
|
let tcx = self.hir.tcx();
|
||||||
|
let discriminant_span = discriminant.span();
|
||||||
let discriminant_place = unpack!(block = self.as_place(block, discriminant));
|
let discriminant_place = unpack!(block = self.as_place(block, discriminant));
|
||||||
|
|
||||||
// Matching on a `discriminant_place` with an uninhabited type doesn't
|
// Matching on a `discriminant_place` with an uninhabited type doesn't
|
||||||
|
@ -96,7 +97,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
let scope = self.declare_bindings(None, body.span,
|
let scope = self.declare_bindings(None, body.span,
|
||||||
LintLevel::Inherited,
|
LintLevel::Inherited,
|
||||||
&arm.patterns[0],
|
&arm.patterns[0],
|
||||||
ArmHasGuard(arm.guard.is_some()));
|
ArmHasGuard(arm.guard.is_some()),
|
||||||
|
Some((Some(&discriminant_place), discriminant_span)));
|
||||||
(body, scope.unwrap_or(self.source_scope))
|
(body, scope.unwrap_or(self.source_scope))
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
|
@ -254,7 +256,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let place = unpack!(block = self.as_place(block, initializer));
|
let place = unpack!(block = self.as_place(block, initializer));
|
||||||
self.place_into_pattern(block, irrefutable_pat, &place)
|
self.place_into_pattern(block, irrefutable_pat, &place, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,7 +264,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
pub fn place_into_pattern(&mut self,
|
pub fn place_into_pattern(&mut self,
|
||||||
mut block: BasicBlock,
|
mut block: BasicBlock,
|
||||||
irrefutable_pat: Pattern<'tcx>,
|
irrefutable_pat: Pattern<'tcx>,
|
||||||
initializer: &Place<'tcx>)
|
initializer: &Place<'tcx>,
|
||||||
|
set_match_place: bool)
|
||||||
-> BlockAnd<()> {
|
-> BlockAnd<()> {
|
||||||
// create a dummy candidate
|
// create a dummy candidate
|
||||||
let mut candidate = Candidate {
|
let mut candidate = Candidate {
|
||||||
|
@ -288,6 +291,25 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
candidate.match_pairs);
|
candidate.match_pairs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for matches and function arguments, the place that is being matched
|
||||||
|
// can be set when creating the variables. But the place for
|
||||||
|
// let PATTERN = ... might not even exist until we do the assignment.
|
||||||
|
// so we set it here instead
|
||||||
|
if set_match_place {
|
||||||
|
for binding in &candidate.bindings {
|
||||||
|
let local = self.var_local_id(binding.var_id, OutsideGuard);
|
||||||
|
|
||||||
|
if let Some(ClearCrossCrate::Set(BindingForm::Var(
|
||||||
|
VarBindingForm {opt_match_place: Some((ref mut match_place, _)), .. }
|
||||||
|
))) = self.local_decls[local].is_user_variable
|
||||||
|
{
|
||||||
|
*match_place = Some(initializer.clone());
|
||||||
|
} else {
|
||||||
|
bug!("Let binding to non-user variable.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// now apply the bindings, which will also declare the variables
|
// now apply the bindings, which will also declare the variables
|
||||||
self.bind_matched_candidate_for_arm_body(block, &candidate.bindings);
|
self.bind_matched_candidate_for_arm_body(block, &candidate.bindings);
|
||||||
|
|
||||||
|
@ -302,7 +324,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
scope_span: Span,
|
scope_span: Span,
|
||||||
lint_level: LintLevel,
|
lint_level: LintLevel,
|
||||||
pattern: &Pattern<'tcx>,
|
pattern: &Pattern<'tcx>,
|
||||||
has_guard: ArmHasGuard)
|
has_guard: ArmHasGuard,
|
||||||
|
opt_match_place: Option<(Option<&Place<'tcx>>, Span)>)
|
||||||
-> Option<SourceScope> {
|
-> Option<SourceScope> {
|
||||||
assert!(!(visibility_scope.is_some() && lint_level.is_explicit()),
|
assert!(!(visibility_scope.is_some() && lint_level.is_explicit()),
|
||||||
"can't have both a visibility and a lint scope at the same time");
|
"can't have both a visibility and a lint scope at the same time");
|
||||||
|
@ -326,7 +349,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
};
|
};
|
||||||
let visibility_scope = visibility_scope.unwrap();
|
let visibility_scope = visibility_scope.unwrap();
|
||||||
this.declare_binding(source_info, visibility_scope, mutability, name, mode, var,
|
this.declare_binding(source_info, visibility_scope, mutability, name, mode, var,
|
||||||
ty, has_guard);
|
ty, has_guard, opt_match_place.map(|(x, y)| (x.cloned(), y)));
|
||||||
});
|
});
|
||||||
visibility_scope
|
visibility_scope
|
||||||
}
|
}
|
||||||
|
@ -1121,7 +1144,8 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
mode: BindingMode,
|
mode: BindingMode,
|
||||||
var_id: NodeId,
|
var_id: NodeId,
|
||||||
var_ty: Ty<'tcx>,
|
var_ty: Ty<'tcx>,
|
||||||
has_guard: ArmHasGuard)
|
has_guard: ArmHasGuard,
|
||||||
|
opt_match_place: Option<(Option<Place<'tcx>>, Span)>)
|
||||||
{
|
{
|
||||||
debug!("declare_binding(var_id={:?}, name={:?}, mode={:?}, var_ty={:?}, \
|
debug!("declare_binding(var_id={:?}, name={:?}, mode={:?}, var_ty={:?}, \
|
||||||
visibility_scope={:?}, source_info={:?})",
|
visibility_scope={:?}, source_info={:?})",
|
||||||
|
@ -1146,6 +1170,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
// idents in pat; but complex w/ unclear UI payoff.
|
// idents in pat; but complex w/ unclear UI payoff.
|
||||||
// Instead, just abandon providing diagnostic info.
|
// Instead, just abandon providing diagnostic info.
|
||||||
opt_ty_info: None,
|
opt_ty_info: None,
|
||||||
|
opt_match_place,
|
||||||
}))),
|
}))),
|
||||||
};
|
};
|
||||||
let for_arm_body = self.local_decls.push(local.clone());
|
let for_arm_body = self.local_decls.push(local.clone());
|
||||||
|
|
|
@ -705,6 +705,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
|
|
||||||
if let Some(pattern) = pattern {
|
if let Some(pattern) = pattern {
|
||||||
let pattern = self.hir.pattern_from_hir(pattern);
|
let pattern = self.hir.pattern_from_hir(pattern);
|
||||||
|
let span = pattern.span;
|
||||||
|
|
||||||
match *pattern.kind {
|
match *pattern.kind {
|
||||||
// Don't introduce extra copies for simple bindings
|
// Don't introduce extra copies for simple bindings
|
||||||
|
@ -716,15 +717,19 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
|
||||||
} else {
|
} else {
|
||||||
let binding_mode = ty::BindingMode::BindByValue(mutability.into());
|
let binding_mode = ty::BindingMode::BindByValue(mutability.into());
|
||||||
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
|
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
|
||||||
binding_mode, opt_ty_info })))
|
binding_mode,
|
||||||
|
opt_ty_info,
|
||||||
|
opt_match_place: Some((Some(place.clone()), span)),
|
||||||
|
})))
|
||||||
};
|
};
|
||||||
self.var_indices.insert(var, LocalsForNode::One(local));
|
self.var_indices.insert(var, LocalsForNode::One(local));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
scope = self.declare_bindings(scope, ast_body.span,
|
scope = self.declare_bindings(scope, ast_body.span,
|
||||||
LintLevel::Inherited, &pattern,
|
LintLevel::Inherited, &pattern,
|
||||||
matches::ArmHasGuard(false));
|
matches::ArmHasGuard(false),
|
||||||
unpack!(block = self.place_into_pattern(block, pattern, &place));
|
Some((Some(&place), span)));
|
||||||
|
unpack!(block = self.place_into_pattern(block, pattern, &place, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,8 +109,7 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
|
||||||
match *place {
|
match *place {
|
||||||
Place::Local(local) => Ok(self.builder.data.rev_lookup.locals[local]),
|
Place::Local(local) => Ok(self.builder.data.rev_lookup.locals[local]),
|
||||||
Place::Static(..) => {
|
Place::Static(..) => {
|
||||||
let span = self.builder.mir.source_info(self.loc).span;
|
Err(MoveError::cannot_move_out_of(self.loc, Static))
|
||||||
Err(MoveError::cannot_move_out_of(span, Static))
|
|
||||||
}
|
}
|
||||||
Place::Projection(ref proj) => {
|
Place::Projection(ref proj) => {
|
||||||
self.move_path_for_projection(place, proj)
|
self.move_path_for_projection(place, proj)
|
||||||
|
@ -133,13 +132,13 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
|
||||||
let mir = self.builder.mir;
|
let mir = self.builder.mir;
|
||||||
let tcx = self.builder.tcx;
|
let tcx = self.builder.tcx;
|
||||||
let place_ty = proj.base.ty(mir, tcx).to_ty(tcx);
|
let place_ty = proj.base.ty(mir, tcx).to_ty(tcx);
|
||||||
match place_ty.sty {
|
match place_ty.sty {
|
||||||
ty::TyRef(..) | ty::TyRawPtr(..) =>
|
ty::TyRef(..) | ty::TyRawPtr(..) =>
|
||||||
return Err(MoveError::cannot_move_out_of(
|
return Err(MoveError::cannot_move_out_of(
|
||||||
mir.source_info(self.loc).span,
|
self.loc,
|
||||||
BorrowedContent { target_ty: place.ty(mir, tcx).to_ty(tcx) })),
|
BorrowedContent { target_ty: place.ty(mir, tcx).to_ty(tcx) })),
|
||||||
ty::TyAdt(adt, _) if adt.has_dtor(tcx) && !adt.is_box() =>
|
ty::TyAdt(adt, _) if adt.has_dtor(tcx) && !adt.is_box() =>
|
||||||
return Err(MoveError::cannot_move_out_of(mir.source_info(self.loc).span,
|
return Err(MoveError::cannot_move_out_of(self.loc,
|
||||||
InteriorOfTypeWithDestructor {
|
InteriorOfTypeWithDestructor {
|
||||||
container_ty: place_ty
|
container_ty: place_ty
|
||||||
})),
|
})),
|
||||||
|
@ -148,7 +147,7 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
|
||||||
return Err(MoveError::UnionMove { path: base }),
|
return Err(MoveError::UnionMove { path: base }),
|
||||||
ty::TySlice(_) =>
|
ty::TySlice(_) =>
|
||||||
return Err(MoveError::cannot_move_out_of(
|
return Err(MoveError::cannot_move_out_of(
|
||||||
mir.source_info(self.loc).span,
|
self.loc,
|
||||||
InteriorOfSliceOrArray {
|
InteriorOfSliceOrArray {
|
||||||
ty: place_ty, is_index: match proj.elem {
|
ty: place_ty, is_index: match proj.elem {
|
||||||
ProjectionElem::Index(..) => true,
|
ProjectionElem::Index(..) => true,
|
||||||
|
@ -158,7 +157,7 @@ impl<'b, 'a, 'gcx, 'tcx> Gatherer<'b, 'a, 'gcx, 'tcx> {
|
||||||
ty::TyArray(..) => match proj.elem {
|
ty::TyArray(..) => match proj.elem {
|
||||||
ProjectionElem::Index(..) =>
|
ProjectionElem::Index(..) =>
|
||||||
return Err(MoveError::cannot_move_out_of(
|
return Err(MoveError::cannot_move_out_of(
|
||||||
mir.source_info(self.loc).span,
|
self.loc,
|
||||||
InteriorOfSliceOrArray {
|
InteriorOfSliceOrArray {
|
||||||
ty: place_ty, is_index: true
|
ty: place_ty, is_index: true
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -209,7 +209,7 @@ pub enum InitKind {
|
||||||
Deep,
|
Deep,
|
||||||
/// Only does a shallow init
|
/// Only does a shallow init
|
||||||
Shallow,
|
Shallow,
|
||||||
/// This doesn't initialize the variabe on panic (and a panic is possible).
|
/// This doesn't initialize the variable on panic (and a panic is possible).
|
||||||
NonPanicPathOnly,
|
NonPanicPathOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +271,7 @@ impl<'tcx> MovePathLookup<'tcx> {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct IllegalMoveOrigin<'tcx> {
|
pub struct IllegalMoveOrigin<'tcx> {
|
||||||
pub(crate) span: Span,
|
pub(crate) location: Location,
|
||||||
pub(crate) kind: IllegalMoveOriginKind<'tcx>,
|
pub(crate) kind: IllegalMoveOriginKind<'tcx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,8 +304,8 @@ pub enum MoveError<'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'tcx> MoveError<'tcx> {
|
impl<'tcx> MoveError<'tcx> {
|
||||||
fn cannot_move_out_of(span: Span, kind: IllegalMoveOriginKind<'tcx>) -> Self {
|
fn cannot_move_out_of(location: Location, kind: IllegalMoveOriginKind<'tcx>) -> Self {
|
||||||
let origin = IllegalMoveOrigin { span, kind };
|
let origin = IllegalMoveOrigin { location, kind };
|
||||||
MoveError::IllegalMove { cannot_move_out_of: origin }
|
MoveError::IllegalMove { cannot_move_out_of: origin }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,6 +315,15 @@ pub enum LogicalOp {
|
||||||
Or,
|
Or,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'tcx> ExprRef<'tcx> {
|
||||||
|
pub fn span(&self) -> Span {
|
||||||
|
match self {
|
||||||
|
ExprRef::Hair(expr) => expr.span,
|
||||||
|
ExprRef::Mirror(expr) => expr.span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// The Mirror trait
|
// The Mirror trait
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue