Skip to content

Commit 3807905

Browse files
committed
Handle to_vec on for loop expression rust-lang#8069
1 parent 290f74b commit 3807905

11 files changed

+829
-139
lines changed

clippy_lints/src/methods/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ mod suspicious_splitn;
5656
mod uninit_assumed_init;
5757
mod unnecessary_filter_map;
5858
mod unnecessary_fold;
59+
mod unnecessary_iter_cloned;
5960
mod unnecessary_lazy_eval;
6061
mod unnecessary_to_owned;
6162
mod unwrap_or_else_default;
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::higher::ForLoop;
3+
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait};
5+
use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage};
6+
use rustc_errors::Applicability;
7+
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
8+
use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat};
9+
use rustc_lint::LateContext;
10+
use rustc_middle::{hir::map::Map, ty};
11+
use rustc_span::{sym, Symbol};
12+
13+
use super::UNNECESSARY_TO_OWNED;
14+
15+
pub fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, receiver: &'tcx Expr<'tcx>) -> bool {
16+
if_chain! {
17+
if let Some(parent) = get_parent_expr(cx, expr);
18+
if let Some(callee_def_id) = fn_def_id(cx, parent);
19+
if is_into_iter(cx, callee_def_id);
20+
then {
21+
check_for_loop_iter(cx, parent, method_name, receiver)
22+
} else {
23+
false
24+
}
25+
}
26+
}
27+
28+
/// Checks whether `expr` is an iterator in a `for` loop and, if so, determines whether the
29+
/// iterated-over items could be iterated over by reference. The reason why `check` above does not
30+
/// include this code directly is so that it can be called from
31+
/// `unnecessary_into_owned::check_into_iter_call_arg`.
32+
pub fn check_for_loop_iter(
33+
cx: &LateContext<'tcx>,
34+
expr: &'tcx Expr<'tcx>,
35+
method_name: Symbol,
36+
receiver: &'tcx Expr<'tcx>,
37+
) -> bool {
38+
if_chain! {
39+
if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent));
40+
if let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent);
41+
let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body);
42+
if !clone_or_copy_needed;
43+
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
44+
then {
45+
let snippet = if_chain! {
46+
if let ExprKind::MethodCall(maybe_iter_method_name, _, [collection], _) = receiver.kind;
47+
if maybe_iter_method_name.ident.name == sym::iter;
48+
49+
if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
50+
let receiver_ty = cx.typeck_results().expr_ty(receiver);
51+
if implements_trait(cx, receiver_ty, iterator_trait_id, &[]);
52+
if let Some(iter_item_ty) = get_iterator_item_ty(cx, receiver_ty);
53+
54+
if let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator);
55+
let collection_ty = cx.typeck_results().expr_ty(collection);
56+
if implements_trait(cx, collection_ty, into_iterator_trait_id, &[]);
57+
if let Some(into_iter_item_ty) = get_associated_type(cx, collection_ty, into_iterator_trait_id, "Item");
58+
59+
if iter_item_ty == into_iter_item_ty;
60+
if let Some(collection_snippet) = snippet_opt(cx, collection.span);
61+
then {
62+
collection_snippet
63+
} else {
64+
receiver_snippet
65+
}
66+
};
67+
span_lint_and_then(
68+
cx,
69+
UNNECESSARY_TO_OWNED,
70+
expr.span,
71+
&format!("unnecessary use of `{}`", method_name),
72+
|diag| {
73+
diag.span_suggestion(expr.span, "use", snippet, Applicability::MachineApplicable);
74+
for addr_of_expr in addr_of_exprs {
75+
match addr_of_expr.kind {
76+
ExprKind::AddrOf(_, _, referent) => {
77+
let span = addr_of_expr.span.with_hi(referent.span.lo());
78+
diag.span_suggestion(span, "remove this `&`", String::new(), Applicability::MachineApplicable);
79+
}
80+
_ => unreachable!(),
81+
}
82+
}
83+
}
84+
);
85+
return true;
86+
}
87+
}
88+
false
89+
}
90+
91+
/// The core logic of `check_for_loop_iter` above, this function wraps a use of
92+
/// `CloneOrCopyVisitor`.
93+
fn clone_or_copy_needed(
94+
cx: &LateContext<'tcx>,
95+
pat: &Pat<'tcx>,
96+
body: &'tcx Expr<'tcx>,
97+
) -> (bool, Vec<&'tcx Expr<'tcx>>) {
98+
let mut visitor = CloneOrCopyVisitor {
99+
cx,
100+
binding_hir_ids: pat_bindings(pat),
101+
clone_or_copy_needed: false,
102+
addr_of_exprs: Vec::new(),
103+
};
104+
visitor.visit_expr(body);
105+
(visitor.clone_or_copy_needed, visitor.addr_of_exprs)
106+
}
107+
108+
/// Returns a vector of all `HirId`s bound by the pattern.
109+
fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
110+
let mut collector = usage::ParamBindingIdCollector {
111+
binding_hir_ids: Vec::new(),
112+
};
113+
collector.visit_pat(pat);
114+
collector.binding_hir_ids
115+
}
116+
117+
/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
118+
/// operations performed on `binding_hir_ids` are:
119+
/// * to take non-mutable references to them
120+
/// * to use them as non-mutable `&self` in method calls
121+
/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
122+
/// when `CloneOrCopyVisitor` is done visiting.
123+
struct CloneOrCopyVisitor<'cx, 'tcx> {
124+
cx: &'cx LateContext<'tcx>,
125+
binding_hir_ids: Vec<HirId>,
126+
clone_or_copy_needed: bool,
127+
addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
128+
}
129+
130+
impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
131+
type Map = Map<'tcx>;
132+
133+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
134+
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
135+
}
136+
137+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
138+
walk_expr(self, expr);
139+
if self.is_binding(expr) {
140+
if let Some(parent) = get_parent_expr(self.cx, expr) {
141+
match parent.kind {
142+
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
143+
self.addr_of_exprs.push(parent);
144+
return;
145+
},
146+
ExprKind::MethodCall(_, _, args, _) => {
147+
if_chain! {
148+
if args.iter().skip(1).all(|arg| !self.is_binding(arg));
149+
if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
150+
let method_ty = self.cx.tcx.type_of(method_def_id);
151+
let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
152+
if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
153+
then {
154+
return;
155+
}
156+
}
157+
},
158+
_ => {},
159+
}
160+
}
161+
self.clone_or_copy_needed = true;
162+
}
163+
}
164+
}
165+
166+
impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
167+
fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
168+
self.binding_hir_ids
169+
.iter()
170+
.any(|hir_id| path_to_local_id(expr, *hir_id))
171+
}
172+
}
173+
174+
/// Returns true if the named method is `IntoIterator::into_iter`.
175+
pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool {
176+
cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id)
177+
}

