Skip to content

Commit 250fd09

Browse files
committed
Auto merge of rust-lang#12324 - GuillaumeGomez:useless_allocation2, r=y21
Extend `unnecessary_to_owned` to handle `Borrow` trait in map types Fixes rust-lang/rust-clippy#8088. Alternative to rust-lang#12315. r? `@y21` changelog: Extend `unnecessary_to_owned` to handle `Borrow` trait in map types
2 parents a056ff3 + 635acb6 commit 250fd09

File tree

6 files changed

+201
-5
lines changed

6 files changed

+201
-5
lines changed

clippy_config/src/conf.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ fn deserialize(file: &SourceFile) -> TryConf {
648648
extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
649649
extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
650650
// TODO: THIS SHOULD BE TESTED, this comment will be gone soon
651-
if conf.conf.allowed_idents_below_min_chars.contains(&"..".to_owned()) {
651+
if conf.conf.allowed_idents_below_min_chars.contains("..") {
652652
conf.conf
653653
.allowed_idents_below_min_chars
654654
.extend(DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS.iter().map(ToString::to_string));

clippy_lints/src/methods/unnecessary_to_owned.rs

+96-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use super::unnecessary_iter_cloned::{self, is_into_iter};
33
use clippy_config::msrvs::{self, Msrv};
44
use clippy_utils::diagnostics::span_lint_and_sugg;
55
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+
};
79
use clippy_utils::visitors::find_all_ret_expressions;
810
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
911
use rustc_errors::Applicability;
@@ -16,7 +18,8 @@ use rustc_lint::LateContext;
1618
use rustc_middle::mir::Mutability;
1719
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
1820
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,
2023
};
2124
use rustc_span::{sym, Symbol};
2225
use 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+
}

clippy_lints/src/min_ident_chars.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl MinIdentChars {
5353
&& str.len() <= self.min_ident_chars_threshold as usize
5454
&& !str.starts_with('_')
5555
&& !str.is_empty()
56-
&& self.allowed_idents_below_min_chars.get(&str.to_owned()).is_none()
56+
&& !self.allowed_idents_below_min_chars.contains(str)
5757
}
5858
}
5959

tests/ui/unnecessary_to_owned.fixed

+36
Original file line numberDiff line numberDiff line change
@@ -530,3 +530,39 @@ mod issue_11952 {
530530
IntoFuture::into_future(foo([], &0));
531531
}
532532
}
533+
534+
fn borrow_checks() {
535+
use std::borrow::Borrow;
536+
use std::collections::HashSet;
537+
538+
fn inner(a: &[&str]) {
539+
let mut s = HashSet::from([vec!["a"]]);
540+
s.remove(a); //~ ERROR: unnecessary use of `to_vec`
541+
}
542+
543+
let mut s = HashSet::from(["a".to_string()]);
544+
s.remove("b"); //~ ERROR: unnecessary use of `to_owned`
545+
s.remove("b"); //~ ERROR: unnecessary use of `to_string`
546+
// Should not warn.
547+
s.remove("b");
548+
549+
let mut s = HashSet::from([vec!["a"]]);
550+
s.remove(["b"].as_slice()); //~ ERROR: unnecessary use of `to_vec`
551+
s.remove((&["b"]).as_slice()); //~ ERROR: unnecessary use of `to_vec`
552+
553+
// Should not warn.
554+
s.remove(&["b"].to_vec().clone());
555+
s.remove(["a"].as_slice());
556+
557+
trait SetExt {
558+
fn foo<Q: Borrow<str>>(&self, _: &String);
559+
}
560+
561+
impl<K> SetExt for HashSet<K> {
562+
fn foo<Q: Borrow<str>>(&self, _: &String) {}
563+
}
564+
565+
// Should not lint!
566+
HashSet::<i32>::new().foo::<&str>(&"".to_owned());
567+
HashSet::<String>::new().get(&1.to_string());
568+
}

tests/ui/unnecessary_to_owned.rs

+36
Original file line numberDiff line numberDiff line change
@@ -530,3 +530,39 @@ mod issue_11952 {
530530
IntoFuture::into_future(foo([].to_vec(), &0));
531531
}
532532
}
533+
534+
fn borrow_checks() {
535+
use std::borrow::Borrow;
536+
use std::collections::HashSet;
537+
538+
fn inner(a: &[&str]) {
539+
let mut s = HashSet::from([vec!["a"]]);
540+
s.remove(&a.to_vec()); //~ ERROR: unnecessary use of `to_vec`
541+
}
542+
543+
let mut s = HashSet::from(["a".to_string()]);
544+
s.remove(&"b".to_owned()); //~ ERROR: unnecessary use of `to_owned`
545+
s.remove(&"b".to_string()); //~ ERROR: unnecessary use of `to_string`
546+
// Should not warn.
547+
s.remove("b");
548+
549+
let mut s = HashSet::from([vec!["a"]]);
550+
s.remove(&["b"].to_vec()); //~ ERROR: unnecessary use of `to_vec`
551+
s.remove(&(&["b"]).to_vec()); //~ ERROR: unnecessary use of `to_vec`
552+
553+
// Should not warn.
554+
s.remove(&["b"].to_vec().clone());
555+
s.remove(["a"].as_slice());
556+
557+
trait SetExt {
558+
fn foo<Q: Borrow<str>>(&self, _: &String);
559+
}
560+
561+
impl<K> SetExt for HashSet<K> {
562+
fn foo<Q: Borrow<str>>(&self, _: &String) {}
563+
}
564+
565+
// Should not lint!
566+
HashSet::<i32>::new().foo::<&str>(&"".to_owned());
567+
HashSet::<String>::new().get(&1.to_string());
568+
}

tests/ui/unnecessary_to_owned.stderr

+31-1
Original file line numberDiff line numberDiff line change
@@ -523,5 +523,35 @@ error: unnecessary use of `to_vec`
523523
LL | IntoFuture::into_future(foo([].to_vec(), &0));
524524
| ^^^^^^^^^^^ help: use: `[]`
525525

526-
error: aborting due to 80 previous errors
526+
error: unnecessary use of `to_vec`
527+
--> tests/ui/unnecessary_to_owned.rs:540:18
528+
|
529+
LL | s.remove(&a.to_vec());
530+
| ^^^^^^^^^^^ help: replace it with: `a`
531+
532+
error: unnecessary use of `to_owned`
533+
--> tests/ui/unnecessary_to_owned.rs:544:14
534+
|
535+
LL | s.remove(&"b".to_owned());
536+
| ^^^^^^^^^^^^^^^ help: replace it with: `"b"`
537+
538+
error: unnecessary use of `to_string`
539+
--> tests/ui/unnecessary_to_owned.rs:545:14
540+
|
541+
LL | s.remove(&"b".to_string());
542+
| ^^^^^^^^^^^^^^^^ help: replace it with: `"b"`
543+
544+
error: unnecessary use of `to_vec`
545+
--> tests/ui/unnecessary_to_owned.rs:550:14
546+
|
547+
LL | s.remove(&["b"].to_vec());
548+
| ^^^^^^^^^^^^^^^ help: replace it with: `["b"].as_slice()`
549+
550+
error: unnecessary use of `to_vec`
551+
--> tests/ui/unnecessary_to_owned.rs:551:14
552+
|
553+
LL | s.remove(&(&["b"]).to_vec());
554+
| ^^^^^^^^^^^^^^^^^^ help: replace it with: `(&["b"]).as_slice()`
555+
556+
error: aborting due to 85 previous errors
527557

0 commit comments

Comments
 (0)