1// ignore-tidy-file-filelength23use std::iter;4use std::ops::ControlFlow;56use either::Either;7use hir::{ClosureKind, Path};8use rustc_data_structures::fx::FxIndexSet;9use rustc_errors::codes::*;10use rustc_errors::{Applicability, Diag, MultiSpan, struct_span_code_err};11use rustc_hir as hir;12use rustc_hir::attrs::diagnostic::{CustomDiagnostic, FormatArgs};13use rustc_hir::attrs::lang_items::LangItem;14use rustc_hir::def::{DefKind, Res};15use rustc_hir::intravisit::{Visitor, walk_block, walk_expr};16use rustc_hir::{CoroutineDesugaring, CoroutineKind, CoroutineSource, PatField, find_attr};17use rustc_index::bit_set::DenseBitSet;18use rustc_infer::traits::TraitErrors;19use rustc_middle::bug;20use rustc_middle::hir::nested_filter::OnlyBodies;21use rustc_middle::mir::{22 self, AggregateKind, BindingForm, BorrowKind, ClearCrossCrate, ConstraintCategory,23 FakeBorrowKind, FakeReadCause, LocalDecl, LocalInfo, LocalKind, Location, MutBorrowKind,24 Operand, Place, PlaceRef, PlaceTy, ProjectionElem, Rvalue, Statement, StatementKind,25 Terminator, TerminatorKind, VarBindingForm, VarDebugInfoContents,26};27use rustc_middle::ty::print::PrintTraitRefExt as _;28use rustc_middle::ty::{29 self, PredicateKind, RegionExt, Ty, TyCtxt, TypeSuperVisitable, TypeVisitor, Upcast,30 suggest_constraining_type_params,31};32use rustc_mir_dataflow::move_paths::{Init, InitKind, InitLocation, MoveOutIndex, MovePathIndex};33use rustc_span::def_id::{DefId, LocalDefId};34use rustc_span::hygiene::DesugaringKind;35use rustc_span::{BytePos, ExpnKind, Ident, MacroKind, Span, Symbol, kw, sym};36use rustc_trait_selection::error_reporting::InferCtxtErrorExt;37use rustc_trait_selection::error_reporting::traits::FindExprBySpan;38use rustc_trait_selection::error_reporting::traits::call_kind::CallKind;39use rustc_trait_selection::infer::InferCtxtExt;40use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;41use rustc_trait_selection::traits::{42 Obligation, ObligationCause, ObligationCtxt, supertrait_def_ids,43};44use tracing::{debug, instrument};4546use super::explain_borrow::{BorrowExplanation, LaterUseKind};47use super::{DescribePlaceOpt, RegionName, RegionNameSource, UseSpans};48use crate::borrow_set::{BorrowData, TwoPhaseActivation};49use crate::consumers::OutlivesConstraint;50use crate::diagnostics::conflict_errors::StorageDeadOrDrop::LocalStorageDead;51use crate::diagnostics::{CapturedMessageOpt, call_kind, find_all_local_uses};52use crate::{InitializationRequiringAction, MirBorrowckCtxt, WriteKind, borrowck_errors};5354#[derive(Debug)]55struct MoveSite {56 /// Index of the "move out" that we found. The `MoveData` can57 /// then tell us where the move occurred.58 moi: MoveOutIndex,5960 /// `true` if we traversed a back edge while walking from the point61 /// of error to the move site.62 traversed_back_edge: bool,63}6465/// Which case a StorageDeadOrDrop is for.66#[derive(Copy, Clone, PartialEq, Eq, Debug)]67enum StorageDeadOrDrop<'tcx> {68 LocalStorageDead,69 BoxedStorageDead,70 Destructor(Ty<'tcx>),71}7273impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> {74 pub(crate) fn report_use_of_moved_or_uninitialized(75 &mut self,76 location: Location,77 desired_action: InitializationRequiringAction,78 (moved_place, used_place, span): (PlaceRef<'tcx>, PlaceRef<'tcx>, Span),79 mpi: MovePathIndex,80 ) {81 debug!(82 "report_use_of_moved_or_uninitialized: location={:?} desired_action={:?} \83 moved_place={:?} used_place={:?} span={:?} mpi={:?}",84 location, desired_action, moved_place, used_place, span, mpi85 );8687 let use_spans =88 self.move_spans(moved_place, location).or_else(|| self.borrow_spans(span, location));89 let span = use_spans.args_or_use();9091 let (move_site_vec, maybe_reinitialized_locations) = self.get_moved_indexes(location, mpi);92 debug!(93 "report_use_of_moved_or_uninitialized: move_site_vec={:?} use_spans={:?}",94 move_site_vec, use_spans95 );96 let move_out_indices: Vec<_> =97 move_site_vec.iter().map(|move_site| move_site.moi).collect();9899 if move_out_indices.is_empty() {100 let root_local = used_place.local;101102 if !self.uninitialized_error_reported.insert(root_local) {103 debug!(104 "report_use_of_moved_or_uninitialized place: error about {:?} suppressed",105 root_local106 );107 return;108 }109110 let err = self.report_use_of_uninitialized(111 mpi,112 used_place,113 moved_place,114 desired_action,115 location,116 span,117 use_spans,118 );119 self.buffer_error(err);120 } else {121 if let Some((reported_place, _)) = self.has_move_error(&move_out_indices) {122 if used_place.is_prefix_of(*reported_place) {123 debug!(124 "report_use_of_moved_or_uninitialized place: error suppressed mois={:?}",125 move_out_indices126 );127 return;128 }129 }130131 let is_partial_move = move_site_vec.iter().any(|move_site| {132 let move_out = self.move_data.move_outs[(*move_site).moi];133 let moved_place = &self.move_data.move_paths[move_out.path].place;134 // `*(_1)` where `_1` is a `Box` is actually a move out.135 let is_box_move = moved_place.as_ref().projection == [ProjectionElem::Deref]136 && self.body.local_decls[moved_place.local].ty.is_box();137138 !is_box_move139 && used_place != moved_place.as_ref()140 && used_place.is_prefix_of(moved_place.as_ref())141 });142143 let partial_str = if is_partial_move { "partial " } else { "" };144 let partially_str = if is_partial_move { "partially " } else { "" };145146 let (on_move_message, on_move_label, on_move_notes) = if let ty::Adt(item_def, args) =147 self.body.local_decls[moved_place.local].ty.kind()148 && let Some(Some(directive)) = find_attr!(self.infcx.tcx, item_def.did(), OnMove { directive, .. } => directive)149 {150 let this = self.infcx.tcx.item_name(item_def.did()).to_string();151 let mut generic_args: Vec<_> = self152 .infcx153 .tcx154 .generics_of(item_def.did())155 .own_params156 .iter()157 .filter_map(|param| Some((param.name, args[param.index as usize].to_string())))158 .collect();159 generic_args.push((kw::SelfUpper, this.clone()));160161 let args = FormatArgs { this, generic_args, .. };162 let CustomDiagnostic { message, label, notes, parent_label: _ } =163 directive.eval(None, &args);164165 (message, label, notes)166 } else {167 (None, None, Vec::new())168 };169170 let mut err = self.cannot_act_on_moved_value(171 span,172 desired_action.as_noun(),173 partially_str,174 self.describe_place_with_options(175 moved_place,176 DescribePlaceOpt { including_downcast: true, including_tuple_field: true },177 ),178 on_move_message,179 );180181 for note in on_move_notes {182 err.note(note);183 }184185 let reinit_spans = maybe_reinitialized_locations186 .iter()187 .take(3)188 .map(|loc| {189 self.move_spans(self.move_data.move_paths[mpi].place.as_ref(), *loc)190 .args_or_use()191 })192 .collect::<Vec<Span>>();193194 let reinits = maybe_reinitialized_locations.len();195 if reinits == 1 {196 err.span_label(reinit_spans[0], "this reinitialization might get skipped");197 } else if reinits > 1 {198 err.span_note(199 MultiSpan::from_spans(reinit_spans),200 if reinits <= 3 {201 format!("these {reinits} reinitializations might get skipped")202 } else {203 format!(204 "these 3 reinitializations and {} other{} might get skipped",205 reinits - 3,206 if reinits == 4 { "" } else { "s" }207 )208 },209 );210 }211212 let closure = self.add_moved_or_invoked_closure_note(location, used_place, &mut err);213214 let mut is_loop_move = false;215 let mut seen_spans = FxIndexSet::default();216217 for move_site in &move_site_vec {218 let move_out = self.move_data.move_outs[(*move_site).moi];219 let moved_place = &self.move_data.move_paths[move_out.path].place;220221 let move_spans = self.move_spans(moved_place.as_ref(), move_out.source);222 let move_span = move_spans.args_or_use();223224 let is_move_msg = move_spans.for_closure();225226 let is_loop_message = location == move_out.source || move_site.traversed_back_edge;227228 if location == move_out.source {229 is_loop_move = true;230 }231232 let mut has_suggest_reborrow = false;233 if !seen_spans.contains(&move_span) {234 self.suggest_ref_or_clone(235 mpi,236 &mut err,237 move_spans,238 moved_place.as_ref(),239 &mut has_suggest_reborrow,240 closure,241 );242243 let msg_opt = CapturedMessageOpt {244 is_partial_move,245 is_loop_message,246 is_move_msg,247 is_loop_move,248 has_suggest_reborrow,249 maybe_reinitialized_locations_is_empty: maybe_reinitialized_locations250 .is_empty(),251 };252 self.explain_captures(253 &mut err,254 span,255 move_span,256 move_spans,257 *moved_place,258 msg_opt,259 );260 }261 seen_spans.insert(move_span);262 }263264 use_spans.var_path_only_subdiag(&mut err, desired_action);265266 if !is_loop_move {267 err.span_label(268 span,269 format!(270 "value {} here after {partial_str}move",271 desired_action.as_verb_in_past_tense(),272 ),273 );274 }275276 let ty = used_place.ty(self.body, self.infcx.tcx).ty;277 let needs_note = match ty.kind() {278 ty::Closure(id, _) => {279 self.infcx.tcx.closure_kind_origin(id.expect_local()).is_none()280 }281 _ => true,282 };283284 let mpi = self.move_data.move_outs[move_out_indices[0]].path;285 let place = &self.move_data.move_paths[mpi].place;286 let ty = place.ty(self.body, self.infcx.tcx).ty;287288 if self.infcx.param_env.caller_bounds().iter().any(|c| {289 c.as_trait_clause().is_some_and(|pred| {290 pred.skip_binder().self_ty() == ty && self.infcx.tcx.is_fn_trait(pred.def_id())291 })292 }) {293 // Suppress the next suggestion since we don't want to put more bounds onto294 // something that already has `Fn`-like bounds (or is a closure), so we can't295 // restrict anyways.296 } else {297 let copy_did = self.infcx.tcx.require_lang_item(LangItem::Copy, span);298 self.suggest_adding_bounds(&mut err, ty, copy_did, span);299 }300301 let opt_name = self.describe_place_with_options(302 place.as_ref(),303 DescribePlaceOpt { including_downcast: true, including_tuple_field: true },304 );305 let note_msg = match opt_name {306 Some(name) => format!("`{name}`"),307 None => "value".to_owned(),308 };309 if needs_note {310 if let Some(local) = place.as_local() {311 let span = self.body.local_decls[local].source_info.span;312 if let Some(on_move_label) = on_move_label {313 err.span_label(span, on_move_label);314 } else {315 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Label {316 is_partial_move,317 ty,318 place: ¬e_msg,319 span,320 });321 }322 } else {323 err.subdiagnostic(crate::session_diagnostics::TypeNoCopy::Note {324 is_partial_move,325 ty,326 place: ¬e_msg,327 });328 };329 }330331 if let UseSpans::FnSelfUse {332 kind: CallKind::DerefCoercion { deref_target_span, deref_target_ty, .. },333 ..334 } = use_spans335 {336 err.note(format!(337 "{} occurs due to deref coercion to `{deref_target_ty}`",338 desired_action.as_noun(),339 ));340341 // Check first whether the source is accessible (issue #87060)342 if let Some(deref_target_span) = deref_target_span343 && self.infcx.tcx.sess.source_map().is_span_accessible(deref_target_span)344 {345 err.span_note(deref_target_span, "deref defined here");346 }347 }348349 self.buffer_move_error(move_out_indices, (used_place, err));350 }351 }352353 fn suggest_ref_or_clone(354 &self,355 mpi: MovePathIndex,356 err: &mut Diag<'_>,357 move_spans: UseSpans<'tcx>,358 moved_place: PlaceRef<'tcx>,359 has_suggest_reborrow: &mut bool,360 moved_or_invoked_closure: bool,361 ) {362 let move_span = match move_spans {363 UseSpans::ClosureUse { capture_kind_span, .. } => capture_kind_span,364 _ => move_spans.args_or_use(),365 };366 struct ExpressionFinder<'hir> {367 expr_span: Span,368 expr: Option<&'hir hir::Expr<'hir>>,369 pat: Option<&'hir hir::Pat<'hir>>,370 parent_pat: Option<&'hir hir::Pat<'hir>>,371 tcx: TyCtxt<'hir>,372 }373 impl<'hir> Visitor<'hir> for ExpressionFinder<'hir> {374 type NestedFilter = OnlyBodies;375376 fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {377 self.tcx378 }379380 fn visit_expr(&mut self, e: &'hir hir::Expr<'hir>) {381 if e.span == self.expr_span {382 self.expr = Some(e);383 }384 hir::intravisit::walk_expr(self, e);385 }386 fn visit_pat(&mut self, p: &'hir hir::Pat<'hir>) {387 if p.span == self.expr_span {388 self.pat = Some(p);389 }390 if let hir::PatKind::Binding(hir::BindingMode::NONE, _, i, sub) = p.kind {391 if i.span == self.expr_span || p.span == self.expr_span {392 self.pat = Some(p);393 }394 // Check if we are in a situation of `ident @ ident` where we want to suggest395 // `ref ident @ ref ident` or `ref ident @ Struct { ref ident }`.396 if let Some(subpat) = sub397 && self.pat.is_none()398 {399 self.visit_pat(subpat);400 if self.pat.is_some() {401 self.parent_pat = Some(p);402 }403 return;404 }405 }406 hir::intravisit::walk_pat(self, p);407 }408 }409 let tcx = self.infcx.tcx;410 if let Some(body) = tcx.hir_maybe_body_owned_by(self.mir_def_id()) {411 let expr = body.value;412 let place = &self.move_data.move_paths[mpi].place;413 let span = place.as_local().map(|local| self.body.local_decls[local].source_info.span);414 let mut finder = ExpressionFinder {415 expr_span: move_span,416 expr: None,417 pat: None,418 parent_pat: None,419 tcx,420 };421 finder.visit_expr(expr);422 if let Some(span) = span423 && let Some(expr) = finder.expr424 {425 for (_, expr) in tcx.hir_parent_iter(expr.hir_id) {426 if let hir::Node::Expr(expr) = expr {427 if expr.span.contains(span) {428 // If the let binding occurs within the same loop, then that429 // loop isn't relevant, like in the following, the outermost `loop`430 // doesn't play into `x` being moved.431 // ```432 // loop {433 // let x = String::new();434 // loop {435 // foo(x);436 // }437 // }438 // ```439 break;440 }441 if let hir::ExprKind::Loop(.., loop_span) = expr.kind {442 err.span_label(loop_span, "inside of this loop");443 }444 }445 }446 let typeck = self.infcx.tcx.typeck(self.mir_def_id());447 let parent = self.infcx.tcx.parent_hir_node(expr.hir_id);448 let (def_id, args, offset) = if let hir::Node::Expr(parent_expr) = parent449 && let hir::ExprKind::MethodCall(_, _, args, _) = parent_expr.kind450 {451 let def_id = typeck.type_dependent_def_id(parent_expr.hir_id);452 (def_id, args, 1)453 } else if let hir::Node::Expr(parent_expr) = parent454 && let hir::ExprKind::Call(call, args) = parent_expr.kind455 && let ty::FnDef(def_id, _) = typeck.node_type(call.hir_id).kind()456 {457 (Some(*def_id), args, 0)458 } else {459 (None, &[][..], 0)460 };461 let ty = place.ty(self.body, self.infcx.tcx).ty;462463 let mut can_suggest_clone = true;464 if let Some(def_id) = def_id465 && let Some(pos) = args.iter().position(|arg| arg.hir_id == expr.hir_id)466 {467 // The move occurred as one of the arguments to a function call. Is that468 // argument generic? `def_id` can't be a closure here, so using `fn_sig` is fine469 let arg_param = if self.infcx.tcx.def_kind(def_id).is_fn_like()470 && let sig =471 self.infcx.tcx.fn_sig(def_id).instantiate_identity().skip_binder()472 && let Some(arg_ty) = sig.inputs().get(pos + offset)473 && let ty::Param(arg_param) = arg_ty.kind()474 {475 Some(arg_param)476 } else {477 None478 };479480 // If the moved value is a mut reference, it is used in a481 // generic function and it's type is a generic param, it can be482 // reborrowed to avoid moving.483 // for example:484 // struct Y(u32);485 // x's type is '& mut Y' and it is used in `fn generic<T>(x: T) {}`.486 if let ty::Ref(_, _, hir::Mutability::Mut) = ty.kind()487 && arg_param.is_some()488 {489 *has_suggest_reborrow = true;490 self.suggest_reborrow(err, expr.span, moved_place);491 return;492 }493494 // If the moved place is used generically by the callee and a reference to it495 // would still satisfy any bounds on its type, suggest borrowing.496 if let Some(¶m) = arg_param497 && let hir::Node::Expr(call_expr) = parent498 && let Some(ref_mutability) = self.suggest_borrow_generic_arg(499 err,500 typeck,501 call_expr,502 def_id,503 param,504 moved_place,505 pos + offset,506 ty,507 expr.span,508 )509 {510 can_suggest_clone = ref_mutability.is_mut();511 } else if let Some(local_def_id) = def_id.as_local()512 && let node = self.infcx.tcx.hir_node_by_def_id(local_def_id)513 && let Some(fn_decl) = node.fn_decl()514 && let Some(ident) = node.ident()515 && let Some(arg) = fn_decl.inputs.get(pos + offset)516 {517 // If we can't suggest borrowing in the call, but the function definition518 // is local, instead offer changing the function to borrow that argument.519 let mut span: MultiSpan = arg.span.into();520 span.push_span_label(521 arg.span,522 "this parameter takes ownership of the value".to_string(),523 );524 let descr = match node.fn_kind() {525 Some(hir::intravisit::FnKind::ItemFn(..)) | None => "function",526 Some(hir::intravisit::FnKind::Method(..)) => "method",527 Some(hir::intravisit::FnKind::Closure) => "closure",528 };529 span.push_span_label(ident.span, format!("in this {descr}"));530 err.span_note(531 span,532 format!(533 "consider changing this parameter type in {descr} `{ident}` to \534 borrow instead if owning the value isn't necessary",535 ),536 );537 }538 }539 if let hir::Node::Expr(parent_expr) = parent540 && let hir::ExprKind::Call(call_expr, _) = parent_expr.kind541 && let hir::ExprKind::Path(qpath) = call_expr.kind542 && tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter)543 {544 // Do not suggest `.clone()` in a `for` loop, we already suggest borrowing.545 } else if let UseSpans::FnSelfUse { kind: CallKind::Normal { .. }, .. } = move_spans546 {547 // We already suggest cloning for these cases in `explain_captures`.548 } else if moved_or_invoked_closure {549 // Do not suggest `closure.clone()()`.550 } else if let UseSpans::ClosureUse {551 closure_kind:552 ClosureKind::Coroutine(CoroutineKind::Desugared(_, CoroutineSource::Block)),553 ..554 } = move_spans555 && can_suggest_clone556 {557 self.suggest_cloning(err, place.as_ref(), ty, expr, Some(move_spans));558 } else if self.suggest_hoisting_call_outside_loop(err, expr) && can_suggest_clone {559 // The place where the type moves would be misleading to suggest clone.560 // #121466561 self.suggest_cloning(err, place.as_ref(), ty, expr, Some(move_spans));562 }563 }564565 self.suggest_ref_for_dbg_args(expr, place, move_span, err);566567 // it's useless to suggest inserting `ref` when the span don't comes from local code568 if let Some(pat) = finder.pat569 && !move_span.is_dummy()570 && !self.infcx.tcx.sess.source_map().is_imported(move_span)571 {572 let mut sugg = vec![(pat.span.shrink_to_lo(), "ref ".to_string())];573 if let Some(pat) = finder.parent_pat {574 sugg.insert(0, (pat.span.shrink_to_lo(), "ref ".to_string()));575 }576 err.multipart_suggestion(577 "borrow this binding in the pattern to avoid moving the value",578 sugg,579 Applicability::MachineApplicable,580 );581 }582 }583 }584585 // for dbg!(x) which may take ownership, suggest dbg!(&x) instead586 // but here we actually do not check whether the macro name is `dbg!`587 // so that we may extend the scope a bit larger to cover more cases588 fn suggest_ref_for_dbg_args(589 &self,590 body: &hir::Expr<'_>,591 place: &Place<'tcx>,592 move_span: Span,593 err: &mut Diag<'_>,594 ) {595 let var_info = self.body.var_debug_info.iter().find(|info| match info.value {596 VarDebugInfoContents::Place(ref p) => p == place,597 _ => false,598 });599 let Some(var_info) = var_info else { return };600 let arg_name = var_info.name;601 struct MatchArgFinder {602 expr_span: Span,603 match_arg_span: Option<Span>,604 arg_name: Symbol,605 }606 impl Visitor<'_> for MatchArgFinder {607 fn visit_expr(&mut self, e: &hir::Expr<'_>) {608 // dbg! is expanded into a match pattern, we need to find the right argument span609 if let hir::ExprKind::Match(expr, ..) = &e.kind610 && let hir::ExprKind::Path(hir::QPath::Resolved(611 _,612 path @ Path { segments: [seg], .. },613 )) = &expr.kind614 && seg.ident.name == self.arg_name615 && self.expr_span.source_callsite().contains(expr.span)616 {617 self.match_arg_span = Some(path.span);618 }619 hir::intravisit::walk_expr(self, e);620 }621 }622623 let mut finder = MatchArgFinder { expr_span: move_span, match_arg_span: None, arg_name };624 finder.visit_expr(body);625 if let Some(macro_arg_span) = finder.match_arg_span {626 err.span_suggestion_verbose(627 macro_arg_span.shrink_to_lo(),628 "consider borrowing instead of transferring ownership",629 "&",630 Applicability::MachineApplicable,631 );632 }633 }634635 pub(crate) fn suggest_reborrow(636 &self,637 err: &mut Diag<'_>,638 span: Span,639 moved_place: PlaceRef<'tcx>,640 ) {641 err.span_suggestion_verbose(642 span.shrink_to_lo(),643 format!(644 "consider creating a fresh reborrow of {} here",645 self.describe_place(moved_place)646 .map(|n| format!("`{n}`"))647 .unwrap_or_else(|| "the mutable reference".to_string()),648 ),649 "&mut *",650 Applicability::MachineApplicable,651 );652 }653654 /// If a place is used after being moved as an argument to a function, the function is generic655 /// in that argument, and a reference to the argument's type would still satisfy the function's656 /// bounds, suggest borrowing. This covers, e.g., borrowing an `impl Fn()` argument being passed657 /// in an `impl FnOnce()` position.658 /// Returns `Some(mutability)` when suggesting to borrow with mutability `mutability`, or `None`659 /// if no suggestion is made.660 fn suggest_borrow_generic_arg(661 &self,662 err: &mut Diag<'_>,663 typeck: &ty::TypeckResults<'tcx>,664 call_expr: &hir::Expr<'tcx>,665 callee_did: DefId,666 param: ty::ParamTy,667 moved_place: PlaceRef<'tcx>,668 moved_arg_pos: usize,669 moved_arg_ty: Ty<'tcx>,670 place_span: Span,671 ) -> Option<ty::Mutability> {672 let tcx = self.infcx.tcx;673 let sig = tcx.fn_sig(callee_did).instantiate_identity().skip_binder();674 let clauses = tcx.clauses_of(callee_did);675676 let generic_args = match call_expr.kind {677 // For method calls, generic arguments are attached to the call node.678 hir::ExprKind::MethodCall(..) => typeck.node_args_opt(call_expr.hir_id)?,679 // For normal calls, generic arguments are in the callee's type.680 // This diagnostic is only run for `FnDef` callees.681 hir::ExprKind::Call(callee, _)682 if let &ty::FnDef(_, args) = typeck.node_type(callee.hir_id).kind() =>683 {684 args.no_bound_vars().unwrap()685 }686 _ => return None,687 };688689 // First, is there at least one method on one of `param`'s trait bounds?690 // This keeps us from suggesting borrowing the argument to `mem::drop`, e.g.691 if !clauses.instantiate_identity(tcx).clauses.iter().any(|clause| {692 clause.as_trait_clause().is_some_and(|tc| {693 tc.self_ty().skip_binder().is_param(param.index)694 && tc.polarity() == ty::PredicatePolarity::Positive695 && supertrait_def_ids(tcx, tc.def_id())696 .flat_map(|trait_did| tcx.associated_items(trait_did).in_definition_order())697 .any(|item| item.is_method())698 })699 }) {700 return None;701 }702703 // Try borrowing a shared reference first, then mutably.704 if let Some(mutbl) = [ty::Mutability::Not, ty::Mutability::Mut].into_iter().find(|&mutbl| {705 let re = self.infcx.tcx.lifetimes.re_erased;706 let ref_ty = Ty::new_ref(self.infcx.tcx, re, moved_arg_ty, mutbl);707708 // Ensure that substituting `ref_ty` in the callee's signature doesn't break709 // other inputs or the return type.710 let new_args = tcx.mk_args_from_iter(generic_args.iter().enumerate().map(711 |(i, arg)| {712 if i == param.index as usize { ref_ty.into() } else { arg }713 },714 ));715 let can_subst = |ty: Ty<'tcx>| {716 // Normalize before comparing to see through type aliases and projections.717 let old_ty = ty::EarlyBinder::bind(tcx, ty).instantiate(tcx, generic_args);718 let new_ty = ty::EarlyBinder::bind(tcx, ty).instantiate(tcx, new_args);719 if let Ok(old_ty) = tcx.try_normalize_erasing_regions(720 self.infcx.typing_env(self.infcx.param_env),721 old_ty,722 ) && let Ok(new_ty) = tcx.try_normalize_erasing_regions(723 self.infcx.typing_env(self.infcx.param_env),724 new_ty,725 ) {726 old_ty == new_ty727 } else {728 false729 }730 };731 if !can_subst(sig.output())732 || sig733 .inputs()734 .iter()735 .enumerate()736 .any(|(i, &input_ty)| i != moved_arg_pos && !can_subst(input_ty))737 {738 return false;739 }740741 // Test the callee's clauses, substituting in `ref_ty` for the moved argument type.742 clauses.instantiate(tcx, new_args).clauses.iter().all(|clause| {743 // Normalize before testing to see through type aliases and projections.744 let normalized = tcx745 .try_normalize_erasing_regions(746 self.infcx.typing_env(self.infcx.param_env),747 *clause,748 )749 .unwrap_or_else(|_| clause.skip_norm_wip());750 self.infcx.predicate_must_hold_modulo_regions(&Obligation::new(751 tcx,752 ObligationCause::dummy(),753 self.infcx.param_env,754 normalized,755 ))756 })757 }) {758 let place_desc = if let Some(desc) = self.describe_place(moved_place) {759 format!("`{desc}`")760 } else {761 "here".to_owned()762 };763 err.span_suggestion_verbose(764 place_span.shrink_to_lo(),765 format!("consider {}borrowing {place_desc}", mutbl.mutably_str()),766 mutbl.ref_prefix_str(),767 Applicability::MaybeIncorrect,768 );769 Some(mutbl)770 } else {771 None772 }773 }774775 /// Returns `true` if the given initialization can reach the error location.776 ///777 /// This is used to determine whether an initialization should be considered778 /// when reporting diagnostics at `err_location`.779 ///780 /// The check proceeds in two stages:781 ///782 /// 1. If the initialization originates from a function argument, it is783 /// considered reachable by definition.784 /// 2. If the initialization's basic block dominates the error block, then785 /// every path to the error must pass through the initialization, so it is786 /// reachable.787 /// 3. Otherwise, perform a graph traversal over the MIR control-flow graph to788 /// determine whether any path exists from the initialization block to the789 /// error block.790 ///791 /// The dominance check acts as a fast path for the common case, while the CFG792 /// traversal handles cases where the initialization does not dominate the793 /// error location but can still reach it through an alternate control-flow794 /// path.795 fn is_init_reachable(&self, init: &Init, err_location: mir::Location) -> bool {796 let dominators = self.body.basic_blocks.dominators();797 let init_block = match init.location {798 InitLocation::Argument(_) => return true,799 InitLocation::Statement(location) => location.block,800 };801 let err_block = err_location.block;802 if dominators.dominates(init_block, err_block) {803 return true;804 }805 // If init_block doesn't dominate error_block, check if there is any valid path from the806 // initialization block to the error block in the Control Flow Graph.807 let mut visited = DenseBitSet::new_empty(self.body.basic_blocks.len());808 let mut stack = vec![init_block];809 while let Some(block) = stack.pop() {810 if block == err_block {811 return true;812 }813 if visited.insert(block) {814 let data = &self.body.basic_blocks[block];815 for successor in data.terminator().successors() {816 stack.push(successor);817 }818 }819 }820 false821 }822823 fn report_use_of_uninitialized(824 &self,825 mpi: MovePathIndex,826 used_place: PlaceRef<'tcx>,827 moved_place: PlaceRef<'tcx>,828 desired_action: InitializationRequiringAction,829 location: Location,830 span: Span,831 use_spans: UseSpans<'tcx>,832 ) -> Diag<'diag> {833 // We need all statements in the body where the binding was assigned to later find all834 // the branching code paths where the binding *wasn't* assigned to.835 let inits = &self.move_data.init_path_map[mpi];836 let move_path = &self.move_data.move_paths[mpi];837 let decl_span = self.body.local_decls[move_path.place.local].source_info.span;838 let mut all_init_spans_set = FxIndexSet::default();839 let mut reachable_spans_set = FxIndexSet::default();840 for init_idx in inits {841 let init = &self.move_data.inits[*init_idx];842 let span = init.span(self.body);843 if !span.is_dummy() {844 all_init_spans_set.insert(span);845 if self.is_init_reachable(init, location) {846 reachable_spans_set.insert(span);847 }848 }849 }850 let all_init_spans: Vec<_> = all_init_spans_set.into_iter().collect();851 let reachable_spans: Vec<_> = reachable_spans_set.into_iter().collect();852853 let (name, desc) = match self.describe_place_with_options(854 moved_place,855 DescribePlaceOpt { including_downcast: true, including_tuple_field: true },856 ) {857 Some(name) => (format!("`{name}`"), format!("`{name}` ")),858 None => ("the variable".to_string(), String::new()),859 };860 let path = match self.describe_place_with_options(861 used_place,862 DescribePlaceOpt { including_downcast: true, including_tuple_field: true },863 ) {864 Some(name) => format!("`{name}`"),865 None => "value".to_string(),866 };867868 // We use the statements were the binding was initialized, and inspect the HIR to look869 // for the branching codepaths that aren't covered, to point at them.870 let tcx = self.infcx.tcx;871 let body = tcx.hir_body_owned_by(self.mir_def_id());872 let mut visitor =873 ConditionVisitor { tcx, spans: all_init_spans.clone(), name, errors: vec![] };874 visitor.visit_body(&body);875876 let mut show_assign_sugg = false;877 let isnt_initialized = if let InitializationRequiringAction::PartialAssignment878 | InitializationRequiringAction::Assignment = desired_action879 {880 // The same error is emitted for bindings that are *sometimes* initialized and the ones881 // that are *partially* initialized by assigning to a field of an uninitialized882 // binding. We differentiate between them for more accurate wording here.883 "isn't fully initialized"884 } else if !reachable_spans.iter().any(|i| {885 // We filter these to avoid misleading wording in cases like the following,886 // where `x` has an `init`, but it is in the same place we're looking at:887 // ```888 // let x;889 // x += 1;890 // ```891 !i.contains(span)892 // We filter these to avoid incorrect main message on `match-cfg-fake-edges.rs`893 && !visitor894 .errors895 .iter()896 .map(|error| error.span)897 .any(|sp| span < sp && !sp.contains(span))898 }) {899 show_assign_sugg = true;900 if all_init_spans.iter().any(|init_span| !init_span.contains(span))901 && reachable_spans.is_empty()902 {903 "isn't initialized on any path leading to this point"904 } else {905 "isn't initialized"906 }907 } else {908 "is possibly-uninitialized"909 };910911 let used = desired_action.as_general_verb_in_past_tense();912 let mut err = struct_span_code_err!(913 self.dcx(),914 span,915 E0381,916 "{used} binding {desc}{isnt_initialized}"917 );918 use_spans.var_path_only_subdiag(&mut err, desired_action);919920 if let InitializationRequiringAction::PartialAssignment921 | InitializationRequiringAction::Assignment = desired_action922 {923 err.help(924 "partial initialization isn't supported, fully initialize the binding with a \925 default value and mutate it, or use `std::mem::MaybeUninit`",926 );927 }928 err.span_label(span, format!("{path} {used} here but it {isnt_initialized}"));929930 let mut shown = false;931 let mut shown_condition_value = false;932 for error in visitor.errors {933 if error.span < span && !error.span.overlaps(span) {934 // When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention935 // match arms coming after the primary span because they aren't relevant:936 // ```937 // let x;938 // match y {939 // _ if { x = 2; true } => {}940 // _ if {941 // x; //~ ERROR942 // false943 // } => {}944 // _ => {} // We don't want to point to this.945 // };946 // ```947 shown_condition_value |= error.kind.describes_condition_value();948 err.span_label(error.span, error.label);949 shown = true;950 }951 }952 if !shown {953 for sp in &reachable_spans {954 if *sp < span && !sp.overlaps(span) {955 err.span_label(*sp, "binding initialized here in some conditions");956 }957 }958 }959960 err.span_label(decl_span, "binding declared here but left uninitialized");961 if shown_condition_value {962 err.note(963 "when checking initialization, the compiler describes possible control-flow paths \964 without evaluating whether branch conditions can actually have the values shown",965 );966 }967 if show_assign_sugg {968 struct LetVisitor {969 decl_span: Span,970 sugg: Option<(Span, bool)>,971 }972973 impl<'v> Visitor<'v> for LetVisitor {974 fn visit_stmt(&mut self, ex: &'v hir::Stmt<'v>) {975 if self.sugg.is_some() {976 return;977 }978979 // FIXME: We make sure that this is a normal top-level binding,980 // but we could suggest `todo!()` for all uninitialized bindings in the pattern981 if let hir::StmtKind::Let(hir::LetStmt { span, ty, init: None, pat, .. }) =982 &ex.kind983 && let hir::PatKind::Binding(binding_mode, ..) = pat.kind984 && span.contains(self.decl_span)985 {986 // Insert after the whole binding pattern so suggestions stay valid for987 // bindings with `@` subpatterns like `ref mut x @ v`.988 let strip_ref = matches!(binding_mode.0, hir::ByRef::Yes(..));989 self.sugg =990 ty.map_or(Some((pat.span, strip_ref)), |ty| Some((ty.span, strip_ref)));991 }992 hir::intravisit::walk_stmt(self, ex);993 }994 }995996 let mut visitor = LetVisitor { decl_span, sugg: None };997 visitor.visit_body(&body);998 if let Some((span, strip_ref)) = visitor.sugg {999 self.suggest_assign_value(&mut err, moved_place, span, strip_ref);1000 }1001 }1002 err1003 }10041005 fn suggest_assign_value(1006 &self,1007 err: &mut Diag<'_>,1008 moved_place: PlaceRef<'tcx>,1009 sugg_span: Span,1010 strip_ref: bool,1011 ) {1012 let mut ty = moved_place.ty(self.body, self.infcx.tcx).ty;1013 if strip_ref && let ty::Ref(_, inner, _) = ty.kind() {1014 ty = *inner;1015 }1016 debug!("ty: {:?}, kind: {:?}", ty, ty.kind());10171018 let Some(assign_value) = self.infcx.err_ctxt().ty_kind_suggestion(self.infcx.param_env, ty)1019 else {1020 return;1021 };10221023 err.span_suggestion_verbose(1024 sugg_span.shrink_to_hi(),1025 "consider assigning a value",1026 format!(" = {assign_value}"),1027 Applicability::MaybeIncorrect,1028 );1029 }10301031 /// In a move error that occurs on a call within a loop, we try to identify cases where cloning1032 /// the value would lead to a logic error. We infer these cases by seeing if the moved value is1033 /// part of the logic to break the loop, either through an explicit `break` or if the expression1034 /// is part of a `while let`.1035 fn suggest_hoisting_call_outside_loop(&self, err: &mut Diag<'_>, expr: &hir::Expr<'_>) -> bool {1036 let tcx = self.infcx.tcx;1037 let mut can_suggest_clone = true;10381039 // If the moved value is a locally declared binding, we'll look upwards on the expression1040 // tree until the scope where it is defined, and no further, as suggesting to move the1041 // expression beyond that point would be illogical.1042 let local_hir_id = if let hir::ExprKind::Path(hir::QPath::Resolved(1043 _,1044 hir::Path { res: hir::def::Res::Local(local_hir_id), .. },1045 )) = expr.kind1046 {1047 Some(local_hir_id)1048 } else {1049 // This case would be if the moved value comes from an argument binding, we'll just1050 // look within the entire item, that's fine.1051 None1052 };10531054 /// This will allow us to look for a specific `HirId`, in our case `local_hir_id` where the1055 /// binding was declared, within any other expression. We'll use it to search for the1056 /// binding declaration within every scope we inspect.1057 struct Finder {1058 hir_id: hir::HirId,1059 }1060 impl<'hir> Visitor<'hir> for Finder {1061 type Result = ControlFlow<()>;1062 fn visit_pat(&mut self, pat: &'hir hir::Pat<'hir>) -> Self::Result {1063 if pat.hir_id == self.hir_id {1064 return ControlFlow::Break(());1065 }1066 hir::intravisit::walk_pat(self, pat)1067 }1068 fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) -> Self::Result {1069 if ex.hir_id == self.hir_id {1070 return ControlFlow::Break(());1071 }1072 hir::intravisit::walk_expr(self, ex)1073 }1074 }1075 // The immediate HIR parent of the moved expression. We'll look for it to be a call.1076 let mut parent = None;1077 // The top-most loop where the moved expression could be moved to a new binding.1078 let mut outer_most_loop: Option<&hir::Expr<'_>> = None;1079 for (_, node) in tcx.hir_parent_iter(expr.hir_id) {1080 let e = match node {1081 hir::Node::Expr(e) => e,1082 hir::Node::LetStmt(hir::LetStmt { els: Some(els), .. }) => {1083 let mut finder = BreakFinder { found_breaks: vec![], found_continues: vec![] };1084 finder.visit_block(els);1085 if !finder.found_breaks.is_empty() {1086 // Don't suggest clone as it could be will likely end in an infinite1087 // loop.1088 // let Some(_) = foo(non_copy.clone()) else { break; }1089 // --- ^^^^^^^^ -----1090 can_suggest_clone = false;1091 }1092 continue;1093 }1094 _ => continue,1095 };1096 if let Some(&hir_id) = local_hir_id {1097 if (Finder { hir_id }).visit_expr(e).is_break() {1098 // The current scope includes the declaration of the binding we're accessing, we1099 // can't look up any further for loops.1100 break;1101 }1102 }1103 if parent.is_none() {1104 parent = Some(e);1105 }1106 match e.kind {1107 hir::ExprKind::Let(_) => {1108 match tcx.parent_hir_node(e.hir_id) {1109 hir::Node::Expr(hir::Expr {1110 kind: hir::ExprKind::If(cond, ..), ..1111 }) => {1112 if (Finder { hir_id: expr.hir_id }).visit_expr(cond).is_break() {1113 // The expression where the move error happened is in a `while let`1114 // condition Don't suggest clone as it will likely end in an1115 // infinite loop.1116 // while let Some(_) = foo(non_copy.clone()) { }1117 // --------- ^^^^^^^^1118 can_suggest_clone = false;1119 }1120 }1121 _ => {}1122 }1123 }1124 hir::ExprKind::Loop(..) => {1125 outer_most_loop = Some(e);1126 }1127 _ => {}1128 }1129 }1130 let loop_count: usize = tcx1131 .hir_parent_iter(expr.hir_id)1132 .map(|(_, node)| match node {1133 hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Loop(..), .. }) => 1,1134 _ => 0,1135 })1136 .sum();11371138 let sm = tcx.sess.source_map();1139 if let Some(in_loop) = outer_most_loop {1140 let mut finder = BreakFinder { found_breaks: vec![], found_continues: vec![] };1141 finder.visit_expr(in_loop);1142 // All of the spans for `break` and `continue` expressions.1143 let spans = finder1144 .found_breaks1145 .iter()1146 .chain(finder.found_continues.iter())1147 .map(|(_, span)| *span)1148 .filter(|span| {1149 !matches!(1150 span.desugaring_kind(),1151 Some(DesugaringKind::ForLoop | DesugaringKind::WhileLoop)1152 )1153 })1154 .collect::<Vec<Span>>();1155 // All of the spans for the loops above the expression with the move error.1156 let loop_spans: Vec<_> = tcx1157 .hir_parent_iter(expr.hir_id)1158 .filter_map(|(_, node)| match node {1159 hir::Node::Expr(hir::Expr { span, kind: hir::ExprKind::Loop(..), .. }) => {1160 Some(*span)1161 }1162 _ => None,1163 })1164 .collect();1165 // It is possible that a user written `break` or `continue` is in the wrong place. We1166 // point them out at the user for them to make a determination. (#92531)1167 if !spans.is_empty() && loop_count > 1 {1168 // Getting fancy: if the spans of the loops *do not* overlap, we only use the line1169 // number when referring to them. If there *are* overlaps (multiple loops on the1170 // same line) then we use the more verbose span output (`file.rs:col:ll`).1171 let mut lines: Vec<_> =1172 loop_spans.iter().map(|sp| sm.lookup_char_pos(sp.lo()).line).collect();1173 lines.sort();1174 lines.dedup();1175 let fmt_span = |span: Span| {1176 if lines.len() == loop_spans.len() {1177 format!("line {}", sm.lookup_char_pos(span.lo()).line)1178 } else {1179 sm.span_to_diagnostic_string(span)1180 }1181 };1182 let mut spans: MultiSpan = spans.into();1183 // Point at all the `continue`s and explicit `break`s in the relevant loops.1184 for (desc, elements) in [1185 ("`break` exits", &finder.found_breaks),1186 ("`continue` advances", &finder.found_continues),1187 ] {1188 for (destination, sp) in elements {1189 if let Ok(hir_id) = destination.target_id1190 && let hir::Node::Expr(expr) = tcx.hir_node(hir_id)1191 && !matches!(1192 sp.desugaring_kind(),1193 Some(DesugaringKind::ForLoop | DesugaringKind::WhileLoop)1194 )1195 {1196 spans.push_span_label(1197 *sp,1198 format!("this {desc} the loop at {}", fmt_span(expr.span)),1199 );1200 }1201 }1202 }1203 // Point at all the loops that are between this move and the parent item.1204 for span in loop_spans {1205 spans.push_span_label(sm.guess_head_span(span), "");1206 }12071208 // note: verify that your loop breaking logic is correct1209 // --> $DIR/nested-loop-moved-value-wrong-continue.rs:41:171210 // |1211 // 28 | for foo in foos {1212 // | ---------------1213 // ...1214 // 33 | for bar in &bars {1215 // | ----------------1216 // ...1217 // 41 | continue;1218 // | ^^^^^^^^ this `continue` advances the loop at line 331219 err.span_note(spans, "verify that your loop breaking logic is correct");1220 }1221 if let Some(parent) = parent1222 && let hir::ExprKind::MethodCall(..) | hir::ExprKind::Call(..) = parent.kind1223 {1224 // FIXME: We could check that the call's *parent* takes `&mut val` to make the1225 // suggestion more targeted to the `mk_iter(val).next()` case. Maybe do that only to1226 // check for whether to suggest `let value` or `let mut value`.12271228 let span = in_loop.span;1229 if !finder.found_breaks.is_empty()1230 && let Ok(value) = sm.span_to_snippet(parent.span)1231 {1232 // We know with high certainty that this move would affect the early return of a1233 // loop, so we suggest moving the expression with the move out of the loop.1234 let indent = if let Some(indent) = sm.indentation_before(span) {1235 format!("\n{indent}")1236 } else {1237 " ".to_string()1238 };1239 err.multipart_suggestion(1240 "consider moving the expression out of the loop so it is only moved once",1241 vec![1242 (span.shrink_to_lo(), format!("let mut value = {value};{indent}")),1243 (parent.span, "value".to_string()),1244 ],1245 Applicability::MaybeIncorrect,1246 );1247 }1248 }1249 }1250 can_suggest_clone1251 }12521253 /// We have `S { foo: val, ..base }`, and we suggest instead writing1254 /// `S { foo: val, bar: base.bar.clone(), .. }` when valid.1255 fn suggest_cloning_on_functional_record_update(1256 &self,1257 err: &mut Diag<'_>,1258 ty: Ty<'tcx>,1259 expr: &hir::Expr<'_>,1260 ) {1261 let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());1262 let hir::ExprKind::Struct(struct_qpath, fields, hir::StructTailExpr::Base(base)) =1263 expr.kind1264 else {1265 return;1266 };1267 let hir::QPath::Resolved(_, path) = struct_qpath else { return };1268 let hir::def::Res::Def(_, def_id) = path.res else { return };1269 let Some(expr_ty) = typeck_results.node_type_opt(expr.hir_id) else { return };1270 let ty::Adt(def, args) = expr_ty.kind() else { return };1271 let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = base.kind else { return };1272 let (hir::def::Res::Local(_)1273 | hir::def::Res::Def(1274 DefKind::Const { .. }1275 | DefKind::ConstParam1276 | DefKind::Static { .. }1277 | DefKind::AssocConst { .. },1278 _,1279 )) = path.res1280 else {1281 return;1282 };1283 let Ok(base_str) = self.infcx.tcx.sess.source_map().span_to_snippet(base.span) else {1284 return;1285 };12861287 // 1. look for the fields of type `ty`.1288 // 2. check if they are clone and add them to suggestion1289 // 3. check if there are any values left to `..` and remove it if not1290 // 4. emit suggestion to clone the field directly as `bar: base.bar.clone()`12911292 let mut final_field_count = fields.len();1293 let Some(variant) = def.variants().iter().find(|variant| variant.def_id == def_id) else {1294 // When we have an enum, look for the variant that corresponds to the variant the user1295 // wrote.1296 return;1297 };1298 let mut sugg = vec![];1299 for field in &variant.fields {1300 // In practice unless there are more than one field with the same type, we'll be1301 // suggesting a single field at a type, because we don't aggregate multiple borrow1302 // checker errors involving the functional record update syntax into a single one.1303 let field_ty = field.ty(self.infcx.tcx, args).skip_norm_wip();1304 let ident = field.ident(self.infcx.tcx);1305 if field_ty == ty && fields.iter().all(|field| field.ident.name != ident.name) {1306 // Suggest adding field and cloning it.1307 sugg.push(format!("{ident}: {base_str}.{ident}.clone()"));1308 final_field_count += 1;1309 }1310 }1311 let (span, sugg) = match fields {1312 [.., last] => (1313 if final_field_count == variant.fields.len() {1314 // We'll remove the `..base` as there aren't any fields left.1315 last.span.shrink_to_hi().with_hi(base.span.hi())1316 } else {1317 last.span.shrink_to_hi()1318 },1319 format!(", {}", sugg.join(", ")),1320 ),1321 // Account for no fields in suggestion span.1322 [] => (1323 expr.span.with_lo(struct_qpath.span().hi()),1324 if final_field_count == variant.fields.len() {1325 // We'll remove the `..base` as there aren't any fields left.1326 format!(" {{ {} }}", sugg.join(", "))1327 } else {1328 format!(" {{ {}, ..{base_str} }}", sugg.join(", "))1329 },1330 ),1331 };1332 let prefix = if !self.implements_clone(ty) {1333 let msg = format!("`{ty}` doesn't implement `Copy` or `Clone`");1334 if let ty::Adt(def, _) = ty.kind() {1335 err.span_note(self.infcx.tcx.def_span(def.did()), msg);1336 } else {1337 err.note(msg);1338 }1339 format!("if `{ty}` implemented `Clone`, you could ")1340 } else {1341 String::new()1342 };1343 let msg = format!(1344 "{prefix}clone the value from the field instead of using the functional record update \1345 syntax",1346 );1347 err.span_suggestion_verbose(span, msg, sugg, Applicability::MachineApplicable);1348 }13491350 pub(crate) fn suggest_cloning(1351 &self,1352 err: &mut Diag<'_>,1353 place: PlaceRef<'tcx>,1354 ty: Ty<'tcx>,1355 expr: &'tcx hir::Expr<'tcx>,1356 use_spans: Option<UseSpans<'tcx>>,1357 ) {1358 if let hir::ExprKind::Struct(_, _, hir::StructTailExpr::Base(_)) = expr.kind {1359 // We have `S { foo: val, ..base }`. In `check_aggregate_rvalue` we have a single1360 // `Location` that covers both the `S { ... }` literal, all of its fields and the1361 // `base`. If the move happens because of `S { foo: val, bar: base.bar }` the `expr`1362 // will already be correct. Instead, we see if we can suggest writing.1363 self.suggest_cloning_on_functional_record_update(err, ty, expr);1364 return;1365 }13661367 if self.implements_clone(ty) {1368 if self.in_move_closure(expr) {1369 if let Some(name) = self.describe_place(place) {1370 self.suggest_clone_of_captured_var_in_move_closure(err, &name, use_spans);1371 }1372 } else {1373 self.suggest_cloning_inner(err, ty, expr);1374 }1375 } else if let ty::Adt(def, args) = ty.kind()1376 && let Some(local_did) = def.did().as_local()1377 && def.variants().iter().all(|variant| {1378 variant.fields.iter().all(|field| {1379 self.implements_clone(field.ty(self.infcx.tcx, args).skip_norm_wip())1380 })1381 })1382 {1383 let ty_span = self.infcx.tcx.def_span(def.did());1384 let mut span: MultiSpan = ty_span.into();1385 let mut derive_clone = false;1386 self.infcx.tcx.for_each_relevant_impl(1387 self.infcx.tcx.lang_items().clone_trait().unwrap(),1388 ty,1389 |def_id| {1390 if self.infcx.tcx.is_automatically_derived(def_id) {1391 derive_clone = true;1392 span.push_span_label(1393 self.infcx.tcx.def_span(def_id),1394 "derived `Clone` adds implicit bounds on type parameters",1395 );1396 if let Some(generics) = self.infcx.tcx.hir_get_generics(local_did) {1397 for param in generics.params {1398 if let hir::GenericParamKind::Type { .. } = param.kind {1399 span.push_span_label(1400 param.span,1401 format!(1402 "introduces an implicit `{}: Clone` bound",1403 param.name.ident()1404 ),1405 );1406 }1407 }1408 }1409 }1410 },1411 );1412 let msg = if !derive_clone {1413 span.push_span_label(1414 ty_span,1415 format!(1416 "consider {}implementing `Clone` for this type",1417 if derive_clone { "manually " } else { "" }1418 ),1419 );1420 format!("if `{ty}` implemented `Clone`, you could clone the value")1421 } else {1422 format!("if all bounds were met, you could clone the value")1423 };1424 span.push_span_label(expr.span, "you could clone this value");1425 err.span_note(span, msg);1426 if derive_clone {1427 err.help("consider manually implementing `Clone` to avoid undesired bounds");1428 }1429 } else if let ty::Param(param) = ty.kind()1430 && let Some(_clone_trait_def) = self.infcx.tcx.lang_items().clone_trait()1431 && let generics = self.infcx.tcx.generics_of(self.mir_def_id())1432 && let generic_param = generics.type_param(*param, self.infcx.tcx)1433 && let param_span = self.infcx.tcx.def_span(generic_param.def_id)1434 && if let Some(UseSpans::FnSelfUse { kind, .. }) = use_spans1435 && let CallKind::FnCall { fn_trait_id, self_ty } = kind1436 && let ty::Param(_) = self_ty.kind()1437 && ty == self_ty1438 && self.infcx.tcx.fn_trait_kind_from_def_id(fn_trait_id).is_some()1439 {1440 // Do not suggest `F: FnOnce() + Clone`.1441 false1442 } else {1443 true1444 }1445 {1446 let mut span: MultiSpan = param_span.into();1447 span.push_span_label(1448 param_span,1449 "consider constraining this type parameter with `Clone`",1450 );1451 span.push_span_label(expr.span, "you could clone this value");1452 err.span_help(1453 span,1454 format!("if `{ty}` implemented `Clone`, you could clone the value"),1455 );1456 } else if let ty::Adt(_, _) = ty.kind()1457 && let Some(clone_trait) = self.infcx.tcx.lang_items().clone_trait()1458 {1459 // For cases like `Option<NonClone>`, where `Option<T>: Clone` if `T: Clone`, we point1460 // at the types that should be `Clone`.1461 let ocx = ObligationCtxt::new_with_diagnostics(self.infcx);1462 let cause = ObligationCause::misc(expr.span, self.mir_def_id());1463 ocx.register_bound(cause, self.infcx.param_env, ty, clone_trait);1464 let errors = ocx.evaluate_obligations_error_on_ambiguity();1465 if let TraitErrors::HasErrors(errors) = errors1466 && errors.iter().all(|error| {1467 match error.obligation.predicate.as_clause().and_then(|c| c.as_trait_clause()) {1468 Some(clause) => match clause.self_ty().skip_binder().kind() {1469 ty::Adt(def, _) => {1470 def.did().is_local() && clause.def_id() == clone_trait1471 }1472 _ => false,1473 },1474 None => false,1475 }1476 })1477 {1478 let mut type_spans = vec![];1479 let mut types = FxIndexSet::default();1480 for clause in errors1481 .iter()1482 .filter_map(|e| e.obligation.predicate.as_clause())1483 .filter_map(|c| c.as_trait_clause())1484 {1485 let ty::Adt(def, _) = clause.self_ty().skip_binder().kind() else { continue };1486 type_spans.push(self.infcx.tcx.def_span(def.did()));1487 types.insert(1488 self.infcx1489 .tcx1490 .short_string(clause.self_ty().skip_binder(), &mut err.long_ty_path()),1491 );1492 }1493 let mut span: MultiSpan = type_spans.clone().into();1494 for sp in type_spans {1495 span.push_span_label(sp, "consider implementing `Clone` for this type");1496 }1497 span.push_span_label(expr.span, "you could clone this value");1498 let types: Vec<_> = types.into_iter().collect();1499 let msg = match &types[..] {1500 [only] => format!("`{only}`"),1501 [head @ .., last] => format!(1502 "{} and `{last}`",1503 head.iter().map(|t| format!("`{t}`")).collect::<Vec<_>>().join(", ")1504 ),1505 [] => unreachable!(),1506 };1507 err.span_note(1508 span,1509 format!("if {msg} implemented `Clone`, you could clone the value"),1510 );1511 }1512 }1513 }15141515 pub(crate) fn implements_clone(&self, ty: Ty<'tcx>) -> bool {1516 let Some(clone_trait_def) = self.infcx.tcx.lang_items().clone_trait() else { return false };1517 self.infcx1518 .type_implements_trait(clone_trait_def, [ty], self.infcx.param_env)1519 .must_apply_modulo_regions()1520 }15211522 /// Given an expression, check if it is a method call `foo.clone()`, where `foo` and1523 /// `foo.clone()` both have the same type, returning the span for `.clone()` if so.1524 pub(crate) fn clone_on_reference(&self, expr: &hir::Expr<'_>) -> Option<Span> {1525 let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());1526 if let hir::ExprKind::MethodCall(segment, rcvr, args, span) = expr.kind1527 && let Some(expr_ty) = typeck_results.node_type_opt(expr.hir_id)1528 && let Some(rcvr_ty) = typeck_results.node_type_opt(rcvr.hir_id)1529 && rcvr_ty == expr_ty1530 && segment.ident.name == sym::clone1531 && args.is_empty()1532 {1533 Some(span)1534 } else {1535 None1536 }1537 }15381539 fn in_move_closure(&self, expr: &hir::Expr<'_>) -> bool {1540 for (_, node) in self.infcx.tcx.hir_parent_iter(expr.hir_id) {1541 if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Closure(closure), .. }) = node1542 && let hir::CaptureBy::Value { .. } = closure.capture_clause1543 {1544 // `move || x.clone()` will not work. FIXME: suggest `let y = x.clone(); move || y`1545 return true;1546 }1547 }1548 false1549 }15501551 fn suggest_cloning_inner(1552 &self,1553 err: &mut Diag<'_>,1554 ty: Ty<'tcx>,1555 expr: &hir::Expr<'_>,1556 ) -> bool {1557 let tcx = self.infcx.tcx;15581559 // Don't suggest `.clone()` in a derive macro expansion.1560 if let ExpnKind::Macro(MacroKind::Derive, _) = self.body.span.ctxt().outer_expn_data().kind1561 {1562 return false;1563 }1564 if let Some(_) = self.clone_on_reference(expr) {1565 // Avoid redundant clone suggestion already suggested in `explain_captures`.1566 // See `tests/ui/moves/needs-clone-through-deref.rs`1567 return false;1568 }1569 // We don't want to suggest `.clone()` in a move closure, since the value has already been1570 // captured.1571 if self.in_move_closure(expr) {1572 return false;1573 }1574 // We also don't want to suggest cloning a closure itself, since the value has already been1575 // captured.1576 if let hir::ExprKind::Closure(_) = expr.kind {1577 return false;1578 }1579 // Try to find predicates on *generic params* that would allow copying `ty`1580 let mut suggestion =1581 if let Some(symbol) = tcx.hir_maybe_get_struct_pattern_shorthand_field(expr) {1582 format!(": {symbol}.clone()")1583 } else {1584 ".clone()".to_owned()1585 };1586 let mut sugg = Vec::with_capacity(2);1587 let mut inner_expr = expr;1588 let mut is_raw_ptr = false;1589 let typeck_result = self.infcx.tcx.typeck(self.mir_def_id());1590 // Remove uses of `&` and `*` when suggesting `.clone()`.1591 while let hir::ExprKind::AddrOf(.., inner) | hir::ExprKind::Unary(hir::UnOp::Deref, inner) =1592 &inner_expr.kind1593 {1594 if let hir::ExprKind::AddrOf(_, hir::Mutability::Mut, _) = inner_expr.kind {1595 // We assume that `&mut` refs are desired for their side-effects, so cloning the1596 // value wouldn't do what the user wanted.1597 return false;1598 }1599 inner_expr = inner;1600 if let Some(inner_type) = typeck_result.node_type_opt(inner.hir_id) {1601 if matches!(inner_type.kind(), ty::RawPtr(..)) {1602 is_raw_ptr = true;1603 break;1604 }1605 }1606 }1607 // Cloning the raw pointer doesn't make sense in some cases and would cause a type mismatch1608 // error. (see #126863)1609 if inner_expr.span.lo() != expr.span.lo() && !is_raw_ptr {1610 // Remove "(*" or "(&"1611 sugg.push((expr.span.with_hi(inner_expr.span.lo()), String::new()));1612 }1613 // Check whether `expr` is surrounded by parentheses or not.1614 let span = if inner_expr.span.hi() != expr.span.hi() {1615 // Account for `(*x)` to suggest `x.clone()`.1616 if is_raw_ptr {1617 expr.span.shrink_to_hi()1618 } else {1619 // Remove the close parenthesis ")"1620 expr.span.with_lo(inner_expr.span.hi())1621 }1622 } else {1623 if is_raw_ptr {1624 sugg.push((expr.span.shrink_to_lo(), "(".to_string()));1625 suggestion = ").clone()".to_string();1626 }1627 expr.span.shrink_to_hi()1628 };1629 sugg.push((span, suggestion));1630 let msg = if let ty::Adt(def, _) = ty.kind()1631 && [tcx.get_diagnostic_item(sym::Arc), tcx.get_diagnostic_item(sym::Rc)]1632 .contains(&Some(def.did()))1633 {1634 "clone the value to increment its reference count"1635 } else {1636 "consider cloning the value if the performance cost is acceptable"1637 };1638 err.multipart_suggestion(msg, sugg, Applicability::MachineApplicable);1639 true1640 }16411642 fn suggest_adding_bounds(&self, err: &mut Diag<'_>, ty: Ty<'tcx>, def_id: DefId, span: Span) {1643 let tcx = self.infcx.tcx;1644 let generics = tcx.generics_of(self.mir_def_id());16451646 let Some(hir_generics) =1647 tcx.hir_get_generics(tcx.typeck_root_def_id_local(self.mir_def_id()))1648 else {1649 return;1650 };1651 // Try to find predicates on *generic params* that would allow copying `ty`1652 let ocx = ObligationCtxt::new_with_diagnostics(self.infcx);1653 let cause = ObligationCause::misc(span, self.mir_def_id());16541655 ocx.register_bound(cause, self.infcx.param_env, ty, def_id);1656 let errors = ocx.evaluate_obligations_error_on_ambiguity();16571658 // Only emit suggestion if all required predicates are on generic1659 let predicates: Result<Vec<_>, _> = errors1660 .into_iter()1661 .map(|err| match err.obligation.predicate.kind().skip_binder() {1662 PredicateKind::Clause(ty::ClauseKind::Trait(predicate)) => {1663 match *predicate.self_ty().kind() {1664 ty::Param(param_ty) => Ok((1665 generics.type_param(param_ty, tcx),1666 predicate.trait_ref.print_trait_sugared().to_string(),1667 Some(predicate.trait_ref.def_id),1668 )),1669 _ => Err(()),1670 }1671 }1672 _ => Err(()),1673 })1674 .collect();16751676 if let Ok(predicates) = predicates {1677 suggest_constraining_type_params(1678 tcx,1679 hir_generics,1680 err,1681 predicates.iter().map(|(param, constraint, def_id)| {1682 (param.name.as_str(), &**constraint, *def_id)1683 }),1684 None,1685 );1686 }1687 }16881689 pub(crate) fn report_move_out_while_borrowed(1690 &mut self,1691 location: Location,1692 (place, span): (Place<'tcx>, Span),1693 borrow: &BorrowData<'tcx>,1694 ) {1695 debug!(1696 "report_move_out_while_borrowed: location={:?} place={:?} span={:?} borrow={:?}",1697 location, place, span, borrow1698 );1699 let value_msg = self.describe_any_place(place.as_ref());1700 let borrow_msg = self.describe_any_place(borrow.borrowed_place.as_ref());17011702 let borrow_spans = self.retrieve_borrow_spans(borrow);1703 let borrow_span = borrow_spans.args_or_use();17041705 let move_spans = self.move_spans(place.as_ref(), location);1706 let span = move_spans.args_or_use();17071708 let mut err = self.cannot_move_when_borrowed(1709 span,1710 borrow_span,1711 &self.describe_any_place(place.as_ref()),1712 &borrow_msg,1713 &value_msg,1714 );1715 self.note_due_to_edition_2024_opaque_capture_rules(borrow, &mut err);17161717 borrow_spans.var_path_only_subdiag(&mut err, crate::InitializationRequiringAction::Borrow);17181719 move_spans.var_subdiag(&mut err, None, |kind, var_span| {1720 use crate::session_diagnostics::CaptureVarCause::*;1721 match kind {1722 hir::ClosureKind::Coroutine(_) => MoveUseInCoroutine { var_span },1723 hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {1724 MoveUseInClosure { var_span }1725 }1726 }1727 });17281729 self.explain_why_borrow_contains_point(location, borrow, None)1730 .add_explanation_to_diagnostic(&self, &mut err, "", Some(borrow_span), None);1731 self.suggest_copy_for_type_in_cloned_ref(&mut err, place);1732 let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());1733 if let Some(expr) = self.find_expr(borrow_span) {1734 // This is a borrow span, so we want to suggest cloning the referent.1735 if let hir::ExprKind::AddrOf(_, _, borrowed_expr) = expr.kind1736 && let Some(ty) = typeck_results.expr_ty_opt(borrowed_expr)1737 {1738 self.suggest_cloning(&mut err, place.as_ref(), ty, borrowed_expr, Some(move_spans));1739 } else if typeck_results.expr_adjustments(expr).first().is_some_and(|adj| {1740 matches!(1741 adj.kind,1742 ty::adjustment::Adjust::Borrow(ty::adjustment::AutoBorrow::Ref(1743 ty::adjustment::AutoBorrowMutability::Not1744 | ty::adjustment::AutoBorrowMutability::Mut {1745 allow_two_phase_borrow: ty::adjustment::AllowTwoPhase::No1746 }1747 ))1748 )1749 }) && let Some(ty) = typeck_results.expr_ty_opt(expr)1750 {1751 self.suggest_cloning(&mut err, place.as_ref(), ty, expr, Some(move_spans));1752 }1753 }1754 self.buffer_error(err);1755 }17561757 pub(crate) fn report_use_while_mutably_borrowed(1758 &self,1759 location: Location,1760 (place, _span): (Place<'tcx>, Span),1761 borrow: &BorrowData<'tcx>,1762 ) -> Diag<'diag> {1763 let borrow_spans = self.retrieve_borrow_spans(borrow);1764 let borrow_span = borrow_spans.args_or_use();17651766 // Conflicting borrows are reported separately, so only check for move1767 // captures.1768 let use_spans = self.move_spans(place.as_ref(), location);1769 let span = use_spans.var_or_use();17701771 // If the attempted use is in a closure then we do not care about the path span of the1772 // place we are currently trying to use we call `var_span_label` on `borrow_spans` to1773 // annotate if the existing borrow was in a closure.1774 let mut err = self.cannot_use_when_mutably_borrowed(1775 span,1776 &self.describe_any_place(place.as_ref()),1777 borrow_span,1778 &self.describe_any_place(borrow.borrowed_place.as_ref()),1779 );1780 self.note_due_to_edition_2024_opaque_capture_rules(borrow, &mut err);17811782 borrow_spans.var_subdiag(&mut err, Some(borrow.kind), |kind, var_span| {1783 use crate::session_diagnostics::CaptureVarCause::*;1784 let place = &borrow.borrowed_place;1785 let desc_place = self.describe_any_place(place.as_ref());1786 match kind {1787 hir::ClosureKind::Coroutine(_) => {1788 BorrowUsePlaceCoroutine { place: desc_place, var_span, is_single_var: true }1789 }1790 hir::ClosureKind::Closure | hir::ClosureKind::CoroutineClosure(_) => {1791 BorrowUsePlaceClosure { place: desc_place, var_span, is_single_var: true }1792 }1793 }1794 });17951796 self.explain_why_borrow_contains_point(location, borrow, None)1797 .add_explanation_to_diagnostic(&self, &mut err, "", None, None);1798 err1799 }18001801 pub(crate) fn report_conflicting_borrow(1802 &self,1803 location: Location,1804 (place, span): (Place<'tcx>, Span),1805 gen_borrow_kind: BorrowKind,1806 issued_borrow: &BorrowData<'tcx>,1807 ) -> Diag<'diag> {1808 let issued_spans = self.retrieve_borrow_spans(issued_borrow);1809 let issued_span = issued_spans.args_or_use();18101811 let borrow_spans = self.borrow_spans(span, location);1812 let span = borrow_spans.args_or_use();18131814 let container_name = if issued_spans.for_coroutine() || borrow_spans.for_coroutine() {1815 "coroutine"1816 } else {1817 "closure"1818 };18191820 let (desc_place, msg_place, msg_borrow, union_type_name) =1821 self.describe_place_for_conflicting_borrow(place, issued_borrow.borrowed_place);18221823 let explanation = self.explain_why_borrow_contains_point(location, issued_borrow, None);1824 let second_borrow_desc = if explanation.is_explained() { "second " } else { "" };18251826 // FIXME: supply non-"" `opt_via` when appropriate1827 let first_borrow_desc;1828 let mut err = match (gen_borrow_kind, issued_borrow.kind) {1829 (1830 BorrowKind::Shared | BorrowKind::Fake(FakeBorrowKind::Deep),1831 BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow },1832 ) => {1833 first_borrow_desc = "mutable ";1834 let mut err = self.cannot_reborrow_already_borrowed(1835 span,1836 &desc_place,1837 &msg_place,1838 "immutable",1839 issued_span,1840 "it",1841 "mutable",1842 &msg_borrow,1843 None,1844 );1845 self.suggest_slice_method_if_applicable(1846 &mut err,1847 place,1848 issued_borrow.borrowed_place,1849 span,1850 issued_span,1851 );1852 err1853 }1854 (1855 BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow },1856 BorrowKind::Shared | BorrowKind::Fake(FakeBorrowKind::Deep),1857 ) => {1858 first_borrow_desc = "immutable ";1859 let mut err = self.cannot_reborrow_already_borrowed(1860 span,1861 &desc_place,1862 &msg_place,1863 "mutable",1864 issued_span,1865 "it",1866 "immutable",1867 &msg_borrow,1868 None,1869 );1870 self.suggest_slice_method_if_applicable(1871 &mut err,1872 place,1873 issued_borrow.borrowed_place,1874 span,1875 issued_span,1876 );1877 self.suggest_binding_for_closure_capture_self(&mut err, &issued_spans);1878 self.suggest_using_closure_argument_instead_of_capture(1879 &mut err,1880 issued_borrow.borrowed_place,1881 &issued_spans,1882 );1883 self.explain_iterator_invalidation_in_for_loop_if_applicable(1884 &mut err,1885 &issued_spans,1886 place,1887 issued_borrow.borrowed_place,1888 issued_borrow.kind,1889 span,1890 );1891 err1892 }18931894 (1895 BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow },1896 BorrowKind::Mut { kind: MutBorrowKind::Default | MutBorrowKind::TwoPhaseBorrow },1897 ) => {1898 first_borrow_desc = "first ";1899 let mut err = self.cannot_mutably_borrow_multiply(1900 span,1901 &desc_place,1902 &msg_place,1903 issued_span,1904 &msg_borrow,1905 None,1906 );1907 self.suggest_slice_method_if_applicable(1908 &mut err,1909 place,1910 issued_borrow.borrowed_place,1911 span,1912 issued_span,1913 );1914 self.explain_iterator_invalidation_in_for_loop_if_applicable(1915 &mut err,1916 &issued_spans,1917 place,1918 issued_borrow.borrowed_place,1919 issued_borrow.kind,1920 span,1921 );1922 self.suggest_using_closure_argument_instead_of_capture(1923 &mut err,1924 issued_borrow.borrowed_place,1925 &issued_spans,1926 );1927 self.explain_iterator_advancement_in_for_loop_if_applicable(1928 &mut err,1929 span,1930 &issued_spans,1931 );1932 err1933 }19341935 (1936 BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture },1937 BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture },1938 ) => {1939 first_borrow_desc = "first ";1940 self.cannot_uniquely_borrow_by_two_closures(span, &desc_place, issued_span, None)1941 }19421943 (BorrowKind::Mut { .. }, BorrowKind::Fake(FakeBorrowKind::Shallow)) => {1944 if let Some(immutable_section_description) =1945 self.classify_immutable_section(issued_borrow.assigned_place)1946 {1947 let mut err = self.cannot_mutate_in_immutable_section(1948 span,1949 issued_span,1950 &desc_place,1951 immutable_section_description,1952 "mutably borrow",1953 );1954 borrow_spans.var_subdiag(1955 &mut err,1956 Some(BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }),1957 |kind, var_span| {1958 use crate::session_diagnostics::CaptureVarCause::*;1959 match kind {1960 hir::ClosureKind::Coroutine(_) => BorrowUsePlaceCoroutine {1961 place: desc_place,1962 var_span,1963 is_single_var: true,1964 },1965 hir::ClosureKind::Closure1966 | hir::ClosureKind::CoroutineClosure(_) => BorrowUsePlaceClosure {1967 place: desc_place,1968 var_span,1969 is_single_var: true,1970 },1971 }1972 },1973 );1974 return err;1975 } else {1976 first_borrow_desc = "immutable ";1977 self.cannot_reborrow_already_borrowed(1978 span,1979 &desc_place,1980 &msg_place,1981 "mutable",1982 issued_span,1983 "it",1984 "immutable",1985 &msg_borrow,1986 None,1987 )1988 }1989 }19901991 (BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture }, _) => {1992 first_borrow_desc = "first ";1993 self.cannot_uniquely_borrow_by_one_closure(1994 span,1995 container_name,1996 &desc_place,1997 "",1998 issued_span,1999 "it",2000 "",
Findings
✓ No findings reported for this file.