1 use clippy_utils
::diagnostics
::{span_lint_hir, span_lint_hir_and_then}
;
2 use clippy_utils
::mir
::{visit_local_usage, LocalUsage, PossibleBorrowerMap}
;
3 use clippy_utils
::source
::snippet_opt
;
4 use clippy_utils
::ty
::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth}
;
5 use clippy_utils
::{fn_has_unsatisfiable_preds, match_def_path, paths}
;
6 use if_chain
::if_chain
;
7 use rustc_errors
::Applicability
;
8 use rustc_hir
::intravisit
::FnKind
;
9 use rustc_hir
::{def_id, Body, FnDecl, LangItem}
;
10 use rustc_lint
::{LateContext, LateLintPass}
;
11 use rustc_middle
::mir
;
12 use rustc_middle
::ty
::{self, Ty}
;
13 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
14 use rustc_span
::def_id
::LocalDefId
;
15 use rustc_span
::{sym, BytePos, Span}
;
17 macro_rules
! unwrap_or_continue
{
26 declare_clippy_lint
! {
28 /// Checks for a redundant `clone()` (and its relatives) which clones an owned
29 /// value that is going to be dropped without further use.
31 /// ### Why is this bad?
32 /// It is not always possible for the compiler to eliminate useless
33 /// allocations and deallocations generated by redundant `clone()`s.
35 /// ### Known problems
36 /// False-negatives: analysis performed by this lint is conservative and limited.
40 /// # use std::path::Path;
41 /// # #[derive(Clone)]
44 /// # fn new() -> Self { Foo {} }
46 /// # fn call(x: Foo) {}
48 /// let x = Foo::new();
50 /// call(x.clone()); // this can just pass `x`
53 /// ["lorem", "ipsum"].join(" ").to_string();
55 /// Path::new("/a/b").join("c").to_path_buf();
57 #[clippy::version = "1.32.0"]
60 "`clone()` of an owned value that is going to be dropped immediately"
63 declare_lint_pass
!(RedundantClone
=> [REDUNDANT_CLONE
]);
65 impl<'tcx
> LateLintPass
<'tcx
> for RedundantClone
{
66 #[expect(clippy::too_many_lines)]
69 cx
: &LateContext
<'tcx
>,
76 // Building MIR for `fn`s with unsatisfiable preds results in ICE.
77 if fn_has_unsatisfiable_preds(cx
, def_id
.to_def_id()) {
81 let mir
= cx
.tcx
.optimized_mir(def_id
.to_def_id());
83 let mut possible_borrower
= PossibleBorrowerMap
::new(cx
, mir
);
85 for (bb
, bbdata
) in mir
.basic_blocks
.iter_enumerated() {
86 let terminator
= bbdata
.terminator();
88 if terminator
.source_info
.span
.from_expansion() {
93 if terminator
.successors().any(|s
| s
== bb
) {
97 let (fn_def_id
, arg
, arg_ty
, clone_ret
) =
98 unwrap_or_continue
!(is_call_with_ref_arg(cx
, mir
, &terminator
.kind
));
100 let from_borrow
= match_def_path(cx
, fn_def_id
, &paths
::CLONE_TRAIT_METHOD
)
101 || cx
.tcx
.is_diagnostic_item(sym
::to_owned_method
, fn_def_id
)
102 || (cx
.tcx
.is_diagnostic_item(sym
::to_string_method
, fn_def_id
)
103 && is_type_lang_item(cx
, arg_ty
, LangItem
::String
));
105 let from_deref
= !from_borrow
106 && (match_def_path(cx
, fn_def_id
, &paths
::PATH_TO_PATH_BUF
)
107 || match_def_path(cx
, fn_def_id
, &paths
::OS_STR_TO_OS_STRING
));
109 if !from_borrow
&& !from_deref
{
113 if let ty
::Adt(def
, _
) = arg_ty
.kind() {
114 if def
.is_manually_drop() {
119 // `{ arg = &cloned; clone(move arg); }` or `{ arg = &cloned; to_path_buf(arg); }`
120 let (cloned
, cannot_move_out
) = unwrap_or_continue
!(find_stmt_assigns_to(cx
, mir
, arg
, from_borrow
, bb
));
122 let loc
= mir
::Location
{
124 statement_index
: bbdata
.statements
.len(),
127 // `Local` to be cloned, and a local of `clone` call's destination
128 let (local
, ret_local
) = if from_borrow
{
129 // `res = clone(arg)` can be turned into `res = move arg;`
130 // if `arg` is the only borrow of `cloned` at this point.
132 if cannot_move_out
|| !possible_borrower
.only_borrowers(&[arg
], cloned
, loc
) {
138 // `arg` is a reference as it is `.deref()`ed in the previous block.
139 // Look into the predecessor block and find out the source of deref.
141 let ps
= &mir
.basic_blocks
.predecessors()[bb
];
145 let pred_terminator
= mir
[ps
[0]].terminator();
147 // receiver of the `deref()` call
148 let (pred_arg
, deref_clone_ret
) = if_chain
! {
149 if let Some((pred_fn_def_id
, pred_arg
, pred_arg_ty
, res
)) =
150 is_call_with_ref_arg(cx
, mir
, &pred_terminator
.kind
);
152 if cx
.tcx
.is_diagnostic_item(sym
::deref_method
, pred_fn_def_id
);
153 if is_type_diagnostic_item(cx
, pred_arg_ty
, sym
::PathBuf
)
154 || is_type_diagnostic_item(cx
, pred_arg_ty
, sym
::OsString
);
162 let (local
, cannot_move_out
) =
163 unwrap_or_continue
!(find_stmt_assigns_to(cx
, mir
, pred_arg
, true, ps
[0]));
164 let loc
= mir
::Location
{
166 statement_index
: mir
.basic_blocks
[bb
].statements
.len(),
169 // This can be turned into `res = move local` if `arg` and `cloned` are not borrowed
170 // at the last statement:
173 // pred_arg = &local;
174 // cloned = deref(pred_arg);
176 // StorageDead(pred_arg);
177 // res = to_path_buf(cloned);
179 if cannot_move_out
|| !possible_borrower
.only_borrowers(&[arg
, cloned
], local
, loc
) {
183 (local
, deref_clone_ret
)
186 let clone_usage
= if local
== ret_local
{
189 cloned_consume_or_mutate_loc
: None
,
190 clone_consumed_or_mutated
: true,
193 let clone_usage
= visit_clone_usage(local
, ret_local
, mir
, bb
);
194 if clone_usage
.cloned_used
&& clone_usage
.clone_consumed_or_mutated
{
195 // cloned value is used, and the clone is modified or moved
197 } else if let Some(loc
) = clone_usage
.cloned_consume_or_mutate_loc
{
198 // cloned value is mutated, and the clone is alive.
199 if possible_borrower
.local_is_alive_at(ret_local
, loc
) {
206 let span
= terminator
.source_info
.span
;
207 let scope
= terminator
.source_info
.scope
;
208 let node
= mir
.source_scopes
[scope
]
211 .assert_crate_local()
215 if let Some(snip
) = snippet_opt(cx
, span
);
216 if let Some(dot
) = snip
.rfind('
.'
);
218 let sugg_span
= span
.with_lo(
219 span
.lo() + BytePos(u32::try_from(dot
).unwrap())
221 let mut app
= Applicability
::MaybeIncorrect
;
223 let call_snip
= &snip
[dot
+ 1..];
224 // Machine applicable when `call_snip` looks like `foobar()`
225 if let Some(call_snip
) = call_snip
.strip_suffix("()").map(str::trim
) {
226 if call_snip
.as_bytes().iter().all(|b
| b
.is_ascii_alphabetic() || *b
== b'_'
) {
227 app
= Applicability
::MachineApplicable
;
231 span_lint_hir_and_then(cx
, REDUNDANT_CLONE
, node
, sugg_span
, "redundant clone", |diag
| {
232 diag
.span_suggestion(
238 if clone_usage
.cloned_used
{
241 "cloned value is neither consumed nor mutated",
245 span
.with_hi(span
.lo() + BytePos(u32::try_from(dot
).unwrap())),
246 "this value is dropped without further use",
251 span_lint_hir(cx
, REDUNDANT_CLONE
, node
, span
, "redundant clone");
258 /// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`.
259 fn is_call_with_ref_arg
<'tcx
>(
260 cx
: &LateContext
<'tcx
>,
261 mir
: &'tcx mir
::Body
<'tcx
>,
262 kind
: &'tcx mir
::TerminatorKind
<'tcx
>,
263 ) -> Option
<(def_id
::DefId
, mir
::Local
, Ty
<'tcx
>, mir
::Local
)> {
265 if let mir
::TerminatorKind
::Call { func, args, destination, .. }
= kind
;
267 if let mir
::Operand
::Move(mir
::Place { local, .. }
) = &args
[0];
268 if let ty
::FnDef(def_id
, _
) = *func
.ty(mir
, cx
.tcx
).kind();
269 if let (inner_ty
, 1) = walk_ptrs_ty_depth(args
[0].ty(mir
, cx
.tcx
));
270 if !is_copy(cx
, inner_ty
);
272 Some((def_id
, *local
, inner_ty
, destination
.as_local()?
))
279 type CannotMoveOut
= bool
;
281 /// Finds the first `to = (&)from`, and returns
282 /// ``Some((from, whether `from` cannot be moved out))``.
283 fn find_stmt_assigns_to
<'tcx
>(
284 cx
: &LateContext
<'tcx
>,
285 mir
: &mir
::Body
<'tcx
>,
286 to_local
: mir
::Local
,
289 ) -> Option
<(mir
::Local
, CannotMoveOut
)> {
290 let rvalue
= mir
.basic_blocks
[bb
].statements
.iter().rev().find_map(|stmt
| {
291 if let mir
::StatementKind
::Assign(box (mir
::Place { local, .. }
, v
)) = &stmt
.kind
{
292 return if *local
== to_local { Some(v) }
else { None }
;
298 match (by_ref
, rvalue
) {
299 (true, mir
::Rvalue
::Ref(_
, _
, place
)) | (false, mir
::Rvalue
::Use(mir
::Operand
::Copy(place
))) => {
300 Some(base_local_and_movability(cx
, mir
, *place
))
302 (false, mir
::Rvalue
::Ref(_
, _
, place
)) => {
303 if let [mir
::ProjectionElem
::Deref
] = place
.as_ref().projection
{
304 Some(base_local_and_movability(cx
, mir
, *place
))
313 /// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself
314 /// if it is already a `Local`.
316 /// Also reports whether given `place` cannot be moved out.
317 fn base_local_and_movability
<'tcx
>(
318 cx
: &LateContext
<'tcx
>,
319 mir
: &mir
::Body
<'tcx
>,
320 place
: mir
::Place
<'tcx
>,
321 ) -> (mir
::Local
, CannotMoveOut
) {
322 // Dereference. You cannot move things out from a borrowed value.
323 let mut deref
= false;
324 // Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509.
325 let mut field
= false;
326 // If projection is a slice index then clone can be removed only if the
327 // underlying type implements Copy
328 let mut slice
= false;
330 for (base
, elem
) in place
.as_ref().iter_projections() {
331 let base_ty
= base
.ty(&mir
.local_decls
, cx
.tcx
).ty
;
332 deref
|= matches
!(elem
, mir
::ProjectionElem
::Deref
);
333 field
|= matches
!(elem
, mir
::ProjectionElem
::Field(..)) && has_drop(cx
, base_ty
);
334 slice
|= matches
!(elem
, mir
::ProjectionElem
::Index(..)) && !is_copy(cx
, base_ty
);
337 (place
.local
, deref
|| field
|| slice
)
342 /// Whether the cloned value is used after the clone.
344 /// The first location where the cloned value is consumed or mutated, if any.
345 cloned_consume_or_mutate_loc
: Option
<mir
::Location
>,
346 /// Whether the clone value is mutated.
347 clone_consumed_or_mutated
: bool
,
350 fn visit_clone_usage(cloned
: mir
::Local
, clone
: mir
::Local
, mir
: &mir
::Body
<'_
>, bb
: mir
::BasicBlock
) -> CloneUsage
{
353 local_use_locs
: cloned_use_locs
,
354 local_consume_or_mutate_locs
: cloned_consume_or_mutate_locs
,
358 local_consume_or_mutate_locs
: clone_consume_or_mutate_locs
,
360 )) = visit_local_usage(
365 statement_index
: mir
.basic_blocks
[bb
].statements
.len(),
368 .map(|mut vec
| (vec
.remove(0), vec
.remove(0)))
371 cloned_used
: !cloned_use_locs
.is_empty(),
372 cloned_consume_or_mutate_loc
: cloned_consume_or_mutate_locs
.first().copied(),
373 // Consider non-temporary clones consumed.
374 // TODO: Actually check for mutation of non-temporaries.
375 clone_consumed_or_mutated
: mir
.local_kind(clone
) != mir
::LocalKind
::Temp
376 || !clone_consume_or_mutate_locs
.is_empty(),
381 cloned_consume_or_mutate_loc
: None
,
382 clone_consumed_or_mutated
: true,