| use rustc_abi::FieldIdx; | |
| use rustc_data_structures::fx::FxHashSet; | |
| use rustc_errors::{Applicability, Diag}; | |
| use rustc_hir::intravisit::Visitor; | |
| use rustc_hir::{self as hir, CaptureBy, ExprKind, HirId, Node}; | |
| use rustc_middle::mir::*; | |
| use rustc_middle::ty::{self, Ty, TyCtxt}; | |
| use rustc_mir_dataflow::move_paths::{LookupResult, MovePathIndex}; | |
| use rustc_span::def_id::DefId; | |
| use rustc_span::{BytePos, ExpnKind, MacroKind, Span, bug, sym}; | |
| use rustc_trait_selection::error_reporting::traits::FindExprBySpan; | |
| use rustc_trait_selection::infer::InferCtxtExt; | |
| use tracing::debug; | |
| use crate::MirBorrowckCtxt; | |
| use crate::diagnostics::{ | |
| BorrowedContentSource, CapturedMessageOpt, CloneSuggestion, DescribePlaceOpt, UseSpans, | |
| }; | |
| use crate::prefixes::PrefixSet; | |
| #[derive(Debug)] | |
| pub(crate) enum IllegalMoveOriginKind<'tcx> { | |
| /// Illegal move due to attempt to move from behind a reference. | |
| BorrowedContent { | |
| /// The place the reference refers to: if erroneous code was trying to | |
| /// move from `(*x).f` this will be `*x`. | |
| target_place: Place<'tcx>, | |
| }, | |
| /// Illegal move due to attempt to move from field of an ADT that | |
| /// implements `Drop`. Rust maintains invariant that all `Drop` | |
| /// ADT's remain fully-initialized so that user-defined destructor | |
| /// can safely read from all of the ADT's fields. | |
| InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> }, | |
| /// Illegal move due to attempt to move out of a slice or array. | |
| InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool }, | |
| } | |
| #[derive(Debug)] | |
| pub(crate) struct MoveError<'tcx> { | |
| place: Place<'tcx>, | |
| location: Location, | |
| kind: IllegalMoveOriginKind<'tcx>, | |
| } | |
| impl<'tcx> MoveError<'tcx> { | |
| pub(crate) fn new( | |
| place: Place<'tcx>, | |
| location: Location, | |
| kind: IllegalMoveOriginKind<'tcx>, | |
| ) -> Self { | |
| MoveError { place, location, kind } | |
| } | |
| } | |
| // Often when desugaring a pattern match we may have many individual moves in | |
| // MIR that are all part of one operation from the user's point-of-view. For | |
| // example: | |
| // | |
| // let (x, y) = foo() | |
| // | |
| // would move x from the 0 field of some temporary, and y from the 1 field. We | |
| // group such errors together for cleaner error reporting. | |
| // | |
| // Errors are kept separate if they are from places with different parent move | |
| // paths. For example, this generates two errors: | |
| // | |
| // let (&x, &y) = (&String::new(), &String::new()); | |
| #[derive(Debug)] | |
| enum GroupedMoveError<'tcx> { | |
| // Place expression can't be moved from, | |
| // e.g., match x[0] { s => (), } where x: &[String] | |
| MovesFromPlace { | |
| original_path: Place<'tcx>, | |
| span: Span, | |
| move_from: Place<'tcx>, | |
| kind: IllegalMoveOriginKind<'tcx>, | |
| binds_to: Vec<Local>, | |
| }, | |
| // Part of a value expression can't be moved from, | |
| // e.g., match &String::new() { &x => (), } | |
| MovesFromValue { | |
| original_path: Place<'tcx>, | |
| span: Span, | |
| move_from: MovePathIndex, | |
| kind: IllegalMoveOriginKind<'tcx>, | |
| binds_to: Vec<Local>, | |
| }, | |
| // Everything that isn't from pattern matching. | |
| OtherIllegalMove { | |
| original_path: Place<'tcx>, | |
| use_spans: UseSpans<'tcx>, | |
| kind: IllegalMoveOriginKind<'tcx>, | |
| }, | |
| } | |
| struct PatternBindingInfo { | |
| pat_span: Span, | |
| binding_spans: Vec<Span>, | |
| has_mutable_by_value_binding: bool, | |
| } | |
| impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { | |
| pub(crate) fn report_move_errors(&mut self) { | |
| let grouped_errors = self.group_move_errors(); | |
| for error in grouped_errors { | |
| self.report(error); | |
| } | |
| } | |
| fn group_move_errors(&mut self) -> Vec<GroupedMoveError<'tcx>> { | |
| let mut grouped_errors = Vec::new(); | |
| let errors = std::mem::take(&mut self.move_errors); | |
| for error in errors { | |
| self.append_to_grouped_errors(&mut grouped_errors, error); | |
| } | |
| grouped_errors | |
| } | |
| fn append_to_grouped_errors( | |
| &self, | |
| grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, | |
| MoveError { place: original_path, location, kind }: MoveError<'tcx>, | |
| ) { | |
| // Note: that the only time we assign a place isn't a temporary | |
| // to a user variable is when initializing it. | |
| // If that ever stops being the case, then the ever initialized | |
| // flow could be used. | |
| if let Some(StatementKind::Assign((place, Rvalue::Use(Operand::Move(move_from), _)))) = | |
| self.body.basic_blocks[location.block] | |
| .statements | |
| .get(location.statement_index) | |
| .map(|stmt| &stmt.kind) | |
| && let Some(local) = place.as_local() | |
| { | |
| let local_decl = &self.body.local_decls[local]; | |
| // opt_match_place is the | |
| // match_span is the span of the expression being matched on | |
| // match *x.y { ... } match_place is Some(*x.y) | |
| // ^^^^ match_span is the span of *x.y | |
| // | |
| // opt_match_place is None for let [mut] x = ... statements, | |
| // whether or not the right-hand side is a place expression | |
| if let LocalInfo::User(BindingForm::Var(VarBindingForm { | |
| opt_match_place: Some((opt_match_place, match_span)), | |
| .. | |
| })) = *local_decl.local_info() | |
| { | |
| let stmt_source_info = self.body.source_info(location); | |
| self.append_binding_error( | |
| grouped_errors, | |
| kind, | |
| original_path, | |
| *move_from, | |
| local, | |
| opt_match_place, | |
| match_span, | |
| stmt_source_info.span, | |
| ); | |
| return; | |
| } | |
| } | |
| let move_spans = self.move_spans(original_path.as_ref(), location); | |
| grouped_errors.push(GroupedMoveError::OtherIllegalMove { | |
| use_spans: move_spans, | |
| original_path, | |
| kind, | |
| }); | |
| } | |
| fn append_binding_error( | |
| &self, | |
| grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, | |
| kind: IllegalMoveOriginKind<'tcx>, | |
| original_path: Place<'tcx>, | |
| move_from: Place<'tcx>, | |
| bind_to: Local, | |
| match_place: Option<Place<'tcx>>, | |
| match_span: Span, | |
| statement_span: Span, | |
| ) { | |
| debug!(?match_place, ?match_span, "append_binding_error"); | |
| let from_simple_let = match_place.is_none(); | |
| let match_place = match_place.unwrap_or(move_from); | |
| match self.move_data.rev_lookup.find(match_place.as_ref()) { | |
| // Error with the match place | |
| LookupResult::Parent(_) => { | |
| for ge in &mut *grouped_errors { | |
| if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge | |
| && match_span == *span | |
| { | |
| debug!("appending local({bind_to:?}) to list"); | |
| if !binds_to.is_empty() { | |
| binds_to.push(bind_to); | |
| } | |
| return; | |
| } | |
| } | |
| debug!("found a new move error location"); | |
| // Don't need to point to x in let x = ... . | |
| let (binds_to, span) = if from_simple_let { | |
| (vec![], statement_span) | |
| } else { | |
| (vec![bind_to], match_span) | |
| }; | |
| grouped_errors.push(GroupedMoveError::MovesFromPlace { | |
| span, | |
| move_from, | |
| original_path, | |
| kind, | |
| binds_to, | |
| }); | |
| } | |
| // Error with the pattern | |
| LookupResult::Exact(_) => { | |
| let LookupResult::Parent(Some(mpi)) = | |
| self.move_data.rev_lookup.find(move_from.as_ref()) | |
| else { | |
| // move_from should be a projection from match_place. | |
| unreachable!("Probably not unreachable..."); | |
| }; | |
| for ge in &mut *grouped_errors { | |
| if let GroupedMoveError::MovesFromValue { | |
| span, | |
| move_from: other_mpi, | |
| binds_to, | |
| .. | |
| } = ge | |
| { | |
| if match_span == *span && mpi == *other_mpi { | |
| debug!("appending local({bind_to:?}) to list"); | |
| binds_to.push(bind_to); | |
| return; | |
| } | |
| } | |
| } | |
| debug!("found a new move error location"); | |
| grouped_errors.push(GroupedMoveError::MovesFromValue { | |
| span: match_span, | |
| move_from: mpi, | |
| original_path, | |
| kind, | |
| binds_to: vec![bind_to], | |
| }); | |
| } | |
| }; | |
| } | |
| fn report(&mut self, error: GroupedMoveError<'tcx>) { | |
| let (span, use_spans, original_path, kind) = match error { | |
| GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. } | |
| | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => { | |
| (span, None, original_path, kind) | |
| } | |
| GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => { | |
| (use_spans.args_or_use(), Some(use_spans), original_path, kind) | |
| } | |
| }; | |
| debug!( | |
| "report: original_path={:?} span={:?}, kind={:?} \ | |
| original_path.is_upvar_field_projection={:?}", | |
| original_path, | |
| span, | |
| kind, | |
| self.is_upvar_field_projection(original_path.as_ref()) | |
| ); | |
| if self.has_ambiguous_copy(original_path.ty(self.body, self.infcx.tcx).ty) { | |
| // If the type may implement Copy, skip the error. | |
| // It's an error with the Copy implementation (e.g. duplicate Copy) rather than borrow check | |
| self.dcx() | |
| .span_delayed_bug(span, "Type may implement copy, but there is no other error."); | |
| return; | |
| } | |
| let mut has_clone_suggestion = CloneSuggestion::NotEmitted; | |
| let mut err = match kind { | |
| &IllegalMoveOriginKind::BorrowedContent { target_place } => { | |
| let (diag, clone_sugg) = self.report_cannot_move_from_borrowed_content( | |
| original_path, | |
| target_place, | |
| span, | |
| use_spans, | |
| ); | |
| has_clone_suggestion = clone_sugg; | |
| diag | |
| } | |
| &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => { | |
| self.cannot_move_out_of_interior_of_drop(span, ty) | |
| } | |
| &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => { | |
| self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index)) | |
| } | |
| }; | |
| self.add_move_hints(error, &mut err, span, has_clone_suggestion); | |
| self.buffer_error(err); | |
| } | |
| fn has_ambiguous_copy(&mut self, ty: Ty<'tcx>) -> bool { | |
| let Some(copy_def_id) = self.infcx.tcx.lang_items().copy_trait() else { return false }; | |
| // Avoid bogus move errors because of an incoherent `Copy` impl. | |
| self.infcx.type_implements_trait(copy_def_id, [ty], self.infcx.param_env).may_apply() | |
| && self.infcx.tcx.ensure_result().coherent_trait(copy_def_id).is_err() | |
| } | |
| fn report_cannot_move_from_static(&mut self, place: Place<'tcx>, span: Span) -> Diag<'diag> { | |
| let description = if place.projection.len() == 1 { | |
| format!("static item {}", self.describe_any_place(place.as_ref())) | |
| } else { | |
| let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] }; | |
| format!( | |
| "{} as {} is a static item", | |
| self.describe_any_place(place.as_ref()), | |
| self.describe_any_place(base_static), | |
| ) | |
| }; | |
| self.cannot_move_out_of(span, &description) | |
| } | |
| pub(in crate::diagnostics) fn suggest_clone_of_captured_var_in_move_closure( | |
| &self, | |
| err: &mut Diag<'_>, | |
| upvar_name: &str, | |
| use_spans: Option<UseSpans<'tcx>>, | |
| ) { | |
| let tcx = self.infcx.tcx; | |
| let Some(use_spans) = use_spans else { return }; | |
| // We only care about the case where a closure captured a binding. | |
| let UseSpans::ClosureUse { args_span, .. } = use_spans else { return }; | |
| let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return }; | |
| // Find the closure that captured the binding. | |
| let mut expr_finder = FindExprBySpan::new(args_span, tcx); | |
| expr_finder.include_closures = true; | |
| expr_finder.visit_expr(tcx.hir_body(body_id).value); | |
| let Some(closure_expr) = expr_finder.result else { return }; | |
| let ExprKind::Closure(closure) = closure_expr.kind else { return }; | |
| // We'll only suggest cloning the binding if it's a `move` closure. | |
| let CaptureBy::Value { .. } = closure.capture_clause else { return }; | |
| // Find the expression within the closure where the binding is consumed. | |
| let mut suggested = false; | |
| let use_span = use_spans.var_or_use(); | |
| let mut expr_finder = FindExprBySpan::new(use_span, tcx); | |
| expr_finder.include_closures = true; | |
| expr_finder.visit_expr(tcx.hir_body(body_id).value); | |
| let Some(use_expr) = expr_finder.result else { return }; | |
| let parent = tcx.parent_hir_node(use_expr.hir_id); | |
| if let Node::Expr(expr) = parent | |
| && let ExprKind::Assign(lhs, ..) = expr.kind | |
| && lhs.hir_id == use_expr.hir_id | |
| { | |
| // Cloning the value being assigned makes no sense: | |
| // | |
| // error[E0507]: cannot move out of `var`, a captured variable in an `FnMut` closure | |
| // --> $DIR/option-content-move2.rs:11:9 | |
| // | | |
| // LL | let mut var = None; | |
| // | ------- captured outer variable | |
| // LL | func(|| { | |
| // | -- captured by this `FnMut` closure | |
| // LL | // Shouldn't suggest `move ||.as_ref()` here | |
| // LL | move || { | |
| // | ^^^^^^^ `var` is moved here | |
| // LL | | |
| // LL | var = Some(NotCopyable); | |
| // | --- | |
| // | | | |
| // | variable moved due to use in closure | |
| // | move occurs because `var` has type `Option<NotCopyable>`, which does not implement the `Copy` trait | |
| // | | |
| return; | |
| } | |
| // Search for an appropriate place for the structured `.clone()` suggestion to be applied. | |
| // If we encounter a statement before the borrow error, we insert a statement there. | |
| for (_, node) in tcx.hir_parent_iter(closure_expr.hir_id) { | |
| if let Node::Stmt(stmt) = node { | |
| let padding = tcx | |
| .sess | |
| .source_map() | |
| .indentation_before(stmt.span) | |
| .unwrap_or_else(|| " ".to_string()); | |
| err.multipart_suggestion( | |
| "consider cloning the value before moving it into the closure", | |
| vec![ | |
| ( | |
| stmt.span.shrink_to_lo(), | |
| format!("let value = {upvar_name}.clone();\n{padding}"), | |
| ), | |
| (use_span, "value".to_string()), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| suggested = true; | |
| break; | |
| } else if let Node::Expr(expr) = node | |
| && let ExprKind::Closure(_) = expr.kind | |
| { | |
| // We want to suggest cloning only on the first closure, not | |
| // subsequent ones (like `ui/suggestions/option-content-move2.rs`). | |
| break; | |
| } | |
| } | |
| if !suggested { | |
| // If we couldn't find a statement for us to insert a new `.clone()` statement before, | |
| // we have a bare expression, so we suggest the creation of a new block inline to go | |
| // from `move || val` to `{ let value = val.clone(); move || value }`. | |
| let padding = tcx | |
| .sess | |
| .source_map() | |
| .indentation_before(closure_expr.span) | |
| .unwrap_or_else(|| " ".to_string()); | |
| err.multipart_suggestion( | |
| "consider cloning the value before moving it into the closure", | |
| vec![ | |
| ( | |
| closure_expr.span.shrink_to_lo(), | |
| format!("{{\n{padding}let value = {upvar_name}.clone();\n{padding}"), | |
| ), | |
| (use_spans.var_or_use(), "value".to_string()), | |
| (closure_expr.span.shrink_to_hi(), format!("\n{padding}}}")), | |
| ], | |
| Applicability::MachineApplicable, | |
| ); | |
| } | |
| } | |
| fn report_cannot_move_from_borrowed_content( | |
| &mut self, | |
| move_place: Place<'tcx>, | |
| deref_target_place: Place<'tcx>, | |
| span: Span, | |
| use_spans: Option<UseSpans<'tcx>>, | |
| ) -> (Diag<'diag>, CloneSuggestion) { | |
| let tcx = self.infcx.tcx; | |
| // Inspect the type of the content behind the | |
| // borrow to provide feedback about why this | |
| // was a move rather than a copy. | |
| let ty = deref_target_place.ty(self.body, tcx).ty; | |
| let upvar_field = self | |
| .prefixes(move_place.as_ref(), PrefixSet::All) | |
| .find_map(|p| self.is_upvar_field_projection(p)); | |
| let deref_base = match deref_target_place.projection.as_ref() { | |
| [proj_base @ .., ProjectionElem::Deref] => { | |
| PlaceRef { local: deref_target_place.local, projection: proj_base } | |
| } | |
| _ => bug!("deref_target_place is not a deref projection"), | |
| }; | |
| if let PlaceRef { local, projection: [] } = deref_base { | |
| let decl = &self.body.local_decls[local]; | |
| let local_name = self.local_name(local).map(|sym| format!("`{sym}`")); | |
| if decl.is_ref_for_guard() { | |
| return ( | |
| self.cannot_move_out_of( | |
| span, | |
| &format!( | |
| "{} in pattern guard", | |
| local_name.as_deref().unwrap_or("the place") | |
| ), | |
| ) | |
| .with_note( | |
| "variables bound in patterns cannot be moved from \ | |
| until after the end of the pattern guard", | |
| ), | |
| CloneSuggestion::NotEmitted, | |
| ); | |
| } else if decl.is_ref_to_static() { | |
| return ( | |
| self.report_cannot_move_from_static(move_place, span), | |
| CloneSuggestion::NotEmitted, | |
| ); | |
| } | |
| } | |
| debug!("report: ty={:?}", ty); | |
| let mut err = match ty.kind() { | |
| ty::Array(..) | ty::Slice(..) => { | |
| self.cannot_move_out_of_interior_noncopy(span, ty, None) | |
| } | |
| ty::Closure(def_id, closure_args) | |
| if def_id.as_local() == Some(self.mir_def_id()) | |
| && let Some(upvar_field) = upvar_field => | |
| { | |
| self.report_closure_move_error( | |
| span, | |
| move_place, | |
| *def_id, | |
| closure_args.as_closure().kind_ty(), | |
| upvar_field, | |
| ty::Asyncness::No, | |
| ) | |
| } | |
| ty::CoroutineClosure(def_id, closure_args) | |
| if def_id.as_local() == Some(self.mir_def_id()) | |
| && let Some(upvar_field) = upvar_field | |
| && self | |
| .get_closure_bound_clause_span(*def_id, ty::Asyncness::Yes) | |
| .is_some() => | |
| { | |
| self.report_closure_move_error( | |
| span, | |
| move_place, | |
| *def_id, | |
| closure_args.as_coroutine_closure().kind_ty(), | |
| upvar_field, | |
| ty::Asyncness::Yes, | |
| ) | |
| } | |
| _ => { | |
| let source = self.borrowed_content_source(deref_base); | |
| let move_place_ref = move_place.as_ref(); | |
| match ( | |
| self.describe_place_with_options( | |
| move_place_ref, | |
| DescribePlaceOpt { | |
| including_downcast: false, | |
| including_tuple_field: false, | |
| }, | |
| ), | |
| self.describe_name(move_place_ref), | |
| source.describe_for_named_place(), | |
| ) { | |
| (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of( | |
| span, | |
| &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"), | |
| ), | |
| (Some(place_desc), Some(name), None) => self.cannot_move_out_of( | |
| span, | |
| &format!("`{place_desc}` as enum variant `{name}`"), | |
| ), | |
| (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of( | |
| span, | |
| &format!("`{place_desc}` which is behind a {source_desc}"), | |
| ), | |
| (_, _, _) => self.cannot_move_out_of( | |
| span, | |
| &source.describe_for_unnamed_place(tcx), | |
| ), | |
| } | |
| } | |
| }; | |
| let msg_opt = CapturedMessageOpt { | |
| is_partial_move: false, | |
| is_loop_message: false, | |
| is_move_msg: false, | |
| is_loop_move: false, | |
| has_suggest_reborrow: false, | |
| maybe_reinitialized_locations_is_empty: true, | |
| }; | |
| let suggested_cloning = if let Some(use_spans) = use_spans { | |
| self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt) | |
| } else { | |
| CloneSuggestion::NotEmitted | |
| }; | |
| (err, suggested_cloning) | |
| } | |
| fn report_closure_move_error( | |
| &self, | |
| span: Span, | |
| move_place: Place<'tcx>, | |
| def_id: DefId, | |
| closure_kind_ty: Ty<'tcx>, | |
| upvar_field: FieldIdx, | |
| asyncness: ty::Asyncness, | |
| ) -> Diag<'diag> { | |
| let tcx = self.infcx.tcx; | |
| let closure_kind = match closure_kind_ty.to_opt_closure_kind() { | |
| Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind, | |
| Some(ty::ClosureKind::FnOnce) => { | |
| bug!("closure kind does not match first argument type") | |
| } | |
| None => bug!("closure kind not inferred by borrowck"), | |
| }; | |
| let async_prefix = if asyncness.is_async() { "Async" } else { "" }; | |
| let capture_description = | |
| format!("captured variable in an `{async_prefix}{closure_kind}` closure"); | |
| let upvar = &self.upvars[upvar_field.index()]; | |
| let upvar_hir_id = upvar.get_root_variable(); | |
| let upvar_name = upvar.to_string(tcx); | |
| let upvar_span = tcx.hir_span(upvar_hir_id); | |
| let place_name = self.describe_any_place(move_place.as_ref()); | |
| let place_description = if self.is_upvar_field_projection(move_place.as_ref()).is_some() { | |
| format!("{place_name}, a {capture_description}") | |
| } else { | |
| format!("{place_name}, as `{upvar_name}` is a {capture_description}") | |
| }; | |
| debug!(?closure_kind_ty, ?closure_kind, ?place_description); | |
| let closure_span = tcx.def_span(def_id); | |
| let help_msg = format!( | |
| "`{async_prefix}Fn` and `{async_prefix}FnMut` closures require captured values to \ | |
| be able to be consumed multiple times, but `{async_prefix}FnOnce` closures may \ | |
| consume them only once" | |
| ); | |
| let mut err = self | |
| .cannot_move_out_of(span, &place_description) | |
| .with_span_label(upvar_span, "captured outer variable") | |
| .with_span_label( | |
| closure_span, | |
| format!("captured by this `{async_prefix}{closure_kind}` closure"), | |
| ); | |
| if let Some(bound_span) = self.get_closure_bound_clause_span(def_id, asyncness) { | |
| err.span_help(bound_span, help_msg); | |
| } else if !asyncness.is_async() { | |
| // For sync closures, always emit the help message even without a span. | |
| // For async closures, we only enter this branch if we found a valid span | |
| // (due to the match guard), so no fallback is needed. | |
| err.help(help_msg); | |
| } | |
| err | |
| } | |
| fn get_closure_bound_clause_span( | |
| &self, | |
| def_id: DefId, | |
| asyncness: ty::Asyncness, | |
| ) -> Option<Span> { | |
| let tcx = self.infcx.tcx; | |
| let typeck_result = tcx.typeck(self.mir_def_id()); | |
| // Check whether the closure is an argument to a call, if so, | |
| // get the instantiated where-bounds of that call. | |
| let closure_hir_id = tcx.local_def_id_to_hir_id(def_id.expect_local()); | |
| let hir::Node::Expr(parent) = tcx.parent_hir_node(closure_hir_id) else { return None }; | |
| let gen_clauses = match parent.kind { | |
| hir::ExprKind::Call(callee, _) => { | |
| let ty = typeck_result.node_type_opt(callee.hir_id)?; | |
| let ty::FnDef(fn_def_id, args) = *ty.kind() else { return None }; | |
| tcx.clauses_of(fn_def_id).instantiate(tcx, args.no_bound_vars().unwrap()) | |
| } | |
| hir::ExprKind::MethodCall(..) => { | |
| let (_, method) = typeck_result.type_dependent_def(parent.hir_id)?; | |
| let args = typeck_result.node_args(parent.hir_id); | |
| tcx.clauses_of(method).instantiate(tcx, args) | |
| } | |
| _ => return None, | |
| }; | |
| // Check whether one of the where-bounds requires the closure to impl `Fn[Mut]` | |
| // or `AsyncFn[Mut]`. | |
| for (clause, span) in gen_clauses.clauses.iter().zip(gen_clauses.spans.iter()) { | |
| let clause = clause.skip_norm_wip(); | |
| let dominated_by_fn_trait = self | |
| .closure_clause_kind(clause, def_id, asyncness) | |
| .is_some_and(|kind| matches!(kind, ty::ClosureKind::Fn | ty::ClosureKind::FnMut)); | |
| if dominated_by_fn_trait { | |
| // Found `<TyOfCapturingClosure as FnMut>` or | |
| // `<TyOfCapturingClosure as AsyncFnMut>`. | |
| // We point at the bound that coerced the closure, which could be changed | |
| // to `FnOnce()` or `AsyncFnOnce()` to avoid the move error. | |
| return Some(*span); | |
| } | |
| } | |
| None | |
| } | |
| /// If `pred` is a trait clause binding the closure `def_id` to `Fn`/`FnMut`/`FnOnce` | |
| /// (or their async equivalents based on `asyncness`), returns the corresponding | |
| /// `ClosureKind`. Otherwise returns `None`. | |
| fn closure_clause_kind( | |
| &self, | |
| pred: ty::Clause<'tcx>, | |
| def_id: DefId, | |
| asyncness: ty::Asyncness, | |
| ) -> Option<ty::ClosureKind> { | |
| let tcx = self.infcx.tcx; | |
| let clause = pred.as_trait_clause()?; | |
| let kind = match asyncness { | |
| ty::Asyncness::Yes => tcx.async_fn_trait_kind_from_def_id(clause.def_id()), | |
| ty::Asyncness::No => tcx.fn_trait_kind_from_def_id(clause.def_id()), | |
| }?; | |
| match clause.self_ty().skip_binder().kind() { | |
| ty::Closure(id, _) | ty::CoroutineClosure(id, _) if *id == def_id => Some(kind), | |
| _ => None, | |
| } | |
| } | |
| /// Suggest cloning via UFCS when a move occurs through a custom `Deref` impl. | |
| /// | |
| /// A simple `.clone()` on a type like `MyBox<Vec<i32>>` would clone the wrapper, | |
| /// not the inner `Vec<i32>`. Instead, we suggest `<Vec<i32> as Clone>::clone(&val)`. | |
| fn suggest_cloning_through_overloaded_deref( | |
| &self, | |
| err: &mut Diag<'_>, | |
| ty: Ty<'tcx>, | |
| span: Span, | |
| ) -> CloneSuggestion { | |
| let tcx = self.infcx.tcx; | |
| let Some(clone_trait) = tcx.lang_items().clone_trait() else { | |
| return CloneSuggestion::NotEmitted; | |
| }; | |
| let Some(errors) = | |
| self.infcx.type_implements_trait_shallow(clone_trait, ty, self.infcx.param_env) | |
| else { | |
| return CloneSuggestion::NotEmitted; | |
| }; | |
| if errors.has_errors() { | |
| return CloneSuggestion::NotEmitted; | |
| } | |
| let sugg = vec![ | |
| (span.shrink_to_lo(), format!("<{ty} as Clone>::clone(&")), | |
| (span.shrink_to_hi(), ")".to_string()), | |
| ]; | |
| err.multipart_suggestion( | |
| "you can `clone` the value and consume it, but this might not be \ | |
| your desired behavior", | |
| sugg, | |
| Applicability::MaybeIncorrect, | |
| ); | |
| CloneSuggestion::Emitted | |
| } | |
| fn add_move_hints( | |
| &self, | |
| error: GroupedMoveError<'tcx>, | |
| err: &mut Diag<'_>, | |
| span: Span, | |
| has_clone_suggestion: CloneSuggestion, | |
| ) { | |
| match error { | |
| GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { | |
| binds_to.sort(); | |
| binds_to.dedup(); | |
| if binds_to.is_empty() { | |
| self.add_borrow_suggestions(err, span, false); | |
| let place_ty = move_from.ty(self.body, self.infcx.tcx).ty; | |
| let place_desc = match self.describe_place(move_from.as_ref()) { | |
| Some(desc) => format!("`{desc}`"), | |
| None => "value".to_string(), | |
| }; | |
| if let Some(expr) = self.find_expr(span) { | |
| self.suggest_cloning(err, move_from.as_ref(), place_ty, expr, None); | |
| } | |
| err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label { | |
| is_partial_move: false, | |
| ty: place_ty, | |
| place: &place_desc, | |
| span, | |
| }); | |
| } else { | |
| let binding_info = self.pattern_binding_info(&binds_to); | |
| let suggest_pattern_binding = binding_info.as_ref().is_some_and(|info| { | |
| self.should_suggest_pattern_binding_instead(span, info) | |
| }); | |
| let desugar_spans = if suggest_pattern_binding { | |
| self.add_move_error_suggestions(err, &binds_to) | |
| } else { | |
| if self.should_suggest_borrow_instead(span, binding_info.as_ref()) { | |
| self.add_borrow_suggestions(err, span, true); | |
| } | |
| None | |
| }; | |
| self.add_move_error_details( | |
| err, | |
| &binds_to, | |
| desugar_spans.as_deref().unwrap_or_default(), | |
| ); | |
| } | |
| } | |
| GroupedMoveError::MovesFromValue { mut binds_to, .. } => { | |
| binds_to.sort(); | |
| binds_to.dedup(); | |
| let desugar_spans = | |
| self.add_move_error_suggestions(err, &binds_to).unwrap_or_default(); | |
| self.add_move_error_details(err, &binds_to, &desugar_spans); | |
| } | |
| // No binding. Nothing to suggest. | |
| GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => { | |
| let mut use_span = use_spans.var_or_use(); | |
| let place_ty = original_path.ty(self.body, self.infcx.tcx).ty; | |
| let place_desc = match self.describe_place(original_path.as_ref()) { | |
| Some(desc) => format!("`{desc}`"), | |
| None => "value".to_string(), | |
| }; | |
| if has_clone_suggestion == CloneSuggestion::NotEmitted { | |
| // Check if the move is directly through a custom Deref impl | |
| // (e.g. `*my_box` where MyBox implements Deref). | |
| // A simple `.clone()` would clone the wrapper type rather than | |
| // the inner value, so we need a UFCS suggestion instead. | |
| // | |
| // However, if there are further projections after the deref | |
| // (e.g. `(*rc).field`), the value accessed is already the inner | |
| // type and a simple `.clone()` works correctly. | |
| let needs_ufcs = original_path.projection.last() | |
| == Some(&ProjectionElem::Deref) | |
| && original_path.iter_projections().any(|(place, elem)| { | |
| matches!(elem, ProjectionElem::Deref) | |
| && matches!( | |
| self.borrowed_content_source(place), | |
| BorrowedContentSource::OverloadedDeref(_) | |
| | BorrowedContentSource::OverloadedIndex(_) | |
| ) | |
| }); | |
| let emitted_ufcs = if needs_ufcs { | |
| self.suggest_cloning_through_overloaded_deref(err, place_ty, use_span) | |
| } else { | |
| CloneSuggestion::NotEmitted | |
| }; | |
| if emitted_ufcs == CloneSuggestion::NotEmitted { | |
| if let Some(expr) = self.find_expr(use_span) { | |
| self.suggest_cloning( | |
| err, | |
| original_path.as_ref(), | |
| place_ty, | |
| expr, | |
| Some(use_spans), | |
| ); | |
| } | |
| } | |
| } | |
| if let Some(upvar_field) = self | |
| .prefixes(original_path.as_ref(), PrefixSet::All) | |
| .find_map(|p| self.is_upvar_field_projection(p)) | |
| { | |
| // Look for the introduction of the original binding being moved. | |
| let upvar = &self.upvars[upvar_field.index()]; | |
| let upvar_hir_id = upvar.get_root_variable(); | |
| use_span = match self.infcx.tcx.parent_hir_node(upvar_hir_id) { | |
| hir::Node::Param(param) => { | |
| // Instead of pointing at the path where we access the value within a | |
| // closure, we point at the type of the outer `fn` argument. | |
| param.ty_span | |
| } | |
| hir::Node::LetStmt(stmt) => match (stmt.ty, stmt.init) { | |
| // We point at the type of the outer let-binding. | |
| (Some(ty), _) => ty.span, | |
| // We point at the initializer of the outer let-binding, but only if it | |
| // isn't something that spans multiple lines, like a closure, as the | |
| // ASCII art gets messy. | |
| (None, Some(init)) | |
| if !self.infcx.tcx.sess.source_map().is_multiline(init.span) => | |
| { | |
| init.span | |
| } | |
| _ => use_span, | |
| }, | |
| _ => use_span, | |
| }; | |
| } | |
| err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label { | |
| is_partial_move: false, | |
| ty: place_ty, | |
| place: &place_desc, | |
| span: use_span, | |
| }); | |
| let mut pointed_at_span = false; | |
| use_spans.args_subdiag(err, |args_span| { | |
| if args_span == span || args_span == use_span { | |
| pointed_at_span = true; | |
| } | |
| crate::session_diagnostics::CaptureArgLabel::MoveOutPlace { | |
| place: place_desc.clone(), | |
| args_span, | |
| } | |
| }); | |
| if !pointed_at_span && use_span != span { | |
| err.subdiagnostic(crate::session_diagnostics::CaptureArgLabel::MoveOutPlace { | |
| place: place_desc, | |
| args_span: span, | |
| }); | |
| } | |
| self.add_note_for_packed_struct_derive(err, original_path.local); | |
| } | |
| } | |
| } | |
| fn add_borrow_suggestions( | |
| &self, | |
| err: &mut Diag<'_>, | |
| span: Span, | |
| is_destructuring_pattern_move: bool, | |
| ) { | |
| match self.infcx.tcx.sess.source_map().span_to_snippet(span) { | |
| Ok(snippet) if snippet.starts_with('*') => { | |
| let sp = span.with_lo(span.lo() + BytePos(1)); | |
| let inner = self.find_expr(sp); | |
| let mut is_raw_ptr = false; | |
| let mut is_ref = false; | |
| let mut is_destructuring_assignment = false; | |
| let mut is_nested_deref = false; | |
| if let Some(inner) = inner { | |
| is_nested_deref = | |
| matches!(inner.kind, hir::ExprKind::Unary(hir::UnOp::Deref, _)); | |
| let typck_result = self.infcx.tcx.typeck(self.mir_def_id()); | |
| if let Some(inner_type) = typck_result.node_type_opt(inner.hir_id) { | |
| if matches!(inner_type.kind(), ty::RawPtr(..)) { | |
| is_raw_ptr = true; | |
| } else if matches!(inner_type.kind(), ty::Ref(..)) { | |
| is_ref = true; | |
| } | |
| } | |
| is_destructuring_assignment = | |
| self.infcx.tcx.hir_parent_iter(inner.hir_id).any(|(_, node)| { | |
| matches!( | |
| node, | |
| hir::Node::LetStmt(&hir::LetStmt { | |
| source: hir::LocalSource::AssignDesugar, | |
| .. | |
| }) | |
| ) | |
| }); | |
| } | |
| // If the `inner` is a raw pointer, do not suggest removing the "*", see #126863 | |
| // FIXME: need to check whether the assigned object can be a raw pointer, see `tests/ui/borrowck/issue-20801.rs`. | |
| if is_raw_ptr { | |
| return; | |
| } | |
| if !is_destructuring_pattern_move || is_ref { | |
| err.span_suggestion_verbose( | |
| span.with_hi(span.lo() + BytePos(1)), | |
| "consider removing the dereference here", | |
| String::new(), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } else if !is_destructuring_assignment && !is_nested_deref { | |
| err.span_suggestion_verbose( | |
| span.shrink_to_lo(), | |
| "consider borrowing here", | |
| '&', | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } else { | |
| err.span_help( | |
| span, | |
| "destructuring assignment cannot borrow from this expression; consider using a `let` binding instead", | |
| ); | |
| } | |
| } | |
| _ => { | |
| err.span_suggestion_verbose( | |
| span.shrink_to_lo(), | |
| "consider borrowing here", | |
| '&', | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } | |
| } | |
| fn should_suggest_pattern_binding_instead( | |
| &self, | |
| span: Span, | |
| binding_info: &PatternBindingInfo, | |
| ) -> bool { | |
| let Some(expr) = self.find_expr(span) else { | |
| return false; | |
| }; | |
| let typeck_results = self.infcx.tcx.typeck(self.mir_def_id()); | |
| let projection_qualifies = match expr.kind { | |
| hir::ExprKind::Field(base, ..) => { | |
| !typeck_results.node_type_opt(base.hir_id).is_some_and(|base_ty| { | |
| binding_info.has_mutable_by_value_binding | |
| && matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not)) | |
| }) | |
| } | |
| hir::ExprKind::Index(base, ..) => typeck_results | |
| .node_type_opt(base.hir_id) | |
| .is_some_and(|base_ty| match base_ty.kind() { | |
| ty::Ref(_, _, hir::Mutability::Not) | ty::RawPtr(..) => false, | |
| ty::Ref(_, _, hir::Mutability::Mut) => { | |
| binding_info.has_mutable_by_value_binding | |
| } | |
| _ => true, | |
| }), | |
| _ => false, | |
| }; | |
| if !projection_qualifies { | |
| return false; | |
| } | |
| let is_single_binding = binding_info.binding_spans.len() == 1 | |
| && binding_info.binding_spans[0] == binding_info.pat_span; | |
| !is_single_binding | |
| } | |
| fn should_suggest_borrow_instead( | |
| &self, | |
| span: Span, | |
| binding_info: Option<&PatternBindingInfo>, | |
| ) -> bool { | |
| if !binding_info.is_some_and(|info| info.has_mutable_by_value_binding) { | |
| return true; | |
| } | |
| let Some(expr) = self.find_expr(span) else { | |
| return true; | |
| }; | |
| let Some(base) = (match expr.kind { | |
| hir::ExprKind::Field(base, _) | hir::ExprKind::Index(base, ..) => Some(base), | |
| _ => None, | |
| }) else { | |
| return true; | |
| }; | |
| !self | |
| .infcx | |
| .tcx | |
| .typeck(self.mir_def_id()) | |
| .node_type_opt(base.hir_id) | |
| .is_some_and(|base_ty| matches!(base_ty.kind(), ty::Ref(_, _, hir::Mutability::Not))) | |
| } | |
| fn pattern_binding_info(&self, binds_to: &[Local]) -> Option<PatternBindingInfo> { | |
| let mut pat_span = None; | |
| let mut binding_spans = Vec::new(); | |
| let mut has_mutable_by_value_binding = false; | |
| for local in binds_to { | |
| let bind_to = &self.body.local_decls[*local]; | |
| if let LocalInfo::User(BindingForm::Var(VarBindingForm { | |
| pat_span: pat_sp, | |
| binding_mode, | |
| .. | |
| })) = *bind_to.local_info() | |
| { | |
| pat_span = Some(pat_sp); | |
| binding_spans.push(bind_to.source_info.span); | |
| has_mutable_by_value_binding |= | |
| matches!(binding_mode, hir::BindingMode(hir::ByRef::No, hir::Mutability::Mut)); | |
| } | |
| } | |
| Some(PatternBindingInfo { | |
| pat_span: pat_span?, | |
| binding_spans, | |
| has_mutable_by_value_binding, | |
| }) | |
| } | |
| fn add_move_error_suggestions( | |
| &self, | |
| err: &mut Diag<'_>, | |
| binds_to: &[Local], | |
| ) -> Option<Vec<Span>> { | |
| /// A HIR visitor to associate each binding with a `&` or `&mut` that could be removed to | |
| /// make it bind by reference instead (if possible) | |
| struct BindingFinder<'tcx> { | |
| typeck_results: &'tcx ty::TypeckResults<'tcx>, | |
| tcx: TyCtxt<'tcx>, | |
| /// Input: the span of the pattern we're finding bindings in | |
| pat_span: Span, | |
| /// Input: the spans of the bindings we're providing suggestions for | |
| binding_spans: Vec<Span>, | |
| /// Internal state: have we reached the pattern we're finding bindings in? | |
| found_pat: bool, | |
| /// Internal state: the innermost `&` or `&mut` "above" the visitor | |
| ref_pat: Option<&'tcx hir::Pat<'tcx>>, | |
| /// Internal state: could removing a `&` give bindings unexpected types? | |
| has_adjustments: bool, | |
| /// Output: for each input binding, the `&` or `&mut` to remove to make it by-ref | |
| ref_pat_for_binding: Vec<(Span, Option<&'tcx hir::Pat<'tcx>>)>, | |
| /// Output: ref patterns that can't be removed straightforwardly | |
| cannot_remove: FxHashSet<HirId>, | |
| /// Output: binding spans from destructuring assignment desugaring | |
| desugar_binding_spans: Vec<Span>, | |
| } | |
| impl<'tcx> Visitor<'tcx> for BindingFinder<'tcx> { | |
| type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies; | |
| fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { | |
| self.tcx | |
| } | |
| fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) -> Self::Result { | |
| // Don't walk into const patterns or anything else that might confuse this | |
| if !self.found_pat { | |
| hir::intravisit::walk_expr(self, ex) | |
| } | |
| } | |
| fn visit_pat(&mut self, p: &'tcx hir::Pat<'tcx>) { | |
| if p.span == self.pat_span { | |
| self.found_pat = true; | |
| } | |
| let parent_has_adjustments = self.has_adjustments; | |
| self.has_adjustments |= | |
| self.typeck_results.pat_adjustments().contains_key(p.hir_id); | |
| // Track the innermost `&` or `&mut` enclosing bindings, to suggest removing it. | |
| let parent_ref_pat = self.ref_pat; | |
| if let hir::PatKind::Ref(..) = p.kind { | |
| self.ref_pat = Some(p); | |
| // To avoid edition-dependent logic to figure out how many refs this `&` can | |
| // peel off, simply don't remove the "parent" `&`. | |
| self.cannot_remove.extend(parent_ref_pat.map(|r| r.hir_id)); | |
| if self.has_adjustments { | |
| // Removing this `&` could give child bindings unexpected types, so don't. | |
| self.cannot_remove.insert(p.hir_id); | |
| // As long the `&` stays, child patterns' types should be as expected. | |
| self.has_adjustments = false; | |
| } | |
| } | |
| if let hir::PatKind::Binding(_, _, ident, _) = p.kind { | |
| // Skip synthetic bindings from destructuring assignment desugaring | |
| // These have name `lhs` and their parent is a `LetStmt` with | |
| // `LocalSource::AssignDesugar` | |
| let dominated_by_desugar_assign = ident.name == sym::lhs | |
| && self.tcx.hir_parent_iter(p.hir_id).any(|(_, node)| { | |
| matches!( | |
| node, | |
| hir::Node::LetStmt(&hir::LetStmt { | |
| source: hir::LocalSource::AssignDesugar, | |
| .. | |
| }) | |
| ) | |
| }); | |
| if dominated_by_desugar_assign { | |
| if let Some(&bind_sp) = | |
| self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span)) | |
| { | |
| self.desugar_binding_spans.push(bind_sp); | |
| } | |
| } else { | |
| // the spans in `binding_spans` encompass both the ident and binding mode | |
| if let Some(&bind_sp) = | |
| self.binding_spans.iter().find(|bind_sp| bind_sp.contains(ident.span)) | |
| { | |
| self.ref_pat_for_binding.push((bind_sp, self.ref_pat)); | |
| } else { | |
| // we've encountered a binding that we're not reporting a move error for. | |
| // we don't want to change its type, so don't remove the surrounding `&`. | |
| if let Some(ref_pat) = self.ref_pat { | |
| self.cannot_remove.insert(ref_pat.hir_id); | |
| } | |
| } | |
| } | |
| } | |
| hir::intravisit::walk_pat(self, p); | |
| self.ref_pat = parent_ref_pat; | |
| self.has_adjustments = parent_has_adjustments; | |
| } | |
| } | |
| let Some(binding_info) = self.pattern_binding_info(binds_to) else { | |
| return None; | |
| }; | |
| let tcx = self.infcx.tcx; | |
| let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) else { | |
| return None; | |
| }; | |
| let typeck_results = self.infcx.tcx.typeck(self.mir_def_id()); | |
| let mut finder = BindingFinder { | |
| typeck_results, | |
| tcx, | |
| pat_span: binding_info.pat_span, | |
| binding_spans: binding_info.binding_spans, | |
| found_pat: false, | |
| ref_pat: None, | |
| has_adjustments: false, | |
| ref_pat_for_binding: Vec::new(), | |
| cannot_remove: FxHashSet::default(), | |
| desugar_binding_spans: Vec::new(), | |
| }; | |
| finder.visit_body(body); | |
| let mut suggestions = Vec::new(); | |
| for (binding_span, opt_ref_pat) in finder.ref_pat_for_binding { | |
| if let Some(ref_pat) = opt_ref_pat | |
| && !finder.cannot_remove.contains(&ref_pat.hir_id) | |
| && let hir::PatKind::Ref(subpat, pinned, mutbl) = ref_pat.kind | |
| && let Some(ref_span) = ref_pat.span.trim_end(subpat.span) | |
| { | |
| let pinned_str = if pinned.is_pinned() { "pinned " } else { "" }; | |
| let mutable_str = if mutbl.is_mut() { "mutable " } else { "" }; | |
| let msg = format!("consider removing the {pinned_str}{mutable_str}borrow"); | |
| suggestions.push((ref_span, msg, "".to_string())); | |
| } else { | |
| let msg = "consider borrowing the pattern binding".to_string(); | |
| suggestions.push((binding_span.shrink_to_lo(), msg, "ref ".to_string())); | |
| } | |
| } | |
| suggestions.sort_unstable_by_key(|&(span, _, _)| span); | |
| suggestions.dedup_by_key(|&mut (span, _, _)| span); | |
| for (span, msg, suggestion) in suggestions { | |
| err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable); | |
| } | |
| Some(finder.desugar_binding_spans) | |
| } | |
| fn add_move_error_details( | |
| &self, | |
| err: &mut Diag<'_>, | |
| binds_to: &[Local], | |
| desugar_spans: &[Span], | |
| ) { | |
| for (j, local) in binds_to.iter().enumerate() { | |
| let bind_to = &self.body.local_decls[*local]; | |
| let binding_span = bind_to.source_info.span; | |
| if binds_to.len() == 1 { | |
| let place_desc = self.local_name(*local).map(|sym| format!("`{sym}`")); | |
| err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::LabelMovedHere { | |
| ty: bind_to.ty, | |
| place: place_desc.as_deref().unwrap_or("the place"), | |
| span: binding_span, | |
| }); | |
| if !desugar_spans.contains(&binding_span) | |
| && let Some(expr) = self.find_expr(binding_span) | |
| { | |
| let local_place: PlaceRef<'tcx> = (*local).into(); | |
| self.suggest_cloning(err, local_place, bind_to.ty, expr, None); | |
| } | |
| } else if j == 0 { | |
| err.span_label(binding_span, "data moved here"); | |
| } else if j == 5 && binds_to.len() > 6 && !self.infcx.tcx.sess.opts.verbose { | |
| err.note(format!("...and {} other places", binds_to.len() - 5)); | |
| break; | |
| } else { | |
| err.span_label(binding_span, "...and here"); | |
| } | |
| } | |
| if binds_to.len() > 1 { | |
| err.note( | |
| "move occurs because these variables have types that don't implement the `Copy` \ | |
| trait", | |
| ); | |
| } | |
| } | |
| /// Adds an explanatory note if the move error occurs in a derive macro | |
| /// expansion of a packed struct. | |
| /// Such errors happen because derive macro expansions shy away from taking | |
| /// references to the struct's fields since doing so would be undefined behaviour | |
| fn add_note_for_packed_struct_derive(&self, err: &mut Diag<'_>, local: Local) { | |
| let local_place: PlaceRef<'tcx> = local.into(); | |
| let local_ty = local_place.ty(self.body.local_decls(), self.infcx.tcx).ty.peel_refs(); | |
| if let Some(adt) = local_ty.ty_adt_def() | |
| && adt.repr().packed() | |
| && let ExpnKind::Macro(MacroKind::Derive, name) = | |
| self.body.span.ctxt().outer_expn_data().kind | |
| { | |
| err.note(format!("`#[derive({name})]` triggers a move because taking references to the fields of a packed struct is undefined behaviour")); | |
| } | |
| } | |
| } |