clippy_lints/src/methods/unnecessary_to_owned.rs

Lines changed: 99 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use super::implicit_clone::is_clone_like;
2+
use super::unnecessary_iter_cloned::{self, is_into_iter};
23
use clippy_utils::diagnostics::span_lint_and_sugg;
34
use clippy_utils::source::snippet_opt;
4-
use clippy_utils::ty::{implements_trait, is_copy, peel_mid_ty_refs};
5-
use clippy_utils::{get_parent_expr, is_diag_item_method, is_diag_trait_item};
5+
use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
6+
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
67
use rustc_errors::Applicability;
78
use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
89
use rustc_lint::LateContext;
@@ -18,17 +19,23 @@ use super::UNNECESSARY_TO_OWNED;
1819
pub fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) {
1920
if_chain! {
2021
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
21-
if is_to_owned_like(cx, method_name, method_def_id);
2222
if let [receiver] = args;
2323
then {
24-
// At this point, we know the call is of a `to_owned`-like function. The functions
25-
// `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
26-
// based on its context, that is, whether it is a referent in an `AddrOf` expression or
27-
// an argument in a function call.
28-
if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
29-
return;
24+
if is_cloned_or_copied(cx, method_name, method_def_id) {
25+
unnecessary_iter_cloned::check(cx, expr, method_name, receiver);
26+
} else if is_to_owned_like(cx, method_name, method_def_id) {
27+
// At this point, we know the call is of a `to_owned`-like function. The functions
28+
// `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
29+
// based on its context, that is, whether it is a referent in an `AddrOf` expression, an
30+
// argument in a `into_iter` call, or an argument in the call of some other function.
31+
if check_addr_of_expr(cx, expr, method_name, method_def_id, receiver) {
32+
return;
33+
}
34+
if check_into_iter_call_arg(cx, expr, method_name, receiver) {
35+
return;
36+
}
37+
check_other_call_arg(cx, expr, method_name, receiver);
3038
}
31-
check_call_arg(cx, expr, method_name, receiver);
3239
}
3340
}
3441
}
@@ -116,29 +123,34 @@ fn check_addr_of_expr(
116123
return true;
117124
}
118125
}
119-
if implements_deref_trait(cx, receiver_ty, target_ty) {
120-
if n_receiver_refs > 0 {
121-
span_lint_and_sugg(
122-
cx,
123-
UNNECESSARY_TO_OWNED,
124-
parent.span,
125-
&format!("unnecessary use of `{}`", method_name),
126-
"use",
127-
receiver_snippet,
128-
Applicability::MachineApplicable,
129-
);
130-
} else {
131-
span_lint_and_sugg(
132-
cx,
133-
UNNECESSARY_TO_OWNED,
134-
expr.span.with_lo(receiver.span.hi()),
135-
&format!("unnecessary use of `{}`", method_name),
136-
"remove this",
137-
String::new(),
138-
Applicability::MachineApplicable,
139-
);
126+
if_chain! {
127+
if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
128+
if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
129+
if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
130+
then {
131+
if n_receiver_refs > 0 {
132+
span_lint_and_sugg(
133+
cx,
134+
UNNECESSARY_TO_OWNED,
135+
parent.span,
136+
&format!("unnecessary use of `{}`", method_name),
137+
"use",
138+
receiver_snippet,
139+
Applicability::MachineApplicable,
140+
);
141+
} else {
142+
span_lint_and_sugg(
143+
cx,
144+
UNNECESSARY_TO_OWNED,
145+
expr.span.with_lo(receiver.span.hi()),
146+
&format!("unnecessary use of `{}`", method_name),
147+
"remove this",
148+
String::new(),
149+
Applicability::MachineApplicable,
150+
);
151+
}
152+
return true;
140153
}
141-
return true;
142154
}
143155
if_chain! {
144156
if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
@@ -161,9 +173,55 @@ fn check_addr_of_expr(
161173
false
162174
}
163175

176+
/// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
177+
/// call of a `to_owned`-like function is unnecessary.
178+
fn check_into_iter_call_arg(
179+
cx: &LateContext<'tcx>,
180+
expr: &'tcx Expr<'tcx>,
181+
method_name: Symbol,
182+
receiver: &'tcx Expr<'tcx>,
183+
) -> bool {
184+
if_chain! {
185+
if let Some(parent) = get_parent_expr(cx, expr);
186+
if let Some(callee_def_id) = fn_def_id(cx, parent);
187+
if is_into_iter(cx, callee_def_id);
188+
if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
189+
let parent_ty = cx.typeck_results().expr_ty(parent);
190+
if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
191+
if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
192+
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
193+
then {
194+
if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver) {
195+
return true;
196+
}
197+
let cloned_or_copied = if is_copy(cx, item_ty) {
198+
"copied"
199+
} else {
200+
"cloned"
201+
};
202+
span_lint_and_sugg(
203+
cx,
204+
UNNECESSARY_TO_OWNED,
205+
parent.span,
206+
&format!("unnecessary use of `{}`", method_name),
207+
"use",
208+
format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
209+
Applicability::MachineApplicable,
210+
);
211+
return true;
212+
}
213+
}
214+
false
215+
}
216+
164217
/// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
165218
/// of a `to_owned`-like function is unnecessary.
166-
fn check_call_arg(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, receiver: &'tcx Expr<'tcx>) {
219+
fn check_other_call_arg(
220+
cx: &LateContext<'tcx>,
221+
expr: &'tcx Expr<'tcx>,
222+
method_name: Symbol,
223+
receiver: &'tcx Expr<'tcx>,
224+
) -> bool {
167225
if_chain! {
168226
if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
169227
if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call);
@@ -186,7 +244,8 @@ fn check_call_arg(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: S
186244
if let [projection_predicate] = projection_predicates[..] {
187245
let normalized_ty =
188246
cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.ty);
189-
implements_deref_trait(cx, receiver_ty, normalized_ty)
247+
implements_trait(cx, receiver_ty, deref_trait_id, &[])
248+
&& get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(normalized_ty)
190249
} else {
191250
false
192251
}
@@ -215,8 +274,10 @@ fn check_call_arg(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: S
215274
format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
216275
Applicability::MachineApplicable,
217276
);
277+
return true;
218278
}
219279
}
280+
false
220281
}
221282

222283
/// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
@@ -315,22 +376,10 @@ fn compose_substs(cx: &LateContext<'tcx>, left: &[GenericArg<'tcx>], right: Subs
315376
.collect()
316377
}
317378

318-
/// Helper function to check whether a type implements the `Deref` trait.
319-
fn implements_deref_trait(cx: &LateContext<'tcx>, ty: Ty<'tcx>, deref_target_ty: Ty<'tcx>) -> bool {
320-
if_chain! {
321-
if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
322-
if implements_trait(cx, ty, deref_trait_id, &[]);
323-
if let Some(deref_target_id) = cx.tcx.lang_items().deref_target();
324-
let substs = cx.tcx.mk_substs_trait(ty, &[]);
325-
let projection_ty = cx.tcx.mk_projection(deref_target_id, substs);
326-
let normalized_ty = cx.tcx.normalize_erasing_regions(cx.param_env, projection_ty);
327-
if normalized_ty == deref_target_ty;
328-
then {
329-
true
330-
} else {
331-
false
332-
}
333-
}
379+
/// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
380+
fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
381+
(method_name.as_str() == "cloned" || method_name.as_str() == "copied")
382+
&& is_diag_trait_item(cx, method_def_id, sym::Iterator)
334383
}
335384

336385
/// Returns true if the named method can be used to convert the receiver to its "owned"

0 commit comments

Comments
 (0)