]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/borrow_check/move_errors.rs
d979851376a2616e8504c2a8c31ce93b747b3a5d
[rust.git] / src / librustc_mir / borrow_check / move_errors.rs
1 // Copyright 2018 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.
4 //
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.
10
11 use rustc::hir;
12 use rustc::mir::*;
13 use rustc::ty;
14 use rustc_errors::DiagnosticBuilder;
15 use syntax_pos::Span;
16
17 use borrow_check::MirBorrowckCtxt;
18 use dataflow::move_paths::{IllegalMoveOrigin, IllegalMoveOriginKind};
19 use dataflow::move_paths::{LookupResult, MoveError, MovePathIndex};
20 use util::borrowck_errors::{BorrowckErrors, Origin};
21
22 // Often when desugaring a pattern match we may have many individual moves in
23 // MIR that are all part of one operation from the user's point-of-view. For
24 // example:
25 //
26 // let (x, y) = foo()
27 //
28 // would move x from the 0 field of some temporary, and y from the 1 field. We
29 // group such errors together for cleaner error reporting.
30 //
31 // Errors are kept separate if they are from places with different parent move
32 // paths. For example, this generates two errors:
33 //
34 // let (&x, &y) = (&String::new(), &String::new());
35 #[derive(Debug)]
36 enum GroupedMoveError<'tcx> {
37     // Match place can't be moved from
38     // e.g. match x[0] { s => (), } where x: &[String]
39     MovesFromMatchPlace {
40         span: Span,
41         move_from: Place<'tcx>,
42         kind: IllegalMoveOriginKind<'tcx>,
43         binds_to: Vec<Local>,
44     },
45     // Part of a pattern can't be moved from,
46     // e.g. match &String::new() { &x => (), }
47     MovesFromPattern {
48         span: Span,
49         move_from: MovePathIndex,
50         kind: IllegalMoveOriginKind<'tcx>,
51         binds_to: Vec<Local>,
52     },
53     // Everything that isn't from pattern matching.
54     OtherIllegalMove {
55         span: Span,
56         kind: IllegalMoveOriginKind<'tcx>,
57     },
58 }
59
60 impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
61     pub(crate) fn report_move_errors(&self, move_errors: Vec<MoveError<'tcx>>) {
62         let grouped_errors = self.group_move_errors(move_errors);
63         for error in grouped_errors {
64             self.report(error);
65         }
66     }
67
68     fn group_move_errors(&self, errors: Vec<MoveError<'tcx>>) -> Vec<GroupedMoveError<'tcx>> {
69         let mut grouped_errors = Vec::new();
70         for error in errors {
71             self.append_to_grouped_errors(&mut grouped_errors, error);
72         }
73         grouped_errors
74     }
75
76     fn append_to_grouped_errors(
77         &self,
78         grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
79         error: MoveError<'tcx>,
80     ) {
81         match error {
82             MoveError::UnionMove { .. } => {
83                 unimplemented!("don't know how to report union move errors yet.")
84             }
85             MoveError::IllegalMove {
86                 cannot_move_out_of: IllegalMoveOrigin { location, kind },
87             } => {
88                 let stmt_source_info = self.mir.source_info(location);
89                 if let Some(StatementKind::Assign(
90                     Place::Local(local),
91                     Rvalue::Use(Operand::Move(move_from)),
92                 )) = self.mir.basic_blocks()[location.block]
93                     .statements
94                     .get(location.statement_index)
95                     .map(|stmt| &stmt.kind)
96                 {
97                     let local_decl = &self.mir.local_decls[*local];
98                     if let Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
99                         opt_match_place: Some((ref opt_match_place, match_span)),
100                         binding_mode: _,
101                         opt_ty_info: _,
102                     }))) = local_decl.is_user_variable
103                     {
104                         // opt_match_place is the
105                         // match_span is the span of the expression being matched on
106                         // match *x.y { ... }        match_place is Some(*x.y)
107                         //       ^^^^                match_span is the span of *x.y
108                         // opt_match_place is None for let [mut] x = ... statements,
109                         // whether or not the right-hand side is a place expression
110
111                         // HACK use scopes to determine if this assignment is
112                         // the initialization of a variable.
113                         // FIXME(matthewjasper) This would probably be more
114                         // reliable if it used the ever initialized dataflow
115                         // but move errors are currently reported before the
116                         // rest of borrowck has run.
117                         if self
118                             .mir
119                             .is_sub_scope(local_decl.source_info.scope, stmt_source_info.scope)
120                         {
121                             self.append_binding_error(
122                                 grouped_errors,
123                                 kind,
124                                 move_from,
125                                 *local,
126                                 opt_match_place,
127                                 match_span,
128                             );
129                         }
130                         return;
131                     }
132                 }
133                 grouped_errors.push(GroupedMoveError::OtherIllegalMove {
134                     span: stmt_source_info.span,
135                     kind,
136                 });
137             }
138         }
139     }
140
141     fn append_binding_error(
142         &self,
143         grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,
144         kind: IllegalMoveOriginKind<'tcx>,
145         move_from: &Place<'tcx>,
146         bind_to: Local,
147         match_place: &Option<Place<'tcx>>,
148         match_span: Span,
149     ) {
150         debug!(
151             "append_to_grouped_errors(match_place={:?}, match_span={:?})",
152             match_place, match_span
153         );
154
155         let from_simple_let = match_place.is_none();
156         let match_place = match_place.as_ref().unwrap_or(move_from);
157
158         match self.move_data.rev_lookup.find(match_place) {
159             // Error with the match place
160             LookupResult::Parent(_) => {
161                 for ge in &mut *grouped_errors {
162                     if let GroupedMoveError::MovesFromMatchPlace { span, binds_to, .. } = ge {
163                         if match_span == *span {
164                             debug!("appending local({:?}) to list", bind_to);
165                             if !binds_to.is_empty() {
166                                 binds_to.push(bind_to);
167                             }
168                             return;
169                         }
170                     }
171                 }
172                 debug!("found a new move error location");
173
174                 // Don't need to point to x in let x = ... .
175                 let binds_to = if from_simple_let {
176                     vec![]
177                 } else {
178                     vec![bind_to]
179                 };
180                 grouped_errors.push(GroupedMoveError::MovesFromMatchPlace {
181                     span: match_span,
182                     move_from: match_place.clone(),
183                     kind,
184                     binds_to,
185                 });
186             }
187             // Error with the pattern
188             LookupResult::Exact(_) => {
189                 let mpi = match self.move_data.rev_lookup.find(move_from) {
190                     LookupResult::Parent(Some(mpi)) => mpi,
191                     // move_from should be a projection from match_place.
192                     _ => unreachable!("Probably not unreachable..."),
193                 };
194                 for ge in &mut *grouped_errors {
195                     if let GroupedMoveError::MovesFromPattern {
196                         span,
197                         move_from: other_mpi,
198                         binds_to,
199                         ..
200                     } = ge
201                     {
202                         if match_span == *span && mpi == *other_mpi {
203                             debug!("appending local({:?}) to list", bind_to);
204                             binds_to.push(bind_to);
205                             return;
206                         }
207                     }
208                 }
209                 debug!("found a new move error location");
210                 grouped_errors.push(GroupedMoveError::MovesFromPattern {
211                     span: match_span,
212                     move_from: mpi,
213                     kind,
214                     binds_to: vec![bind_to],
215                 });
216             }
217         };
218     }
219
220     fn report(&self, error: GroupedMoveError<'tcx>) {
221         let (mut err, err_span) = {
222             let (span, kind): (Span, &IllegalMoveOriginKind) = match error {
223                 GroupedMoveError::MovesFromMatchPlace { span, ref kind, .. }
224                 | GroupedMoveError::MovesFromPattern { span, ref kind, .. }
225                 | GroupedMoveError::OtherIllegalMove { span, ref kind } => (span, kind),
226             };
227             let origin = Origin::Mir;
228             (
229                 match kind {
230                     IllegalMoveOriginKind::Static => {
231                         self.tcx.cannot_move_out_of(span, "static item", origin)
232                     }
233                     IllegalMoveOriginKind::BorrowedContent { target_ty: ty } => {
234                         // Inspect the type of the content behind the
235                         // borrow to provide feedback about why this
236                         // was a move rather than a copy.
237                         match ty.sty {
238                             ty::TyArray(..) | ty::TySlice(..) => self
239                                 .tcx
240                                 .cannot_move_out_of_interior_noncopy(span, ty, None, origin),
241                             _ => self
242                                 .tcx
243                                 .cannot_move_out_of(span, "borrowed content", origin),
244                         }
245                     }
246                     IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {
247                         self.tcx
248                             .cannot_move_out_of_interior_of_drop(span, ty, origin)
249                     }
250                     IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => self
251                         .tcx
252                         .cannot_move_out_of_interior_noncopy(span, ty, Some(*is_index), origin),
253                 },
254                 span,
255             )
256         };
257
258         self.add_move_hints(error, &mut err, err_span);
259         err.emit();
260     }
261
262     fn add_move_hints(
263         &self,
264         error: GroupedMoveError<'tcx>,
265         err: &mut DiagnosticBuilder<'a>,
266         span: Span,
267     ) {
268         match error {
269             GroupedMoveError::MovesFromMatchPlace {
270                 mut binds_to,
271                 move_from,
272                 ..
273             } => {
274                 // Ok to suggest a borrow, since the target can't be moved from
275                 // anyway.
276                 if let Ok(snippet) = self.tcx.sess.codemap().span_to_snippet(span) {
277                     match move_from {
278                         Place::Projection(ref proj)
279                             if self.suitable_to_remove_deref(proj, &snippet) =>
280                         {
281                             err.span_suggestion(
282                                 span,
283                                 "consider removing this dereference operator",
284                                 format!("{}", &snippet[1..]),
285                             );
286                         }
287                         _ => {
288                             err.span_suggestion(
289                                 span,
290                                 "consider using a reference instead",
291                                 format!("&{}", snippet),
292                             );
293                         }
294                     }
295
296                     binds_to.sort();
297                     binds_to.dedup();
298                     for local in binds_to {
299                         let bind_to = &self.mir.local_decls[local];
300                         let binding_span = bind_to.source_info.span;
301                         err.span_label(
302                             binding_span,
303                             format!(
304                                 "move occurs because {} has type `{}`, \
305                                  which does not implement the `Copy` trait",
306                                 bind_to.name.unwrap(),
307                                 bind_to.ty
308                             ),
309                         );
310                     }
311                 }
312             }
313             GroupedMoveError::MovesFromPattern { mut binds_to, .. } => {
314                 // Suggest ref, since there might be a move in
315                 // another match arm
316                 binds_to.sort();
317                 binds_to.dedup();
318                 for local in binds_to {
319                     let bind_to = &self.mir.local_decls[local];
320                     let binding_span = bind_to.source_info.span;
321
322                     // Suggest ref mut when the user has already written mut.
323                     let ref_kind = match bind_to.mutability {
324                         Mutability::Not => "ref",
325                         Mutability::Mut => "ref mut",
326                     };
327                     match bind_to.name {
328                         Some(name) => {
329                             err.span_suggestion(
330                                 binding_span,
331                                 "to prevent move, use ref or ref mut",
332                                 format!("{} {:?}", ref_kind, name),
333                             );
334                         }
335                         None => {
336                             err.span_label(
337                                 span,
338                                 format!("Local {:?} is not suitable for ref", bind_to),
339                             );
340                         }
341                     }
342                 }
343             }
344             // Nothing to suggest.
345             GroupedMoveError::OtherIllegalMove { .. } => (),
346         }
347     }
348
349     fn suitable_to_remove_deref(&self, proj: &PlaceProjection<'tcx>, snippet: &str) -> bool {
350         let is_shared_ref = |ty: ty::Ty| match ty.sty {
351             ty::TypeVariants::TyRef(.., hir::Mutability::MutImmutable) => true,
352             _ => false,
353         };
354
355         proj.elem == ProjectionElem::Deref && snippet.starts_with('*') && match proj.base {
356             Place::Local(local) => {
357                 let local_decl = &self.mir.local_decls[local];
358                 // If this is a temporary, then this could be from an
359                 // overloaded * operator.
360                 local_decl.is_user_variable.is_some() && is_shared_ref(local_decl.ty)
361             }
362             Place::Static(ref st) => is_shared_ref(st.ty),
363             Place::Projection(ref proj) => match proj.elem {
364                 ProjectionElem::Field(_, ty) => is_shared_ref(ty),
365                 _ => false,
366             },
367         }
368     }
369 }