@@ -6,7 +6,7 @@ use rustc_abi::FieldIdx;
66use rustc_errors:: { Applicability , Diag } ;
77use rustc_hir:: def_id:: DefId ;
88use rustc_hir:: intravisit:: Visitor ;
9- use rustc_hir:: { self as hir, BindingMode , ByRef , Node } ;
9+ use rustc_hir:: { self as hir, BindingMode , ByRef , Expr , Node } ;
1010use rustc_middle:: bug;
1111use rustc_middle:: hir:: place:: PlaceBase ;
1212use rustc_middle:: mir:: visit:: PlaceContext ;
@@ -669,7 +669,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
669669 err : & ' a mut Diag < ' infcx > ,
670670 ty : Ty < ' tcx > ,
671671 suggested : bool ,
672+ infcx : & ' a rustc_infer:: infer:: InferCtxt < ' tcx > ,
672673 }
674+
673675 impl < ' a , ' infcx , ' tcx > Visitor < ' tcx > for SuggestIndexOperatorAlternativeVisitor < ' a , ' infcx , ' tcx > {
674676 fn visit_stmt ( & mut self , stmt : & ' tcx hir:: Stmt < ' tcx > ) {
675677 hir:: intravisit:: walk_stmt ( self , stmt) ;
@@ -680,60 +682,166 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
680682 return ;
681683 }
682684 } ;
685+
686+ // Because of TypeChecking and indexing, we know: index is &Q
687+ // with K: Eq + Hash + Borrow<Q>,
688+ // with Q: Eq + Hash + ?Sized,
689+ //
690+ // which fulfill the requirements of `get_mut`. If Q=K or Q=&{n}K, the requirements
691+ // of `entry` and `insert` are fulfilled too after dereferencing. If K is not
692+ // copy, a subsequent `clone` call may be needed.
693+
694+ /// Taken straight from https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.peel_hir_ty_refs.html
695+ /// Adapted to mid using https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.peel_refs
696+ /// Simplified to counting only
697+ /// Peels off all references on the type. Returns the number of references
698+ /// removed.
699+ fn count_ty_refs < ' tcx > ( mut ty : Ty < ' tcx > ) -> usize {
700+ let mut count = 0 ;
701+ while let ty:: Ref ( _, inner_ty, _) = ty. kind ( ) {
702+ ty = * inner_ty;
703+ count += 1 ;
704+ }
705+ count
706+ }
707+
708+ /// Try to strip `n` `&` reference from an expression.
709+ /// If the expression does not have enough leading `&`, return an Error
710+ /// containing a count of the successfully stripped ones and the stripped
711+ /// expression.
712+ fn strip_n_refs < ' a , ' b > (
713+ mut expr : & ' a Expr < ' b > ,
714+ n : usize ,
715+ ) -> Result < & ' a Expr < ' b > , ( usize , & ' a Expr < ' b > ) > {
716+ for count in 0 ..n {
717+ match expr {
718+ Expr {
719+ kind : ExprKind :: AddrOf ( hir:: BorrowKind :: Ref , _, inner) ,
720+ ..
721+ } => expr = inner,
722+ _ => return Err ( ( count, expr) ) ,
723+ }
724+ }
725+ Ok ( expr)
726+ }
727+
728+ // we know ty is a map, with a key type at walk distance 2.
729+ let key_ty = self . ty . walk ( ) . nth ( 1 ) . unwrap ( ) . expect_ty ( ) ;
730+
683731 if let hir:: ExprKind :: Assign ( place, rv, _sp) = expr. kind
684732 && let hir:: ExprKind :: Index ( val, index, _) = place. kind
685733 && ( expr. span == self . assign_span || place. span == self . assign_span )
686734 {
687735 // val[index] = rv;
688- // ---------- place
689- self . err . multipart_suggestions (
690- format ! (
691- "use `.insert()` to insert a value into a `{}`, `.get_mut()` \
692- to modify it, or the entry API for more flexibility",
693- self . ty,
694- ) ,
695- vec ! [
736+ let index_ty =
737+ self . infcx . tcx . typeck ( val. hir_id . owner . def_id ) . expr_ty ( index) ;
738+
739+ let ( borrowed_prefix, borrowed_index) ;
740+
741+ // only suggest `insert` and `entry` if index is of type K or &{n}K or *{n}K (when there is a Borrow impl for this case).
742+ // We use `peel_refs` because borrow lifetimes may differ in both index and
743+ // key. I.e, if they are of the same base type:
744+ if index_ty. peel_refs ( ) == key_ty. peel_refs ( ) {
745+ let ( index_refs, key_refs) =
746+ ( count_ty_refs ( index_ty) , count_ty_refs ( key_ty) ) ;
747+
748+ let ( deref_prefix, deref_index) = if index_refs >= key_refs {
749+ // index is &{n}K
750+ strip_n_refs ( index, index_refs - key_refs)
751+ . map ( |val| ( "" . to_string ( ) , val) )
752+ . unwrap_or_else ( |( depth, val) | {
753+ (
754+ if key_refs == 0 {
755+ "*" . repeat (
756+ ( index_refs-key_refs) . checked_sub ( depth) . expect ( "return depth from strip_n_refs should be smaller than the input" )
757+ )
758+ } else {
759+ String :: new ( ) //if key K is a ref, autoderef finish this for us.
760+ } ,
761+ val,
762+ )
763+ } )
764+ } else {
765+ // in this case the minimal ref addition works for all subcases
766+ ( "&" . repeat ( key_refs - index_refs) , index)
767+ } ;
768+
769+ self . err . multipart_suggestion (
770+ format ! ( "use `.insert()` to insert a value into a `{}`" , self . ty) ,
696771 vec ! [
697- // val.insert(index , rv);
772+ // val.insert({deref_prefix}{deref_index} , rv);
698773 (
699- val. span. shrink_to_hi( ) . with_hi( index . span. lo( ) ) ,
700- ".insert(" . to_string ( ) ,
774+ val. span. shrink_to_hi( ) . with_hi( deref_index . span. lo( ) ) ,
775+ format! ( ".insert({deref_prefix}" ) ,
701776 ) ,
702777 (
703- index . span. shrink_to_hi( ) . with_hi( rv. span. lo( ) ) ,
778+ deref_index . span. shrink_to_hi( ) . with_hi( rv. span. lo( ) ) ,
704779 ", " . to_string( ) ,
705780 ) ,
706781 ( rv. span. shrink_to_hi( ) , ")" . to_string( ) ) ,
707782 ] ,
783+ Applicability :: MaybeIncorrect ,
784+ ) ;
785+ self . err . multipart_suggestion (
786+ format ! (
787+ "use the entry API to modify a `{}` for more flexibility" ,
788+ self . ty
789+ ) ,
708790 vec ! [
709- // if let Some(v) = val.get_mut(index) { *v = rv; }
710- ( val. span. shrink_to_lo( ) , "if let Some(val) = " . to_string( ) ) ,
711- (
712- val. span. shrink_to_hi( ) . with_hi( index. span. lo( ) ) ,
713- ".get_mut(" . to_string( ) ,
714- ) ,
715- (
716- index. span. shrink_to_hi( ) . with_hi( place. span. hi( ) ) ,
717- ") { *val" . to_string( ) ,
718- ) ,
719- ( rv. span. shrink_to_hi( ) , "; }" . to_string( ) ) ,
720- ] ,
721- vec![
722- // let x = val.entry(index).or_insert(rv);
791+ // let x = val.entry({deref_prefix}{deref_index}).insert_entry(rv);
723792 ( val. span. shrink_to_lo( ) , "let val = " . to_string( ) ) ,
724793 (
725- val. span. shrink_to_hi( ) . with_hi( index . span. lo( ) ) ,
726- ".entry(" . to_string ( ) ,
794+ val. span. shrink_to_hi( ) . with_hi( deref_index . span. lo( ) ) ,
795+ format! ( ".entry({deref_prefix}" ) ,
727796 ) ,
728797 (
729- index . span. shrink_to_hi( ) . with_hi( rv. span. lo( ) ) ,
730- ").or_insert (" . to_string( ) ,
798+ deref_index . span. shrink_to_hi( ) . with_hi( rv. span. lo( ) ) ,
799+ ").insert_entry (" . to_string( ) ,
731800 ) ,
732801 ( rv. span. shrink_to_hi( ) , ")" . to_string( ) ) ,
733802 ] ,
803+ Applicability :: MaybeIncorrect ,
804+ ) ;
805+
806+ // we can make the next suggestions nicer by stripping as many leading `&` as
807+ // we can, autoderef will do the rest
808+ ( borrowed_prefix, borrowed_index) = (
809+ String :: new ( ) ,
810+ if index_refs > key_refs {
811+ strip_n_refs ( index, index_refs - key_refs - 1 )
812+ . unwrap_or_else ( |( _depth, val) | val)
813+ // even if we tried to strip more, we can stop there thanks to autoderef
814+ } else {
815+ // when the diff is negative or zero, we already are in the index=&Q case.
816+ index
817+ } ,
818+ ) ;
819+ } else {
820+ ( borrowed_prefix, borrowed_index) = ( String :: new ( ) , index)
821+ }
822+ // in all cases, suggest get_mut because K:Borrow<K> or Q:Borrow<K> as a
823+ // requirement of indexing.
824+ self . err . multipart_suggestion (
825+ format ! (
826+ "use `.get_mut()` to modify an existing key in a `{}`" ,
827+ self . ty,
828+ ) ,
829+ vec ! [
830+ // if let Some(v) = val.get_mut({borrowed_prefix}{borrowed_index}) { *v = rv; }
831+ ( val. span. shrink_to_lo( ) , "if let Some(val) = " . to_string( ) ) ,
832+ (
833+ val. span. shrink_to_hi( ) . with_hi( borrowed_index. span. lo( ) ) ,
834+ format!( ".get_mut({borrowed_prefix}" ) ,
835+ ) ,
836+ (
837+ borrowed_index. span. shrink_to_hi( ) . with_hi( place. span. hi( ) ) ,
838+ ") { *val" . to_string( ) ,
839+ ) ,
840+ ( rv. span. shrink_to_hi( ) , "; }" . to_string( ) ) ,
734841 ] ,
735- Applicability :: MachineApplicable ,
842+ Applicability :: MaybeIncorrect ,
736843 ) ;
844+
737845 self . suggested = true ;
738846 } else if let hir:: ExprKind :: MethodCall ( _path, receiver, _, sp) = expr. kind
739847 && let hir:: ExprKind :: Index ( val, index, _) = receiver. kind
@@ -769,6 +877,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
769877 err,
770878 ty,
771879 suggested : false ,
880+ infcx : self . infcx ,
772881 } ;
773882 v. visit_body ( & body) ;
774883 if !v. suggested {
0 commit comments