@@ -3,7 +3,9 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
3
3
use clippy_config:: msrvs:: { self , Msrv } ;
4
4
use clippy_utils:: diagnostics:: span_lint_and_sugg;
5
5
use clippy_utils:: source:: snippet_opt;
6
- use clippy_utils:: ty:: { get_iterator_item_ty, implements_trait, is_copy, is_type_lang_item, peel_mid_ty_refs} ;
6
+ use clippy_utils:: ty:: {
7
+ get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs,
8
+ } ;
7
9
use clippy_utils:: visitors:: find_all_ret_expressions;
8
10
use clippy_utils:: { fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty} ;
9
11
use rustc_errors:: Applicability ;
@@ -16,7 +18,8 @@ use rustc_lint::LateContext;
16
18
use rustc_middle:: mir:: Mutability ;
17
19
use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , OverloadedDeref } ;
18
20
use rustc_middle:: ty:: {
19
- self , ClauseKind , GenericArg , GenericArgKind , GenericArgsRef , ParamTy , ProjectionPredicate , TraitPredicate , Ty ,
21
+ self , ClauseKind , GenericArg , GenericArgKind , GenericArgsRef , ImplPolarity , ParamTy , ProjectionPredicate ,
22
+ TraitPredicate , Ty ,
20
23
} ;
21
24
use rustc_span:: { sym, Symbol } ;
22
25
use rustc_trait_selection:: traits:: query:: evaluate_obligation:: InferCtxtExt as _;
@@ -53,6 +56,8 @@ pub fn check<'tcx>(
53
56
}
54
57
check_other_call_arg ( cx, expr, method_name, receiver) ;
55
58
}
59
+ } else {
60
+ check_borrow_predicate ( cx, expr) ;
56
61
}
57
62
}
58
63
@@ -590,3 +595,92 @@ fn is_to_string_on_string_like<'a>(
590
595
false
591
596
}
592
597
}
598
+
599
+ fn is_a_std_map_type ( cx : & LateContext < ' _ > , ty : Ty < ' _ > ) -> bool {
600
+ is_type_diagnostic_item ( cx, ty, sym:: HashSet )
601
+ || is_type_diagnostic_item ( cx, ty, sym:: HashMap )
602
+ || is_type_diagnostic_item ( cx, ty, sym:: BTreeMap )
603
+ || is_type_diagnostic_item ( cx, ty, sym:: BTreeSet )
604
+ }
605
+
606
+ fn is_str_and_string ( cx : & LateContext < ' _ > , arg_ty : Ty < ' _ > , original_arg_ty : Ty < ' _ > ) -> bool {
607
+ original_arg_ty. is_str ( ) && is_type_lang_item ( cx, arg_ty, LangItem :: String )
608
+ }
609
+
610
+ fn is_slice_and_vec ( cx : & LateContext < ' _ > , arg_ty : Ty < ' _ > , original_arg_ty : Ty < ' _ > ) -> bool {
611
+ ( original_arg_ty. is_slice ( ) || original_arg_ty. is_array ( ) || original_arg_ty. is_array_slice ( ) )
612
+ && is_type_diagnostic_item ( cx, arg_ty, sym:: Vec )
613
+ }
614
+
615
+ // This function will check the following:
616
+ // 1. The argument is a non-mutable reference.
617
+ // 2. It calls `to_owned()`, `to_string()` or `to_vec()`.
618
+ // 3. That the method is called on `String` or on `Vec` (only types supported for the moment).
619
+ fn check_if_applicable_to_argument < ' tcx > ( cx : & LateContext < ' tcx > , arg : & Expr < ' tcx > ) {
620
+ if let ExprKind :: AddrOf ( BorrowKind :: Ref , Mutability :: Not , expr) = arg. kind
621
+ && let ExprKind :: MethodCall ( method_path, caller, & [ ] , _) = expr. kind
622
+ && let Some ( method_def_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id )
623
+ && let method_name = method_path. ident . name . as_str ( )
624
+ && match method_name {
625
+ "to_owned" => cx. tcx . is_diagnostic_item ( sym:: to_owned_method, method_def_id) ,
626
+ "to_string" => cx. tcx . is_diagnostic_item ( sym:: to_string_method, method_def_id) ,
627
+ "to_vec" => cx
628
+ . tcx
629
+ . impl_of_method ( method_def_id)
630
+ . filter ( |& impl_did| cx. tcx . type_of ( impl_did) . instantiate_identity ( ) . is_slice ( ) )
631
+ . is_some ( ) ,
632
+ _ => false ,
633
+ }
634
+ && let original_arg_ty = cx. typeck_results ( ) . node_type ( caller. hir_id ) . peel_refs ( )
635
+ && let arg_ty = cx. typeck_results ( ) . expr_ty ( arg)
636
+ && let ty:: Ref ( _, arg_ty, Mutability :: Not ) = arg_ty. kind ( )
637
+ // FIXME: try to fix `can_change_type` to make it work in this case.
638
+ // && can_change_type(cx, caller, *arg_ty)
639
+ && let arg_ty = arg_ty. peel_refs ( )
640
+ // For now we limit this lint to `String` and `Vec`.
641
+ && ( is_str_and_string ( cx, arg_ty, original_arg_ty) || is_slice_and_vec ( cx, arg_ty, original_arg_ty) )
642
+ && let Some ( snippet) = snippet_opt ( cx, caller. span )
643
+ {
644
+ span_lint_and_sugg (
645
+ cx,
646
+ UNNECESSARY_TO_OWNED ,
647
+ arg. span ,
648
+ & format ! ( "unnecessary use of `{method_name}`" ) ,
649
+ "replace it with" ,
650
+ if original_arg_ty. is_array ( ) {
651
+ format ! ( "{snippet}.as_slice()" )
652
+ } else {
653
+ snippet
654
+ } ,
655
+ Applicability :: MaybeIncorrect ,
656
+ ) ;
657
+ }
658
+ }
659
+
660
+ // In std "map types", the getters all expect a `Borrow<Key>` generic argument. So in here, we
661
+ // check that:
662
+ // 1. This is a method with only one argument that doesn't come from a trait.
663
+ // 2. That it has `Borrow` in its generic predicates.
664
+ // 3. `Self` is a std "map type" (ie `HashSet`, `HashMap`, BTreeSet`, `BTreeMap`).
665
+ fn check_borrow_predicate < ' tcx > ( cx : & LateContext < ' tcx > , expr : & Expr < ' tcx > ) {
666
+ if let ExprKind :: MethodCall ( _, caller, & [ arg] , _) = expr. kind
667
+ && let Some ( method_def_id) = cx. typeck_results ( ) . type_dependent_def_id ( expr. hir_id )
668
+ && cx. tcx . trait_of_item ( method_def_id) . is_none ( )
669
+ && let Some ( borrow_id) = cx. tcx . get_diagnostic_item ( sym:: Borrow )
670
+ && cx. tcx . predicates_of ( method_def_id) . predicates . iter ( ) . any ( |( pred, _) | {
671
+ if let ClauseKind :: Trait ( trait_pred) = pred. kind ( ) . skip_binder ( )
672
+ && trait_pred. polarity == ImplPolarity :: Positive
673
+ && trait_pred. trait_ref . def_id == borrow_id
674
+ {
675
+ true
676
+ } else {
677
+ false
678
+ }
679
+ } )
680
+ && let caller_ty = cx. typeck_results ( ) . expr_ty ( caller)
681
+ // For now we limit it to "map types".
682
+ && is_a_std_map_type ( cx, caller_ty)
683
+ {
684
+ check_if_applicable_to_argument ( cx, & arg) ;
685
+ }
686
+ }
0 commit comments