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