@@ -218,6 +218,38 @@ impl<'a, 'gcx, 'tcx> MirBorrowckCtxt<'a, 'gcx, 'tcx> {
218218 debug ! ( "report_mutability_error: act={:?}, acted_on={:?}" , act, acted_on) ;
219219
220220 match the_place_err {
221+ // Suggest making an existing shared borrow in a struct definition a mutable borrow.
222+ //
223+ // This is applicable when we have a deref of a field access to a deref of a local -
224+ // something like `*((*_1).0`. The local that we get will be a reference to the
225+ // struct we've got a field access of (it must be a reference since there's a deref
226+ // after the field access).
227+ Place :: Projection ( box Projection {
228+ base : Place :: Projection ( box Projection {
229+ base : Place :: Projection ( box Projection {
230+ base,
231+ elem : ProjectionElem :: Deref ,
232+ } ) ,
233+ elem : ProjectionElem :: Field ( field, _) ,
234+ } ) ,
235+ elem : ProjectionElem :: Deref ,
236+ } ) => {
237+ err. span_label ( span, format ! ( "cannot {ACT}" , ACT = act) ) ;
238+
239+ if let Some ( ( span, message) ) = annotate_struct_field (
240+ self . infcx . tcx ,
241+ base. ty ( self . mir , self . infcx . tcx ) . to_ty ( self . infcx . tcx ) ,
242+ field,
243+ ) {
244+ err. span_suggestion_with_applicability (
245+ span,
246+ "consider changing this to be mutable" ,
247+ message,
248+ Applicability :: MaybeIncorrect ,
249+ ) ;
250+ }
251+ } ,
252+
221253 // Suggest removing a `&mut` from the use of a mutable reference.
222254 Place :: Local ( local)
223255 if {
@@ -592,3 +624,54 @@ fn suggest_ampmut<'cx, 'gcx, 'tcx>(
592624fn is_closure_or_generator ( ty : ty:: Ty ) -> bool {
593625 ty. is_closure ( ) || ty. is_generator ( )
594626}
627+
628+ /// Add a suggestion to a struct definition given a field access to a local.
629+ /// This function expects the local to be a reference to a struct in order to produce a suggestion.
630+ ///
631+ /// ```text
632+ /// LL | s: &'a String
633+ /// | ---------- use `&'a mut String` here to make mutable
634+ /// ```
635+ fn annotate_struct_field (
636+ tcx : TyCtxt < ' cx , ' gcx , ' tcx > ,
637+ ty : ty:: Ty < ' tcx > ,
638+ field : & mir:: Field ,
639+ ) -> Option < ( Span , String ) > {
640+ // Expect our local to be a reference to a struct of some kind.
641+ if let ty:: TyKind :: Ref ( _, ty, _) = ty. sty {
642+ if let ty:: TyKind :: Adt ( def, _) = ty. sty {
643+ let field = def. all_fields ( ) . nth ( field. index ( ) ) ?;
644+ // Use the HIR types to construct the diagnostic message.
645+ let node_id = tcx. hir . as_local_node_id ( field. did ) ?;
646+ let node = tcx. hir . find ( node_id) ?;
647+ // Now we're dealing with the actual struct that we're going to suggest a change to,
648+ // we can expect a field that is an immutable reference to a type.
649+ if let hir:: Node :: Field ( field) = node {
650+ if let hir:: TyKind :: Rptr ( lifetime, hir:: MutTy {
651+ mutbl : hir:: Mutability :: MutImmutable ,
652+ ref ty
653+ } ) = field. ty . node {
654+ // Get the snippets in two parts - the named lifetime (if there is one) and
655+ // type being referenced, that way we can reconstruct the snippet without loss
656+ // of detail.
657+ let type_snippet = tcx. sess . source_map ( ) . span_to_snippet ( ty. span ) . ok ( ) ?;
658+ let lifetime_snippet = if !lifetime. is_elided ( ) {
659+ format ! ( "{} " , tcx. sess. source_map( ) . span_to_snippet( lifetime. span) . ok( ) ?)
660+ } else {
661+ String :: new ( )
662+ } ;
663+
664+ return Some ( (
665+ field. ty . span ,
666+ format ! (
667+ "&{}mut {}" ,
668+ lifetime_snippet, & * type_snippet,
669+ ) ,
670+ ) ) ;
671+ }
672+ }
673+ }
674+ }
675+
676+ None
677+ }
0 commit comments