1use rustc_abi::FieldIdx;2use rustc_data_structures::fx::FxHashSet;3use rustc_errors::{Applicability, Diag};4use rustc_hir::intravisit::Visitor;5use rustc_hir::{self as hir, CaptureBy, ExprKind, HirId, Node};6use rustc_middle::bug;7use rustc_middle::mir::*;8use rustc_middle::ty::{self, Ty, TyCtxt};9use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex};10use rustc_span::def_id::DefId;11use rustc_span::{BytePos, ExpnKind, MacroKind, Span, sym};12use rustc_trait_selection::error_reporting::traits::FindExprBySpan;13use rustc_trait_selection::infer::InferCtxtExt;14use tracing::debug;1516use crate::MirBorrowckCtxt;17use crate::diagnostics::{18 BorrowedContentSource, CapturedMessageOpt, CloneSuggestion, DescribePlaceOpt, UseSpans,19};20use crate::prefixes::PrefixSet;2122#[derive(Debug)]23pub(crate) enum IllegalMoveOriginKind<'tcx> {24 /// Illegal move due to attempt to move from behind a reference.25 BorrowedContent {26 /// The place the reference refers to: if erroneous code was trying to27 /// move from `(*x).f` this will be `*x`.28 target_place: Place<'tcx>,29 },3031 /// Illegal move due to attempt to move from field of an ADT that32 /// implements `Drop`. Rust maintains invariant that all `Drop`33 /// ADT's remain fully-initialized so that user-defined destructor34 /// can safely read from all of the ADT's fields.35 InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },3637 /// Illegal move due to attempt to move out of a slice or array.38 InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool },39}4041#[derive(Debug)]42pub(crate) struct MoveError<'tcx> {43 place: Place<'tcx>,44 location: Location,45 kind: IllegalMoveOriginKind<'tcx>,46}4748impl<'tcx> MoveError<'tcx> {49 pub(crate) fn new(50 place: Place<'tcx>,51 location: Location,52 kind: IllegalMoveOriginKind<'tcx>,53 ) -> Self {54 MoveError { place, location, kind }55 }56}5758// Often when desugaring a pattern match we may have many individual moves in59// MIR that are all part of one operation from the user's point-of-view. For60// example:61//62// let (x, y) = foo()63//64// would move x from the 0 field of some temporary, and y from the 1 field. We65// group such errors together for cleaner error reporting.66//67// Errors are kept separate if they are from places with different parent move68// paths. For example, this generates two errors:69//70// let (&x, &y) = (&String::new(), &String::new());71#[derive(Debug)]72enum GroupedMoveError<'tcx> {73 // Place expression can't be moved from,74 // e.g., match x[0] { s => (), } where x: &[String]75 MovesFromPlace {76 original_path: Place<'tcx>,77 span: Span,78 move_from: Place<'tcx>,79 kind: IllegalMoveOriginKind<'tcx>,80 binds_to: Vec<Local>,81 },82 // Part of a value expression can't be moved from,83 // e.g., match &String::new() { &x => (), }84 MovesFromValue {85 original_path: Place<'tcx>,86 span: Span,87 move_from: MovePathIndex,88 kind: IllegalMoveOriginKind<'tcx>,89 binds_to: Vec<Local>,90 },91 // Everything that isn't from pattern matching.92 OtherIllegalMove {93 original_path: Place<'tcx>,94 use_spans: UseSpans<'tcx>,95 kind: IllegalMoveOriginKind<'tcx>,96 },97}9899impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {100 pub(crate) fn report_move_errors(&mut self) {101 let grouped_errors = self.group_move_errors();102 for error in grouped_errors {103 self.report(error);104 }105 }106107 fn group_move_errors(&mut self) -> Vec<GroupedMoveError<'tcx>> {108 let mut grouped_errors = Vec::new();109 let errors = std::mem::take(&mut self.move_errors);110 for error in errors {111 self.append_to_grouped_errors(&mut grouped_errors, error);112 }113 grouped_errors114 }115116 fn append_to_grouped_errors(117 &self,118 grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,119 MoveError { place: original_path, location, kind }: MoveError<'tcx>,120 ) {121 // Note: that the only time we assign a place isn't a temporary122 // to a user variable is when initializing it.123 // If that ever stops being the case, then the ever initialized124 // flow could be used.125 if let Some(StatementKind::Assign(box (place, Rvalue::Use(Operand::Move(move_from), _)))) =126 self.body.basic_blocks[location.block]127 .statements128 .get(location.statement_index)129 .map(|stmt| &stmt.kind)130 && let Some(local) = place.as_local()131 {132 let local_decl = &self.body.local_decls[local];133 // opt_match_place is the134 // match_span is the span of the expression being matched on135 // match *x.y { ... } match_place is Some(*x.y)136 // ^^^^ match_span is the span of *x.y137 //138 // opt_match_place is None for let [mut] x = ... statements,139 // whether or not the right-hand side is a place expression140 if let LocalInfo::User(BindingForm::Var(VarBindingForm {141 opt_match_place: Some((opt_match_place, match_span)),142 ..143 })) = *local_decl.local_info()144 {145 let stmt_source_info = self.body.source_info(location);146 self.append_binding_error(147 grouped_errors,148 kind,149 original_path,150 *move_from,151 local,152 opt_match_place,153 match_span,154 stmt_source_info.span,155 );156 return;157 }158 }159160 let move_spans = self.move_spans(original_path.as_ref(), location);161 grouped_errors.push(GroupedMoveError::OtherIllegalMove {162 use_spans: move_spans,163 original_path,164 kind,165 });166 }167168 fn append_binding_error(169 &self,170 grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,171 kind: IllegalMoveOriginKind<'tcx>,172 original_path: Place<'tcx>,173 move_from: Place<'tcx>,174 bind_to: Local,175 match_place: Option<Place<'tcx>>,176 match_span: Span,177 statement_span: Span,178 ) {179 debug!(?match_place, ?match_span, "append_binding_error");180181 let from_simple_let = match_place.is_none();182 let match_place = match_place.unwrap_or(move_from);183184 match self.move_data.rev_lookup.find(match_place.as_ref()) {185 // Error with the match place186 LookupResult::Parent(_) => {187 for ge in &mut *grouped_errors {188 if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge189 && match_span == *span190 {191 debug!("appending local({bind_to:?}) to list");192 if !binds_to.is_empty() {193 binds_to.push(bind_to);194 }195 return;196 }197 }198 debug!("found a new move error location");199200 // Don't need to point to x in let x = ... .201 let (binds_to, span) = if from_simple_let {202 (vec![], statement_span)203 } else {204 (vec![bind_to], match_span)205 };206 grouped_errors.push(GroupedMoveError::MovesFromPlace {207 span,208 move_from,209 original_path,210 kind,211 binds_to,212 });213 }214 // Error with the pattern215 LookupResult::Exact(_) => {216 let LookupResult::Parent(Some(mpi)) =217 self.move_data.rev_lookup.find(move_from.as_ref())218 else {219 // move_from should be a projection from match_place.220 unreachable!("Probably not unreachable...");221 };222 for ge in &mut *grouped_errors {223 if let GroupedMoveError::MovesFromValue {224 span,225 move_from: other_mpi,226 binds_to,227 ..228 } = ge229 {230 if match_span == *span && mpi == *other_mpi {231 debug!("appending local({bind_to:?}) to list");232 binds_to.push(bind_to);233 return;234 }235 }236 }237 debug!("found a new move error location");238 grouped_errors.push(GroupedMoveError::MovesFromValue {239 span: match_span,240 move_from: mpi,241 original_path,242 kind,243 binds_to: vec![bind_to],244 });245 }246 };247 }248249 fn report(&mut self, error: GroupedMoveError<'tcx>) {250 let (span, use_spans, original_path, kind) = match error {251 GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. }252 | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => {253 (span, None, original_path, kind)254 }255 GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => {256 (use_spans.args_or_use(), Some(use_spans), original_path, kind)257 }258 };259 debug!(260 "report: original_path={:?} span={:?}, kind={:?} \261 original_path.is_upvar_field_projection={:?}",262 original_path,263 span,264 kind,265 self.is_upvar_field_projection(original_path.as_ref())266 );267 if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) {268 // If the type may implement Copy, skip the error.269 // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check270 self.dcx()271 .span_delayed_bug(span, "Type may implement copy, but there is no other error.");272 return;273 }274275 let mut has_clone_suggestion = CloneSuggestion::NotEmitted;276 let mut err = match kind {277 &IllegalMoveOriginKind::BorrowedContent { target_place } => {278 let (diag, clone_sugg) = self.report_cannot_move_from_borrowed_content(279 original_path,280 target_place,281 span,282 use_spans,283 );284 has_clone_suggestion = clone_sugg;285 diag286 }287 &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {288 self.cannot_move_out_of_interior_of_drop(span, ty)289 }290 &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {291 self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index))292 }293 };294295 self.add_move_hints(error, &mut err, span, has_clone_suggestion);296 self.buffer_error(err);297 }298299 fn has_ambiguous_copy(&mut self, ty: Ty<'tcx>) -> bool {300 let Some(copy_def_id) = self.infcx.tcx.lang_items().copy_trait() else { return false };301302 // Avoid bogus move errors because of an incoherent `Copy` impl.303 self.infcx.type_implements_trait(copy_def_id, [ty], self.infcx.param_env).may_apply()304 && self.infcx.tcx.ensure_result().coherent_trait(copy_def_id).is_err()305 }306307 fn report_cannot_move_from_static(&mut self, place: Place<'tcx>, span: Span) -> Diag<'infcx> {308 let description = if place.projection.len() == 1 {309 format!("static item {}", self.describe_any_place(place.as_ref()))310 } else {311 let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] };312313 format!(314 "{} as {} is a static item",315 self.describe_any_place(place.as_ref()),316 self.describe_any_place(base_static),317 )318 };319320 self.cannot_move_out_of(span, &description)321 }322323 pub(in crate::diagnostics) fn suggest_clone_of_captured_var_in_move_closure(324 &self,325 err: &mut Diag<'_>,326 upvar_name: &str,327 use_spans: Option<UseSpans<'tcx>>,328 ) {329 let tcx = self.infcx.tcx;330 let Some(use_spans) = use_spans else { return };331 // We only care about the case where a closure captured a binding.332 let UseSpans::ClosureUse { args_span, .. } = use_spans else { return };333 let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return };334 // Find the closure that captured the binding.335 let mut expr_finder = FindExprBySpan::new(args_span, tcx);336 expr_finder.include_closures = true;337 expr_finder.visit_expr(tcx.hir_body(body_id).value);338 let Some(closure_expr) = expr_finder.result else { return };339 let ExprKind::Closure(closure) = closure_expr.kind else { return };340 // We'll only suggest cloning the binding if it's a `move` closure.341 let CaptureBy::Value { .. } = closure.capture_clause else { return };342 // Find the expression within the closure where the binding is consumed.343 let mut suggested = false;344 let use_span = use_spans.var_or_use();345 let mut expr_finder = FindExprBySpan::new(use_span, tcx);346 expr_finder.include_closures = true;347 expr_finder.visit_expr(tcx.hir_body(body_id).value);348 let Some(use_expr) = expr_finder.result else { return };349 let parent = tcx.parent_hir_node(use_expr.hir_id);350 if let Node::Expr(expr) = parent351 && let ExprKind::Assign(lhs, ..) = expr.kind352 && lhs.hir_id == use_expr.hir_id353 {354 // Cloning the value being assigned makes no sense:355 //356 // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure357 // --> $DIR/option-content-move2.rs:11:9358 // |359 // LL | let mut var = None;360 // | ------- captured outer variable361 // LL | func(|| {362 // | -- captured by this `FnMut` closure363 // LL | // Shouldn't suggest `move ||.as_ref()` here364 // LL | move || {365 // | ^^^^^^^ `var` is moved here366 // LL |367 // LL | var = Some(NotCopyable);368 // | ---369 // | |370 // | variable moved due to use in closure371 // | move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait372 // |373 return;374 }375376 // Search for an appropriate place for the structured `.clone()` suggestion to be applied.377 // If we encounter a statement before the borrow error, we insert a statement there.378 for (_, node) in tcx.hir_parent_iter(closure_expr.hir_id) {379 if let Node::Stmt(stmt) = node {380 let padding = tcx381 .sess382 .source_map()383 .indentation_before(stmt.span)384 .unwrap_or_else(|| " ".to_string());385 err.multipart_suggestion(386 "consider cloning the value before moving it into the closure",387 vec![388 (389 stmt.span.shrink_to_lo(),390 format!("let value = {upvar_name}.clone();\n{padding}"),391 ),392 (use_span, "value".to_string()),393 ],394 Applicability::MachineApplicable,395 );396 suggested = true;397 break;398 } else if let Node::Expr(expr) = node399 && let ExprKind::Closure(_) = expr.kind400 {401 // We want to suggest cloning only on the first closure, not402 // subsequent ones (like `ui/suggestions/option-content-move2.rs`).403 break;404 }405 }406 if !suggested {407 // If we couldn't find a statement for us to insert a new `.clone()` statement before,408 // we have a bare expression, so we suggest the creation of a new block inline to go409 // from `move || val` to `{ let value = val.clone(); move || value }`.410 let padding = tcx411 .sess412 .source_map()413 .indentation_before(closure_expr.span)414 .unwrap_or_else(|| " ".to_string());415 err.multipart_suggestion(416 "consider cloning the value before moving it into the closure",417 vec![418 (419 closure_expr.span.shrink_to_lo(),420 format!("{{\n{padding}let value = {upvar_name}.clone();\n{padding}"),421 ),422 (use_spans.var_or_use(), "value".to_string()),423 (closure_expr.span.shrink_to_hi(), format!("\n{padding}}}")),424 ],425 Applicability::MachineApplicable,426 );427 }428 }429430 fn report_cannot_move_from_borrowed_content(431 &mut self,432 move_place: Place<'tcx>,433 deref_target_place: Place<'tcx>,434 span: Span,435 use_spans: Option<UseSpans<'tcx>>,436 ) -> (Diag<'infcx>, CloneSuggestion) {437 let tcx = self.infcx.tcx;438 // Inspect the type of the content behind the439 // borrow to provide feedback about why this440 // was a move rather than a copy.441 let ty = deref_target_place.ty(self.body, tcx).ty;442 let upvar_field = self443 .prefixes(move_place.as_ref(), PrefixSet::All)444 .find_map(|p| self.is_upvar_field_projection(p));445446 let deref_base = match deref_target_place.projection.as_ref() {447 [proj_base @ .., ProjectionElem::Deref] => {448 PlaceRef { local: deref_target_place.local, projection: proj_base }449 }450 _ => bug!("deref_target_place is not a deref projection"),451 };452453 if let PlaceRef { local, projection: [] } = deref_base {454 let decl = &self.body.local_decls[local];455 let local_name = self.local_name(local).map(|sym| format!("`{sym}`"));456 if decl.is_ref_for_guard() {457 return (458 self.cannot_move_out_of(459 span,460 &format!(461 "{} in pattern guard",462 local_name.as_deref().unwrap_or("the place")463 ),464 )465 .with_note(466 "variables bound in patterns cannot be moved from \467 until after the end of the pattern guard",468 ),469 CloneSuggestion::NotEmitted,470 );471 } else if decl.is_ref_to_static() {472 return (473 self.report_cannot_move_from_static(move_place, span),474 CloneSuggestion::NotEmitted,475 );476 }477 }478479 debug!("report: ty={:?}", ty);480 let mut err = match ty.kind() {481 ty::Array(..) | ty::Slice(..) => {482 self.cannot_move_out_of_interior_noncopy(span, ty, None)483 }484 ty::Closure(def_id, closure_args)485 if def_id.as_local() == Some(self.mir_def_id())486 && let Some(upvar_field) = upvar_field =>487 {488 self.report_closure_move_error(489 span,490 move_place,491 *def_id,492 closure_args.as_closure().kind_ty(),493 upvar_field,494 ty::Asyncness::No,495 )496 }497 ty::CoroutineClosure(def_id, closure_args)498 if def_id.as_local() == Some(self.mir_def_id())499 && let Some(upvar_field) = upvar_field500 && self501 .get_closure_bound_clause_span(*def_id, ty::Asyncness::Yes)502 .is_some() =>503 {504 self.report_closure_move_error(505 span,506 move_place,507 *def_id,508 closure_args.as_coroutine_closure().kind_ty(),509 upvar_field,510 ty::Asyncness::Yes,511 )512 }513 _ => {514 let source = self.borrowed_content_source(deref_base);515 let move_place_ref = move_place.as_ref();516 match (517 self.describe_place_with_options(518 move_place_ref,519 DescribePlaceOpt {520 including_downcast: false,521 including_tuple_field: false,522 },523 ),524 self.describe_name(move_place_ref),525 source.describe_for_named_place(),526 ) {527 (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of(528 span,529 &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"),530 ),531 (Some(place_desc), Some(name), None) => self.cannot_move_out_of(532 span,533 &format!("`{place_desc}` as enum variant `{name}`"),534 ),535 (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of(536 span,537 &format!("`{place_desc}` which is behind a {source_desc}"),538 ),539 (_, _, _) => self.cannot_move_out_of(540 span,541 &source.describe_for_unnamed_place(tcx),542 ),543 }544 }545 };546 let msg_opt = CapturedMessageOpt {547 is_partial_move: false,548 is_loop_message: false,549 is_move_msg: false,550 is_loop_move: false,551 has_suggest_reborrow: false,552 maybe_reinitialized_locations_is_empty: true,553 };554 let suggested_cloning = if let Some(use_spans) = use_spans {555 self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt)556 } else {557 CloneSuggestion::NotEmitted558 };559 (err, suggested_cloning)560 }561562 fn report_closure_move_error(563 &self,564 span: Span,565 move_place: Place<'tcx>,566 def_id: DefId,567 closure_kind_ty: Ty<'tcx>,568 upvar_field: FieldIdx,569 asyncness: ty::Asyncness,570 ) -> Diag<'infcx> {571 let tcx = self.infcx.tcx;572573 let closure_kind = match closure_kind_ty.to_opt_closure_kind() {574 Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,575 Some(ty::ClosureKind::FnOnce) => {576 bug!("closure kind does not match first argument type")577 }578 None => bug!("closure kind not inferred by borrowck"),579 };580581 let async_prefix = if asyncness.is_async() { "Async" } else { "" };582 let capture_description =583 format!("captured variable in an `{async_prefix}{closure_kind}` closure");584585 let upvar = &self.upvars[upvar_field.index()];586 let upvar_hir_id = upvar.get_root_variable();587 let upvar_name = upvar.to_string(tcx);588 let upvar_span = tcx.hir_span(upvar_hir_id);589590 let place_name = self.describe_any_place(move_place.as_ref());591592 let place_description = if self.is_upvar_field_projection(move_place.as_ref()).is_some() {593 format!("{place_name}, a {capture_description}")594 } else {595 format!("{place_name}, as `{upvar_name}` is a {capture_description}")596 };597598 debug!(?closure_kind_ty, ?closure_kind, ?place_description);599600 let closure_span = tcx.def_span(def_id);601602 let help_msg = format!(603 "`{async_prefix}Fn` and `{async_prefix}FnMut` closures require captured values to \604 be able to be consumed multiple times, but `{async_prefix}FnOnce` closures may \605 consume them only once"606 );607608 let mut err = self609 .cannot_move_out_of(span, &place_description)610 .with_span_label(upvar_span, "captured outer variable")611 .with_span_label(612 closure_span,613 format!("captured by this `{async_prefix}{closure_kind}` closure"),614 );615616 if let Some(bound_span) = self.get_closure_bound_clause_span(def_id, asyncness) {617 err.span_help(bound_span, help_msg);618 } else if !asyncness.is_async() {619 // For sync closures, always emit the help message even without a span.620 // For async closures, we only enter this branch if we found a valid span621 // (due to the match guard), so no fallback is needed.622 err.help(help_msg);623 }624625 err626 }627628 fn get_closure_bound_clause_span(629 &self,630 def_id: DefId,631 asyncness: ty::Asyncness,632 ) -> Option<Span> {633 let tcx = self.infcx.tcx;634 let typeck_result = tcx.typeck(self.mir_def_id());635 // Check whether the closure is an argument to a call, if so,636 // get the instantiated where-bounds of that call.637 let closure_hir_id = tcx.local_def_id_to_hir_id(def_id.expect_local());638 let hir::Node::Expr(parent) = tcx.parent_hir_node(closure_hir_id) else { return None };639640 let predicates = match parent.kind {641 hir::ExprKind::Call(callee, _) => {642 let ty = typeck_result.node_type_opt(callee.hir_id)?;643 let ty::FnDef(fn_def_id, args) = *ty.kind() else { return None };644 tcx.predicates_of(fn_def_id).instantiate(tcx, args)645 }646 hir::ExprKind::MethodCall(..) => {647 let (_, method) = typeck_result.type_dependent_def(parent.hir_id)?;648 let args = typeck_result.node_args(parent.hir_id);649 tcx.predicates_of(method).instantiate(tcx, args)650 }651 _ => return None,652 };653654 // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`655 // or `AsyncFn[Mut]`.656 for (pred, span) in predicates.predicates.iter().zip(predicates.spans.iter()) {657 let pred = pred.skip_norm_wip();658 let dominated_by_fn_trait = self659 .closure_clause_kind(pred, def_id, asyncness)660 .is_some_and(|kind| matches!(kind, ty::ClosureKind::Fn | ty::ClosureKind::FnMut));661 if dominated_by_fn_trait {662 // Found `<TyOfCapturingClosure as FnMut>` or663 // `<TyOfCapturingClosure as AsyncFnMut>`.664 // We point at the bound that coerced the closure, which could be changed665 // to `FnOnce()` or `AsyncFnOnce()` to avoid the move error.666 return Some(*span);667 }668 }669 None670 }671672 /// If `pred` is a trait clause binding the closure `def_id` to `Fn`/`FnMut`/`FnOnce`673 /// (or their async equivalents based on `asyncness`), returns the corresponding674 /// `ClosureKind`. Otherwise returns `None`.675 fn closure_clause_kind(676 &self,677 pred: ty::Clause<'tcx>,678 def_id: DefId,679 asyncness: ty::Asyncness,680 ) -> Option<ty::ClosureKind> {681 let tcx = self.infcx.tcx;682 let clause = pred.as_trait_clause()?;683 let kind = match asyncness {684 ty::Asyncness::Yes => tcx.async_fn_trait_kind_from_def_id(clause.def_id()),685 ty::Asyncness::No => tcx.fn_trait_kind_from_def_id(clause.def_id()),686 }?;687 match clause.self_ty().skip_binder().kind() {688 ty::Closure(id, _) | ty::CoroutineClosure(id, _) if *id == def_id => Some(kind),689 _ => None,690 }691 }692693 /// Suggest cloning via UFCS when a move occurs through a custom `Deref` impl.694 ///695 /// A simple `.clone()` on a type like `MyBox<Vec<i32>>` would clone the wrapper,696 /// not the inner `Vec<i32>`. Instead, we suggest `<Vec<i32> as Clone>::clone(&val)`.697 fn suggest_cloning_through_overloaded_deref(698 &self,699 err: &mut Diag<'_>,700 ty: Ty<'tcx>,701 span: Span,702 ) -> CloneSuggestion {703 let tcx = self.infcx.tcx;704 let Some(clone_trait) = tcx.lang_items().clone_trait() else {705 return CloneSuggestion::NotEmitted;706 };707 let Some(errors) =708 self.infcx.type_implements_trait_shallow(clone_trait, ty, self.infcx.param_env)709 else {710 return CloneSuggestion::NotEmitted;711 };712713 if !errors.is_empty() {714 return CloneSuggestion::NotEmitted;715 }716 let sugg = vec![717 (span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")),718 (span.shrink_to_hi(), ")".to_string()),719 ];720 err.multipart_suggestion(721 "you can `clone` the value and consume it, but this might not be \722 your desired behavior",723 sugg,724 Applicability::MaybeIncorrect,725 );726 CloneSuggestion::Emitted727 }728729 fn add_move_hints(730 &self,731 error: GroupedMoveError<'tcx>,732 err: &mut Diag<'_>,733 span: Span,734 has_clone_suggestion: CloneSuggestion,735 ) {736 match error {737 GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {738 self.add_borrow_suggestions(err, span, !binds_to.is_empty());739 if binds_to.is_empty() {740 let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;741 let place_desc = match self.describe_place(move_from.as_ref()) {742 Some(desc) => format!("`{desc}`"),743 None => "value".to_string(),744 };745746 if let Some(expr) = self.find_expr(span) {747 self.suggest_cloning(err, move_from.as_ref(), place_ty, expr, None);748 }749750 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {751 is_partial_move: false,752 ty: place_ty,753 place: &place_desc,754 span,755 });756 } else {757 binds_to.sort();758 binds_to.dedup();759760 self.add_move_error_details(err, &binds_to, &[]);761 }762 }763 GroupedMoveError::MovesFromValue { mut binds_to, .. } => {764 binds_to.sort();765 binds_to.dedup();766 let desugar_spans = self.add_move_error_suggestions(err, &binds_to);767 self.add_move_error_details(err, &binds_to, &desugar_spans);768 }769 // No binding. Nothing to suggest.770 GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {771 let mut use_span = use_spans.var_or_use();772 let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;773 let place_desc = match self.describe_place(original_path.as_ref()) {774 Some(desc) => format!("`{desc}`"),775 None => "value".to_string(),776 };777778 if has_clone_suggestion == CloneSuggestion::NotEmitted {779 // Check if the move is directly through a custom Deref impl780 // (e.g. `*my_box` where MyBox implements Deref).781 // A simple `.clone()` would clone the wrapper type rather than782 // the inner value, so we need a UFCS suggestion instead.783 //784 // However, if there are further projections after the deref785 // (e.g. `(*rc).field`), the value accessed is already the inner786 // type and a simple `.clone()` works correctly.787 let needs_ufcs = original_path.projection.last()788 == Some(&ProjectionElem::Deref)789 && original_path.iter_projections().any(|(place, elem)| {790 matches!(elem, ProjectionElem::Deref)791 && matches!(792 self.borrowed_content_source(place),793 BorrowedContentSource::OverloadedDeref(_)794 | BorrowedContentSource::OverloadedIndex(_)795 )796 });797798 let emitted_ufcs = if needs_ufcs {799 self.suggest_cloning_through_overloaded_deref(err, place_ty, use_span)800 } else {801 CloneSuggestion::NotEmitted802 };803804 if emitted_ufcs == CloneSuggestion::NotEmitted {805 if let Some(expr) = self.find_expr(use_span) {806 self.suggest_cloning(807 err,808 original_path.as_ref(),809 place_ty,810 expr,811 Some(use_spans),812 );813 }814 }815 }816817 if let Some(upvar_field) = self818 .prefixes(original_path.as_ref(), PrefixSet::All)819 .find_map(|p| self.is_upvar_field_projection(p))820 {821 // Look for the introduction of the original binding being moved.822 let upvar = &self.upvars[upvar_field.index()];823 let upvar_hir_id = upvar.get_root_variable();824 use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) {825 hir::Node::Param(param) => {826 // Instead of pointing at the path where we access the value within a827 // closure, we point at the type of the outer `fn` argument.828 param.ty_span829 }830 hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) {831 // We point at the type of the outer let-binding.832 (Some(ty), _) => ty.span,833 // We point at the initializer of the outer let-binding, but only if it834 // isn't something that spans multiple lines, like a closure, as the835 // ASCII art gets messy.836 (None, Some(init))837 if !self.infcx.tcx.sess.source_map().is_multiline(init.span) =>838 {839 init.span840 }841 _ => use_span,842 },843 _ => use_span,844 };845 }846847 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {848 is_partial_move: false,849 ty: place_ty,850 place: &place_desc,851 span: use_span,852 });853854 let mut pointed_at_span = false;855 use_spans.args_subdiag(err, |args_span| {856 if args_span == span || args_span == use_span {857 pointed_at_span = true;858 }859 crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {860 place: place_desc.clone(),861 args_span,862 }863 });864 if !pointed_at_span && use_span != span {865 err.subdiagnostic(crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {866 place: place_desc,867 args_span: span,868 });869 }870871 self.add_note_for_packed_struct_derive(err, original_path.local);872 }873 }874 }875876 fn add_borrow_suggestions(877 &self,878 err: &mut Diag<'_>,879 span: Span,880 is_destructuring_pattern_move: bool,881 ) {882 match self.infcx.tcx.sess.source_map().span_to_snippet(span) {883 Ok(snippet) if snippet.starts_with('*') => {884 let sp = span.with_lo(span.lo() + BytePos(1));885 let inner = self.find_expr(sp);886 let mut is_raw_ptr = false;887 let mut is_ref = false;888 let mut is_destructuring_assignment = false;889 let mut is_nested_deref = false;890 if let Some(inner) = inner {891 is_nested_deref =892 matches!(inner.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _));893 let typck_result = self.infcx.tcx.typeck(self.mir_def_id());894 if let Some(inner_type) = typck_result.node_type_opt(inner.hir_id) {895 if matches!(inner_type.kind(), ty::RawPtr(..)) {896 is_raw_ptr = true;897 } else if matches!(inner_type.kind(), ty::Ref(..)) {898 is_ref = true;899 }900 }901 is_destructuring_assignment =902 self.infcx.tcx.hir_parent_iter(inner.hir_id).any(|(_, node)| {903 matches!(904 node,905 hir::Node::LetStmt(&hir::LetStmt {906 source: hir::LocalSource::AssignDesugar,907 ..908 })909 )910 });911 }912 // If the `inner` is a raw pointer, do not suggest removing the "*", see #126863913 // FIXME: need to check whether the assigned object can be a raw pointer, see `tests/ui/borrowck/issue-20801.rs`.914 if is_raw_ptr {915 return;916 }917918 if !is_destructuring_pattern_move || is_ref {919 err.span_suggestion_verbose(920 span.with_hi(span.lo() + BytePos(1)),921 "consider removing the dereference here",922 String::new(),923 Applicability::MaybeIncorrect,924 );925 } else if !is_destructuring_assignment && !is_nested_deref {926 err.span_suggestion_verbose(927 span.shrink_to_lo(),928 "consider borrowing here",929 '&',930 Applicability::MaybeIncorrect,931 );932 } else {933 err.span_help(934 span,935 "destructuring assignment cannot borrow from this expression; consider using a `let` binding instead",936 );937 }938 }939 _ => {940 err.span_suggestion_verbose(941 span.shrink_to_lo(),942 "consider borrowing here",943 '&',944 Applicability::MaybeIncorrect,945 );946 }947 }948 }949950 fn add_move_error_suggestions(&self, err: &mut Diag<'_>, binds_to: &[Local]) -> Vec<Span> {951 /// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to952 /// make it bind by reference instead (if possible)953 struct BindingFinder<'tcx> {954 typeck_results: &'tcx ty::TypeckResults<'tcx>,955 tcx: TyCtxt<'tcx>,956 /// Input: the span of the pattern we're finding bindings in957 pat_span: Span,958 /// Input: the spans of the bindings we're providing suggestions for959 binding_spans: Vec<Span>,960 /// Internal state: have we reached the pattern we're finding bindings in?961 found_pat: bool,962 /// Internal state: the innermost `&` or `&mut` "above" the visitor963 ref_pat: Option<&'tcx hir::Pat<'tcx>>,964 /// Internal state: could removing a `&` give bindings unexpected types?965 has_adjustments: bool,966 /// Output: for each input binding, the `&` or `&mut` to remove to make it by-ref967 ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>,968 /// Output: ref patterns that can't be removed straightforwardly969 cannot_remove: FxHashSet<HirId>,970 /// Output: binding spans from destructuring assignment desugaring971 desugar_binding_spans: Vec<Span>,972 }973 impl<'tcx> Visitor<'tcx> for BindingFinder<'tcx> {974 type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;975976 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {977 self.tcx978 }979980 fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result {981 // Don't walk into const patterns or anything else that might confuse this982 if !self.found_pat {983 hir::intravisit::walk_expr(self, ex)984 }985 }986987 fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {988 if p.span == self.pat_span {989 self.found_pat = true;990 }991992 let parent_has_adjustments = self.has_adjustments;993 self.has_adjustments |=994 self.typeck_results.pat_adjustments().contains_key(p.hir_id);995996 // Track the innermost `&` or `&mut` enclosing bindings, to suggest removing it.997 let parent_ref_pat = self.ref_pat;998 if let hir::PatKind::Ref(..) = p.kind {999 self.ref_pat = Some(p);1000 // To avoid edition-dependent logic to figure out how many refs this `&` can1001 // peel off, simply don't remove the "parent" `&`.1002 self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id));1003 if self.has_adjustments {1004 // Removing this `&` could give child bindings unexpected types, so don't.1005 self.cannot_remove.insert(p.hir_id);1006 // As long the `&` stays, child patterns' types should be as expected.1007 self.has_adjustments = false;1008 }1009 }10101011 if let hir::PatKind::Binding(_, _, ident, _) = p.kind {1012 // Skip synthetic bindings from destructuring assignment desugaring1013 // These have name `lhs` and their parent is a `LetStmt` with1014 // `LocalSource::AssignDesugar`1015 let dominated_by_desugar_assign = ident.name == sym::lhs1016 && self.tcx.hir_parent_iter(p.hir_id).any(|(_, node)| {1017 matches!(1018 node,1019 hir::Node::LetStmt(&hir::LetStmt {1020 source: hir::LocalSource::AssignDesugar,1021 ..1022 })1023 )1024 });10251026 if dominated_by_desugar_assign {1027 if let Some(&bind_sp) =1028 self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1029 {1030 self.desugar_binding_spans.push(bind_sp);1031 }1032 } else {1033 // the spans in `binding_spans` encompass both the ident and binding mode1034 if let Some(&bind_sp) =1035 self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1036 {1037 self.ref_pat_for_binding.push((bind_sp, self.ref_pat));1038 } else {1039 // we've encountered a binding that we're not reporting a move error for.1040 // we don't want to change its type, so don't remove the surrounding `&`.1041 if let Some(ref_pat) = self.ref_pat {1042 self.cannot_remove.insert(ref_pat.hir_id);1043 }1044 }1045 }1046 }10471048 hir::intravisit::walk_pat(self, p);1049 self.ref_pat = parent_ref_pat;1050 self.has_adjustments = parent_has_adjustments;1051 }1052 }1053 let mut pat_span = None;1054 let mut binding_spans = Vec::new();1055 for local in binds_to {1056 let bind_to = &self.body.local_decls[*local];1057 if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span: pat_sp, .. })) =1058 *bind_to.local_info()1059 {1060 pat_span = Some(pat_sp);1061 binding_spans.push(bind_to.source_info.span);1062 }1063 }1064 let Some(pat_span) = pat_span else { return Vec::new() };10651066 let tcx = self.infcx.tcx;1067 let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else { return Vec::new() };1068 let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());1069 let mut finder = BindingFinder {1070 typeck_results,1071 tcx,1072 pat_span,1073 binding_spans,1074 found_pat: false,1075 ref_pat: None,1076 has_adjustments: false,1077 ref_pat_for_binding: Vec::new(),1078 cannot_remove: FxHashSet::default(),1079 desugar_binding_spans: Vec::new(),1080 };1081 finder.visit_body(body);10821083 let mut suggestions = Vec::new();1084 for (binding_span, opt_ref_pat) in finder.ref_pat_for_binding {1085 if let Some(ref_pat) = opt_ref_pat1086 && !finder.cannot_remove.contains(&ref_pat.hir_id)1087 && let hir::PatKind::Ref(subpat, pinned, mutbl) = ref_pat.kind1088 && let Some(ref_span) = ref_pat.span.trim_end(subpat.span)1089 {1090 let pinned_str = if pinned.is_pinned() { "pinned " } else { "" };1091 let mutable_str = if mutbl.is_mut() { "mutable " } else { "" };1092 let msg = format!("consider removing the {pinned_str}{mutable_str}borrow");1093 suggestions.push((ref_span, msg, "".to_string()));1094 } else {1095 let msg = "consider borrowing the pattern binding".to_string();1096 suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string()));1097 }1098 }1099 suggestions.sort_unstable_by_key(|&(span, _, _)| span);1100 suggestions.dedup_by_key(|&mut (span, _, _)| span);1101 for (span, msg, suggestion) in suggestions {1102 err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable);1103 }1104 finder.desugar_binding_spans1105 }11061107 fn add_move_error_details(1108 &self,1109 err: &mut Diag<'_>,1110 binds_to: &[Local],1111 desugar_spans: &[Span],1112 ) {1113 for (j, local) in binds_to.iter().enumerate() {1114 let bind_to = &self.body.local_decls[*local];1115 let binding_span = bind_to.source_info.span;11161117 if binds_to.len() == 1 {1118 let place_desc = self.local_name(*local).map(|sym| format!("`{sym}`"));11191120 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::LabelMovedHere {1121 ty: bind_to.ty,1122 place: place_desc.as_deref().unwrap_or("the place"),1123 span: binding_span,1124 });11251126 if !desugar_spans.contains(&binding_span)1127 && let Some(expr) = self.find_expr(binding_span)1128 {1129 let local_place: PlaceRef<'tcx> = (*local).into();1130 self.suggest_cloning(err, local_place, bind_to.ty, expr, None);1131 }1132 } else if j == 0 {1133 err.span_label(binding_span, "data moved here");1134 } else {1135 err.span_label(binding_span, "...and here");1136 }1137 }11381139 if binds_to.len() > 1 {1140 err.note(1141 "move occurs because these variables have types that don't implement the `Copy` \1142 trait",1143 );1144 }1145 }11461147 /// Adds an explanatory note if the move error occurs in a derive macro1148 /// expansion of a packed struct.1149 /// Such errors happen because derive macro expansions shy away from taking1150 /// references to the struct's fields since doing so would be undefined behaviour1151 fn add_note_for_packed_struct_derive(&self, err: &mut Diag<'_>, local: Local) {1152 let local_place: PlaceRef<'tcx> = local.into();1153 let local_ty = local_place.ty(self.body.local_decls(), self.infcx.tcx).ty.peel_refs();11541155 if let Some(adt) = local_ty.ty_adt_def()1156 && adt.repr().packed()1157 && let ExpnKind::Macro(MacroKind::Derive, name) =1158 self.body.span.ctxt().outer_expn_data().kind1159 {1160 err.note(format!("`#[derive({name})]` triggers a move because taking references to the fields of a packed struct is undefined behaviour"));1161 }1162 }1163}