1 use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; 2 use rustc_middle::mir::*; 3 use rustc_middle::ty; 4 use rustc_mir_dataflow::move_paths::{ 5 IllegalMoveOrigin, IllegalMoveOriginKind, LookupResult, MoveError, MovePathIndex, 6 }; 7 use rustc_span::{BytePos, Span}; 8 9 use crate::diagnostics::CapturedMessageOpt; 10 use crate::diagnostics::{DescribePlaceOpt, UseSpans}; 11 use crate::prefixes::PrefixSet; 12 use crate::MirBorrowckCtxt; 13 14 // Often when desugaring a pattern match we may have many individual moves in 15 // MIR that are all part of one operation from the user's point-of-view. For 16 // example: 17 // 18 // let (x, y) = foo() 19 // 20 // would move x from the 0 field of some temporary, and y from the 1 field. We 21 // group such errors together for cleaner error reporting. 22 // 23 // Errors are kept separate if they are from places with different parent move 24 // paths. For example, this generates two errors: 25 // 26 // let (&x, &y) = (&String::new(), &String::new()); 27 #[derive(Debug)] 28 enum GroupedMoveError<'tcx> { 29 // Place expression can't be moved from, 30 // e.g., match x[0] { s => (), } where x: &[String] 31 MovesFromPlace { 32 original_path: Place<'tcx>, 33 span: Span, 34 move_from: Place<'tcx>, 35 kind: IllegalMoveOriginKind<'tcx>, 36 binds_to: Vec<Local>, 37 }, 38 // Part of a value expression can't be moved from, 39 // e.g., match &String::new() { &x => (), } 40 MovesFromValue { 41 original_path: Place<'tcx>, 42 span: Span, 43 move_from: MovePathIndex, 44 kind: IllegalMoveOriginKind<'tcx>, 45 binds_to: Vec<Local>, 46 }, 47 // Everything that isn't from pattern matching. 48 OtherIllegalMove { 49 original_path: Place<'tcx>, 50 use_spans: UseSpans<'tcx>, 51 kind: IllegalMoveOriginKind<'tcx>, 52 }, 53 } 54 55 impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>)56 pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) { 57 let grouped_errors = self.group_move_errors(move_errors); 58 for error in grouped_errors { 59 self.report(error); 60 } 61 } 62 group_move_errors( &self, errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, ) -> Vec<GroupedMoveError<'tcx>>63 fn group_move_errors( 64 &self, 65 errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, 66 ) -> Vec<GroupedMoveError<'tcx>> { 67 let mut grouped_errors = Vec::new(); 68 for (original_path, error) in errors { 69 self.append_to_grouped_errors(&mut grouped_errors, original_path, error); 70 } 71 grouped_errors 72 } 73 append_to_grouped_errors( &self, grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, original_path: Place<'tcx>, error: MoveError<'tcx>, )74 fn append_to_grouped_errors( 75 &self, 76 grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, 77 original_path: Place<'tcx>, 78 error: MoveError<'tcx>, 79 ) { 80 match error { 81 MoveError::UnionMove { .. } => { 82 unimplemented!("don't know how to report union move errors yet.") 83 } 84 MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => { 85 // Note: that the only time we assign a place isn't a temporary 86 // to a user variable is when initializing it. 87 // If that ever stops being the case, then the ever initialized 88 // flow could be used. 89 if let Some(StatementKind::Assign(box ( 90 place, 91 Rvalue::Use(Operand::Move(move_from)), 92 ))) = self.body.basic_blocks[location.block] 93 .statements 94 .get(location.statement_index) 95 .map(|stmt| &stmt.kind) 96 { 97 if let Some(local) = place.as_local() { 98 let local_decl = &self.body.local_decls[local]; 99 // opt_match_place is the 100 // match_span is the span of the expression being matched on 101 // match *x.y { ... } match_place is Some(*x.y) 102 // ^^^^ match_span is the span of *x.y 103 // 104 // opt_match_place is None for let [mut] x = ... statements, 105 // whether or not the right-hand side is a place expression 106 if let LocalInfo::User(BindingForm::Var(VarBindingForm { 107 opt_match_place: Some((opt_match_place, match_span)), 108 binding_mode: _, 109 opt_ty_info: _, 110 pat_span: _, 111 })) = *local_decl.local_info() 112 { 113 let stmt_source_info = self.body.source_info(location); 114 self.append_binding_error( 115 grouped_errors, 116 kind, 117 original_path, 118 *move_from, 119 local, 120 opt_match_place, 121 match_span, 122 stmt_source_info.span, 123 ); 124 return; 125 } 126 } 127 } 128 129 let move_spans = self.move_spans(original_path.as_ref(), location); 130 grouped_errors.push(GroupedMoveError::OtherIllegalMove { 131 use_spans: move_spans, 132 original_path, 133 kind, 134 }); 135 } 136 } 137 } 138 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, )139 fn append_binding_error( 140 &self, 141 grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, 142 kind: IllegalMoveOriginKind<'tcx>, 143 original_path: Place<'tcx>, 144 move_from: Place<'tcx>, 145 bind_to: Local, 146 match_place: Option<Place<'tcx>>, 147 match_span: Span, 148 statement_span: Span, 149 ) { 150 debug!(?match_place, ?match_span, "append_binding_error"); 151 152 let from_simple_let = match_place.is_none(); 153 let match_place = match_place.unwrap_or(move_from); 154 155 match self.move_data.rev_lookup.find(match_place.as_ref()) { 156 // Error with the match place 157 LookupResult::Parent(_) => { 158 for ge in &mut *grouped_errors { 159 if let GroupedMoveError::MovesFromPlace { span, binds_to, .. } = ge 160 && match_span == *span 161 { 162 debug!("appending local({bind_to:?}) to list"); 163 if !binds_to.is_empty() { 164 binds_to.push(bind_to); 165 } 166 return; 167 } 168 } 169 debug!("found a new move error location"); 170 171 // Don't need to point to x in let x = ... . 172 let (binds_to, span) = if from_simple_let { 173 (vec![], statement_span) 174 } else { 175 (vec![bind_to], match_span) 176 }; 177 grouped_errors.push(GroupedMoveError::MovesFromPlace { 178 span, 179 move_from, 180 original_path, 181 kind, 182 binds_to, 183 }); 184 } 185 // Error with the pattern 186 LookupResult::Exact(_) => { 187 let LookupResult::Parent(Some(mpi)) = self.move_data.rev_lookup.find(move_from.as_ref()) else { 188 // move_from should be a projection from match_place. 189 unreachable!("Probably not unreachable..."); 190 }; 191 for ge in &mut *grouped_errors { 192 if let GroupedMoveError::MovesFromValue { 193 span, 194 move_from: other_mpi, 195 binds_to, 196 .. 197 } = ge 198 { 199 if match_span == *span && mpi == *other_mpi { 200 debug!("appending local({bind_to:?}) to list"); 201 binds_to.push(bind_to); 202 return; 203 } 204 } 205 } 206 debug!("found a new move error location"); 207 grouped_errors.push(GroupedMoveError::MovesFromValue { 208 span: match_span, 209 move_from: mpi, 210 original_path, 211 kind, 212 binds_to: vec![bind_to], 213 }); 214 } 215 }; 216 } 217 report(&mut self, error: GroupedMoveError<'tcx>)218 fn report(&mut self, error: GroupedMoveError<'tcx>) { 219 let (mut err, err_span) = { 220 let (span, use_spans, original_path, kind) = match error { 221 GroupedMoveError::MovesFromPlace { span, original_path, ref kind, .. } 222 | GroupedMoveError::MovesFromValue { span, original_path, ref kind, .. } => { 223 (span, None, original_path, kind) 224 } 225 GroupedMoveError::OtherIllegalMove { use_spans, original_path, ref kind } => { 226 (use_spans.args_or_use(), Some(use_spans), original_path, kind) 227 } 228 }; 229 debug!( 230 "report: original_path={:?} span={:?}, kind={:?} \ 231 original_path.is_upvar_field_projection={:?}", 232 original_path, 233 span, 234 kind, 235 self.is_upvar_field_projection(original_path.as_ref()) 236 ); 237 ( 238 match kind { 239 &IllegalMoveOriginKind::BorrowedContent { target_place } => self 240 .report_cannot_move_from_borrowed_content( 241 original_path, 242 target_place, 243 span, 244 use_spans, 245 ), 246 &IllegalMoveOriginKind::InteriorOfTypeWithDestructor { container_ty: ty } => { 247 self.cannot_move_out_of_interior_of_drop(span, ty) 248 } 249 &IllegalMoveOriginKind::InteriorOfSliceOrArray { ty, is_index } => { 250 self.cannot_move_out_of_interior_noncopy(span, ty, Some(is_index)) 251 } 252 }, 253 span, 254 ) 255 }; 256 257 self.add_move_hints(error, &mut err, err_span); 258 self.buffer_error(err); 259 } 260 report_cannot_move_from_static( &mut self, place: Place<'tcx>, span: Span, ) -> DiagnosticBuilder<'a, ErrorGuaranteed>261 fn report_cannot_move_from_static( 262 &mut self, 263 place: Place<'tcx>, 264 span: Span, 265 ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { 266 let description = if place.projection.len() == 1 { 267 format!("static item {}", self.describe_any_place(place.as_ref())) 268 } else { 269 let base_static = PlaceRef { local: place.local, projection: &[ProjectionElem::Deref] }; 270 271 format!( 272 "{} as {} is a static item", 273 self.describe_any_place(place.as_ref()), 274 self.describe_any_place(base_static), 275 ) 276 }; 277 278 self.cannot_move_out_of(span, &description) 279 } 280 report_cannot_move_from_borrowed_content( &mut self, move_place: Place<'tcx>, deref_target_place: Place<'tcx>, span: Span, use_spans: Option<UseSpans<'tcx>>, ) -> DiagnosticBuilder<'a, ErrorGuaranteed>281 fn report_cannot_move_from_borrowed_content( 282 &mut self, 283 move_place: Place<'tcx>, 284 deref_target_place: Place<'tcx>, 285 span: Span, 286 use_spans: Option<UseSpans<'tcx>>, 287 ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { 288 // Inspect the type of the content behind the 289 // borrow to provide feedback about why this 290 // was a move rather than a copy. 291 let ty = deref_target_place.ty(self.body, self.infcx.tcx).ty; 292 let upvar_field = self 293 .prefixes(move_place.as_ref(), PrefixSet::All) 294 .find_map(|p| self.is_upvar_field_projection(p)); 295 296 let deref_base = match deref_target_place.projection.as_ref() { 297 [proj_base @ .., ProjectionElem::Deref] => { 298 PlaceRef { local: deref_target_place.local, projection: &proj_base } 299 } 300 _ => bug!("deref_target_place is not a deref projection"), 301 }; 302 303 if let PlaceRef { local, projection: [] } = deref_base { 304 let decl = &self.body.local_decls[local]; 305 if decl.is_ref_for_guard() { 306 let mut err = self.cannot_move_out_of( 307 span, 308 &format!("`{}` in pattern guard", self.local_names[local].unwrap()), 309 ); 310 err.note( 311 "variables bound in patterns cannot be moved from \ 312 until after the end of the pattern guard", 313 ); 314 return err; 315 } else if decl.is_ref_to_static() { 316 return self.report_cannot_move_from_static(move_place, span); 317 } 318 } 319 320 debug!("report: ty={:?}", ty); 321 let mut err = match ty.kind() { 322 ty::Array(..) | ty::Slice(..) => { 323 self.cannot_move_out_of_interior_noncopy(span, ty, None) 324 } 325 ty::Closure(def_id, closure_substs) 326 if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() => 327 { 328 let closure_kind_ty = closure_substs.as_closure().kind_ty(); 329 let closure_kind = match closure_kind_ty.to_opt_closure_kind() { 330 Some(kind @ (ty::ClosureKind::Fn | ty::ClosureKind::FnMut)) => kind, 331 Some(ty::ClosureKind::FnOnce) => { 332 bug!("closure kind does not match first argument type") 333 } 334 None => bug!("closure kind not inferred by borrowck"), 335 }; 336 let capture_description = 337 format!("captured variable in an `{closure_kind}` closure"); 338 339 let upvar = &self.upvars[upvar_field.unwrap().index()]; 340 let upvar_hir_id = upvar.place.get_root_variable(); 341 let upvar_name = upvar.place.to_string(self.infcx.tcx); 342 let upvar_span = self.infcx.tcx.hir().span(upvar_hir_id); 343 344 let place_name = self.describe_any_place(move_place.as_ref()); 345 346 let place_description = 347 if self.is_upvar_field_projection(move_place.as_ref()).is_some() { 348 format!("{place_name}, a {capture_description}") 349 } else { 350 format!("{place_name}, as `{upvar_name}` is a {capture_description}") 351 }; 352 353 debug!( 354 "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}", 355 closure_kind_ty, closure_kind, place_description, 356 ); 357 358 let mut diag = self.cannot_move_out_of(span, &place_description); 359 360 diag.span_label(upvar_span, "captured outer variable"); 361 diag.span_label( 362 self.infcx.tcx.def_span(def_id), 363 format!("captured by this `{closure_kind}` closure"), 364 ); 365 366 diag 367 } 368 _ => { 369 let source = self.borrowed_content_source(deref_base); 370 let move_place_ref = move_place.as_ref(); 371 match ( 372 self.describe_place_with_options( 373 move_place_ref, 374 DescribePlaceOpt { 375 including_downcast: false, 376 including_tuple_field: false, 377 }, 378 ), 379 self.describe_name(move_place_ref), 380 source.describe_for_named_place(), 381 ) { 382 (Some(place_desc), Some(name), Some(source_desc)) => self.cannot_move_out_of( 383 span, 384 &format!("`{place_desc}` as enum variant `{name}` which is behind a {source_desc}"), 385 ), 386 (Some(place_desc), Some(name), None) => self.cannot_move_out_of( 387 span, 388 &format!("`{place_desc}` as enum variant `{name}`"), 389 ), 390 (Some(place_desc), _, Some(source_desc)) => self.cannot_move_out_of( 391 span, 392 &format!("`{place_desc}` which is behind a {source_desc}"), 393 ), 394 (_, _, _) => self.cannot_move_out_of( 395 span, 396 &source.describe_for_unnamed_place(self.infcx.tcx), 397 ), 398 } 399 } 400 }; 401 let msg_opt = CapturedMessageOpt { 402 is_partial_move: false, 403 is_loop_message: false, 404 is_move_msg: false, 405 is_loop_move: false, 406 maybe_reinitialized_locations_is_empty: true, 407 }; 408 if let Some(use_spans) = use_spans { 409 self.explain_captures(&mut err, span, span, use_spans, move_place, msg_opt); 410 } 411 err 412 } 413 add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span)414 fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span) { 415 match error { 416 GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { 417 self.add_borrow_suggestions(err, span); 418 if binds_to.is_empty() { 419 let place_ty = move_from.ty(self.body, self.infcx.tcx).ty; 420 let place_desc = match self.describe_place(move_from.as_ref()) { 421 Some(desc) => format!("`{desc}`"), 422 None => "value".to_string(), 423 }; 424 425 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label { 426 is_partial_move: false, 427 ty: place_ty, 428 place: &place_desc, 429 span, 430 }); 431 } else { 432 binds_to.sort(); 433 binds_to.dedup(); 434 435 self.add_move_error_details(err, &binds_to); 436 } 437 } 438 GroupedMoveError::MovesFromValue { mut binds_to, .. } => { 439 binds_to.sort(); 440 binds_to.dedup(); 441 self.add_move_error_suggestions(err, &binds_to); 442 self.add_move_error_details(err, &binds_to); 443 } 444 // No binding. Nothing to suggest. 445 GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => { 446 let span = use_spans.var_or_use(); 447 let place_ty = original_path.ty(self.body, self.infcx.tcx).ty; 448 let place_desc = match self.describe_place(original_path.as_ref()) { 449 Some(desc) => format!("`{desc}`"), 450 None => "value".to_string(), 451 }; 452 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label { 453 is_partial_move: false, 454 ty: place_ty, 455 place: &place_desc, 456 span, 457 }); 458 459 use_spans.args_subdiag(err, |args_span| { 460 crate::session_diagnostics::CaptureArgLabel::MoveOutPlace { 461 place: place_desc, 462 args_span, 463 } 464 }); 465 } 466 } 467 } 468 add_borrow_suggestions(&self, err: &mut Diagnostic, span: Span)469 fn add_borrow_suggestions(&self, err: &mut Diagnostic, span: Span) { 470 match self.infcx.tcx.sess.source_map().span_to_snippet(span) { 471 Ok(snippet) if snippet.starts_with('*') => { 472 err.span_suggestion_verbose( 473 span.with_hi(span.lo() + BytePos(1)), 474 "consider removing the dereference here", 475 String::new(), 476 Applicability::MaybeIncorrect, 477 ); 478 } 479 _ => { 480 err.span_suggestion_verbose( 481 span.shrink_to_lo(), 482 "consider borrowing here", 483 '&', 484 Applicability::MaybeIncorrect, 485 ); 486 } 487 } 488 } 489 add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local])490 fn add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local]) { 491 let mut suggestions: Vec<(Span, String, String)> = Vec::new(); 492 for local in binds_to { 493 let bind_to = &self.body.local_decls[*local]; 494 if let LocalInfo::User(BindingForm::Var(VarBindingForm { pat_span, .. })) = 495 *bind_to.local_info() 496 { 497 let Ok(pat_snippet) = 498 self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) else { continue; }; 499 let Some(stripped) = pat_snippet.strip_prefix('&') else { 500 suggestions.push(( 501 bind_to.source_info.span.shrink_to_lo(), 502 "consider borrowing the pattern binding".to_string(), 503 "ref ".to_string(), 504 )); 505 continue; 506 }; 507 let inner_pat_snippet = stripped.trim_start(); 508 let (pat_span, suggestion, to_remove) = if inner_pat_snippet.starts_with("mut") 509 && inner_pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace) 510 { 511 let inner_pat_snippet = inner_pat_snippet["mut".len()..].trim_start(); 512 let pat_span = pat_span.with_hi( 513 pat_span.lo() 514 + BytePos((pat_snippet.len() - inner_pat_snippet.len()) as u32), 515 ); 516 (pat_span, String::new(), "mutable borrow") 517 } else { 518 let pat_span = pat_span.with_hi( 519 pat_span.lo() 520 + BytePos( 521 (pat_snippet.len() - inner_pat_snippet.trim_start().len()) as u32, 522 ), 523 ); 524 (pat_span, String::new(), "borrow") 525 }; 526 suggestions.push(( 527 pat_span, 528 format!("consider removing the {to_remove}"), 529 suggestion.to_string(), 530 )); 531 } 532 } 533 suggestions.sort_unstable_by_key(|&(span, _, _)| span); 534 suggestions.dedup_by_key(|&mut (span, _, _)| span); 535 for (span, msg, suggestion) in suggestions { 536 err.span_suggestion_verbose(span, msg, suggestion, Applicability::MachineApplicable); 537 } 538 } 539 add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local])540 fn add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local]) { 541 for (j, local) in binds_to.iter().enumerate() { 542 let bind_to = &self.body.local_decls[*local]; 543 let binding_span = bind_to.source_info.span; 544 545 if j == 0 { 546 err.span_label(binding_span, "data moved here"); 547 } else { 548 err.span_label(binding_span, "...and here"); 549 } 550 551 if binds_to.len() == 1 { 552 let place_desc = &format!("`{}`", self.local_names[*local].unwrap()); 553 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label { 554 is_partial_move: false, 555 ty: bind_to.ty, 556 place: &place_desc, 557 span: binding_span, 558 }); 559 } 560 } 561 562 if binds_to.len() > 1 { 563 err.note( 564 "move occurs because these variables have types that don't implement the `Copy` \ 565 trait", 566 ); 567 } 568 } 569 } 570