@@ -218,6 +218,33 @@ 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_label ( span, message) ;
245+ }
246+ } ,
247+
221248 // Suggest removing a `&mut` from the use of a mutable reference.
222249 Place :: Local ( local)
223250 if {
@@ -592,3 +619,56 @@ fn suggest_ampmut<'cx, 'gcx, 'tcx>(
592619fn is_closure_or_generator ( ty : ty:: Ty ) -> bool {
593620 ty. is_closure ( ) || ty. is_generator ( )
594621}
622+
623+ /// Add a suggestion to a struct definition given a field access to a local.
624+ /// This function expects the local to be a reference to a struct in order to produce a suggestion.
625+ ///
626+ /// ```text
627+ /// LL | s: &'a String
628+ /// | ---------- use `&'a mut String` here to make mutable
629+ /// ```
630+ fn annotate_struct_field (
631+ tcx : TyCtxt < ' cx , ' gcx , ' tcx > ,
632+ ty : ty:: Ty < ' tcx > ,
633+ field : & mir:: Field ,
634+ ) -> Option < ( Span , String ) > {
635+ // Expect our local to be a reference to a struct of some kind.
636+ if let ty:: TyKind :: Ref ( _, ty, _) = ty. sty {
637+ if let ty:: TyKind :: Adt ( def, _) = ty. sty {
638+ let field = def. all_fields ( ) . nth ( field. index ( ) ) ?;
639+ let span = tcx. def_span ( field. did ) ;
640+
641+ // Use the HIR types to construct the diagnostic message.
642+ let node_id = tcx. hir . as_local_node_id ( field. did ) ?;
643+ let node = tcx. hir . find ( node_id) ?;
644+ // Now we're dealing with the actual struct that we're going to suggest a change to,
645+ // we can expect a field that is an immutable reference to a type.
646+ if let hir:: Node :: Field ( field) = node {
647+ if let hir:: TyKind :: Rptr ( lifetime, hir:: MutTy {
648+ mutbl : hir:: Mutability :: MutImmutable ,
649+ ref ty
650+ } ) = field. ty . node {
651+ // Get the snippets in two parts - the named lifetime (if there is one) and
652+ // type being referenced, that way we can reconstruct the snippet without loss
653+ // of detail.
654+ let type_snippet = tcx. sess . source_map ( ) . span_to_snippet ( ty. span ) . ok ( ) ?;
655+ let lifetime_snippet = if !lifetime. is_elided ( ) {
656+ format ! ( "{} " , tcx. sess. source_map( ) . span_to_snippet( lifetime. span) . ok( ) ?)
657+ } else {
658+ String :: new ( )
659+ } ;
660+
661+ return Some ( (
662+ span,
663+ format ! (
664+ "use `&{}mut {}` here to make mutable" ,
665+ lifetime_snippet, & * type_snippet,
666+ ) ,
667+ ) ) ;
668+ }
669+ }
670+ }
671+ }
672+
673+ None
674+ }
0 commit comments