1 use super::implicit_clone::is_clone_like;
2 use super::unnecessary_iter_cloned::{self, is_into_iter};
3 use clippy_utils::diagnostics::span_lint_and_sugg;
4 use clippy_utils::source::snippet_opt;
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};
7 use rustc_errors::Applicability;
8 use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
9 use rustc_lint::LateContext;
10 use rustc_middle::mir::Mutability;
11 use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
12 use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
13 use rustc_middle::ty::{self, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
14 use rustc_span::{sym, Symbol};
17 use super::UNNECESSARY_TO_OWNED;
19 pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, args: &'tcx [Expr<'tcx>]) {
21 if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
22 if let [receiver] = args;
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) {
34 if check_into_iter_call_arg(cx, expr, method_name, receiver) {
37 check_other_call_arg(cx, expr, method_name, receiver);
43 /// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
44 /// call of a `to_owned`-like function is unnecessary.
45 #[allow(clippy::too_many_lines)]
46 fn check_addr_of_expr(
54 if let Some(parent) = get_parent_expr(cx, expr);
55 if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind;
56 let adjustments = cx.typeck_results().expr_adjustments(parent).iter().collect::<Vec<_>>();
57 if let Some(target_ty) = match adjustments[..]
59 // For matching uses of `Cow::from`
62 kind: Adjust::Deref(None),
66 kind: Adjust::Borrow(_),
70 // For matching uses of arrays
73 kind: Adjust::Deref(None),
77 kind: Adjust::Borrow(_),
81 kind: Adjust::Pointer(_),
85 // For matching everything else
88 kind: Adjust::Deref(None),
92 kind: Adjust::Deref(Some(OverloadedDeref { .. })),
96 kind: Adjust::Borrow(_),
102 let receiver_ty = cx.typeck_results().expr_ty(receiver);
103 // Only flag cases where the receiver is copyable or the method is `Cow::into_owned`. This
104 // restriction is to ensure there is not overlap between `redundant_clone` and this lint.
105 if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id);
106 if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
108 let (target_ty, n_target_refs) = peel_mid_ty_refs(target_ty);
109 let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty);
110 if receiver_ty == target_ty && n_target_refs >= n_receiver_refs {
113 UNNECESSARY_TO_OWNED,
115 &format!("unnecessary use of `{}`", method_name),
117 format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs),
118 Applicability::MachineApplicable,
123 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
124 if implements_trait(cx, receiver_ty, deref_trait_id, &[]);
125 if get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(target_ty);
127 if n_receiver_refs > 0 {
130 UNNECESSARY_TO_OWNED,
132 &format!("unnecessary use of `{}`", method_name),
135 Applicability::MachineApplicable,
140 UNNECESSARY_TO_OWNED,
141 expr.span.with_lo(receiver.span.hi()),
142 &format!("unnecessary use of `{}`", method_name),
145 Applicability::MachineApplicable,
152 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
153 if implements_trait(cx, receiver_ty, as_ref_trait_id, &[GenericArg::from(target_ty)]);
157 UNNECESSARY_TO_OWNED,
159 &format!("unnecessary use of `{}`", method_name),
161 format!("{}.as_ref()", receiver_snippet),
162 Applicability::MachineApplicable,
172 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
173 /// call of a `to_owned`-like function is unnecessary.
174 fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool {
176 if let Some(parent) = get_parent_expr(cx, expr);
177 if let Some(callee_def_id) = fn_def_id(cx, parent);
178 if is_into_iter(cx, callee_def_id);
179 if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
180 let parent_ty = cx.typeck_results().expr_ty(parent);
181 if implements_trait(cx, parent_ty, iterator_trait_id, &[]);
182 if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
183 if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
185 if unnecessary_iter_cloned::check_for_loop_iter(
194 let cloned_or_copied = if is_copy(cx, item_ty) {
199 // The next suggestion may be incorrect because the removal of the `to_owned`-like
200 // function could cause the iterator to hold a reference to a resource that is used
201 // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
204 UNNECESSARY_TO_OWNED,
206 &format!("unnecessary use of `{}`", method_name),
208 format!("{}.iter().{}()", receiver_snippet, cloned_or_copied),
209 Applicability::MaybeIncorrect,
217 /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
218 /// of a `to_owned`-like function is unnecessary.
219 fn check_other_call_arg<'tcx>(
220 cx: &LateContext<'tcx>,
221 expr: &'tcx Expr<'tcx>,
223 receiver: &'tcx Expr<'tcx>,
226 if let Some((maybe_call, maybe_arg)) = skip_addr_of_ancestors(cx, expr);
227 if let Some((callee_def_id, call_substs, call_args)) = get_callee_substs_and_args(cx, maybe_call);
228 let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder();
229 if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id);
230 if let Some(input) = fn_sig.inputs().get(i);
231 let (input, n_refs) = peel_mid_ty_refs(input);
232 if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input);
233 if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait();
234 if let [trait_predicate] = trait_predicates
236 .filter(|trait_predicate| trait_predicate.def_id() != sized_def_id)
237 .collect::<Vec<_>>()[..];
238 if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref);
239 if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
240 let receiver_ty = cx.typeck_results().expr_ty(receiver);
241 // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
242 // types of `trait_predicate.trait_ref.substs`.
243 if if trait_predicate.def_id() == deref_trait_id {
244 if let [projection_predicate] = projection_predicates[..] {
246 cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term.ty());
247 implements_trait(cx, receiver_ty, deref_trait_id, &[])
248 && get_associated_type(cx, receiver_ty, deref_trait_id, "Target") == Some(normalized_ty)
252 } else if trait_predicate.def_id() == as_ref_trait_id {
253 let composed_substs = compose_substs(
255 &trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
258 implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
262 // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
264 if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
265 let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
266 if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
270 UNNECESSARY_TO_OWNED,
272 &format!("unnecessary use of `{}`", method_name),
274 format!("{:&>width$}{}", "", receiver_snippet, width = n_refs),
275 Applicability::MachineApplicable,
283 /// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
284 /// expression found (if any) along with the immediately prior expression.
285 fn skip_addr_of_ancestors<'tcx>(
286 cx: &LateContext<'tcx>,
287 mut expr: &'tcx Expr<'tcx>,
288 ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
289 while let Some(parent) = get_parent_expr(cx, expr) {
290 if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = parent.kind {
293 return Some((parent, expr));
299 /// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
300 /// `Substs`, and arguments.
301 fn get_callee_substs_and_args<'tcx>(
302 cx: &LateContext<'tcx>,
303 expr: &'tcx Expr<'tcx>,
304 ) -> Option<(DefId, SubstsRef<'tcx>, &'tcx [Expr<'tcx>])> {
306 if let ExprKind::Call(callee, args) = expr.kind;
307 let callee_ty = cx.typeck_results().expr_ty(callee);
308 if let ty::FnDef(callee_def_id, _) = callee_ty.kind();
310 let substs = cx.typeck_results().node_substs(callee.hir_id);
311 return Some((*callee_def_id, substs, args));
315 if let ExprKind::MethodCall(_, _, args, _) = expr.kind;
316 if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
318 let substs = cx.typeck_results().node_substs(expr.hir_id);
319 return Some((method_def_id, substs, args));
325 /// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
326 fn get_input_traits_and_projections<'tcx>(
327 cx: &LateContext<'tcx>,
328 callee_def_id: DefId,
330 ) -> (Vec<TraitPredicate<'tcx>>, Vec<ProjectionPredicate<'tcx>>) {
331 let mut trait_predicates = Vec::new();
332 let mut projection_predicates = Vec::new();
333 for (predicate, _) in cx.tcx.predicates_of(callee_def_id).predicates.iter() {
334 // `substs` should have 1 + n elements. The first is the type on the left hand side of an
335 // `as`. The remaining n are trait parameters.
336 let is_input_substs = |substs: SubstsRef<'tcx>| {
338 if let Some(arg) = substs.iter().next();
339 if let GenericArgKind::Type(arg_ty) = arg.unpack();
348 match predicate.kind().skip_binder() {
349 PredicateKind::Trait(trait_predicate) => {
350 if is_input_substs(trait_predicate.trait_ref.substs) {
351 trait_predicates.push(trait_predicate);
354 PredicateKind::Projection(projection_predicate) => {
355 if is_input_substs(projection_predicate.projection_ty.substs) {
356 projection_predicates.push(projection_predicate);
362 (trait_predicates, projection_predicates)
365 /// Composes two substitutions by applying the latter to the types of the former.
366 fn compose_substs<'tcx>(
367 cx: &LateContext<'tcx>,
368 left: &[GenericArg<'tcx>],
369 right: SubstsRef<'tcx>,
370 ) -> Vec<GenericArg<'tcx>> {
373 if let GenericArgKind::Type(arg_ty) = arg.unpack() {
374 let normalized_ty = cx.tcx.subst_and_normalize_erasing_regions(right, cx.param_env, arg_ty);
375 GenericArg::from(normalized_ty)
383 /// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
384 fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
385 (method_name.as_str() == "cloned" || method_name.as_str() == "copied")
386 && is_diag_trait_item(cx, method_def_id, sym::Iterator)
389 /// Returns true if the named method can be used to convert the receiver to its "owned"
391 fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
392 is_clone_like(cx, &*method_name.as_str(), method_def_id)
393 || is_cow_into_owned(cx, method_name, method_def_id)
394 || is_to_string(cx, method_name, method_def_id)
397 /// Returns true if the named method is `Cow::into_owned`.
398 fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
399 method_name.as_str() == "into_owned" && is_diag_item_method(cx, method_def_id, sym::Cow)
402 /// Returns true if the named method is `ToString::to_string`.
403 fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
404 method_name.as_str() == "to_string" && is_diag_trait_item(cx, method_def_id, sym::ToString)