]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/borrow_check/diagnostics/mutability_errors.rs
Rollup merge of #67055 - lqd:const_qualif, r=oli-obk
[rust.git] / src / librustc_mir / borrow_check / diagnostics / mutability_errors.rs
1 use rustc::hir;
2 use rustc::hir::Node;
3 use rustc::mir::{self, ClearCrossCrate, Local, LocalInfo, Location, ReadOnlyBodyCache};
4 use rustc::mir::{Mutability, Place, PlaceRef, PlaceBase, ProjectionElem};
5 use rustc::ty::{self, Ty, TyCtxt};
6 use rustc_index::vec::Idx;
7 use syntax_pos::Span;
8 use syntax_pos::symbol::kw;
9
10 use crate::borrow_check::MirBorrowckCtxt;
11 use crate::borrow_check::diagnostics::BorrowedContentSource;
12 use crate::util::collect_writes::FindAssignments;
13 use rustc_errors::Applicability;
14
15 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
16 pub(crate) enum AccessKind {
17     MutableBorrow,
18     Mutate,
19 }
20
21 impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
22     pub(crate) fn report_mutability_error(
23         &mut self,
24         access_place: &Place<'tcx>,
25         span: Span,
26         the_place_err: PlaceRef<'cx, 'tcx>,
27         error_access: AccessKind,
28         location: Location,
29     ) {
30         debug!(
31             "report_mutability_error(\
32                 access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\
33             )",
34             access_place, span, the_place_err, error_access, location,
35         );
36
37         let mut err;
38         let item_msg;
39         let reason;
40         let mut opt_source = None;
41         let access_place_desc = self.describe_place(access_place.as_ref());
42         debug!("report_mutability_error: access_place_desc={:?}", access_place_desc);
43
44         match the_place_err {
45             PlaceRef {
46                 base: PlaceBase::Local(local),
47                 projection: [],
48             } => {
49                 item_msg = format!("`{}`", access_place_desc.unwrap());
50                 if access_place.as_local().is_some() {
51                     reason = ", as it is not declared as mutable".to_string();
52                 } else {
53                     let name = self.local_names[*local]
54                         .expect("immutable unnamed local");
55                     reason = format!(", as `{}` is not declared as mutable", name);
56                 }
57             }
58
59             PlaceRef {
60                 base: _,
61                 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
62             } => {
63                 debug_assert!(is_closure_or_generator(
64                     Place::ty_from(
65                         &the_place_err.base,
66                         proj_base,
67                         *self.body,
68                         self.infcx.tcx
69                     ).ty));
70
71                 item_msg = format!("`{}`", access_place_desc.unwrap());
72                 if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
73                     reason = ", as it is not declared as mutable".to_string();
74                 } else {
75                     let name = self.upvars[upvar_index.index()].name;
76                     reason = format!(", as `{}` is not declared as mutable", name);
77                 }
78             }
79
80             PlaceRef {
81                 base: &PlaceBase::Local(local),
82                 projection: [ProjectionElem::Deref],
83             } if self.body.local_decls[local].is_ref_for_guard() => {
84                 item_msg = format!("`{}`", access_place_desc.unwrap());
85                 reason = ", as it is immutable for the pattern guard".to_string();
86             }
87             PlaceRef {
88                 base: &PlaceBase::Local(local),
89                 projection: [ProjectionElem::Deref],
90             } if self.body.local_decls[local].is_ref_to_static() => {
91                 if access_place.projection.len() == 1 {
92                     item_msg = format!("immutable static item `{}`", access_place_desc.unwrap());
93                     reason = String::new();
94                 } else {
95                     item_msg = format!("`{}`", access_place_desc.unwrap());
96                     let local_info = &self.body.local_decls[local].local_info;
97                     if let LocalInfo::StaticRef { def_id, .. } = *local_info {
98                         let static_name = &self.infcx.tcx.item_name(def_id);
99                         reason = format!(", as `{}` is an immutable static item", static_name);
100                     } else {
101                         bug!("is_ref_to_static return true, but not ref to static?");
102                     }
103                 }
104             }
105             PlaceRef {
106                 base: _,
107                 projection: [proj_base @ .., ProjectionElem::Deref],
108             } => {
109                 if the_place_err.base == &PlaceBase::Local(Local::new(1)) &&
110                     proj_base.is_empty() &&
111                     !self.upvars.is_empty() {
112                     item_msg = format!("`{}`", access_place_desc.unwrap());
113                     debug_assert!(self.body.local_decls[Local::new(1)].ty.is_region_ptr());
114                     debug_assert!(is_closure_or_generator(
115                         Place::ty_from(
116                             the_place_err.base,
117                             the_place_err.projection,
118                             *self.body,
119                             self.infcx.tcx
120                         )
121                         .ty
122                     ));
123
124                     reason =
125                         if self.is_upvar_field_projection(access_place.as_ref()).is_some() {
126                             ", as it is a captured variable in a `Fn` closure".to_string()
127                         } else {
128                             ", as `Fn` closures cannot mutate their captured variables".to_string()
129                         }
130                 } else {
131                     let source = self.borrowed_content_source(PlaceRef {
132                         base: the_place_err.base,
133                         projection: proj_base,
134                     });
135                     let pointer_type = source.describe_for_immutable_place();
136                     opt_source = Some(source);
137                     if let Some(desc) = access_place_desc {
138                         item_msg = format!("`{}`", desc);
139                         reason = match error_access {
140                             AccessKind::Mutate => format!(" which is behind {}", pointer_type),
141                             AccessKind::MutableBorrow => {
142                                 format!(", as it is behind {}", pointer_type)
143                             }
144                         }
145                     } else {
146                         item_msg = format!("data in {}", pointer_type);
147                         reason = String::new();
148                     }
149                 }
150             }
151
152             PlaceRef {
153                 base: PlaceBase::Static(_),
154                 ..
155             }
156             | PlaceRef {
157                 base: _,
158                 projection: [.., ProjectionElem::Index(_)],
159             }
160             | PlaceRef {
161                 base: _,
162                 projection: [.., ProjectionElem::ConstantIndex { .. }],
163             }
164             | PlaceRef {
165                 base: _,
166                 projection: [.., ProjectionElem::Subslice { .. }],
167             }
168             | PlaceRef {
169                 base: _,
170                 projection: [.., ProjectionElem::Downcast(..)],
171             } => bug!("Unexpected immutable place."),
172         }
173
174         debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason);
175
176         // `act` and `acted_on` are strings that let us abstract over
177         // the verbs used in some diagnostic messages.
178         let act;
179         let acted_on;
180
181         let span = match error_access {
182             AccessKind::Mutate => {
183                 err = self.cannot_assign(span, &(item_msg + &reason));
184                 act = "assign";
185                 acted_on = "written";
186                 span
187             }
188             AccessKind::MutableBorrow => {
189                 act = "borrow as mutable";
190                 acted_on = "borrowed as mutable";
191
192                 let borrow_spans = self.borrow_spans(span, location);
193                 let borrow_span = borrow_spans.args_or_use();
194                 err = self.cannot_borrow_path_as_mutable_because(
195                     borrow_span,
196                     &item_msg,
197                     &reason,
198                 );
199                 borrow_spans.var_span_label(
200                     &mut err,
201                     format!(
202                         "mutable borrow occurs due to use of `{}` in closure",
203                         // always Some() if the message is printed.
204                         self.describe_place(access_place.as_ref()).unwrap_or_default(),
205                     )
206                 );
207                 borrow_span
208             }
209         };
210
211         debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on);
212
213         match the_place_err {
214             // Suggest making an existing shared borrow in a struct definition a mutable borrow.
215             //
216             // This is applicable when we have a deref of a field access to a deref of a local -
217             // something like `*((*_1).0`. The local that we get will be a reference to the
218             // struct we've got a field access of (it must be a reference since there's a deref
219             // after the field access).
220             PlaceRef {
221                 base,
222                 projection: [proj_base @ ..,
223                              ProjectionElem::Deref,
224                              ProjectionElem::Field(field, _),
225                              ProjectionElem::Deref,
226                 ],
227             } => {
228                 err.span_label(span, format!("cannot {ACT}", ACT = act));
229
230                 if let Some((span, message)) = annotate_struct_field(
231                     self.infcx.tcx,
232                     Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty,
233                     field,
234                 ) {
235                     err.span_suggestion(
236                         span,
237                         "consider changing this to be mutable",
238                         message,
239                         Applicability::MaybeIncorrect,
240                     );
241                 }
242             },
243
244             // Suggest removing a `&mut` from the use of a mutable reference.
245             PlaceRef {
246                 base: PlaceBase::Local(local),
247                 projection: [],
248             } if {
249                 self.body.local_decls.get(*local).map(|local_decl| {
250                     if let LocalInfo::User(ClearCrossCrate::Set(
251                         mir::BindingForm::ImplicitSelf(kind)
252                     )) = local_decl.local_info {
253                         // Check if the user variable is a `&mut self` and we can therefore
254                         // suggest removing the `&mut`.
255                         //
256                         // Deliberately fall into this case for all implicit self types,
257                         // so that we don't fall in to the next case with them.
258                         kind == mir::ImplicitSelfKind::MutRef
259                     } else if Some(kw::SelfLower) == self.local_names[*local] {
260                         // Otherwise, check if the name is the self kewyord - in which case
261                         // we have an explicit self. Do the same thing in this case and check
262                         // for a `self: &mut Self` to suggest removing the `&mut`.
263                         if let ty::Ref(
264                             _, _, hir::Mutability::Mutable
265                         ) = local_decl.ty.kind {
266                             true
267                         } else {
268                             false
269                         }
270                     } else {
271                         false
272                     }
273                 }).unwrap_or(false)
274             } => {
275                 err.span_label(span, format!("cannot {ACT}", ACT = act));
276                 err.span_label(span, "try removing `&mut` here");
277             },
278
279             // We want to suggest users use `let mut` for local (user
280             // variable) mutations...
281             PlaceRef {
282                 base: PlaceBase::Local(local),
283                 projection: [],
284             } if self.body.local_decls[*local].can_be_made_mutable() => {
285                 // ... but it doesn't make sense to suggest it on
286                 // variables that are `ref x`, `ref mut x`, `&self`,
287                 // or `&mut self` (such variables are simply not
288                 // mutable).
289                 let local_decl = &self.body.local_decls[*local];
290                 assert_eq!(local_decl.mutability, Mutability::Not);
291
292                 err.span_label(span, format!("cannot {ACT}", ACT = act));
293                 err.span_suggestion(
294                     local_decl.source_info.span,
295                     "consider changing this to be mutable",
296                     format!("mut {}", self.local_names[*local].unwrap()),
297                     Applicability::MachineApplicable,
298                 );
299             }
300
301             // Also suggest adding mut for upvars
302             PlaceRef {
303                 base,
304                 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
305             } => {
306                 debug_assert!(is_closure_or_generator(
307                     Place::ty_from(base, proj_base, *self.body, self.infcx.tcx).ty
308                 ));
309
310                 err.span_label(span, format!("cannot {ACT}", ACT = act));
311
312                 let upvar_hir_id = self.upvars[upvar_index.index()].var_hir_id;
313                 if let Some(Node::Binding(pat)) = self.infcx.tcx.hir().find(upvar_hir_id)
314                 {
315                     if let hir::PatKind::Binding(
316                         hir::BindingAnnotation::Unannotated,
317                         _,
318                         upvar_ident,
319                         _,
320                     ) = pat.kind
321                     {
322                         err.span_suggestion(
323                             upvar_ident.span,
324                             "consider changing this to be mutable",
325                             format!("mut {}", upvar_ident.name),
326                             Applicability::MachineApplicable,
327                         );
328                     }
329                 }
330             }
331
332             // complete hack to approximate old AST-borrowck
333             // diagnostic: if the span starts with a mutable borrow of
334             // a local variable, then just suggest the user remove it.
335             PlaceRef {
336                 base: PlaceBase::Local(_),
337                 projection: [],
338             } if {
339                     if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) {
340                         snippet.starts_with("&mut ")
341                     } else {
342                         false
343                     }
344                 } =>
345             {
346                 err.span_label(span, format!("cannot {ACT}", ACT = act));
347                 err.span_label(span, "try removing `&mut` here");
348             }
349
350             PlaceRef {
351                 base: PlaceBase::Local(local),
352                 projection: [ProjectionElem::Deref],
353             } if self.body.local_decls[*local].is_ref_for_guard() => {
354                 err.span_label(span, format!("cannot {ACT}", ACT = act));
355                 err.note(
356                     "variables bound in patterns are immutable until the end of the pattern guard",
357                 );
358             }
359
360             // We want to point out when a `&` can be readily replaced
361             // with an `&mut`.
362             //
363             // FIXME: can this case be generalized to work for an
364             // arbitrary base for the projection?
365             PlaceRef {
366                 base: PlaceBase::Local(local),
367                 projection: [ProjectionElem::Deref],
368             } if self.body.local_decls[*local].is_user_variable() =>
369             {
370                 let local_decl = &self.body.local_decls[*local];
371                 let suggestion = match local_decl.local_info {
372                     LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(_))) => {
373                         Some(suggest_ampmut_self(self.infcx.tcx, local_decl))
374                     }
375
376                     LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
377                         mir::VarBindingForm {
378                             binding_mode: ty::BindingMode::BindByValue(_),
379                             opt_ty_info,
380                             ..
381                         },
382                     ))) => Some(suggest_ampmut(
383                         self.infcx.tcx,
384                         self.body,
385                         *local,
386                         local_decl,
387                         opt_ty_info,
388                     )),
389
390                     LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var(
391                         mir::VarBindingForm {
392                             binding_mode: ty::BindingMode::BindByReference(_),
393                             ..
394                         },
395                     ))) => {
396                         let pattern_span = local_decl.source_info.span;
397                         suggest_ref_mut(self.infcx.tcx, pattern_span)
398                             .map(|replacement| (pattern_span, replacement))
399                     }
400
401                     LocalInfo::User(ClearCrossCrate::Clear) => bug!("saw cleared local state"),
402
403                     _ => unreachable!(),
404                 };
405
406                 let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() {
407                     ("&", "reference")
408                 } else {
409                     ("*const", "pointer")
410                 };
411
412                 if let Some((err_help_span, suggested_code)) = suggestion {
413                     err.span_suggestion(
414                         err_help_span,
415                         &format!("consider changing this to be a mutable {}", pointer_desc),
416                         suggested_code,
417                         Applicability::MachineApplicable,
418                     );
419                 }
420
421                 match self.local_names[*local] {
422                     Some(name) if !local_decl.from_compiler_desugaring() => {
423                         err.span_label(
424                             span,
425                             format!(
426                                 "`{NAME}` is a `{SIGIL}` {DESC}, \
427                                 so the data it refers to cannot be {ACTED_ON}",
428                                 NAME = name,
429                                 SIGIL = pointer_sigil,
430                                 DESC = pointer_desc,
431                                 ACTED_ON = acted_on
432                             ),
433                         );
434                     }
435                     _ => {
436                         err.span_label(
437                             span,
438                             format!(
439                                 "cannot {ACT} through `{SIGIL}` {DESC}",
440                                 ACT = act,
441                                 SIGIL = pointer_sigil,
442                                 DESC = pointer_desc
443                             ),
444                         );
445                     }
446                 }
447             }
448
449             PlaceRef {
450                 base,
451                 projection: [ProjectionElem::Deref],
452             // FIXME document what is this 1 magic number about
453             } if *base == PlaceBase::Local(Local::new(1)) &&
454                   !self.upvars.is_empty() =>
455             {
456                 err.span_label(span, format!("cannot {ACT}", ACT = act));
457                 err.span_help(
458                     self.body.span,
459                     "consider changing this to accept closures that implement `FnMut`"
460                 );
461             }
462
463             PlaceRef {
464                 base: _,
465                 projection: [.., ProjectionElem::Deref],
466             } => {
467                 err.span_label(span, format!("cannot {ACT}", ACT = act));
468
469                 match opt_source {
470                     Some(BorrowedContentSource::OverloadedDeref(ty)) => {
471                         err.help(
472                             &format!(
473                                 "trait `DerefMut` is required to modify through a dereference, \
474                                 but it is not implemented for `{}`",
475                                 ty,
476                             ),
477                         );
478                     },
479                     Some(BorrowedContentSource::OverloadedIndex(ty)) => {
480                         err.help(
481                             &format!(
482                                 "trait `IndexMut` is required to modify indexed content, \
483                                 but it is not implemented for `{}`",
484                                 ty,
485                             ),
486                         );
487                     }
488                     _ => (),
489                 }
490             }
491
492             _ => {
493                 err.span_label(span, format!("cannot {ACT}", ACT = act));
494             }
495         }
496
497         err.buffer(&mut self.errors_buffer);
498     }
499 }
500
501 fn suggest_ampmut_self<'tcx>(
502     tcx: TyCtxt<'tcx>,
503     local_decl: &mir::LocalDecl<'tcx>,
504 ) -> (Span, String) {
505     let sp = local_decl.source_info.span;
506     (sp, match tcx.sess.source_map().span_to_snippet(sp) {
507         Ok(snippet) => {
508             let lt_pos = snippet.find('\'');
509             if let Some(lt_pos) = lt_pos {
510                 format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4])
511             } else {
512                 "&mut self".to_string()
513             }
514         }
515         _ => "&mut self".to_string()
516     })
517 }
518
519 // When we want to suggest a user change a local variable to be a `&mut`, there
520 // are three potential "obvious" things to highlight:
521 //
522 // let ident [: Type] [= RightHandSideExpression];
523 //     ^^^^^    ^^^^     ^^^^^^^^^^^^^^^^^^^^^^^
524 //     (1.)     (2.)              (3.)
525 //
526 // We can always fallback on highlighting the first. But chances are good that
527 // the user experience will be better if we highlight one of the others if possible;
528 // for example, if the RHS is present and the Type is not, then the type is going to
529 // be inferred *from* the RHS, which means we should highlight that (and suggest
530 // that they borrow the RHS mutably).
531 //
532 // This implementation attempts to emulate AST-borrowck prioritization
533 // by trying (3.), then (2.) and finally falling back on (1.).
534 fn suggest_ampmut<'tcx>(
535     tcx: TyCtxt<'tcx>,
536     body: ReadOnlyBodyCache<'_, 'tcx>,
537     local: Local,
538     local_decl: &mir::LocalDecl<'tcx>,
539     opt_ty_info: Option<Span>,
540 ) -> (Span, String) {
541     let locations = body.find_assignments(local);
542     if !locations.is_empty() {
543         let assignment_rhs_span = body.source_info(locations[0]).span;
544         if let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) {
545             if let (true, Some(ws_pos)) = (
546                 src.starts_with("&'"),
547                 src.find(|c: char| -> bool { c.is_whitespace() }),
548             ) {
549                 let lt_name = &src[1..ws_pos];
550                 let ty = &src[ws_pos..];
551                 return (assignment_rhs_span, format!("&{} mut {}", lt_name, ty));
552             } else if src.starts_with('&') {
553                 let borrowed_expr = &src[1..];
554                 return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
555             }
556         }
557     }
558
559     let highlight_span = match opt_ty_info {
560         // if this is a variable binding with an explicit type,
561         // try to highlight that for the suggestion.
562         Some(ty_span) => ty_span,
563
564         // otherwise, just highlight the span associated with
565         // the (MIR) LocalDecl.
566         None => local_decl.source_info.span,
567     };
568
569     if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) {
570         if let (true, Some(ws_pos)) = (
571             src.starts_with("&'"),
572             src.find(|c: char| -> bool { c.is_whitespace() }),
573         ) {
574             let lt_name = &src[1..ws_pos];
575             let ty = &src[ws_pos..];
576             return (highlight_span, format!("&{} mut{}", lt_name, ty));
577         }
578     }
579
580     let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
581     assert_eq!(ty_mut.mutbl, hir::Mutability::Immutable);
582     (highlight_span,
583      if local_decl.ty.is_region_ptr() {
584          format!("&mut {}", ty_mut.ty)
585      } else {
586          format!("*mut {}", ty_mut.ty)
587      })
588 }
589
590 fn is_closure_or_generator(ty: Ty<'_>) -> bool {
591     ty.is_closure() || ty.is_generator()
592 }
593
594 /// Adds a suggestion to a struct definition given a field access to a local.
595 /// This function expects the local to be a reference to a struct in order to produce a suggestion.
596 ///
597 /// ```text
598 /// LL |     s: &'a String
599 ///    |        ---------- use `&'a mut String` here to make mutable
600 /// ```
601 fn annotate_struct_field(
602     tcx: TyCtxt<'tcx>,
603     ty: Ty<'tcx>,
604     field: &mir::Field,
605 ) -> Option<(Span, String)> {
606     // Expect our local to be a reference to a struct of some kind.
607     if let ty::Ref(_, ty, _) = ty.kind {
608         if let ty::Adt(def, _) = ty.kind {
609             let field = def.all_fields().nth(field.index())?;
610             // Use the HIR types to construct the diagnostic message.
611             let hir_id = tcx.hir().as_local_hir_id(field.did)?;
612             let node = tcx.hir().find(hir_id)?;
613             // Now we're dealing with the actual struct that we're going to suggest a change to,
614             // we can expect a field that is an immutable reference to a type.
615             if let hir::Node::Field(field) = node {
616                 if let hir::TyKind::Rptr(lifetime, hir::MutTy {
617                     mutbl: hir::Mutability::Immutable,
618                     ref ty
619                 }) = field.ty.kind {
620                     // Get the snippets in two parts - the named lifetime (if there is one) and
621                     // type being referenced, that way we can reconstruct the snippet without loss
622                     // of detail.
623                     let type_snippet = tcx.sess.source_map().span_to_snippet(ty.span).ok()?;
624                     let lifetime_snippet = if !lifetime.is_elided() {
625                         format!("{} ", tcx.sess.source_map().span_to_snippet(lifetime.span).ok()?)
626                     } else {
627                         String::new()
628                     };
629
630                     return Some((
631                         field.ty.span,
632                         format!(
633                             "&{}mut {}",
634                             lifetime_snippet, &*type_snippet,
635                         ),
636                     ));
637                 }
638             }
639         }
640     }
641
642     None
643 }
644
645 /// If possible, suggest replacing `ref` with `ref mut`.
646 fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> {
647     let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?;
648     if hi_src.starts_with("ref")
649         && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace)
650     {
651         let replacement = format!("ref mut{}", &hi_src["ref".len()..]);
652         Some(replacement)
653     } else {
654         None
655     }
656 }