@@ -3,7 +3,9 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
33use clippy_config:: msrvs:: { self , Msrv } ;
44use clippy_utils:: diagnostics:: span_lint_and_sugg;
55use 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+ } ;
79use clippy_utils:: visitors:: find_all_ret_expressions;
810use clippy_utils:: { fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty} ;
911use rustc_errors:: Applicability ;
@@ -16,7 +18,8 @@ use rustc_lint::LateContext;
1618use rustc_middle:: mir:: Mutability ;
1719use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , OverloadedDeref } ;
1820use 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 ,
2023} ;
2124use rustc_span:: { sym, Symbol } ;
2225use rustc_trait_selection:: traits:: query:: evaluate_obligation:: InferCtxtExt as _;
@@ -53,6 +56,8 @@ pub fn check<'tcx>(
5356 }
5457 check_other_call_arg ( cx, expr, method_name, receiver) ;
5558 }
59+ } else {
60+ check_borrow_predicate ( cx, expr) ;
5661 }
5762}
5863
@@ -590,3 +595,92 @@ fn is_to_string_on_string_like<'a>(
590595 false
591596 }
592597}
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