]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/assigning_clones.rs
bump version to 1.79.0+dfsg1-1~bpo12+pve2
[rustc.git] / src / tools / clippy / clippy_lints / src / assigning_clones.rs
CommitLineData
e8be2606 1use clippy_config::msrvs::{self, Msrv};
c620b35d
FG
2use clippy_utils::diagnostics::span_lint_and_then;
3use clippy_utils::macros::HirNode;
4use clippy_utils::sugg::Sugg;
5use clippy_utils::{is_trait_method, path_to_local};
6use rustc_errors::Applicability;
7use rustc_hir::{self as hir, Expr, ExprKind, Node};
8use rustc_lint::{LateContext, LateLintPass};
9use rustc_middle::ty::{self, Instance, Mutability};
e8be2606 10use rustc_session::impl_lint_pass;
c620b35d
FG
11use rustc_span::def_id::DefId;
12use rustc_span::symbol::sym;
13use rustc_span::ExpnKind;
14
15declare_clippy_lint! {
16 /// ### What it does
17 /// Checks for code like `foo = bar.clone();`
18 ///
19 /// ### Why is this bad?
20 /// Custom `Clone::clone_from()` or `ToOwned::clone_into` implementations allow the objects
21 /// to share resources and therefore avoid allocations.
22 ///
23 /// ### Example
24 /// ```rust
25 /// struct Thing;
26 ///
27 /// impl Clone for Thing {
28 /// fn clone(&self) -> Self { todo!() }
29 /// fn clone_from(&mut self, other: &Self) { todo!() }
30 /// }
31 ///
32 /// pub fn assign_to_ref(a: &mut Thing, b: Thing) {
33 /// *a = b.clone();
34 /// }
35 /// ```
36 /// Use instead:
37 /// ```rust
38 /// struct Thing;
39 /// impl Clone for Thing {
40 /// fn clone(&self) -> Self { todo!() }
41 /// fn clone_from(&mut self, other: &Self) { todo!() }
42 /// }
43 ///
44 /// pub fn assign_to_ref(a: &mut Thing, b: Thing) {
45 /// a.clone_from(&b);
46 /// }
47 /// ```
48 #[clippy::version = "1.77.0"]
49 pub ASSIGNING_CLONES,
50 perf,
51 "assigning the result of cloning may be inefficient"
52}
e8be2606
FG
53
54pub struct AssigningClones {
55 msrv: Msrv,
56}
57
58impl AssigningClones {
59 #[must_use]
60 pub fn new(msrv: Msrv) -> Self {
61 Self { msrv }
62 }
63}
64
65impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
c620b35d
FG
66
67impl<'tcx> LateLintPass<'tcx> for AssigningClones {
e8be2606 68 fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx Expr<'_>) {
c620b35d
FG
69 // Do not fire the lint in macros
70 let expn_data = assign_expr.span().ctxt().outer_expn_data();
71 match expn_data.kind {
72 ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) | ExpnKind::Macro(..) => return,
73 ExpnKind::Root => {},
74 }
75
76 let ExprKind::Assign(lhs, rhs, _span) = assign_expr.kind else {
77 return;
78 };
79
80 let Some(call) = extract_call(cx, rhs) else {
81 return;
82 };
83
e8be2606 84 if is_ok_to_suggest(cx, lhs, &call, &self.msrv) {
c620b35d
FG
85 suggest(cx, assign_expr, lhs, &call);
86 }
87 }
e8be2606
FG
88
89 extract_msrv_attr!(LateContext);
c620b35d
FG
90}
91
92// Try to resolve the call to `Clone::clone` or `ToOwned::to_owned`.
93fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<CallCandidate<'tcx>> {
94 let fn_def_id = clippy_utils::fn_def_id(cx, expr)?;
95
96 // Fast paths to only check method calls without arguments or function calls with a single argument
97 let (target, kind, resolved_method) = match expr.kind {
98 ExprKind::MethodCall(path, receiver, [], _span) => {
99 let args = cx.typeck_results().node_args(expr.hir_id);
100
101 // If we could not resolve the method, don't apply the lint
102 let Ok(Some(resolved_method)) = Instance::resolve(cx.tcx, cx.param_env, fn_def_id, args) else {
103 return None;
104 };
105 if is_trait_method(cx, expr, sym::Clone) && path.ident.name == sym::clone {
106 (TargetTrait::Clone, CallKind::MethodCall { receiver }, resolved_method)
107 } else if is_trait_method(cx, expr, sym::ToOwned) && path.ident.name.as_str() == "to_owned" {
108 (TargetTrait::ToOwned, CallKind::MethodCall { receiver }, resolved_method)
109 } else {
110 return None;
111 }
112 },
113 ExprKind::Call(function, [arg]) => {
114 let kind = cx.typeck_results().node_type(function.hir_id).kind();
115
116 // If we could not resolve the method, don't apply the lint
117 let Ok(Some(resolved_method)) = (match kind {
118 ty::FnDef(_, args) => Instance::resolve(cx.tcx, cx.param_env, fn_def_id, args),
119 _ => Ok(None),
120 }) else {
121 return None;
122 };
123 if cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id) {
124 (
125 TargetTrait::ToOwned,
126 CallKind::FunctionCall { self_arg: arg },
127 resolved_method,
128 )
129 } else if let Some(trait_did) = cx.tcx.trait_of_item(fn_def_id)
130 && cx.tcx.is_diagnostic_item(sym::Clone, trait_did)
131 {
132 (
133 TargetTrait::Clone,
134 CallKind::FunctionCall { self_arg: arg },
135 resolved_method,
136 )
137 } else {
138 return None;
139 }
140 },
141 _ => return None,
142 };
143
144 Some(CallCandidate {
145 target,
146 kind,
147 method_def_id: resolved_method.def_id(),
148 })
149}
150
151// Return true if we find that the called method has a custom implementation and isn't derived or
152// provided by default by the corresponding trait.
e8be2606
FG
153fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>, msrv: &Msrv) -> bool {
154 // For calls to .to_owned we suggest using .clone_into(), which was only stablilized in 1.63.
155 // If the current MSRV is below that, don't suggest the lint.
156 if !msrv.meets(msrvs::ASSIGNING_CLONES) && matches!(call.target, TargetTrait::ToOwned) {
157 return false;
158 }
159
c620b35d
FG
160 // If the left-hand side is a local variable, it might be uninitialized at this point.
161 // In that case we do not want to suggest the lint.
162 if let Some(local) = path_to_local(lhs) {
163 // TODO: This check currently bails if the local variable has no initializer.
164 // That is overly conservative - the lint should fire even if there was no initializer,
165 // but the variable has been initialized before `lhs` was evaluated.
e8be2606 166 if let Some(Node::LetStmt(local)) = cx.tcx.hir().parent_id_iter(local).next().map(|p| cx.tcx.hir_node(p))
c620b35d
FG
167 && local.init.is_none()
168 {
169 return false;
170 }
171 }
172
173 let Some(impl_block) = cx.tcx.impl_of_method(call.method_def_id) else {
174 return false;
175 };
176
177 // If the method implementation comes from #[derive(Clone)], then don't suggest the lint.
178 // Automatically generated Clone impls do not currently override `clone_from`.
179 // See e.g. https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305 for more details.
180 if cx.tcx.is_builtin_derived(impl_block) {
181 return false;
182 }
183
e8be2606
FG
184 // If the call expression is inside an impl block that contains the method invoked by the
185 // call expression, we bail out to avoid suggesting something that could result in endless
186 // recursion.
187 if let Some(local_block_id) = impl_block.as_local()
188 && let Some(block) = cx.tcx.hir_node_by_def_id(local_block_id).as_owner()
189 {
190 let impl_block_owner = block.def_id();
191 if cx
192 .tcx
193 .hir()
194 .parent_id_iter(lhs.hir_id)
195 .any(|parent| parent.owner == impl_block_owner)
196 {
197 return false;
198 }
199 }
200
c620b35d
FG
201 // Find the function for which we want to check that it is implemented.
202 let provided_fn = match call.target {
203 TargetTrait::Clone => cx.tcx.get_diagnostic_item(sym::Clone).and_then(|clone| {
204 cx.tcx
205 .provided_trait_methods(clone)
206 .find(|item| item.name == sym::clone_from)
207 }),
208 TargetTrait::ToOwned => cx.tcx.get_diagnostic_item(sym::ToOwned).and_then(|to_owned| {
209 cx.tcx
210 .provided_trait_methods(to_owned)
211 .find(|item| item.name.as_str() == "clone_into")
212 }),
213 };
214 let Some(provided_fn) = provided_fn else {
215 return false;
216 };
217
218 // Now take a look if the impl block defines an implementation for the method that we're interested
219 // in. If not, then we're using a default implementation, which is not interesting, so we will
220 // not suggest the lint.
221 let implemented_fns = cx.tcx.impl_item_implementor_ids(impl_block);
222 implemented_fns.contains_key(&provided_fn.def_id)
223}
224
e8be2606 225fn suggest<'tcx>(cx: &LateContext<'tcx>, assign_expr: &Expr<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>) {
c620b35d 226 span_lint_and_then(cx, ASSIGNING_CLONES, assign_expr.span, call.message(), |diag| {
e8be2606 227 let mut applicability = Applicability::Unspecified;
c620b35d
FG
228
229 diag.span_suggestion(
230 assign_expr.span,
231 call.suggestion_msg(),
232 call.suggested_replacement(cx, lhs, &mut applicability),
233 applicability,
234 );
235 });
236}
237
238#[derive(Copy, Clone, Debug)]
239enum CallKind<'tcx> {
240 MethodCall { receiver: &'tcx Expr<'tcx> },
241 FunctionCall { self_arg: &'tcx Expr<'tcx> },
242}
243
244#[derive(Copy, Clone, Debug)]
245enum TargetTrait {
246 Clone,
247 ToOwned,
248}
249
250#[derive(Debug)]
251struct CallCandidate<'tcx> {
252 target: TargetTrait,
253 kind: CallKind<'tcx>,
254 // DefId of the called method from an impl block that implements the target trait
255 method_def_id: DefId,
256}
257
258impl<'tcx> CallCandidate<'tcx> {
259 #[inline]
260 fn message(&self) -> &'static str {
261 match self.target {
262 TargetTrait::Clone => "assigning the result of `Clone::clone()` may be inefficient",
263 TargetTrait::ToOwned => "assigning the result of `ToOwned::to_owned()` may be inefficient",
264 }
265 }
266
267 #[inline]
268 fn suggestion_msg(&self) -> &'static str {
269 match self.target {
270 TargetTrait::Clone => "use `clone_from()`",
271 TargetTrait::ToOwned => "use `clone_into()`",
272 }
273 }
274
275 fn suggested_replacement(
276 &self,
277 cx: &LateContext<'tcx>,
e8be2606 278 lhs: &Expr<'tcx>,
c620b35d
FG
279 applicability: &mut Applicability,
280 ) -> String {
281 match self.target {
282 TargetTrait::Clone => {
283 match self.kind {
284 CallKind::MethodCall { receiver } => {
285 let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
286 // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
287 Sugg::hir_with_applicability(cx, ref_expr, "_", applicability)
288 } else {
289 // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)`
290 Sugg::hir_with_applicability(cx, lhs, "_", applicability)
291 }
292 .maybe_par();
293
294 // Determine whether we need to reference the argument to clone_from().
295 let clone_receiver_type = cx.typeck_results().expr_ty(receiver);
296 let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(receiver);
297 let mut arg_sugg = Sugg::hir_with_applicability(cx, receiver, "_", applicability);
298 if clone_receiver_type != clone_receiver_adj_type {
299 // The receiver may have been a value type, so we need to add an `&` to
300 // be sure the argument to clone_from will be a reference.
301 arg_sugg = arg_sugg.addr();
302 };
303
304 format!("{receiver_sugg}.clone_from({arg_sugg})")
305 },
306 CallKind::FunctionCall { self_arg, .. } => {
307 let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
308 // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)`
309 Sugg::hir_with_applicability(cx, ref_expr, "_", applicability)
310 } else {
311 // `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)`
312 Sugg::hir_with_applicability(cx, lhs, "_", applicability).mut_addr()
313 };
314 // The RHS had to be exactly correct before the call, there is no auto-deref for function calls.
315 let rhs_sugg = Sugg::hir_with_applicability(cx, self_arg, "_", applicability);
316
317 format!("Clone::clone_from({self_sugg}, {rhs_sugg})")
318 },
319 }
320 },
321 TargetTrait::ToOwned => {
322 let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind {
323 // `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)`
324 // `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)`
325 let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", applicability).maybe_par();
326 let inner_type = cx.typeck_results().expr_ty(ref_expr);
327 // If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it
328 // deref to a mutable reference.
329 if matches!(inner_type.kind(), ty::Ref(_, _, Mutability::Mut)) {
330 sugg
331 } else {
332 sugg.mut_addr()
333 }
334 } else {
335 // `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)`
336 // `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)`
337 Sugg::hir_with_applicability(cx, lhs, "_", applicability)
338 .maybe_par()
339 .mut_addr()
340 };
341
342 match self.kind {
343 CallKind::MethodCall { receiver } => {
344 let receiver_sugg = Sugg::hir_with_applicability(cx, receiver, "_", applicability);
345 format!("{receiver_sugg}.clone_into({rhs_sugg})")
346 },
347 CallKind::FunctionCall { self_arg, .. } => {
348 let self_sugg = Sugg::hir_with_applicability(cx, self_arg, "_", applicability);
349 format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})")
350 },
351 }
352 },
353 }
354 }
355}