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
::msrvs
::{self, Msrv}
;
5 use clippy_utils
::source
::snippet_opt
;
6 use clippy_utils
::ty
::{get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs}
;
7 use clippy_utils
::visitors
::find_all_ret_expressions
;
8 use clippy_utils
::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty}
;
9 use rustc_errors
::Applicability
;
10 use rustc_hir
::{def_id::DefId, BorrowKind, Expr, ExprKind, ItemKind, Node}
;
11 use rustc_hir_typeck
::{FnCtxt, Inherited}
;
12 use rustc_infer
::infer
::TyCtxtInferExt
;
13 use rustc_lint
::LateContext
;
14 use rustc_middle
::mir
::Mutability
;
15 use rustc_middle
::ty
::adjustment
::{Adjust, Adjustment, OverloadedDeref}
;
16 use rustc_middle
::ty
::subst
::{GenericArg, GenericArgKind, SubstsRef}
;
17 use rustc_middle
::ty
::{self, Clause, EarlyBinder, ParamTy, PredicateKind, ProjectionPredicate, TraitPredicate, Ty}
;
18 use rustc_span
::{sym, Symbol}
;
19 use rustc_trait_selection
::traits
::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause}
;
21 use super::UNNECESSARY_TO_OWNED
;
24 cx
: &LateContext
<'tcx
>,
25 expr
: &'tcx Expr
<'tcx
>,
27 receiver
: &'tcx Expr
<'_
>,
28 args
: &'tcx
[Expr
<'_
>],
32 if let Some(method_def_id
) = cx
.typeck_results().type_dependent_def_id(expr
.hir_id
);
35 if is_cloned_or_copied(cx
, method_name
, method_def_id
) {
36 unnecessary_iter_cloned
::check(cx
, expr
, method_name
, receiver
);
37 } else if is_to_owned_like(cx
, expr
, method_name
, method_def_id
) {
38 // At this point, we know the call is of a `to_owned`-like function. The functions
39 // `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
40 // based on its context, that is, whether it is a referent in an `AddrOf` expression, an
41 // argument in a `into_iter` call, or an argument in the call of some other function.
42 if check_addr_of_expr(cx
, expr
, method_name
, method_def_id
, receiver
) {
45 if check_into_iter_call_arg(cx
, expr
, method_name
, receiver
, msrv
) {
48 check_other_call_arg(cx
, expr
, method_name
, receiver
);
54 /// Checks whether `expr` is a referent in an `AddrOf` expression and, if so, determines whether its
55 /// call of a `to_owned`-like function is unnecessary.
56 #[allow(clippy::too_many_lines)]
57 fn check_addr_of_expr(
65 if let Some(parent
) = get_parent_expr(cx
, expr
);
66 if let ExprKind
::AddrOf(BorrowKind
::Ref
, Mutability
::Not
, _
) = parent
.kind
;
67 let adjustments
= cx
.typeck_results().expr_adjustments(parent
).iter().collect
::<Vec
<_
>>();
69 // For matching uses of `Cow::from`
72 kind
: Adjust
::Deref(None
),
76 kind
: Adjust
::Borrow(_
),
80 // For matching uses of arrays
83 kind
: Adjust
::Deref(None
),
87 kind
: Adjust
::Borrow(_
),
91 kind
: Adjust
::Pointer(_
),
95 // For matching everything else
98 kind
: Adjust
::Deref(None
),
102 kind
: Adjust
::Deref(Some(OverloadedDeref { .. }
)),
106 kind
: Adjust
::Borrow(_
),
110 let receiver_ty
= cx
.typeck_results().expr_ty(receiver
);
111 let (target_ty
, n_target_refs
) = peel_mid_ty_refs(*target_ty
);
112 let (receiver_ty
, n_receiver_refs
) = peel_mid_ty_refs(receiver_ty
);
113 // Only flag cases satisfying at least one of the following three conditions:
114 // * the referent and receiver types are distinct
115 // * the referent/receiver type is a copyable array
116 // * the method is `Cow::into_owned`
117 // This restriction is to ensure there is no overlap between `redundant_clone` and this
118 // lint. It also avoids the following false positive:
119 // https://github.com/rust-lang/rust-clippy/issues/8759
120 // Arrays are a bit of a corner case. Non-copyable arrays are handled by
121 // `redundant_clone`, but copyable arrays are not.
122 if *referent_ty
!= receiver_ty
123 || (matches
!(referent_ty
.kind(), ty
::Array(..)) && is_copy(cx
, *referent_ty
))
124 || is_cow_into_owned(cx
, method_name
, method_def_id
);
125 if let Some(receiver_snippet
) = snippet_opt(cx
, receiver
.span
);
127 if receiver_ty
== target_ty
&& n_target_refs
>= n_receiver_refs
{
130 UNNECESSARY_TO_OWNED
,
132 &format
!("unnecessary use of `{method_name}`"),
135 "{:&>width$}{receiver_snippet}",
137 width
= n_target_refs
- n_receiver_refs
139 Applicability
::MachineApplicable
,
144 if let Some(deref_trait_id
) = cx
.tcx
.get_diagnostic_item(sym
::Deref
);
145 if implements_trait(cx
, receiver_ty
, deref_trait_id
, &[]);
146 if cx
.get_associated_type(receiver_ty
, deref_trait_id
, "Target") == Some(target_ty
);
148 if n_receiver_refs
> 0 {
151 UNNECESSARY_TO_OWNED
,
153 &format
!("unnecessary use of `{method_name}`"),
156 Applicability
::MachineApplicable
,
161 UNNECESSARY_TO_OWNED
,
162 expr
.span
.with_lo(receiver
.span
.hi()),
163 &format
!("unnecessary use of `{method_name}`"),
166 Applicability
::MachineApplicable
,
173 if let Some(as_ref_trait_id
) = cx
.tcx
.get_diagnostic_item(sym
::AsRef
);
174 if implements_trait(cx
, receiver_ty
, as_ref_trait_id
, &[GenericArg
::from(target_ty
)]);
178 UNNECESSARY_TO_OWNED
,
180 &format
!("unnecessary use of `{method_name}`"),
182 format
!("{receiver_snippet}.as_ref()"),
183 Applicability
::MachineApplicable
,
193 /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its
194 /// call of a `to_owned`-like function is unnecessary.
195 fn check_into_iter_call_arg(
196 cx
: &LateContext
<'_
>,
203 if let Some(parent
) = get_parent_expr(cx
, expr
);
204 if let Some(callee_def_id
) = fn_def_id(cx
, parent
);
205 if is_into_iter(cx
, callee_def_id
);
206 if let Some(iterator_trait_id
) = cx
.tcx
.get_diagnostic_item(sym
::Iterator
);
207 let parent_ty
= cx
.typeck_results().expr_ty(parent
);
208 if implements_trait(cx
, parent_ty
, iterator_trait_id
, &[]);
209 if let Some(item_ty
) = get_iterator_item_ty(cx
, parent_ty
);
210 if let Some(receiver_snippet
) = snippet_opt(cx
, receiver
.span
);
212 if unnecessary_iter_cloned
::check_for_loop_iter(cx
, parent
, method_name
, receiver
, true) {
215 let cloned_or_copied
= if is_copy(cx
, item_ty
) && msrv
.meets(msrvs
::ITERATOR_COPIED
) {
220 // The next suggestion may be incorrect because the removal of the `to_owned`-like
221 // function could cause the iterator to hold a reference to a resource that is used
222 // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
225 UNNECESSARY_TO_OWNED
,
227 &format
!("unnecessary use of `{method_name}`"),
229 format
!("{receiver_snippet}.iter().{cloned_or_copied}()"),
230 Applicability
::MaybeIncorrect
,
238 /// Checks whether `expr` is an argument in a function call and, if so, determines whether its call
239 /// of a `to_owned`-like function is unnecessary.
240 fn check_other_call_arg
<'tcx
>(
241 cx
: &LateContext
<'tcx
>,
242 expr
: &'tcx Expr
<'tcx
>,
244 receiver
: &'tcx Expr
<'tcx
>,
247 if let Some((maybe_call
, maybe_arg
)) = skip_addr_of_ancestors(cx
, expr
);
248 if let Some((callee_def_id
, _
, recv
, call_args
)) = get_callee_substs_and_args(cx
, maybe_call
);
249 let fn_sig
= cx
.tcx
.fn_sig(callee_def_id
).subst_identity().skip_binder();
250 if let Some(i
) = recv
.into_iter().chain(call_args
).position(|arg
| arg
.hir_id
== maybe_arg
.hir_id
);
251 if let Some(input
) = fn_sig
.inputs().get(i
);
252 let (input
, n_refs
) = peel_mid_ty_refs(*input
);
253 if let (trait_predicates
, _
) = get_input_traits_and_projections(cx
, callee_def_id
, input
);
254 if let Some(sized_def_id
) = cx
.tcx
.lang_items().sized_trait();
255 if let [trait_predicate
] = trait_predicates
257 .filter(|trait_predicate
| trait_predicate
.def_id() != sized_def_id
)
258 .collect
::<Vec
<_
>>()[..];
259 if let Some(deref_trait_id
) = cx
.tcx
.get_diagnostic_item(sym
::Deref
);
260 if let Some(as_ref_trait_id
) = cx
.tcx
.get_diagnostic_item(sym
::AsRef
);
261 if trait_predicate
.def_id() == deref_trait_id
|| trait_predicate
.def_id() == as_ref_trait_id
;
262 let receiver_ty
= cx
.typeck_results().expr_ty(receiver
);
263 // We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
265 if let Some((n_refs
, receiver_ty
)) = if n_refs
> 0 || is_copy(cx
, receiver_ty
) {
266 Some((n_refs
, receiver_ty
))
267 } else if trait_predicate
.def_id() != deref_trait_id
{
268 Some((1, cx
.tcx
.mk_ref(
269 cx
.tcx
.lifetimes
.re_erased
,
272 mutbl
: Mutability
::Not
,
278 if can_change_type(cx
, maybe_arg
, receiver_ty
);
279 if let Some(receiver_snippet
) = snippet_opt(cx
, receiver
.span
);
283 UNNECESSARY_TO_OWNED
,
285 &format
!("unnecessary use of `{method_name}`"),
287 format
!("{:&>n_refs$}{receiver_snippet}", ""),
288 Applicability
::MachineApplicable
,
296 /// Walks an expression's ancestors until it finds a non-`AddrOf` expression. Returns the first such
297 /// expression found (if any) along with the immediately prior expression.
298 fn skip_addr_of_ancestors
<'tcx
>(
299 cx
: &LateContext
<'tcx
>,
300 mut expr
: &'tcx Expr
<'tcx
>,
301 ) -> Option
<(&'tcx Expr
<'tcx
>, &'tcx Expr
<'tcx
>)> {
302 while let Some(parent
) = get_parent_expr(cx
, expr
) {
303 if let ExprKind
::AddrOf(BorrowKind
::Ref
, Mutability
::Not
, _
) = parent
.kind
{
306 return Some((parent
, expr
));
312 /// Checks whether an expression is a function or method call and, if so, returns its `DefId`,
313 /// `Substs`, and arguments.
314 fn get_callee_substs_and_args
<'tcx
>(
315 cx
: &LateContext
<'tcx
>,
316 expr
: &'tcx Expr
<'tcx
>,
317 ) -> Option
<(DefId
, SubstsRef
<'tcx
>, Option
<&'tcx Expr
<'tcx
>>, &'tcx
[Expr
<'tcx
>])> {
319 if let ExprKind
::Call(callee
, args
) = expr
.kind
;
320 let callee_ty
= cx
.typeck_results().expr_ty(callee
);
321 if let ty
::FnDef(callee_def_id
, _
) = callee_ty
.kind();
323 let substs
= cx
.typeck_results().node_substs(callee
.hir_id
);
324 return Some((*callee_def_id
, substs
, None
, args
));
328 if let ExprKind
::MethodCall(_
, recv
, args
, _
) = expr
.kind
;
329 if let Some(method_def_id
) = cx
.typeck_results().type_dependent_def_id(expr
.hir_id
);
331 let substs
= cx
.typeck_results().node_substs(expr
.hir_id
);
332 return Some((method_def_id
, substs
, Some(recv
), args
));
338 /// Returns the `TraitPredicate`s and `ProjectionPredicate`s for a function's input type.
339 fn get_input_traits_and_projections
<'tcx
>(
340 cx
: &LateContext
<'tcx
>,
341 callee_def_id
: DefId
,
343 ) -> (Vec
<TraitPredicate
<'tcx
>>, Vec
<ProjectionPredicate
<'tcx
>>) {
344 let mut trait_predicates
= Vec
::new();
345 let mut projection_predicates
= Vec
::new();
346 for predicate
in cx
.tcx
.param_env(callee_def_id
).caller_bounds() {
347 match predicate
.kind().skip_binder() {
348 PredicateKind
::Clause(Clause
::Trait(trait_predicate
)) => {
349 if trait_predicate
.trait_ref
.self_ty() == input
{
350 trait_predicates
.push(trait_predicate
);
353 PredicateKind
::Clause(Clause
::Projection(projection_predicate
)) => {
354 if projection_predicate
.projection_ty
.self_ty() == input
{
355 projection_predicates
.push(projection_predicate
);
361 (trait_predicates
, projection_predicates
)
364 fn can_change_type
<'a
>(cx
: &LateContext
<'a
>, mut expr
: &'a Expr
<'a
>, mut ty
: Ty
<'a
>) -> bool
{
365 for (_
, node
) in cx
.tcx
.hir().parent_iter(expr
.hir_id
) {
367 Node
::Stmt(_
) => return true,
368 Node
::Block(..) => continue,
369 Node
::Item(item
) => {
370 if let ItemKind
::Fn(_
, _
, body_id
) = &item
.kind
371 && let output_ty
= return_ty(cx
, item
.owner_id
)
372 && let inherited
= Inherited
::new(cx
.tcx
, item
.owner_id
.def_id
)
373 && let fn_ctxt
= FnCtxt
::new(&inherited
, cx
.param_env
, item
.owner_id
.def_id
)
374 && fn_ctxt
.can_coerce(ty
, output_ty
)
376 if has_lifetime(output_ty
) && has_lifetime(ty
) {
379 let body
= cx
.tcx
.hir().body(*body_id
);
380 let body_expr
= &body
.value
;
382 return find_all_ret_expressions(cx
, body_expr
, |_
| { count += 1; count <= 1 }
);
385 Node
::Expr(parent_expr
) => {
386 if let Some((callee_def_id
, call_substs
, recv
, call_args
)) = get_callee_substs_and_args(cx
, parent_expr
)
388 // FIXME: the `subst_identity()` below seems incorrect, since we eventually
389 // call `tcx.try_subst_and_normalize_erasing_regions` further down
390 // (i.e., we are explicitly not in the identity context).
391 let fn_sig
= cx
.tcx
.fn_sig(callee_def_id
).subst_identity().skip_binder();
392 if let Some(arg_index
) = recv
.into_iter().chain(call_args
).position(|arg
| arg
.hir_id
== expr
.hir_id
)
393 && let Some(param_ty
) = fn_sig
.inputs().get(arg_index
)
394 && let ty
::Param(ParamTy { index: param_index , ..}
) = param_ty
.kind()
395 // https://github.com/rust-lang/rust-clippy/issues/9504 and https://github.com/rust-lang/rust-clippy/issues/10021
396 && (*param_index
as usize) < call_substs
.len()
402 .filter(|(i
, _
)| *i
!= arg_index
)
403 .any(|(_
, ty
)| ty
.contains(*param_ty
))
408 let mut trait_predicates
= cx
.tcx
.param_env(callee_def_id
)
409 .caller_bounds().iter().filter(|predicate
| {
410 if let PredicateKind
::Clause(Clause
::Trait(trait_predicate
))
411 = predicate
.kind().skip_binder()
412 && trait_predicate
.trait_ref
.self_ty() == *param_ty
420 let new_subst
= cx
.tcx
.mk_substs_from_iter(
424 if i
== (*param_index
as usize) {
430 if trait_predicates
.any(|predicate
| {
431 let predicate
= EarlyBinder(predicate
).subst(cx
.tcx
, new_subst
);
432 let obligation
= Obligation
::new(cx
.tcx
, ObligationCause
::dummy(), cx
.param_env
, predicate
);
433 !cx
.tcx
.infer_ctxt().build().predicate_must_hold_modulo_regions(&obligation
)
438 let output_ty
= fn_sig
.output();
439 if output_ty
.contains(*param_ty
) {
440 if let Ok(new_ty
) = cx
.tcx
.try_subst_and_normalize_erasing_regions(
441 new_subst
, cx
.param_env
, EarlyBinder(output_ty
)) {
451 } else if let ExprKind
::Block(..) = parent_expr
.kind
{
463 fn has_lifetime(ty
: Ty
<'_
>) -> bool
{
464 ty
.walk().any(|t
| matches
!(t
.unpack(), GenericArgKind
::Lifetime(_
)))
467 /// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
468 fn is_cloned_or_copied(cx
: &LateContext
<'_
>, method_name
: Symbol
, method_def_id
: DefId
) -> bool
{
469 (method_name
.as_str() == "cloned" || method_name
.as_str() == "copied")
470 && is_diag_trait_item(cx
, method_def_id
, sym
::Iterator
)
473 /// Returns true if the named method can be used to convert the receiver to its "owned"
475 fn is_to_owned_like
<'a
>(cx
: &LateContext
<'a
>, call_expr
: &Expr
<'a
>, method_name
: Symbol
, method_def_id
: DefId
) -> bool
{
476 is_clone_like(cx
, method_name
.as_str(), method_def_id
)
477 || is_cow_into_owned(cx
, method_name
, method_def_id
)
478 || is_to_string_on_string_like(cx
, call_expr
, method_name
, method_def_id
)
481 /// Returns true if the named method is `Cow::into_owned`.
482 fn is_cow_into_owned(cx
: &LateContext
<'_
>, method_name
: Symbol
, method_def_id
: DefId
) -> bool
{
483 method_name
.as_str() == "into_owned" && is_diag_item_method(cx
, method_def_id
, sym
::Cow
)
486 /// Returns true if the named method is `ToString::to_string` and it's called on a type that
487 /// is string-like i.e. implements `AsRef<str>` or `Deref<Target = str>`.
488 fn is_to_string_on_string_like
<'a
>(
489 cx
: &LateContext
<'_
>,
490 call_expr
: &'a Expr
<'a
>,
492 method_def_id
: DefId
,
494 if method_name
!= sym
::to_string
|| !is_diag_trait_item(cx
, method_def_id
, sym
::ToString
) {
498 if let Some(substs
) = cx
.typeck_results().node_substs_opt(call_expr
.hir_id
)
499 && let [generic_arg
] = substs
.as_slice()
500 && let GenericArgKind
::Type(ty
) = generic_arg
.unpack()
501 && let Some(deref_trait_id
) = cx
.tcx
.get_diagnostic_item(sym
::Deref
)
502 && let Some(as_ref_trait_id
) = cx
.tcx
.get_diagnostic_item(sym
::AsRef
)
503 && (cx
.get_associated_type(ty
, deref_trait_id
, "Target") == Some(cx
.tcx
.types
.str_
) ||
504 implements_trait(cx
, ty
, as_ref_trait_id
, &[cx
.tcx
.types
.str_
.into()])) {