| 
1 | 1 | use clippy_config::msrvs::{self, Msrv};  | 
2 | 2 | use clippy_utils::diagnostics::span_lint_and_then;  | 
3 | 3 | use clippy_utils::macros::HirNode;  | 
 | 4 | +use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap};  | 
4 | 5 | use clippy_utils::sugg::Sugg;  | 
5 | 6 | use clippy_utils::{is_trait_method, local_is_initialized, path_to_local};  | 
6 | 7 | use rustc_errors::Applicability;  | 
7 | 8 | use rustc_hir::{self as hir, Expr, ExprKind};  | 
8 | 9 | use rustc_lint::{LateContext, LateLintPass};  | 
 | 10 | +use rustc_middle::mir;  | 
9 | 11 | use rustc_middle::ty::{self, Instance, Mutability};  | 
10 | 12 | use rustc_session::impl_lint_pass;  | 
11 | 13 | use rustc_span::def_id::DefId;  | 
12 | 14 | use rustc_span::symbol::sym;  | 
13 |  | -use rustc_span::{ExpnKind, SyntaxContext};  | 
 | 15 | +use rustc_span::{ExpnKind, Span, SyntaxContext};  | 
14 | 16 | 
 
  | 
15 | 17 | declare_clippy_lint! {  | 
16 | 18 |     /// ### What it does  | 
@@ -144,6 +146,7 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<  | 
144 | 146 |     };  | 
145 | 147 | 
 
  | 
146 | 148 |     Some(CallCandidate {  | 
 | 149 | +        span: expr.span,  | 
147 | 150 |         target,  | 
148 | 151 |         kind,  | 
149 | 152 |         method_def_id: resolved_method.def_id(),  | 
@@ -215,13 +218,85 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC  | 
215 | 218 |         return false;  | 
216 | 219 |     };  | 
217 | 220 | 
 
  | 
 | 221 | +    if clone_source_borrows_from_dest(cx, lhs, call.span) {  | 
 | 222 | +        return false;  | 
 | 223 | +    }  | 
 | 224 | + | 
218 | 225 |     // Now take a look if the impl block defines an implementation for the method that we're interested  | 
219 | 226 |     // in. If not, then we're using a default implementation, which is not interesting, so we will  | 
220 | 227 |     // not suggest the lint.  | 
221 | 228 |     let implemented_fns = cx.tcx.impl_item_implementor_ids(impl_block);  | 
222 | 229 |     implemented_fns.contains_key(&provided_fn.def_id)  | 
223 | 230 | }  | 
224 | 231 | 
 
  | 
 | 232 | +/// Checks if the data being cloned borrows from the place that is being assigned to:  | 
 | 233 | +///  | 
 | 234 | +/// ```  | 
 | 235 | +/// let mut s = String::new();  | 
 | 236 | +/// let s2 = &s;  | 
 | 237 | +/// s = s2.to_owned();  | 
 | 238 | +/// ```  | 
 | 239 | +///  | 
 | 240 | +/// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows.  | 
 | 241 | +fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool {  | 
 | 242 | +    /// If this basic block only exists to drop a local as part of an assignment, returns its  | 
 | 243 | +    /// successor. Otherwise returns the basic block that was passed in.  | 
 | 244 | +    fn skip_drop_block(mir: &mir::Body<'_>, bb: mir::BasicBlock) -> mir::BasicBlock {  | 
 | 245 | +        if let mir::TerminatorKind::Drop { target, .. } = mir.basic_blocks[bb].terminator().kind {  | 
 | 246 | +            target  | 
 | 247 | +        } else {  | 
 | 248 | +            bb  | 
 | 249 | +        }  | 
 | 250 | +    }  | 
 | 251 | + | 
 | 252 | +    let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else {  | 
 | 253 | +        return false;  | 
 | 254 | +    };  | 
 | 255 | +    let PossibleBorrowerMap { map: borrow_map, .. } = PossibleBorrowerMap::new(cx, mir);  | 
 | 256 | + | 
 | 257 | +    // The operation `dest = src.to_owned()` in MIR is split up across 3 blocks *if* the type has `Drop`  | 
 | 258 | +    // code. For types that don't, the second basic block is simply skipped.  | 
 | 259 | +    // For the doc example above that would be roughly:  | 
 | 260 | +    //  | 
 | 261 | +    // bb0:  | 
 | 262 | +    //  s2 = &s  | 
 | 263 | +    //  s_temp = ToOwned::to_owned(move s2) -> bb1  | 
 | 264 | +    //  | 
 | 265 | +    // bb1:  | 
 | 266 | +    //  drop(s) -> bb2  // drop the old string  | 
 | 267 | +    //  | 
 | 268 | +    // bb2:  | 
 | 269 | +    //  s = s_temp  | 
 | 270 | +    for bb in mir.basic_blocks.iter() {  | 
 | 271 | +        let terminator = bb.terminator();  | 
 | 272 | + | 
 | 273 | +        // Look for the to_owned/clone call.  | 
 | 274 | +        if terminator.source_info.span != call_span {  | 
 | 275 | +            continue;  | 
 | 276 | +        }  | 
 | 277 | + | 
 | 278 | +        if let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind  | 
 | 279 | +            && let [source] = &**args  | 
 | 280 | +            && let mir::Operand::Move(source) = &source.node  | 
 | 281 | +            && let assign_bb = skip_drop_block(mir, assign_bb)  | 
 | 282 | +            // Skip any storage statements as they are just noise  | 
 | 283 | +            && let Some(assignment) = mir.basic_blocks[assign_bb].statements  | 
 | 284 | +                .iter()  | 
 | 285 | +                .find(|stmt| {  | 
 | 286 | +                    !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_))  | 
 | 287 | +                })  | 
 | 288 | +            && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind  | 
 | 289 | +            && let Some(borrowers) = borrow_map.get(&borrowed.local)  | 
 | 290 | +            && borrowers.contains(source.local)  | 
 | 291 | +        {  | 
 | 292 | +            return true;  | 
 | 293 | +        }  | 
 | 294 | + | 
 | 295 | +        return false;  | 
 | 296 | +    }  | 
 | 297 | +    false  | 
 | 298 | +}  | 
 | 299 | + | 
225 | 300 | fn suggest<'tcx>(  | 
226 | 301 |     cx: &LateContext<'tcx>,  | 
227 | 302 |     ctxt: SyntaxContext,  | 
@@ -255,6 +330,7 @@ enum TargetTrait {  | 
255 | 330 | 
 
  | 
256 | 331 | #[derive(Debug)]  | 
257 | 332 | struct CallCandidate<'tcx> {  | 
 | 333 | +    span: Span,  | 
258 | 334 |     target: TargetTrait,  | 
259 | 335 |     kind: CallKind<'tcx>,  | 
260 | 336 |     // DefId of the called method from an impl block that implements the target trait  | 
 | 
0 commit comments