]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/borrow_check/mutability_errors.rs
Rollup merge of #53413 - eddyb:featured-in-the-latest-edition, r=varkor
[rust.git] / src / librustc_mir / borrow_check / mutability_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::{self, BindingForm, ClearCrossCrate, Local, Location, Mir};
13 use rustc::mir::{Mutability, Place, Projection, ProjectionElem, Static};
14 use rustc::ty::{self, TyCtxt};
15 use rustc_data_structures::indexed_vec::Idx;
16 use syntax_pos::Span;
17
18 use borrow_check::MirBorrowckCtxt;
19 use util::borrowck_errors::{BorrowckErrors, Origin};
20 use util::collect_writes::FindAssignments;
21 use util::suggest_ref_mut;
22
23 #[derive(Copy, Clone, Debug)]
24 pub(super) enum AccessKind {
25     MutableBorrow,
26     Mutate,
27     Move,
28 }
29
30 impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
31     pub(super) fn report_mutability_error(
32         &mut self,
33         access_place: &Place<'tcx>,
34         span: Span,
35         the_place_err: &Place<'tcx>,
36         error_access: AccessKind,
37         location: Location,
38     ) {
39         debug!(
40             "report_mutability_error(\
41                 access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\
42             )",
43             access_place, span, the_place_err, error_access, location,
44         );
45
46         let mut err;
47         let item_msg;
48         let reason;
49         let access_place_desc = self.describe_place(access_place);
50         debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
51
52         match the_place_err {
53             Place::Local(local) => {
54                 item_msg = format!("`{}`", access_place_desc.unwrap());
55                 if let Place::Local(_) = access_place {
56                     reason = ", as it is not declared as mutable".to_string();
57                 } else {
58                     let name = self.mir.local_decls[*local]
59                         .name
60                         .expect("immutable unnamed local");
61                     reason = format!(", as `{}` is not declared as mutable", name);
62                 }
63             }
64
65             Place::Projection(box Projection {
66                 base,
67                 elem: ProjectionElem::Field(upvar_index, _),
68             }) => {
69                 debug_assert!(is_closure_or_generator(
70                     base.ty(self.mir, self.tcx).to_ty(self.tcx)
71                 ));
72
73                 item_msg = format!("`{}`", access_place_desc.unwrap());
74                 if access_place.is_upvar_field_projection(self.mir, &self.tcx).is_some() {
75                     reason = ", as it is not declared as mutable".to_string();
76                 } else {
77                     let name = self.mir.upvar_decls[upvar_index.index()].debug_name;
78                     reason = format!(", as `{}` is not declared as mutable", name);
79                 }
80             }
81
82             Place::Projection(box Projection {
83                 base,
84                 elem: ProjectionElem::Deref,
85             }) => {
86                 if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() {
87                     item_msg = format!("`{}`", access_place_desc.unwrap());
88                     debug_assert!(self.mir.local_decls[Local::new(1)].ty.is_region_ptr());
89                     debug_assert!(is_closure_or_generator(
90                         the_place_err.ty(self.mir, self.tcx).to_ty(self.tcx)
91                     ));
92
93                     reason = if access_place.is_upvar_field_projection(self.mir,
94                                                                        &self.tcx).is_some() {
95                         ", as it is a captured variable in a `Fn` closure".to_string()
96                     } else {
97                         ", as `Fn` closures cannot mutate their captured variables".to_string()
98                     }
99                 } else if {
100                     if let Place::Local(local) = *base {
101                         if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard))
102                             = self.mir.local_decls[local].is_user_variable {
103                                 true
104                         } else {
105                             false
106                         }
107                     } else {
108                         false
109                     }
110                 } {
111                     item_msg = format!("`{}`", access_place_desc.unwrap());
112                     reason = ", as it is immutable for the pattern guard".to_string();
113                 } else {
114                     let pointer_type =
115                         if base.ty(self.mir, self.tcx).to_ty(self.tcx).is_region_ptr() {
116                             "`&` reference"
117                         } else {
118                             "`*const` pointer"
119                         };
120                     if let Some(desc) = access_place_desc {
121                         item_msg = format!("`{}`", desc);
122                         reason = match error_access {
123                             AccessKind::Move |
124                             AccessKind::Mutate => format!(" which is behind a {}", pointer_type),
125                             AccessKind::MutableBorrow => {
126                                 format!(", as it is behind a {}", pointer_type)
127                             }
128                         }
129                     } else {
130                         item_msg = format!("data in a {}", pointer_type);
131                         reason = "".to_string();
132                     }
133                 }
134             }
135
136             Place::Promoted(_) => unreachable!(),
137
138             Place::Static(box Static { def_id, ty: _ }) => {
139                 if let Place::Static(_) = access_place {
140                     item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
141                     reason = "".to_string();
142                 } else {
143                     item_msg = format!("`{}`", access_place_desc.unwrap());
144                     let static_name = &self.tcx.item_name(*def_id);
145                     reason = format!(", as `{}` is an immutable static item", static_name);
146                 }
147             }
148
149             Place::Projection(box Projection {
150                 base: _,
151                 elem: ProjectionElem::Index(_),
152             })
153             | Place::Projection(box Projection {
154                 base: _,
155                 elem: ProjectionElem::ConstantIndex { .. },
156             })
157             | Place::Projection(box Projection {
158                 base: _,
159                 elem: ProjectionElem::Subslice { .. },
160             })
161             | Place::Projection(box Projection {
162                 base: _,
163                 elem: ProjectionElem::Downcast(..),
164             }) => bug!("Unexpected immutable place."),
165         }
166
167         debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
168
169         // `act` and `acted_on` are strings that let us abstract over
170         // the verbs used in some diagnostic messages.
171         let act;
172         let acted_on;
173
174         let span = match error_access {
175             AccessKind::Move => {
176                 err = self.tcx
177                     .cannot_move_out_of(span, &(item_msg + &reason), Origin::Mir);
178                 act = "move";
179                 acted_on = "moved";
180                 span
181             }
182             AccessKind::Mutate => {
183                 err = self.tcx
184                     .cannot_assign(span, &(item_msg + &reason), Origin::Mir);
185                 act = "assign";
186                 acted_on = "written";
187                 span
188             }
189             AccessKind::MutableBorrow => {
190                 act = "borrow as mutable";
191                 acted_on = "borrowed as mutable";
192
193                 let borrow_spans = self.borrow_spans(span, location);
194                 let borrow_span = borrow_spans.args_or_use();
195                 err = self.tcx.cannot_borrow_path_as_mutable_because(
196                     borrow_span,
197                     &item_msg,
198                     &reason,
199                     Origin::Mir,
200                 );
201                 borrow_spans.var_span_label(
202                     &mut err,
203                     format!(
204                         "mutable borrow occurs due to use of `{}` in closure",
205                         // always Some() if the message is printed.
206                         self.describe_place(access_place).unwrap_or(String::new()),
207                     )
208                 );
209                 borrow_span
210             }
211         };
212
213         debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
214
215         match the_place_err {
216             // We want to suggest users use `let mut` for local (user
217             // variable) mutations...
218             Place::Local(local) if self.mir.local_decls[*local].can_be_made_mutable() => {
219                 // ... but it doesn't make sense to suggest it on
220                 // variables that are `ref x`, `ref mut x`, `&self`,
221                 // or `&mut self` (such variables are simply not
222                 // mutable).
223                 let local_decl = &self.mir.local_decls[*local];
224                 assert_eq!(local_decl.mutability, Mutability::Not);
225
226                 err.span_label(span, format!("cannot {ACT}", ACT = act));
227                 err.span_suggestion(
228                     local_decl.source_info.span,
229                     "consider changing this to be mutable",
230                     format!("mut {}", local_decl.name.unwrap()),
231                 );
232             }
233
234             // Also suggest adding mut for upvars
235             Place::Projection(box Projection {
236                 base,
237                 elem: ProjectionElem::Field(upvar_index, _),
238             }) => {
239                 debug_assert!(is_closure_or_generator(
240                     base.ty(self.mir, self.tcx).to_ty(self.tcx)
241                 ));
242
243                 err.span_label(span, format!("cannot {ACT}", ACT = act));
244
245                 let upvar_hir_id = self.mir.upvar_decls[upvar_index.index()]
246                     .var_hir_id
247                     .assert_crate_local();
248                 let upvar_node_id = self.tcx.hir.hir_to_node_id(upvar_hir_id);
249                 if let Some(hir::map::NodeBinding(pat)) = self.tcx.hir.find(upvar_node_id) {
250                     if let hir::PatKind::Binding(
251                         hir::BindingAnnotation::Unannotated,
252                         _,
253                         upvar_ident,
254                         _,
255                     ) = pat.node
256                     {
257                         err.span_suggestion(
258                             upvar_ident.span,
259                             "consider changing this to be mutable",
260                             format!("mut {}", upvar_ident.name),
261                         );
262                     }
263                 }
264             }
265
266             // complete hack to approximate old AST-borrowck
267             // diagnostic: if the span starts with a mutable borrow of
268             // a local variable, then just suggest the user remove it.
269             Place::Local(_)
270                 if {
271                     if let Ok(snippet) = self.tcx.sess.codemap().span_to_snippet(span) {
272                         snippet.starts_with("&mut ")
273                     } else {
274                         false
275                     }
276                 } =>
277             {
278                 err.span_label(span, format!("cannot {ACT}", ACT = act));
279                 err.span_label(span, "try removing `&mut` here");
280             }
281
282             Place::Projection(box Projection {
283                 base: Place::Local(local),
284                 elem: ProjectionElem::Deref,
285             }) if {
286                 if let Some(ClearCrossCrate::Set(BindingForm::RefForGuard)) =
287                     self.mir.local_decls[*local].is_user_variable
288                 {
289                     true
290                 } else {
291                     false
292                 }
293             } =>
294             {
295                 err.span_label(span, format!("cannot {ACT}", ACT = act));
296                 err.note(
297                     "variables bound in patterns are immutable until the end of the pattern guard",
298                 );
299             }
300
301             // We want to point out when a `&` can be readily replaced
302             // with an `&mut`.
303             //
304             // FIXME: can this case be generalized to work for an
305             // arbitrary base for the projection?
306             Place::Projection(box Projection {
307                 base: Place::Local(local),
308                 elem: ProjectionElem::Deref,
309             }) if self.mir.local_decls[*local].is_user_variable.is_some() =>
310             {
311                 let local_decl = &self.mir.local_decls[*local];
312                 let suggestion = match local_decl.is_user_variable.as_ref().unwrap() {
313                     ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf) => {
314                         Some(suggest_ampmut_self(self.tcx, local_decl))
315                     }
316
317                     ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
318                         binding_mode: ty::BindingMode::BindByValue(_),
319                         opt_ty_info,
320                         ..
321                     })) => Some(suggest_ampmut(
322                         self.tcx,
323                         self.mir,
324                         *local,
325                         local_decl,
326                         *opt_ty_info,
327                     )),
328
329                     ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
330                         binding_mode: ty::BindingMode::BindByReference(_),
331                         ..
332                     })) => {
333                         let pattern_span = local_decl.source_info.span;
334                         suggest_ref_mut(self.tcx, pattern_span)
335                             .map(|replacement| (pattern_span, replacement))
336                     }
337
338                     //
339                     ClearCrossCrate::Set(mir::BindingForm::RefForGuard) => unreachable!(),
340
341                     ClearCrossCrate::Clear => bug!("saw cleared local state"),
342                 };
343
344                 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
345                     ("&", "reference")
346                 } else {
347                     ("*const", "pointer")
348                 };
349
350                 if let Some((err_help_span, suggested_code)) = suggestion {
351                     err.span_suggestion(
352                         err_help_span,
353                         &format!("consider changing this to be a mutable {}", pointer_desc),
354                         suggested_code,
355                     );
356                 }
357
358                 if let Some(name) = local_decl.name {
359                     err.span_label(
360                         span,
361                         format!(
362                             "`{NAME}` is a `{SIGIL}` {DESC}, \
363                              so the data it refers to cannot be {ACTED_ON}",
364                             NAME = name,
365                             SIGIL = pointer_sigil,
366                             DESC = pointer_desc,
367                             ACTED_ON = acted_on
368                         ),
369                     );
370                 } else {
371                     err.span_label(
372                         span,
373                         format!(
374                             "cannot {ACT} through `{SIGIL}` {DESC}",
375                             ACT = act,
376                             SIGIL = pointer_sigil,
377                             DESC = pointer_desc
378                         ),
379                     );
380                 }
381             }
382
383             Place::Projection(box Projection {
384                 base,
385                 elem: ProjectionElem::Deref,
386             }) if *base == Place::Local(Local::new(1)) && !self.mir.upvar_decls.is_empty() =>
387             {
388                 err.span_label(span, format!("cannot {ACT}", ACT = act));
389                 err.span_help(
390                     self.mir.span,
391                     "consider changing this to accept closures that implement `FnMut`"
392                 );
393             }
394
395             _ => {
396                 err.span_label(span, format!("cannot {ACT}", ACT = act));
397             }
398         }
399
400         err.buffer(&mut self.errors_buffer);
401     }
402 }
403
404 fn suggest_ampmut_self<'cx, 'gcx, 'tcx>(
405     tcx: TyCtxt<'cx, 'gcx, 'tcx>,
406     local_decl: &mir::LocalDecl<'tcx>,
407 ) -> (Span, String) {
408     let sp = local_decl.source_info.span;
409     (sp, match tcx.sess.codemap().span_to_snippet(sp) {
410         Ok(snippet) => {
411             let lt_pos = snippet.find('\'');
412             if let Some(lt_pos) = lt_pos {
413                 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
414             } else {
415                 "&mut self".to_string()
416             }
417         }
418         _ => "&mut self".to_string()
419     })
420 }
421
422 // When we want to suggest a user change a local variable to be a `&mut`, there
423 // are three potential "obvious" things to highlight:
424 //
425 // let ident [: Type] [= RightHandSideExpression];
426 //     ^^^^^    ^^^^     ^^^^^^^^^^^^^^^^^^^^^^^
427 //     (1.)     (2.)              (3.)
428 //
429 // We can always fallback on highlighting the first. But chances are good that
430 // the user experience will be better if we highlight one of the others if possible;
431 // for example, if the RHS is present and the Type is not, then the type is going to
432 // be inferred *from* the RHS, which means we should highlight that (and suggest
433 // that they borrow the RHS mutably).
434 //
435 // This implementation attempts to emulate AST-borrowck prioritization
436 // by trying (3.), then (2.) and finally falling back on (1.).
437 fn suggest_ampmut<'cx, 'gcx, 'tcx>(
438     tcx: TyCtxt<'cx, 'gcx, 'tcx>,
439     mir: &Mir<'tcx>,
440     local: Local,
441     local_decl: &mir::LocalDecl<'tcx>,
442     opt_ty_info: Option<Span>,
443 ) -> (Span, String) {
444     let locations = mir.find_assignments(local);
445     if locations.len() > 0 {
446         let assignment_rhs_span = mir.source_info(locations[0]).span;
447         if let Ok(src) = tcx.sess.codemap().span_to_snippet(assignment_rhs_span) {
448             if let (true, Some(ws_pos)) = (
449                 src.starts_with("&'"),
450                 src.find(|c: char| -> bool { c.is_whitespace() }),
451             ) {
452                 let lt_name = &src[1..ws_pos];
453                 let ty = &src[ws_pos..];
454                 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
455             } else if src.starts_with('&') {
456                 let borrowed_expr = src[1..].to_string();
457                 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
458             }
459         }
460     }
461
462     let highlight_span = match opt_ty_info {
463         // if this is a variable binding with an explicit type,
464         // try to highlight that for the suggestion.
465         Some(ty_span) => ty_span,
466
467         // otherwise, just highlight the span associated with
468         // the (MIR) LocalDecl.
469         None => local_decl.source_info.span,
470     };
471
472     if let Ok(src) = tcx.sess.codemap().span_to_snippet(highlight_span) {
473         if let (true, Some(ws_pos)) = (
474             src.starts_with("&'"),
475             src.find(|c: char| -> bool { c.is_whitespace() }),
476         ) {
477             let lt_name = &src[1..ws_pos];
478             let ty = &src[ws_pos..];
479             return (highlight_span, format!("&{} mut{}", lt_name, ty));
480         }
481     }
482
483     let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
484     assert_eq!(ty_mut.mutbl, hir::MutImmutable);
485     (highlight_span,
486      if local_decl.ty.is_region_ptr() {
487          format!("&mut {}", ty_mut.ty)
488      } else {
489          format!("*mut {}", ty_mut.ty)
490      })
491 }
492
493 fn is_closure_or_generator(ty: ty::Ty) -> bool {
494     ty.is_closure() || ty.is_generator()
495 }