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