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