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}9899struct PatternBindingInfo {100 pat_span: Span,101 binding_spans: Vec<Span>,102 has_mutable_by_value_binding: bool,103}104105impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {106 pub(crate) fn report_move_errors(&mut self) {107 let grouped_errors = self.group_move_errors();108 for error in grouped_errors {109 self.report(error);110 }111 }112113 fn group_move_errors(&mut self) -> Vec<GroupedMoveError<'tcx>> {114 let mut grouped_errors = Vec::new();115 let errors = std::mem::take(&mut self.move_errors);116 for error in errors {117 self.append_to_grouped_errors(&mut grouped_errors, error);118 }119 grouped_errors120 }121122 fn append_to_grouped_errors(123 &self,124 grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,125 MoveError { place: original_path, location, kind }: MoveError<'tcx>,126 ) {127 // Note: that the only time we assign a place isn't a temporary128 // to a user variable is when initializing it.129 // If that ever stops being the case, then the ever initialized130 // flow could be used.131 if let Some(StatementKind::Assign((place, Rvalue::Use(Operand::Move(move_from), _)))) =132 self.body.basic_blocks[location.block]133 .statements134 .get(location.statement_index)135 .map(|stmt| &stmt.kind)136 && let Some(local) = place.as_local()137 {138 let local_decl = &self.body.local_decls[local];139 // opt_match_place is the140 // match_span is the span of the expression being matched on141 // match *x.y { ... } match_place is Some(*x.y)142 // ^^^^ match_span is the span of *x.y143 //144 // opt_match_place is None for let [mut] x = ... statements,145 // whether or not the right-hand side is a place expression146 if let LocalInfo::User(BindingForm::Var(VarBindingForm {147 opt_match_place: Some((opt_match_place, match_span)),148 ..149 })) = *local_decl.local_info()150 {151 let stmt_source_info = self.body.source_info(location);152 self.append_binding_error(153 grouped_errors,154 kind,155 original_path,156 *move_from,157 local,158 opt_match_place,159 match_span,160 stmt_source_info.span,161 );162 return;163 }164 }165166 let move_spans = self.move_spans(original_path.as_ref(), location);167 grouped_errors.push(GroupedMoveError::OtherIllegalMove {168 use_spans: move_spans,169 original_path,170 kind,171 });172 }173174 fn append_binding_error(175 &self,176 grouped_errors: &mut Vec<GroupedMoveError<'tcx>>,177 kind: IllegalMoveOriginKind<'tcx>,178 original_path: Place<'tcx>,179 move_from: Place<'tcx>,180 bind_to: Local,181 match_place: Option<Place<'tcx>>,182 match_span: Span,183 statement_span: Span,184 ) {185 debug!(?match_place, ?match_span, "append_binding_error");186187 let from_simple_let = match_place.is_none();188 let match_place = match_place.unwrap_or(move_from);189190 match self.move_data.rev_lookup.find(match_place.as_ref()) {191 // Error with the match place192 LookupResult::Parent(_) => {193 for ge in &mut *grouped_errors {194 if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge195 && match_span == *span196 {197 debug!("appending local({bind_to:?}) to list");198 if !binds_to.is_empty() {199 binds_to.push(bind_to);200 }201 return;202 }203 }204 debug!("found a new move error location");205206 // Don't need to point to x in let x = ... .207 let (binds_to, span) = if from_simple_let {208 (vec![], statement_span)209 } else {210 (vec![bind_to], match_span)211 };212 grouped_errors.push(GroupedMoveError::MovesFromPlace {213 span,214 move_from,215 original_path,216 kind,217 binds_to,218 });219 }220 // Error with the pattern221 LookupResult::Exact(_) => {222 let LookupResult::Parent(Some(mpi)) =223 self.move_data.rev_lookup.find(move_from.as_ref())224 else {225 // move_from should be a projection from match_place.226 unreachable!("Probably not unreachable...");227 };228 for ge in &mut *grouped_errors {229 if let GroupedMoveError::MovesFromValue {230 span,231 move_from: other_mpi,232 binds_to,233 ..234 } = ge235 {236 if match_span == *span && mpi == *other_mpi {237 debug!("appending local({bind_to:?}) to list");238 binds_to.push(bind_to);239 return;240 }241 }242 }243 debug!("found a new move error location");244 grouped_errors.push(GroupedMoveError::MovesFromValue {245 span: match_span,246 move_from: mpi,247 original_path,248 kind,249 binds_to: vec![bind_to],250 });251 }252 };253 }254255 fn report(&mut self, error: GroupedMoveError<'tcx>) {256 let (span, use_spans, original_path, kind) = match error {257 GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. }258 | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => {259 (span, None, original_path, kind)260 }261 GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => {262 (use_spans.args_or_use(), Some(use_spans), original_path, kind)263 }264 };265 debug!(266 "report: original_path={:?} span={:?}, kind={:?} \267 original_path.is_upvar_field_projection={:?}",268 original_path,269 span,270 kind,271 self.is_upvar_field_projection(original_path.as_ref())272 );273 if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) {274 // If the type may implement Copy, skip the error.275 // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check276 self.dcx()277 .span_delayed_bug(span, "Type may implement copy, but there is no other error.");278 return;279 }280281 let mut has_clone_suggestion = CloneSuggestion::NotEmitted;282 let mut err = match kind {283 &IllegalMoveOriginKind::BorrowedContent { target_place } => {284 let (diag, clone_sugg) = self.report_cannot_move_from_borrowed_content(285 original_path,286 target_place,287 span,288 use_spans,289 );290 has_clone_suggestion = clone_sugg;291 diag292 }293 &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => {294 self.cannot_move_out_of_interior_of_drop(span, ty)295 }296 &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => {297 self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index))298 }299 };300301 self.add_move_hints(error, &mut err, span, has_clone_suggestion);302 self.buffer_error(err);303 }304305 fn has_ambiguous_copy(&mut self, ty: Ty<'tcx>) -> bool {306 let Some(copy_def_id) = self.infcx.tcx.lang_items().copy_trait() else { return false };307308 // Avoid bogus move errors because of an incoherent `Copy` impl.309 self.infcx.type_implements_trait(copy_def_id, [ty], self.infcx.param_env).may_apply()310 && self.infcx.tcx.ensure_result().coherent_trait(copy_def_id).is_err()311 }312313 fn report_cannot_move_from_static(&mut self, place: Place<'tcx>, span: Span) -> Diag<'infcx> {314 let description = if place.projection.len() == 1 {315 format!("static item {}", self.describe_any_place(place.as_ref()))316 } else {317 let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] };318319 format!(320 "{} as {} is a static item",321 self.describe_any_place(place.as_ref()),322 self.describe_any_place(base_static),323 )324 };325326 self.cannot_move_out_of(span, &description)327 }328329 pub(in crate::diagnostics) fn suggest_clone_of_captured_var_in_move_closure(330 &self,331 err: &mut Diag<'_>,332 upvar_name: &str,333 use_spans: Option<UseSpans<'tcx>>,334 ) {335 let tcx = self.infcx.tcx;336 let Some(use_spans) = use_spans else { return };337 // We only care about the case where a closure captured a binding.338 let UseSpans::ClosureUse { args_span, .. } = use_spans else { return };339 let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return };340 // Find the closure that captured the binding.341 let mut expr_finder = FindExprBySpan::new(args_span, tcx);342 expr_finder.include_closures = true;343 expr_finder.visit_expr(tcx.hir_body(body_id).value);344 let Some(closure_expr) = expr_finder.result else { return };345 let ExprKind::Closure(closure) = closure_expr.kind else { return };346 // We'll only suggest cloning the binding if it's a `move` closure.347 let CaptureBy::Value { .. } = closure.capture_clause else { return };348 // Find the expression within the closure where the binding is consumed.349 let mut suggested = false;350 let use_span = use_spans.var_or_use();351 let mut expr_finder = FindExprBySpan::new(use_span, tcx);352 expr_finder.include_closures = true;353 expr_finder.visit_expr(tcx.hir_body(body_id).value);354 let Some(use_expr) = expr_finder.result else { return };355 let parent = tcx.parent_hir_node(use_expr.hir_id);356 if let Node::Expr(expr) = parent357 && let ExprKind::Assign(lhs, ..) = expr.kind358 && lhs.hir_id == use_expr.hir_id359 {360 // Cloning the value being assigned makes no sense:361 //362 // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure363 // --> $DIR/option-content-move2.rs:11:9364 // |365 // LL | let mut var = None;366 // | ------- captured outer variable367 // LL | func(|| {368 // | -- captured by this `FnMut` closure369 // LL | // Shouldn't suggest `move ||.as_ref()` here370 // LL | move || {371 // | ^^^^^^^ `var` is moved here372 // LL |373 // LL | var = Some(NotCopyable);374 // | ---375 // | |376 // | variable moved due to use in closure377 // | move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait378 // |379 return;380 }381382 // Search for an appropriate place for the structured `.clone()` suggestion to be applied.383 // If we encounter a statement before the borrow error, we insert a statement there.384 for (_, node) in tcx.hir_parent_iter(closure_expr.hir_id) {385 if let Node::Stmt(stmt) = node {386 let padding = tcx387 .sess388 .source_map()389 .indentation_before(stmt.span)390 .unwrap_or_else(|| " ".to_string());391 err.multipart_suggestion(392 "consider cloning the value before moving it into the closure",393 vec![394 (395 stmt.span.shrink_to_lo(),396 format!("let value = {upvar_name}.clone();\n{padding}"),397 ),398 (use_span, "value".to_string()),399 ],400 Applicability::MachineApplicable,401 );402 suggested = true;403 break;404 } else if let Node::Expr(expr) = node405 && let ExprKind::Closure(_) = expr.kind406 {407 // We want to suggest cloning only on the first closure, not408 // subsequent ones (like `ui/suggestions/option-content-move2.rs`).409 break;410 }411 }412 if !suggested {413 // If we couldn't find a statement for us to insert a new `.clone()` statement before,414 // we have a bare expression, so we suggest the creation of a new block inline to go415 // from `move || val` to `{ let value = val.clone(); move || value }`.416 let padding = tcx417 .sess418 .source_map()419 .indentation_before(closure_expr.span)420 .unwrap_or_else(|| " ".to_string());421 err.multipart_suggestion(422 "consider cloning the value before moving it into the closure",423 vec![424 (425 closure_expr.span.shrink_to_lo(),426 format!("{{\n{padding}let value = {upvar_name}.clone();\n{padding}"),427 ),428 (use_spans.var_or_use(), "value".to_string()),429 (closure_expr.span.shrink_to_hi(), format!("\n{padding}}}")),430 ],431 Applicability::MachineApplicable,432 );433 }434 }435436 fn report_cannot_move_from_borrowed_content(437 &mut self,438 move_place: Place<'tcx>,439 deref_target_place: Place<'tcx>,440 span: Span,441 use_spans: Option<UseSpans<'tcx>>,442 ) -> (Diag<'infcx>, CloneSuggestion) {443 let tcx = self.infcx.tcx;444 // Inspect the type of the content behind the445 // borrow to provide feedback about why this446 // was a move rather than a copy.447 let ty = deref_target_place.ty(self.body, tcx).ty;448 let upvar_field = self449 .prefixes(move_place.as_ref(), PrefixSet::All)450 .find_map(|p| self.is_upvar_field_projection(p));451452 let deref_base = match deref_target_place.projection.as_ref() {453 [proj_base @ .., ProjectionElem::Deref] => {454 PlaceRef { local: deref_target_place.local, projection: proj_base }455 }456 _ => bug!("deref_target_place is not a deref projection"),457 };458459 if let PlaceRef { local, projection: [] } = deref_base {460 let decl = &self.body.local_decls[local];461 let local_name = self.local_name(local).map(|sym| format!("`{sym}`"));462 if decl.is_ref_for_guard() {463 return (464 self.cannot_move_out_of(465 span,466 &format!(467 "{} in pattern guard",468 local_name.as_deref().unwrap_or("the place")469 ),470 )471 .with_note(472 "variables bound in patterns cannot be moved from \473 until after the end of the pattern guard",474 ),475 CloneSuggestion::NotEmitted,476 );477 } else if decl.is_ref_to_static() {478 return (479 self.report_cannot_move_from_static(move_place, span),480 CloneSuggestion::NotEmitted,481 );482 }483 }484485 debug!("report: ty={:?}", ty);486 let mut err = match ty.kind() {487 ty::Array(..) | ty::Slice(..) => {488 self.cannot_move_out_of_interior_noncopy(span, ty, None)489 }490 ty::Closure(def_id, closure_args)491 if def_id.as_local() == Some(self.mir_def_id())492 && let Some(upvar_field) = upvar_field =>493 {494 self.report_closure_move_error(495 span,496 move_place,497 *def_id,498 closure_args.as_closure().kind_ty(),499 upvar_field,500 ty::Asyncness::No,501 )502 }503 ty::CoroutineClosure(def_id, closure_args)504 if def_id.as_local() == Some(self.mir_def_id())505 && let Some(upvar_field) = upvar_field506 && self507 .get_closure_bound_clause_span(*def_id, ty::Asyncness::Yes)508 .is_some() =>509 {510 self.report_closure_move_error(511 span,512 move_place,513 *def_id,514 closure_args.as_coroutine_closure().kind_ty(),515 upvar_field,516 ty::Asyncness::Yes,517 )518 }519 _ => {520 let source = self.borrowed_content_source(deref_base);521 let move_place_ref = move_place.as_ref();522 match (523 self.describe_place_with_options(524 move_place_ref,525 DescribePlaceOpt {526 including_downcast: false,527 including_tuple_field: false,528 },529 ),530 self.describe_name(move_place_ref),531 source.describe_for_named_place(),532 ) {533 (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of(534 span,535 &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"),536 ),537 (Some(place_desc), Some(name), None) => self.cannot_move_out_of(538 span,539 &format!("`{place_desc}` as enum variant `{name}`"),540 ),541 (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of(542 span,543 &format!("`{place_desc}` which is behind a {source_desc}"),544 ),545 (_, _, _) => self.cannot_move_out_of(546 span,547 &source.describe_for_unnamed_place(tcx),548 ),549 }550 }551 };552 let msg_opt = CapturedMessageOpt {553 is_partial_move: false,554 is_loop_message: false,555 is_move_msg: false,556 is_loop_move: false,557 has_suggest_reborrow: false,558 maybe_reinitialized_locations_is_empty: true,559 };560 let suggested_cloning = if let Some(use_spans) = use_spans {561 self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt)562 } else {563 CloneSuggestion::NotEmitted564 };565 (err, suggested_cloning)566 }567568 fn report_closure_move_error(569 &self,570 span: Span,571 move_place: Place<'tcx>,572 def_id: DefId,573 closure_kind_ty: Ty<'tcx>,574 upvar_field: FieldIdx,575 asyncness: ty::Asyncness,576 ) -> Diag<'infcx> {577 let tcx = self.infcx.tcx;578579 let closure_kind = match closure_kind_ty.to_opt_closure_kind() {580 Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind,581 Some(ty::ClosureKind::FnOnce) => {582 bug!("closure kind does not match first argument type")583 }584 None => bug!("closure kind not inferred by borrowck"),585 };586587 let async_prefix = if asyncness.is_async() { "Async" } else { "" };588 let capture_description =589 format!("captured variable in an `{async_prefix}{closure_kind}` closure");590591 let upvar = &self.upvars[upvar_field.index()];592 let upvar_hir_id = upvar.get_root_variable();593 let upvar_name = upvar.to_string(tcx);594 let upvar_span = tcx.hir_span(upvar_hir_id);595596 let place_name = self.describe_any_place(move_place.as_ref());597598 let place_description = if self.is_upvar_field_projection(move_place.as_ref()).is_some() {599 format!("{place_name}, a {capture_description}")600 } else {601 format!("{place_name}, as `{upvar_name}` is a {capture_description}")602 };603604 debug!(?closure_kind_ty, ?closure_kind, ?place_description);605606 let closure_span = tcx.def_span(def_id);607608 let help_msg = format!(609 "`{async_prefix}Fn` and `{async_prefix}FnMut` closures require captured values to \610 be able to be consumed multiple times, but `{async_prefix}FnOnce` closures may \611 consume them only once"612 );613614 let mut err = self615 .cannot_move_out_of(span, &place_description)616 .with_span_label(upvar_span, "captured outer variable")617 .with_span_label(618 closure_span,619 format!("captured by this `{async_prefix}{closure_kind}` closure"),620 );621622 if let Some(bound_span) = self.get_closure_bound_clause_span(def_id, asyncness) {623 err.span_help(bound_span, help_msg);624 } else if !asyncness.is_async() {625 // For sync closures, always emit the help message even without a span.626 // For async closures, we only enter this branch if we found a valid span627 // (due to the match guard), so no fallback is needed.628 err.help(help_msg);629 }630631 err632 }633634 fn get_closure_bound_clause_span(635 &self,636 def_id: DefId,637 asyncness: ty::Asyncness,638 ) -> Option<Span> {639 let tcx = self.infcx.tcx;640 let typeck_result = tcx.typeck(self.mir_def_id());641 // Check whether the closure is an argument to a call, if so,642 // get the instantiated where-bounds of that call.643 let closure_hir_id = tcx.local_def_id_to_hir_id(def_id.expect_local());644 let hir::Node::Expr(parent) = tcx.parent_hir_node(closure_hir_id) else { return None };645646 let predicates = match parent.kind {647 hir::ExprKind::Call(callee, _) => {648 let ty = typeck_result.node_type_opt(callee.hir_id)?;649 let ty::FnDef(fn_def_id, args) = *ty.kind() else { return None };650 tcx.predicates_of(fn_def_id).instantiate(tcx, args)651 }652 hir::ExprKind::MethodCall(..) => {653 let (_, method) = typeck_result.type_dependent_def(parent.hir_id)?;654 let args = typeck_result.node_args(parent.hir_id);655 tcx.predicates_of(method).instantiate(tcx, args)656 }657 _ => return None,658 };659660 // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]`661 // or `AsyncFn[Mut]`.662 for (pred, span) in predicates.predicates.iter().zip(predicates.spans.iter()) {663 let pred = pred.skip_norm_wip();664 let dominated_by_fn_trait = self665 .closure_clause_kind(pred, def_id, asyncness)666 .is_some_and(|kind| matches!(kind, ty::ClosureKind::Fn | ty::ClosureKind::FnMut));667 if dominated_by_fn_trait {668 // Found `<TyOfCapturingClosure as FnMut>` or669 // `<TyOfCapturingClosure as AsyncFnMut>`.670 // We point at the bound that coerced the closure, which could be changed671 // to `FnOnce()` or `AsyncFnOnce()` to avoid the move error.672 return Some(*span);673 }674 }675 None676 }677678 /// If `pred` is a trait clause binding the closure `def_id` to `Fn`/`FnMut`/`FnOnce`679 /// (or their async equivalents based on `asyncness`), returns the corresponding680 /// `ClosureKind`. Otherwise returns `None`.681 fn closure_clause_kind(682 &self,683 pred: ty::Clause<'tcx>,684 def_id: DefId,685 asyncness: ty::Asyncness,686 ) -> Option<ty::ClosureKind> {687 let tcx = self.infcx.tcx;688 let clause = pred.as_trait_clause()?;689 let kind = match asyncness {690 ty::Asyncness::Yes => tcx.async_fn_trait_kind_from_def_id(clause.def_id()),691 ty::Asyncness::No => tcx.fn_trait_kind_from_def_id(clause.def_id()),692 }?;693 match clause.self_ty().skip_binder().kind() {694 ty::Closure(id, _) | ty::CoroutineClosure(id, _) if *id == def_id => Some(kind),695 _ => None,696 }697 }698699 /// Suggest cloning via UFCS when a move occurs through a custom `Deref` impl.700 ///701 /// A simple `.clone()` on a type like `MyBox<Vec<i32>>` would clone the wrapper,702 /// not the inner `Vec<i32>`. Instead, we suggest `<Vec<i32> as Clone>::clone(&val)`.703 fn suggest_cloning_through_overloaded_deref(704 &self,705 err: &mut Diag<'_>,706 ty: Ty<'tcx>,707 span: Span,708 ) -> CloneSuggestion {709 let tcx = self.infcx.tcx;710 let Some(clone_trait) = tcx.lang_items().clone_trait() else {711 return CloneSuggestion::NotEmitted;712 };713 let Some(errors) =714 self.infcx.type_implements_trait_shallow(clone_trait, ty, self.infcx.param_env)715 else {716 return CloneSuggestion::NotEmitted;717 };718719 if !errors.is_empty() {720 return CloneSuggestion::NotEmitted;721 }722 let sugg = vec![723 (span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")),724 (span.shrink_to_hi(), ")".to_string()),725 ];726 err.multipart_suggestion(727 "you can `clone` the value and consume it, but this might not be \728 your desired behavior",729 sugg,730 Applicability::MaybeIncorrect,731 );732 CloneSuggestion::Emitted733 }734735 fn add_move_hints(736 &self,737 error: GroupedMoveError<'tcx>,738 err: &mut Diag<'_>,739 span: Span,740 has_clone_suggestion: CloneSuggestion,741 ) {742 match error {743 GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => {744 binds_to.sort();745 binds_to.dedup();746747 if binds_to.is_empty() {748 self.add_borrow_suggestions(err, span, false);749 let place_ty = move_from.ty(self.body, self.infcx.tcx).ty;750 let place_desc = match self.describe_place(move_from.as_ref()) {751 Some(desc) => format!("`{desc}`"),752 None => "value".to_string(),753 };754755 if let Some(expr) = self.find_expr(span) {756 self.suggest_cloning(err, move_from.as_ref(), place_ty, expr, None);757 }758759 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {760 is_partial_move: false,761 ty: place_ty,762 place: &place_desc,763 span,764 });765 } else {766 let binding_info = self.pattern_binding_info(&binds_to);767 let suggest_pattern_binding = binding_info.as_ref().is_some_and(|info| {768 self.should_suggest_pattern_binding_instead(span, info)769 });770 let desugar_spans = if suggest_pattern_binding {771 self.add_move_error_suggestions(err, &binds_to)772 } else {773 if self.should_suggest_borrow_instead(span, binding_info.as_ref()) {774 self.add_borrow_suggestions(err, span, true);775 }776 None777 };778 self.add_move_error_details(779 err,780 &binds_to,781 desugar_spans.as_deref().unwrap_or_default(),782 );783 }784 }785 GroupedMoveError::MovesFromValue { mut binds_to, .. } => {786 binds_to.sort();787 binds_to.dedup();788 let desugar_spans =789 self.add_move_error_suggestions(err, &binds_to).unwrap_or_default();790 self.add_move_error_details(err, &binds_to, &desugar_spans);791 }792 // No binding. Nothing to suggest.793 GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => {794 let mut use_span = use_spans.var_or_use();795 let place_ty = original_path.ty(self.body, self.infcx.tcx).ty;796 let place_desc = match self.describe_place(original_path.as_ref()) {797 Some(desc) => format!("`{desc}`"),798 None => "value".to_string(),799 };800801 if has_clone_suggestion == CloneSuggestion::NotEmitted {802 // Check if the move is directly through a custom Deref impl803 // (e.g. `*my_box` where MyBox implements Deref).804 // A simple `.clone()` would clone the wrapper type rather than805 // the inner value, so we need a UFCS suggestion instead.806 //807 // However, if there are further projections after the deref808 // (e.g. `(*rc).field`), the value accessed is already the inner809 // type and a simple `.clone()` works correctly.810 let needs_ufcs = original_path.projection.last()811 == Some(&ProjectionElem::Deref)812 && original_path.iter_projections().any(|(place, elem)| {813 matches!(elem, ProjectionElem::Deref)814 && matches!(815 self.borrowed_content_source(place),816 BorrowedContentSource::OverloadedDeref(_)817 | BorrowedContentSource::OverloadedIndex(_)818 )819 });820821 let emitted_ufcs = if needs_ufcs {822 self.suggest_cloning_through_overloaded_deref(err, place_ty, use_span)823 } else {824 CloneSuggestion::NotEmitted825 };826827 if emitted_ufcs == CloneSuggestion::NotEmitted {828 if let Some(expr) = self.find_expr(use_span) {829 self.suggest_cloning(830 err,831 original_path.as_ref(),832 place_ty,833 expr,834 Some(use_spans),835 );836 }837 }838 }839840 if let Some(upvar_field) = self841 .prefixes(original_path.as_ref(), PrefixSet::All)842 .find_map(|p| self.is_upvar_field_projection(p))843 {844 // Look for the introduction of the original binding being moved.845 let upvar = &self.upvars[upvar_field.index()];846 let upvar_hir_id = upvar.get_root_variable();847 use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) {848 hir::Node::Param(param) => {849 // Instead of pointing at the path where we access the value within a850 // closure, we point at the type of the outer `fn` argument.851 param.ty_span852 }853 hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) {854 // We point at the type of the outer let-binding.855 (Some(ty), _) => ty.span,856 // We point at the initializer of the outer let-binding, but only if it857 // isn't something that spans multiple lines, like a closure, as the858 // ASCII art gets messy.859 (None, Some(init))860 if !self.infcx.tcx.sess.source_map().is_multiline(init.span) =>861 {862 init.span863 }864 _ => use_span,865 },866 _ => use_span,867 };868 }869870 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {871 is_partial_move: false,872 ty: place_ty,873 place: &place_desc,874 span: use_span,875 });876877 let mut pointed_at_span = false;878 use_spans.args_subdiag(err, |args_span| {879 if args_span == span || args_span == use_span {880 pointed_at_span = true;881 }882 crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {883 place: place_desc.clone(),884 args_span,885 }886 });887 if !pointed_at_span && use_span != span {888 err.subdiagnostic(crate::session_diagnostics::CaptureArgLabel::MoveOutPlace {889 place: place_desc,890 args_span: span,891 });892 }893894 self.add_note_for_packed_struct_derive(err, original_path.local);895 }896 }897 }898899 fn add_borrow_suggestions(900 &self,901 err: &mut Diag<'_>,902 span: Span,903 is_destructuring_pattern_move: bool,904 ) {905 match self.infcx.tcx.sess.source_map().span_to_snippet(span) {906 Ok(snippet) if snippet.starts_with('*') => {907 let sp = span.with_lo(span.lo() + BytePos(1));908 let inner = self.find_expr(sp);909 let mut is_raw_ptr = false;910 let mut is_ref = false;911 let mut is_destructuring_assignment = false;912 let mut is_nested_deref = false;913 if let Some(inner) = inner {914 is_nested_deref =915 matches!(inner.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _));916 let typck_result = self.infcx.tcx.typeck(self.mir_def_id());917 if let Some(inner_type) = typck_result.node_type_opt(inner.hir_id) {918 if matches!(inner_type.kind(), ty::RawPtr(..)) {919 is_raw_ptr = true;920 } else if matches!(inner_type.kind(), ty::Ref(..)) {921 is_ref = true;922 }923 }924 is_destructuring_assignment =925 self.infcx.tcx.hir_parent_iter(inner.hir_id).any(|(_, node)| {926 matches!(927 node,928 hir::Node::LetStmt(&hir::LetStmt {929 source: hir::LocalSource::AssignDesugar,930 ..931 })932 )933 });934 }935 // If the `inner` is a raw pointer, do not suggest removing the "*", see #126863936 // FIXME: need to check whether the assigned object can be a raw pointer, see `tests/ui/borrowck/issue-20801.rs`.937 if is_raw_ptr {938 return;939 }940941 if !is_destructuring_pattern_move || is_ref {942 err.span_suggestion_verbose(943 span.with_hi(span.lo() + BytePos(1)),944 "consider removing the dereference here",945 String::new(),946 Applicability::MaybeIncorrect,947 );948 } else if !is_destructuring_assignment && !is_nested_deref {949 err.span_suggestion_verbose(950 span.shrink_to_lo(),951 "consider borrowing here",952 '&',953 Applicability::MaybeIncorrect,954 );955 } else {956 err.span_help(957 span,958 "destructuring assignment cannot borrow from this expression; consider using a `let` binding instead",959 );960 }961 }962 _ => {963 err.span_suggestion_verbose(964 span.shrink_to_lo(),965 "consider borrowing here",966 '&',967 Applicability::MaybeIncorrect,968 );969 }970 }971 }972973 fn should_suggest_pattern_binding_instead(974 &self,975 span: Span,976 binding_info: &PatternBindingInfo,977 ) -> bool {978 let Some(expr) = self.find_expr(span) else {979 return false;980 };981982 let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());983 let projection_qualifies = match expr.kind {984 hir::ExprKind::Field(base, ..) => {985 !typeck_results.node_type_opt(base.hir_id).is_some_and(|base_ty| {986 binding_info.has_mutable_by_value_binding987 && matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not))988 })989 }990 hir::ExprKind::Index(base, ..) => typeck_results991 .node_type_opt(base.hir_id)992 .is_some_and(|base_ty| match base_ty.kind() {993 ty::Ref(_, _, hir::Mutability::Not) | ty::RawPtr(..) => false,994 ty::Ref(_, _, hir::Mutability::Mut) => {995 binding_info.has_mutable_by_value_binding996 }997 _ => true,998 }),999 _ => false,1000 };1001 if !projection_qualifies {1002 return false;1003 }10041005 let is_single_binding = binding_info.binding_spans.len() == 11006 && binding_info.binding_spans[0] == binding_info.pat_span;1007 !is_single_binding1008 }10091010 fn should_suggest_borrow_instead(1011 &self,1012 span: Span,1013 binding_info: Option<&PatternBindingInfo>,1014 ) -> bool {1015 if !binding_info.is_some_and(|info| info.has_mutable_by_value_binding) {1016 return true;1017 }10181019 let Some(expr) = self.find_expr(span) else {1020 return true;1021 };10221023 let Some(base) = (match expr.kind {1024 hir::ExprKind::Field(base, _) | hir::ExprKind::Index(base, ..) => Some(base),1025 _ => None,1026 }) else {1027 return true;1028 };10291030 !self1031 .infcx1032 .tcx1033 .typeck(self.mir_def_id())1034 .node_type_opt(base.hir_id)1035 .is_some_and(|base_ty| matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not)))1036 }10371038 fn pattern_binding_info(&self, binds_to: &[Local]) -> Option<PatternBindingInfo> {1039 let mut pat_span = None;1040 let mut binding_spans = Vec::new();1041 let mut has_mutable_by_value_binding = false;1042 for local in binds_to {1043 let bind_to = &self.body.local_decls[*local];1044 if let LocalInfo::User(BindingForm::Var(VarBindingForm {1045 pat_span: pat_sp,1046 binding_mode,1047 ..1048 })) = *bind_to.local_info()1049 {1050 pat_span = Some(pat_sp);1051 binding_spans.push(bind_to.source_info.span);1052 has_mutable_by_value_binding |=1053 matches!(binding_mode, hir::BindingMode(hir::ByRef::No, hir::Mutability::Mut));1054 }1055 }10561057 Some(PatternBindingInfo {1058 pat_span: pat_span?,1059 binding_spans,1060 has_mutable_by_value_binding,1061 })1062 }10631064 fn add_move_error_suggestions(1065 &self,1066 err: &mut Diag<'_>,1067 binds_to: &[Local],1068 ) -> Option<Vec<Span>> {1069 /// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to1070 /// make it bind by reference instead (if possible)1071 struct BindingFinder<'tcx> {1072 typeck_results: &'tcx ty::TypeckResults<'tcx>,1073 tcx: TyCtxt<'tcx>,1074 /// Input: the span of the pattern we're finding bindings in1075 pat_span: Span,1076 /// Input: the spans of the bindings we're providing suggestions for1077 binding_spans: Vec<Span>,1078 /// Internal state: have we reached the pattern we're finding bindings in?1079 found_pat: bool,1080 /// Internal state: the innermost `&` or `&mut` "above" the visitor1081 ref_pat: Option<&'tcx hir::Pat<'tcx>>,1082 /// Internal state: could removing a `&` give bindings unexpected types?1083 has_adjustments: bool,1084 /// Output: for each input binding, the `&` or `&mut` to remove to make it by-ref1085 ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>,1086 /// Output: ref patterns that can't be removed straightforwardly1087 cannot_remove: FxHashSet<HirId>,1088 /// Output: binding spans from destructuring assignment desugaring1089 desugar_binding_spans: Vec<Span>,1090 }1091 impl<'tcx> Visitor<'tcx> for BindingFinder<'tcx> {1092 type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;10931094 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {1095 self.tcx1096 }10971098 fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result {1099 // Don't walk into const patterns or anything else that might confuse this1100 if !self.found_pat {1101 hir::intravisit::walk_expr(self, ex)1102 }1103 }11041105 fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) {1106 if p.span == self.pat_span {1107 self.found_pat = true;1108 }11091110 let parent_has_adjustments = self.has_adjustments;1111 self.has_adjustments |=1112 self.typeck_results.pat_adjustments().contains_key(p.hir_id);11131114 // Track the innermost `&` or `&mut` enclosing bindings, to suggest removing it.1115 let parent_ref_pat = self.ref_pat;1116 if let hir::PatKind::Ref(..) = p.kind {1117 self.ref_pat = Some(p);1118 // To avoid edition-dependent logic to figure out how many refs this `&` can1119 // peel off, simply don't remove the "parent" `&`.1120 self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id));1121 if self.has_adjustments {1122 // Removing this `&` could give child bindings unexpected types, so don't.1123 self.cannot_remove.insert(p.hir_id);1124 // As long the `&` stays, child patterns' types should be as expected.1125 self.has_adjustments = false;1126 }1127 }11281129 if let hir::PatKind::Binding(_, _, ident, _) = p.kind {1130 // Skip synthetic bindings from destructuring assignment desugaring1131 // These have name `lhs` and their parent is a `LetStmt` with1132 // `LocalSource::AssignDesugar`1133 let dominated_by_desugar_assign = ident.name == sym::lhs1134 && self.tcx.hir_parent_iter(p.hir_id).any(|(_, node)| {1135 matches!(1136 node,1137 hir::Node::LetStmt(&hir::LetStmt {1138 source: hir::LocalSource::AssignDesugar,1139 ..1140 })1141 )1142 });11431144 if dominated_by_desugar_assign {1145 if let Some(&bind_sp) =1146 self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1147 {1148 self.desugar_binding_spans.push(bind_sp);1149 }1150 } else {1151 // the spans in `binding_spans` encompass both the ident and binding mode1152 if let Some(&bind_sp) =1153 self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span))1154 {1155 self.ref_pat_for_binding.push((bind_sp, self.ref_pat));1156 } else {1157 // we've encountered a binding that we're not reporting a move error for.1158 // we don't want to change its type, so don't remove the surrounding `&`.1159 if let Some(ref_pat) = self.ref_pat {1160 self.cannot_remove.insert(ref_pat.hir_id);1161 }1162 }1163 }1164 }11651166 hir::intravisit::walk_pat(self, p);1167 self.ref_pat = parent_ref_pat;1168 self.has_adjustments = parent_has_adjustments;1169 }1170 }1171 let Some(binding_info) = self.pattern_binding_info(binds_to) else {1172 return None;1173 };11741175 let tcx = self.infcx.tcx;1176 let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else {1177 return None;1178 };1179 let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());1180 let mut finder = BindingFinder {1181 typeck_results,1182 tcx,1183 pat_span: binding_info.pat_span,1184 binding_spans: binding_info.binding_spans,1185 found_pat: false,1186 ref_pat: None,1187 has_adjustments: false,1188 ref_pat_for_binding: Vec::new(),1189 cannot_remove: FxHashSet::default(),1190 desugar_binding_spans: Vec::new(),1191 };1192 finder.visit_body(body);11931194 let mut suggestions = Vec::new();1195 for (binding_span, opt_ref_pat) in finder.ref_pat_for_binding {1196 if let Some(ref_pat) = opt_ref_pat1197 && !finder.cannot_remove.contains(&ref_pat.hir_id)1198 && let hir::PatKind::Ref(subpat, pinned, mutbl) = ref_pat.kind1199 && let Some(ref_span) = ref_pat.span.trim_end(subpat.span)1200 {1201 let pinned_str = if pinned.is_pinned() { "pinned " } else { "" };1202 let mutable_str = if mutbl.is_mut() { "mutable " } else { "" };1203 let msg = format!("consider removing the {pinned_str}{mutable_str}borrow");1204 suggestions.push((ref_span, msg, "".to_string()));1205 } else {1206 let msg = "consider borrowing the pattern binding".to_string();1207 suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string()));1208 }1209 }1210 suggestions.sort_unstable_by_key(|&(span, _, _)| span);1211 suggestions.dedup_by_key(|&mut (span, _, _)| span);1212 for (span, msg, suggestion) in suggestions {1213 err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable);1214 }12151216 Some(finder.desugar_binding_spans)1217 }12181219 fn add_move_error_details(1220 &self,1221 err: &mut Diag<'_>,1222 binds_to: &[Local],1223 desugar_spans: &[Span],1224 ) {1225 for (j, local) in binds_to.iter().enumerate() {1226 let bind_to = &self.body.local_decls[*local];1227 let binding_span = bind_to.source_info.span;12281229 if binds_to.len() == 1 {1230 let place_desc = self.local_name(*local).map(|sym| format!("`{sym}`"));12311232 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::LabelMovedHere {1233 ty: bind_to.ty,1234 place: place_desc.as_deref().unwrap_or("the place"),1235 span: binding_span,1236 });12371238 if !desugar_spans.contains(&binding_span)1239 && let Some(expr) = self.find_expr(binding_span)1240 {1241 let local_place: PlaceRef<'tcx> = (*local).into();1242 self.suggest_cloning(err, local_place, bind_to.ty, expr, None);1243 }1244 } else if j == 0 {1245 err.span_label(binding_span, "data moved here");1246 } else {1247 err.span_label(binding_span, "...and here");1248 }1249 }12501251 if binds_to.len() > 1 {1252 err.note(1253 "move occurs because these variables have types that don't implement the `Copy` \1254 trait",1255 );1256 }1257 }12581259 /// Adds an explanatory note if the move error occurs in a derive macro1260 /// expansion of a packed struct.1261 /// Such errors happen because derive macro expansions shy away from taking1262 /// references to the struct's fields since doing so would be undefined behaviour1263 fn add_note_for_packed_struct_derive(&self, err: &mut Diag<'_>, local: Local) {1264 let local_place: PlaceRef<'tcx> = local.into();1265 let local_ty = local_place.ty(self.body.local_decls(), self.infcx.tcx).ty.peel_refs();12661267 if let Some(adt) = local_ty.ty_adt_def()1268 && adt.repr().packed()1269 && let ExpnKind::Macro(MacroKind::Derive, name) =1270 self.body.span.ctxt().outer_expn_data().kind1271 {1272 err.note(format!("`#[derive({name})]` triggers a move because taking references to the fields of a packed struct is undefined behaviour"));1273 }1274 }1275}
Findings
✓ No findings reported for this file.