@@ -85,6 +85,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8585
8686 self . annotate_expected_due_to_let_ty ( err, expr, error) ;
8787 self . annotate_loop_expected_due_to_inference ( err, expr, error) ;
88+ if self . annotate_mut_binding_to_immutable_binding ( err, expr, error) {
89+ return ;
90+ }
8891
8992 // FIXME(#73154): For now, we do leak check when coercing function
9093 // pointers in typeck, instead of only during borrowck. This can lead
@@ -795,6 +798,98 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
795798 }
796799 }
797800
801+ /// Detect the following case
802+ ///
803+ /// ```text
804+ /// fn change_object(mut a: &Ty) {
805+ /// let a = Ty::new();
806+ /// b = a;
807+ /// }
808+ /// ```
809+ ///
810+ /// where the user likely meant to modify the value behind there reference, use `a` as an out
811+ /// parameter, instead of mutating the local binding. When encountering this we suggest:
812+ ///
813+ /// ```text
814+ /// fn change_object(a: &'_ mut Ty) {
815+ /// let a = Ty::new();
816+ /// *b = a;
817+ /// }
818+ /// ```
819+ fn annotate_mut_binding_to_immutable_binding (
820+ & self ,
821+ err : & mut Diag < ' _ > ,
822+ expr : & hir:: Expr < ' _ > ,
823+ error : Option < TypeError < ' tcx > > ,
824+ ) -> bool {
825+ if let Some ( TypeError :: Sorts ( ExpectedFound { expected, found } ) ) = error
826+ && let ty:: Ref ( _, inner, hir:: Mutability :: Not ) = expected. kind ( )
827+
828+ // The difference between the expected and found values is one level of borrowing.
829+ && self . can_eq ( self . param_env , * inner, found)
830+
831+ // We have an `ident = expr;` assignment.
832+ && let hir:: Node :: Expr ( hir:: Expr { kind : hir:: ExprKind :: Assign ( lhs, rhs, _) , .. } ) =
833+ self . tcx . parent_hir_node ( expr. hir_id )
834+ && rhs. hir_id == expr. hir_id
835+
836+ // We are assigning to some binding.
837+ && let hir:: ExprKind :: Path ( hir:: QPath :: Resolved (
838+ None ,
839+ hir:: Path { res : hir:: def:: Res :: Local ( hir_id) , .. } ,
840+ ) ) = lhs. kind
841+ && let hir:: Node :: Pat ( pat) = self . tcx . hir_node ( * hir_id)
842+
843+ // The pattern we have is an fn argument.
844+ && let hir:: Node :: Param ( hir:: Param { ty_span, .. } ) =
845+ self . tcx . parent_hir_node ( pat. hir_id )
846+ && let item = self . tcx . hir ( ) . get_parent_item ( pat. hir_id )
847+ && let item = self . tcx . hir_owner_node ( item)
848+ && let Some ( fn_decl) = item. fn_decl ( )
849+
850+ // We have a mutable binding in the argument.
851+ && let hir:: PatKind :: Binding ( hir:: BindingMode :: MUT , _hir_id, ident, _) = pat. kind
852+
853+ // Look for the type corresponding to the argument pattern we have in the argument list.
854+ && let Some ( ty_sugg) = fn_decl
855+ . inputs
856+ . iter ( )
857+ . filter_map ( |ty| {
858+ if ty. span == * ty_span
859+ && let hir:: TyKind :: Ref ( lt, x) = ty. kind
860+ {
861+ // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
862+ Some ( (
863+ x. ty . span . shrink_to_lo ( ) ,
864+ format ! (
865+ "{}mut " ,
866+ if lt. ident. span. lo( ) == lt. ident. span. hi( ) { "" } else { " " }
867+ ) ,
868+ ) )
869+ } else {
870+ None
871+ }
872+ } )
873+ . next ( )
874+ {
875+ let sugg = vec ! [
876+ ty_sugg,
877+ ( pat. span. until( ident. span) , String :: new( ) ) ,
878+ ( lhs. span. shrink_to_lo( ) , "*" . to_string( ) ) ,
879+ ] ;
880+ // We suggest changing the argument from `mut ident: &Ty` to `ident: &'_ mut Ty` and the
881+ // assignment from `ident = val;` to `*ident = val;`.
882+ err. multipart_suggestion_verbose (
883+ "you might have meant to mutate the pointed at value being passed in, instead of \
884+ changing the reference in the local binding",
885+ sugg,
886+ Applicability :: MaybeIncorrect ,
887+ ) ;
888+ return true ;
889+ }
890+ false
891+ }
892+
798893 fn annotate_alternative_method_deref (
799894 & self ,
800895 err : & mut Diag < ' _ > ,
0 commit comments