diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_borrowck/src/diagnostics | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_borrowck/src/diagnostics')
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs | 494 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs | 2773 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs | 744 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs | 26 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/find_use.rs | 128 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/mod.rs | 1127 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/move_errors.rs | 529 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs | 1115 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs | 261 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/region_errors.rs | 904 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/region_name.rs | 896 | ||||
-rw-r--r-- | compiler/rustc_borrowck/src/diagnostics/var_name.rs | 133 |
12 files changed, 9130 insertions, 0 deletions
diff --git a/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs new file mode 100644 index 000000000..1ef2b0ae9 --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs @@ -0,0 +1,494 @@ +use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed}; +use rustc_infer::infer::canonical::Canonical; +use rustc_infer::infer::error_reporting::nice_region_error::NiceRegionError; +use rustc_infer::infer::region_constraints::Constraint; +use rustc_infer::infer::region_constraints::RegionConstraintData; +use rustc_infer::infer::RegionVariableOrigin; +use rustc_infer::infer::{InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt as _}; +use rustc_infer::traits::{Normalized, ObligationCause, TraitEngine, TraitEngineExt}; +use rustc_middle::ty::error::TypeError; +use rustc_middle::ty::RegionVid; +use rustc_middle::ty::UniverseIndex; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable}; +use rustc_span::Span; +use rustc_trait_selection::traits::query::type_op; +use rustc_trait_selection::traits::{SelectionContext, TraitEngineExt as _}; +use rustc_traits::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause}; + +use std::fmt; +use std::rc::Rc; + +use crate::region_infer::values::RegionElement; +use crate::session_diagnostics::HigherRankedErrorCause; +use crate::session_diagnostics::HigherRankedLifetimeError; +use crate::session_diagnostics::HigherRankedSubtypeError; +use crate::MirBorrowckCtxt; + +#[derive(Clone)] +pub(crate) struct UniverseInfo<'tcx>(UniverseInfoInner<'tcx>); + +/// What operation a universe was created for. +#[derive(Clone)] +enum UniverseInfoInner<'tcx> { + /// Relating two types which have binders. + RelateTys { expected: Ty<'tcx>, found: Ty<'tcx> }, + /// Created from performing a `TypeOp`. + TypeOp(Rc<dyn TypeOpInfo<'tcx> + 'tcx>), + /// Any other reason. + Other, +} + +impl<'tcx> UniverseInfo<'tcx> { + pub(crate) fn other() -> UniverseInfo<'tcx> { + UniverseInfo(UniverseInfoInner::Other) + } + + pub(crate) fn relate(expected: Ty<'tcx>, found: Ty<'tcx>) -> UniverseInfo<'tcx> { + UniverseInfo(UniverseInfoInner::RelateTys { expected, found }) + } + + pub(crate) fn report_error( + &self, + mbcx: &mut MirBorrowckCtxt<'_, 'tcx>, + placeholder: ty::PlaceholderRegion, + error_element: RegionElement, + cause: ObligationCause<'tcx>, + ) { + match self.0 { + UniverseInfoInner::RelateTys { expected, found } => { + let err = mbcx.infcx.report_mismatched_types( + &cause, + expected, + found, + TypeError::RegionsPlaceholderMismatch, + ); + mbcx.buffer_error(err); + } + UniverseInfoInner::TypeOp(ref type_op_info) => { + type_op_info.report_error(mbcx, placeholder, error_element, cause); + } + UniverseInfoInner::Other => { + // FIXME: This error message isn't great, but it doesn't show + // up in the existing UI tests. Consider investigating this + // some more. + mbcx.buffer_error( + mbcx.infcx.tcx.sess.create_err(HigherRankedSubtypeError { span: cause.span }), + ); + } + } + } +} + +pub(crate) trait ToUniverseInfo<'tcx> { + fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx>; +} + +impl<'tcx> ToUniverseInfo<'tcx> for crate::type_check::InstantiateOpaqueType<'tcx> { + fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { + UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(crate::type_check::InstantiateOpaqueType { + base_universe: Some(base_universe), + ..self + }))) + } +} + +impl<'tcx> ToUniverseInfo<'tcx> + for Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::prove_predicate::ProvePredicate<'tcx>>> +{ + fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { + UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(PredicateQuery { + canonical_query: self, + base_universe, + }))) + } +} + +impl<'tcx, T: Copy + fmt::Display + TypeFoldable<'tcx> + 'tcx> ToUniverseInfo<'tcx> + for Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>> +{ + fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { + UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(NormalizeQuery { + canonical_query: self, + base_universe, + }))) + } +} + +impl<'tcx> ToUniverseInfo<'tcx> + for Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::AscribeUserType<'tcx>>> +{ + fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { + UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(AscribeUserTypeQuery { + canonical_query: self, + base_universe, + }))) + } +} + +impl<'tcx, F, G> ToUniverseInfo<'tcx> for Canonical<'tcx, type_op::custom::CustomTypeOp<F, G>> { + fn to_universe_info(self, _base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { + // We can't rerun custom type ops. + UniverseInfo::other() + } +} + +impl<'tcx> ToUniverseInfo<'tcx> for ! { + fn to_universe_info(self, _base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> { + self + } +} + +#[allow(unused_lifetimes)] +trait TypeOpInfo<'tcx> { + /// Returns an error to be reported if rerunning the type op fails to + /// recover the error's cause. + fn fallback_error( + &self, + tcx: TyCtxt<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>; + + fn base_universe(&self) -> ty::UniverseIndex; + + fn nice_error( + &self, + mbcx: &mut MirBorrowckCtxt<'_, 'tcx>, + cause: ObligationCause<'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option<ty::Region<'tcx>>, + ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>>; + + fn report_error( + &self, + mbcx: &mut MirBorrowckCtxt<'_, 'tcx>, + placeholder: ty::PlaceholderRegion, + error_element: RegionElement, + cause: ObligationCause<'tcx>, + ) { + let tcx = mbcx.infcx.tcx; + let base_universe = self.base_universe(); + + let Some(adjusted_universe) = + placeholder.universe.as_u32().checked_sub(base_universe.as_u32()) + else { + mbcx.buffer_error(self.fallback_error(tcx, cause.span)); + return; + }; + + let placeholder_region = tcx.mk_region(ty::RePlaceholder(ty::Placeholder { + name: placeholder.name, + universe: adjusted_universe.into(), + })); + + let error_region = + if let RegionElement::PlaceholderRegion(error_placeholder) = error_element { + let adjusted_universe = + error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32()); + adjusted_universe.map(|adjusted| { + tcx.mk_region(ty::RePlaceholder(ty::Placeholder { + name: error_placeholder.name, + universe: adjusted.into(), + })) + }) + } else { + None + }; + + debug!(?placeholder_region); + + let span = cause.span; + let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_region); + + if let Some(nice_error) = nice_error { + mbcx.buffer_error(nice_error); + } else { + mbcx.buffer_error(self.fallback_error(tcx, span)); + } + } +} + +struct PredicateQuery<'tcx> { + canonical_query: + Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::prove_predicate::ProvePredicate<'tcx>>>, + base_universe: ty::UniverseIndex, +} + +impl<'tcx> TypeOpInfo<'tcx> for PredicateQuery<'tcx> { + fn fallback_error( + &self, + tcx: TyCtxt<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + tcx.sess.create_err(HigherRankedLifetimeError { + cause: Some(HigherRankedErrorCause::CouldNotProve { + predicate: self.canonical_query.value.value.predicate.to_string(), + }), + span, + }) + } + + fn base_universe(&self) -> ty::UniverseIndex { + self.base_universe + } + + fn nice_error( + &self, + mbcx: &mut MirBorrowckCtxt<'_, 'tcx>, + cause: ObligationCause<'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option<ty::Region<'tcx>>, + ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { + mbcx.infcx.tcx.infer_ctxt().enter_with_canonical( + cause.span, + &self.canonical_query, + |ref infcx, key, _| { + let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx); + type_op_prove_predicate_with_cause(infcx, &mut *fulfill_cx, key, cause); + try_extract_error_from_fulfill_cx( + fulfill_cx, + infcx, + placeholder_region, + error_region, + ) + }, + ) + } +} + +struct NormalizeQuery<'tcx, T> { + canonical_query: Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>, + base_universe: ty::UniverseIndex, +} + +impl<'tcx, T> TypeOpInfo<'tcx> for NormalizeQuery<'tcx, T> +where + T: Copy + fmt::Display + TypeFoldable<'tcx> + 'tcx, +{ + fn fallback_error( + &self, + tcx: TyCtxt<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + tcx.sess.create_err(HigherRankedLifetimeError { + cause: Some(HigherRankedErrorCause::CouldNotNormalize { + value: self.canonical_query.value.value.value.to_string(), + }), + span, + }) + } + + fn base_universe(&self) -> ty::UniverseIndex { + self.base_universe + } + + fn nice_error( + &self, + mbcx: &mut MirBorrowckCtxt<'_, 'tcx>, + cause: ObligationCause<'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option<ty::Region<'tcx>>, + ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { + mbcx.infcx.tcx.infer_ctxt().enter_with_canonical( + cause.span, + &self.canonical_query, + |ref infcx, key, _| { + let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx); + + let mut selcx = SelectionContext::new(infcx); + + // FIXME(lqd): Unify and de-duplicate the following with the actual + // `rustc_traits::type_op::type_op_normalize` query to allow the span we need in the + // `ObligationCause`. The normalization results are currently different between + // `AtExt::normalize` used in the query and `normalize` called below: the former fails + // to normalize the `nll/relate_tys/impl-fn-ignore-binder-via-bottom.rs` test. Check + // after #85499 lands to see if its fixes have erased this difference. + let (param_env, value) = key.into_parts(); + let Normalized { value: _, obligations } = rustc_trait_selection::traits::normalize( + &mut selcx, + param_env, + cause, + value.value, + ); + fulfill_cx.register_predicate_obligations(infcx, obligations); + + try_extract_error_from_fulfill_cx( + fulfill_cx, + infcx, + placeholder_region, + error_region, + ) + }, + ) + } +} + +struct AscribeUserTypeQuery<'tcx> { + canonical_query: Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::AscribeUserType<'tcx>>>, + base_universe: ty::UniverseIndex, +} + +impl<'tcx> TypeOpInfo<'tcx> for AscribeUserTypeQuery<'tcx> { + fn fallback_error( + &self, + tcx: TyCtxt<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + // FIXME: This error message isn't great, but it doesn't show up in the existing UI tests, + // and is only the fallback when the nice error fails. Consider improving this some more. + tcx.sess.create_err(HigherRankedLifetimeError { cause: None, span }) + } + + fn base_universe(&self) -> ty::UniverseIndex { + self.base_universe + } + + fn nice_error( + &self, + mbcx: &mut MirBorrowckCtxt<'_, 'tcx>, + cause: ObligationCause<'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option<ty::Region<'tcx>>, + ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { + mbcx.infcx.tcx.infer_ctxt().enter_with_canonical( + cause.span, + &self.canonical_query, + |ref infcx, key, _| { + let mut fulfill_cx = <dyn TraitEngine<'_>>::new(infcx.tcx); + type_op_ascribe_user_type_with_span(infcx, &mut *fulfill_cx, key, Some(cause.span)) + .ok()?; + try_extract_error_from_fulfill_cx( + fulfill_cx, + infcx, + placeholder_region, + error_region, + ) + }, + ) + } +} + +impl<'tcx> TypeOpInfo<'tcx> for crate::type_check::InstantiateOpaqueType<'tcx> { + fn fallback_error( + &self, + tcx: TyCtxt<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + // FIXME: This error message isn't great, but it doesn't show up in the existing UI tests, + // and is only the fallback when the nice error fails. Consider improving this some more. + tcx.sess.create_err(HigherRankedLifetimeError { cause: None, span }) + } + + fn base_universe(&self) -> ty::UniverseIndex { + self.base_universe.unwrap() + } + + fn nice_error( + &self, + mbcx: &mut MirBorrowckCtxt<'_, 'tcx>, + _cause: ObligationCause<'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option<ty::Region<'tcx>>, + ) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { + try_extract_error_from_region_constraints( + mbcx.infcx, + placeholder_region, + error_region, + self.region_constraints.as_ref().unwrap(), + // We're using the original `InferCtxt` that we + // started MIR borrowchecking with, so the region + // constraints have already been taken. Use the data from + // our `mbcx` instead. + |vid| mbcx.regioncx.var_infos[vid].origin, + |vid| mbcx.regioncx.var_infos[vid].universe, + ) + } +} + +#[instrument(skip(fulfill_cx, infcx), level = "debug")] +fn try_extract_error_from_fulfill_cx<'tcx>( + mut fulfill_cx: Box<dyn TraitEngine<'tcx> + 'tcx>, + infcx: &InferCtxt<'_, 'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option<ty::Region<'tcx>>, +) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { + // We generally shouldn't have errors here because the query was + // already run, but there's no point using `delay_span_bug` + // when we're going to emit an error here anyway. + let _errors = fulfill_cx.select_all_or_error(infcx); + let region_constraints = infcx.with_region_constraints(|r| r.clone()); + try_extract_error_from_region_constraints( + infcx, + placeholder_region, + error_region, + ®ion_constraints, + |vid| infcx.region_var_origin(vid), + |vid| infcx.universe_of_region(infcx.tcx.mk_region(ty::ReVar(vid))), + ) +} + +fn try_extract_error_from_region_constraints<'tcx>( + infcx: &InferCtxt<'_, 'tcx>, + placeholder_region: ty::Region<'tcx>, + error_region: Option<ty::Region<'tcx>>, + region_constraints: &RegionConstraintData<'tcx>, + mut region_var_origin: impl FnMut(RegionVid) -> RegionVariableOrigin, + mut universe_of_region: impl FnMut(RegionVid) -> UniverseIndex, +) -> Option<DiagnosticBuilder<'tcx, ErrorGuaranteed>> { + let (sub_region, cause) = + region_constraints.constraints.iter().find_map(|(constraint, cause)| { + match *constraint { + Constraint::RegSubReg(sub, sup) if sup == placeholder_region && sup != sub => { + Some((sub, cause.clone())) + } + // FIXME: Should this check the universe of the var? + Constraint::VarSubReg(vid, sup) if sup == placeholder_region => { + Some((infcx.tcx.mk_region(ty::ReVar(vid)), cause.clone())) + } + _ => None, + } + })?; + + debug!(?sub_region, "cause = {:#?}", cause); + let nice_error = match (error_region, *sub_region) { + (Some(error_region), ty::ReVar(vid)) => NiceRegionError::new( + infcx, + RegionResolutionError::SubSupConflict( + vid, + region_var_origin(vid), + cause.clone(), + error_region, + cause.clone(), + placeholder_region, + vec![], + ), + ), + (Some(error_region), _) => NiceRegionError::new( + infcx, + RegionResolutionError::ConcreteFailure(cause.clone(), error_region, placeholder_region), + ), + // Note universe here is wrong... + (None, ty::ReVar(vid)) => NiceRegionError::new( + infcx, + RegionResolutionError::UpperBoundUniverseConflict( + vid, + region_var_origin(vid), + universe_of_region(vid), + cause.clone(), + placeholder_region, + ), + ), + (None, _) => NiceRegionError::new( + infcx, + RegionResolutionError::ConcreteFailure(cause.clone(), sub_region, placeholder_region), + ), + }; + nice_error.try_report_from_nll().or_else(|| { + if let SubregionOrigin::Subtype(trace) = cause { + Some( + infcx.report_and_explain_type_error(*trace, &TypeError::RegionsPlaceholderMismatch), + ) + } else { + None + } + }) +} diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs new file mode 100644 index 000000000..8bc8964bb --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -0,0 +1,2773 @@ +use either::Either; +use rustc_const_eval::util::CallKind; +use rustc_data_structures::captures::Captures; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{ + struct_span_err, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan, +}; +use rustc_hir as hir; +use rustc_hir::intravisit::{walk_block, walk_expr, Visitor}; +use rustc_hir::{AsyncGeneratorKind, GeneratorKind}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::traits::ObligationCause; +use rustc_middle::mir::tcx::PlaceTy; +use rustc_middle::mir::{ + self, AggregateKind, BindingForm, BorrowKind, ClearCrossCrate, ConstraintCategory, + FakeReadCause, LocalDecl, LocalInfo, LocalKind, Location, Operand, Place, PlaceRef, + ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, VarBindingForm, +}; +use rustc_middle::ty::{self, subst::Subst, suggest_constraining_type_params, PredicateKind, Ty}; +use rustc_mir_dataflow::move_paths::{InitKind, MoveOutIndex, MovePathIndex}; +use rustc_span::def_id::LocalDefId; +use rustc_span::hygiene::DesugaringKind; +use rustc_span::symbol::sym; +use rustc_span::{BytePos, Span, Symbol}; +use rustc_trait_selection::infer::InferCtxtExt; +use rustc_trait_selection::traits::TraitEngineExt as _; + +use crate::borrow_set::TwoPhaseActivation; +use crate::borrowck_errors; + +use crate::diagnostics::conflict_errors::StorageDeadOrDrop::LocalStorageDead; +use crate::diagnostics::find_all_local_uses; +use crate::{ + borrow_set::BorrowData, diagnostics::Instance, prefixes::IsPrefixOf, + InitializationRequiringAction, MirBorrowckCtxt, PrefixSet, WriteKind, +}; + +use super::{ + explain_borrow::{BorrowExplanation, LaterUseKind}, + DescribePlaceOpt, RegionName, RegionNameSource, UseSpans, +}; + +#[derive(Debug)] +struct MoveSite { + /// Index of the "move out" that we found. The `MoveData` can + /// then tell us where the move occurred. + moi: MoveOutIndex, + + /// `true` if we traversed a back edge while walking from the point + /// of error to the move site. + traversed_back_edge: bool, +} + +/// Which case a StorageDeadOrDrop is for. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +enum StorageDeadOrDrop<'tcx> { + LocalStorageDead, + BoxedStorageDead, + Destructor(Ty<'tcx>), +} + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + pub(crate) fn report_use_of_moved_or_uninitialized( + &mut self, + location: Location, + desired_action: InitializationRequiringAction, + (moved_place, used_place, span): (PlaceRef<'tcx>, PlaceRef<'tcx>, Span), + mpi: MovePathIndex, + ) { + debug!( + "report_use_of_moved_or_uninitialized: location={:?} desired_action={:?} \ + moved_place={:?} used_place={:?} span={:?} mpi={:?}", + location, desired_action, moved_place, used_place, span, mpi + ); + + let use_spans = + self.move_spans(moved_place, location).or_else(|| self.borrow_spans(span, location)); + let span = use_spans.args_or_use(); + + let (move_site_vec, maybe_reinitialized_locations) = self.get_moved_indexes(location, mpi); + debug!( + "report_use_of_moved_or_uninitialized: move_site_vec={:?} use_spans={:?}", + move_site_vec, use_spans + ); + let move_out_indices: Vec<_> = + move_site_vec.iter().map(|move_site| move_site.moi).collect(); + + if move_out_indices.is_empty() { + let root_place = PlaceRef { projection: &[], ..used_place }; + + if !self.uninitialized_error_reported.insert(root_place) { + debug!( + "report_use_of_moved_or_uninitialized place: error about {:?} suppressed", + root_place + ); + return; + } + + let err = self.report_use_of_uninitialized( + mpi, + used_place, + moved_place, + desired_action, + span, + use_spans, + ); + self.buffer_error(err); + } else { + if let Some((reported_place, _)) = self.has_move_error(&move_out_indices) { + if self.prefixes(*reported_place, PrefixSet::All).any(|p| p == used_place) { + debug!( + "report_use_of_moved_or_uninitialized place: error suppressed mois={:?}", + move_out_indices + ); + return; + } + } + + let is_partial_move = move_site_vec.iter().any(|move_site| { + let move_out = self.move_data.moves[(*move_site).moi]; + let moved_place = &self.move_data.move_paths[move_out.path].place; + // `*(_1)` where `_1` is a `Box` is actually a move out. + let is_box_move = moved_place.as_ref().projection == [ProjectionElem::Deref] + && self.body.local_decls[moved_place.local].ty.is_box(); + + !is_box_move + && used_place != moved_place.as_ref() + && used_place.is_prefix_of(moved_place.as_ref()) + }); + + let partial_str = if is_partial_move { "partial " } else { "" }; + let partially_str = if is_partial_move { "partially " } else { "" }; + + let mut err = self.cannot_act_on_moved_value( + span, + desired_action.as_noun(), + partially_str, + self.describe_place_with_options( + moved_place, + DescribePlaceOpt { including_downcast: true, including_tuple_field: true }, + ), + ); + + let reinit_spans = maybe_reinitialized_locations + .iter() + .take(3) + .map(|loc| { + self.move_spans(self.move_data.move_paths[mpi].place.as_ref(), *loc) + .args_or_use() + }) + .collect::<Vec<Span>>(); + + let reinits = maybe_reinitialized_locations.len(); + if reinits == 1 { + err.span_label(reinit_spans[0], "this reinitialization might get skipped"); + } else if reinits > 1 { + err.span_note( + MultiSpan::from_spans(reinit_spans), + &if reinits <= 3 { + format!("these {} reinitializations might get skipped", reinits) + } else { + format!( + "these 3 reinitializations and {} other{} might get skipped", + reinits - 3, + if reinits == 4 { "" } else { "s" } + ) + }, + ); + } + + self.add_moved_or_invoked_closure_note(location, used_place, &mut err); + + let mut is_loop_move = false; + let mut in_pattern = false; + + for move_site in &move_site_vec { + let move_out = self.move_data.moves[(*move_site).moi]; + let moved_place = &self.move_data.move_paths[move_out.path].place; + + let move_spans = self.move_spans(moved_place.as_ref(), move_out.source); + let move_span = move_spans.args_or_use(); + + let move_msg = if move_spans.for_closure() { " into closure" } else { "" }; + + let loop_message = if location == move_out.source || move_site.traversed_back_edge { + ", in previous iteration of loop" + } else { + "" + }; + + if location == move_out.source { + is_loop_move = true; + } + + self.explain_captures( + &mut err, + span, + move_span, + move_spans, + *moved_place, + Some(used_place), + partially_str, + loop_message, + move_msg, + is_loop_move, + maybe_reinitialized_locations.is_empty(), + ); + + if let (UseSpans::PatUse(span), []) = + (move_spans, &maybe_reinitialized_locations[..]) + { + if maybe_reinitialized_locations.is_empty() { + err.span_suggestion_verbose( + span.shrink_to_lo(), + &format!( + "borrow this field in the pattern to avoid moving {}", + self.describe_place(moved_place.as_ref()) + .map(|n| format!("`{}`", n)) + .unwrap_or_else(|| "the value".to_string()) + ), + "ref ", + Applicability::MachineApplicable, + ); + in_pattern = true; + } + } + } + + use_spans.var_span_label_path_only( + &mut err, + format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()), + ); + + if !is_loop_move { + err.span_label( + span, + format!( + "value {} here after {}move", + desired_action.as_verb_in_past_tense(), + partial_str + ), + ); + } + + let ty = used_place.ty(self.body, self.infcx.tcx).ty; + let needs_note = match ty.kind() { + ty::Closure(id, _) => { + let tables = self.infcx.tcx.typeck(id.expect_local()); + let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(id.expect_local()); + + tables.closure_kind_origins().get(hir_id).is_none() + } + _ => true, + }; + + let mpi = self.move_data.moves[move_out_indices[0]].path; + let place = &self.move_data.move_paths[mpi].place; + let ty = place.ty(self.body, self.infcx.tcx).ty; + + // If we're in pattern, we do nothing in favor of the previous suggestion (#80913). + if is_loop_move & !in_pattern { + if let ty::Ref(_, _, hir::Mutability::Mut) = ty.kind() { + // We have a `&mut` ref, we need to reborrow on each iteration (#62112). + err.span_suggestion_verbose( + span.shrink_to_lo(), + &format!( + "consider creating a fresh reborrow of {} here", + self.describe_place(moved_place) + .map(|n| format!("`{}`", n)) + .unwrap_or_else(|| "the mutable reference".to_string()), + ), + "&mut *", + Applicability::MachineApplicable, + ); + } + } + + let opt_name = self.describe_place_with_options( + place.as_ref(), + DescribePlaceOpt { including_downcast: true, including_tuple_field: true }, + ); + let note_msg = match opt_name { + Some(ref name) => format!("`{}`", name), + None => "value".to_owned(), + }; + if self.suggest_borrow_fn_like(&mut err, ty, &move_site_vec, ¬e_msg) { + // Suppress the next suggestion since we don't want to put more bounds onto + // something that already has `Fn`-like bounds (or is a closure), so we can't + // restrict anyways. + } else { + self.suggest_adding_copy_bounds(&mut err, ty, span); + } + + if needs_note { + let span = if let Some(local) = place.as_local() { + Some(self.body.local_decls[local].source_info.span) + } else { + None + }; + self.note_type_does_not_implement_copy(&mut err, ¬e_msg, ty, span, partial_str); + } + + if let UseSpans::FnSelfUse { + kind: CallKind::DerefCoercion { deref_target, deref_target_ty, .. }, + .. + } = use_spans + { + err.note(&format!( + "{} occurs due to deref coercion to `{}`", + desired_action.as_noun(), + deref_target_ty + )); + + // Check first whether the source is accessible (issue #87060) + if self.infcx.tcx.sess.source_map().is_span_accessible(deref_target) { + err.span_note(deref_target, "deref defined here"); + } + } + + self.buffer_move_error(move_out_indices, (used_place, err)); + } + } + + fn report_use_of_uninitialized( + &self, + mpi: MovePathIndex, + used_place: PlaceRef<'tcx>, + moved_place: PlaceRef<'tcx>, + desired_action: InitializationRequiringAction, + span: Span, + use_spans: UseSpans<'tcx>, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + // We need all statements in the body where the binding was assigned to to later find all + // the branching code paths where the binding *wasn't* assigned to. + let inits = &self.move_data.init_path_map[mpi]; + let move_path = &self.move_data.move_paths[mpi]; + let decl_span = self.body.local_decls[move_path.place.local].source_info.span; + let mut spans = vec![]; + for init_idx in inits { + let init = &self.move_data.inits[*init_idx]; + let span = init.span(&self.body); + if !span.is_dummy() { + spans.push(span); + } + } + + let (name, desc) = match self.describe_place_with_options( + moved_place, + DescribePlaceOpt { including_downcast: true, including_tuple_field: true }, + ) { + Some(name) => (format!("`{name}`"), format!("`{name}` ")), + None => ("the variable".to_string(), String::new()), + }; + let path = match self.describe_place_with_options( + used_place, + DescribePlaceOpt { including_downcast: true, including_tuple_field: true }, + ) { + Some(name) => format!("`{name}`"), + None => "value".to_string(), + }; + + // We use the statements were the binding was initialized, and inspect the HIR to look + // for the branching codepaths that aren't covered, to point at them. + let map = self.infcx.tcx.hir(); + let body_id = map.body_owned_by(self.mir_def_id()); + let body = map.body(body_id); + + let mut visitor = ConditionVisitor { spans: &spans, name: &name, errors: vec![] }; + visitor.visit_body(&body); + + let isnt_initialized = if let InitializationRequiringAction::PartialAssignment + | InitializationRequiringAction::Assignment = desired_action + { + // The same error is emitted for bindings that are *sometimes* initialized and the ones + // that are *partially* initialized by assigning to a field of an uninitialized + // binding. We differentiate between them for more accurate wording here. + "isn't fully initialized" + } else if spans + .iter() + .filter(|i| { + // We filter these to avoid misleading wording in cases like the following, + // where `x` has an `init`, but it is in the same place we're looking at: + // ``` + // let x; + // x += 1; + // ``` + !i.contains(span) + // We filter these to avoid incorrect main message on `match-cfg-fake-edges.rs` + && !visitor + .errors + .iter() + .map(|(sp, _)| *sp) + .any(|sp| span < sp && !sp.contains(span)) + }) + .count() + == 0 + { + "isn't initialized" + } else { + "is possibly-uninitialized" + }; + + let used = desired_action.as_general_verb_in_past_tense(); + let mut err = + struct_span_err!(self, span, E0381, "{used} binding {desc}{isnt_initialized}"); + use_spans.var_span_label_path_only( + &mut err, + format!("{} occurs due to use{}", desired_action.as_noun(), use_spans.describe()), + ); + + if let InitializationRequiringAction::PartialAssignment + | InitializationRequiringAction::Assignment = desired_action + { + err.help( + "partial initialization isn't supported, fully initialize the binding with a \ + default value and mutate it, or use `std::mem::MaybeUninit`", + ); + } + err.span_label(span, format!("{path} {used} here but it {isnt_initialized}")); + + let mut shown = false; + for (sp, label) in visitor.errors { + if sp < span && !sp.overlaps(span) { + // When we have a case like `match-cfg-fake-edges.rs`, we don't want to mention + // match arms coming after the primary span because they aren't relevant: + // ``` + // let x; + // match y { + // _ if { x = 2; true } => {} + // _ if { + // x; //~ ERROR + // false + // } => {} + // _ => {} // We don't want to point to this. + // }; + // ``` + err.span_label(sp, &label); + shown = true; + } + } + if !shown { + for sp in &spans { + if *sp < span && !sp.overlaps(span) { + err.span_label(*sp, "binding initialized here in some conditions"); + } + } + } + err.span_label(decl_span, "binding declared here but left uninitialized"); + err + } + + fn suggest_borrow_fn_like( + &self, + err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + ty: Ty<'tcx>, + move_sites: &[MoveSite], + value_name: &str, + ) -> bool { + let tcx = self.infcx.tcx; + + // Find out if the predicates show that the type is a Fn or FnMut + let find_fn_kind_from_did = + |predicates: ty::EarlyBinder<&[(ty::Predicate<'tcx>, Span)]>, substs| { + predicates.0.iter().find_map(|(pred, _)| { + let pred = if let Some(substs) = substs { + predicates.rebind(*pred).subst(tcx, substs).kind().skip_binder() + } else { + pred.kind().skip_binder() + }; + if let ty::PredicateKind::Trait(pred) = pred && pred.self_ty() == ty { + if Some(pred.def_id()) == tcx.lang_items().fn_trait() { + return Some(hir::Mutability::Not); + } else if Some(pred.def_id()) == tcx.lang_items().fn_mut_trait() { + return Some(hir::Mutability::Mut); + } + } + None + }) + }; + + // If the type is opaque/param/closure, and it is Fn or FnMut, let's suggest (mutably) + // borrowing the type, since `&mut F: FnMut` iff `F: FnMut` and similarly for `Fn`. + // These types seem reasonably opaque enough that they could be substituted with their + // borrowed variants in a function body when we see a move error. + let borrow_level = match ty.kind() { + ty::Param(_) => find_fn_kind_from_did( + tcx.bound_explicit_predicates_of(self.mir_def_id().to_def_id()) + .map_bound(|p| p.predicates), + None, + ), + ty::Opaque(did, substs) => { + find_fn_kind_from_did(tcx.bound_explicit_item_bounds(*did), Some(*substs)) + } + ty::Closure(_, substs) => match substs.as_closure().kind() { + ty::ClosureKind::Fn => Some(hir::Mutability::Not), + ty::ClosureKind::FnMut => Some(hir::Mutability::Mut), + _ => None, + }, + _ => None, + }; + + let Some(borrow_level) = borrow_level else { return false; }; + let sugg = move_sites + .iter() + .map(|move_site| { + let move_out = self.move_data.moves[(*move_site).moi]; + let moved_place = &self.move_data.move_paths[move_out.path].place; + let move_spans = self.move_spans(moved_place.as_ref(), move_out.source); + let move_span = move_spans.args_or_use(); + let suggestion = if borrow_level == hir::Mutability::Mut { + "&mut ".to_string() + } else { + "&".to_string() + }; + (move_span.shrink_to_lo(), suggestion) + }) + .collect(); + err.multipart_suggestion_verbose( + &format!( + "consider {}borrowing {value_name}", + if borrow_level == hir::Mutability::Mut { "mutably " } else { "" } + ), + sugg, + Applicability::MaybeIncorrect, + ); + true + } + + fn suggest_adding_copy_bounds( + &self, + err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + ty: Ty<'tcx>, + span: Span, + ) { + let tcx = self.infcx.tcx; + let generics = tcx.generics_of(self.mir_def_id()); + + let Some(hir_generics) = tcx + .typeck_root_def_id(self.mir_def_id().to_def_id()) + .as_local() + .and_then(|def_id| tcx.hir().get_generics(def_id)) + else { return; }; + // Try to find predicates on *generic params* that would allow copying `ty` + let predicates: Result<Vec<_>, _> = tcx.infer_ctxt().enter(|infcx| { + let mut fulfill_cx = <dyn rustc_infer::traits::TraitEngine<'_>>::new(infcx.tcx); + + let copy_did = infcx.tcx.lang_items().copy_trait().unwrap(); + let cause = ObligationCause::new( + span, + self.mir_hir_id(), + rustc_infer::traits::ObligationCauseCode::MiscObligation, + ); + fulfill_cx.register_bound( + &infcx, + self.param_env, + // Erase any region vids from the type, which may not be resolved + infcx.tcx.erase_regions(ty), + copy_did, + cause, + ); + // Select all, including ambiguous predicates + let errors = fulfill_cx.select_all_or_error(&infcx); + + // Only emit suggestion if all required predicates are on generic + errors + .into_iter() + .map(|err| match err.obligation.predicate.kind().skip_binder() { + PredicateKind::Trait(predicate) => match predicate.self_ty().kind() { + ty::Param(param_ty) => Ok(( + generics.type_param(param_ty, tcx), + predicate.trait_ref.print_only_trait_path().to_string(), + )), + _ => Err(()), + }, + _ => Err(()), + }) + .collect() + }); + + if let Ok(predicates) = predicates { + suggest_constraining_type_params( + tcx, + hir_generics, + err, + predicates + .iter() + .map(|(param, constraint)| (param.name.as_str(), &**constraint, None)), + ); + } + } + + pub(crate) fn report_move_out_while_borrowed( + &mut self, + location: Location, + (place, span): (Place<'tcx>, Span), + borrow: &BorrowData<'tcx>, + ) { + debug!( + "report_move_out_while_borrowed: location={:?} place={:?} span={:?} borrow={:?}", + location, place, span, borrow + ); + let value_msg = self.describe_any_place(place.as_ref()); + let borrow_msg = self.describe_any_place(borrow.borrowed_place.as_ref()); + + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.args_or_use(); + + let move_spans = self.move_spans(place.as_ref(), location); + let span = move_spans.args_or_use(); + + let mut err = + self.cannot_move_when_borrowed(span, &self.describe_any_place(place.as_ref())); + err.span_label(borrow_span, format!("borrow of {} occurs here", borrow_msg)); + err.span_label(span, format!("move out of {} occurs here", value_msg)); + + borrow_spans.var_span_label_path_only( + &mut err, + format!("borrow occurs due to use{}", borrow_spans.describe()), + ); + + move_spans.var_span_label( + &mut err, + format!("move occurs due to use{}", move_spans.describe()), + "moved", + ); + + self.explain_why_borrow_contains_point(location, borrow, None) + .add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + Some(borrow_span), + None, + ); + self.buffer_error(err); + } + + pub(crate) fn report_use_while_mutably_borrowed( + &mut self, + location: Location, + (place, _span): (Place<'tcx>, Span), + borrow: &BorrowData<'tcx>, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.args_or_use(); + + // Conflicting borrows are reported separately, so only check for move + // captures. + let use_spans = self.move_spans(place.as_ref(), location); + let span = use_spans.var_or_use(); + + // If the attempted use is in a closure then we do not care about the path span of the place we are currently trying to use + // we call `var_span_label` on `borrow_spans` to annotate if the existing borrow was in a closure + let mut err = self.cannot_use_when_mutably_borrowed( + span, + &self.describe_any_place(place.as_ref()), + borrow_span, + &self.describe_any_place(borrow.borrowed_place.as_ref()), + ); + + borrow_spans.var_span_label( + &mut err, + { + let place = &borrow.borrowed_place; + let desc_place = self.describe_any_place(place.as_ref()); + format!("borrow occurs due to use of {}{}", desc_place, borrow_spans.describe()) + }, + "mutable", + ); + + self.explain_why_borrow_contains_point(location, borrow, None) + .add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + None, + ); + err + } + + pub(crate) fn report_conflicting_borrow( + &mut self, + location: Location, + (place, span): (Place<'tcx>, Span), + gen_borrow_kind: BorrowKind, + issued_borrow: &BorrowData<'tcx>, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + let issued_spans = self.retrieve_borrow_spans(issued_borrow); + let issued_span = issued_spans.args_or_use(); + + let borrow_spans = self.borrow_spans(span, location); + let span = borrow_spans.args_or_use(); + + let container_name = if issued_spans.for_generator() || borrow_spans.for_generator() { + "generator" + } else { + "closure" + }; + + let (desc_place, msg_place, msg_borrow, union_type_name) = + self.describe_place_for_conflicting_borrow(place, issued_borrow.borrowed_place); + + let explanation = self.explain_why_borrow_contains_point(location, issued_borrow, None); + let second_borrow_desc = if explanation.is_explained() { "second " } else { "" }; + + // FIXME: supply non-"" `opt_via` when appropriate + let first_borrow_desc; + let mut err = match (gen_borrow_kind, issued_borrow.kind) { + (BorrowKind::Shared, BorrowKind::Mut { .. }) => { + first_borrow_desc = "mutable "; + self.cannot_reborrow_already_borrowed( + span, + &desc_place, + &msg_place, + "immutable", + issued_span, + "it", + "mutable", + &msg_borrow, + None, + ) + } + (BorrowKind::Mut { .. }, BorrowKind::Shared) => { + first_borrow_desc = "immutable "; + self.cannot_reborrow_already_borrowed( + span, + &desc_place, + &msg_place, + "mutable", + issued_span, + "it", + "immutable", + &msg_borrow, + None, + ) + } + + (BorrowKind::Mut { .. }, BorrowKind::Mut { .. }) => { + first_borrow_desc = "first "; + let mut err = self.cannot_mutably_borrow_multiply( + span, + &desc_place, + &msg_place, + issued_span, + &msg_borrow, + None, + ); + self.suggest_split_at_mut_if_applicable( + &mut err, + place, + issued_borrow.borrowed_place, + ); + err + } + + (BorrowKind::Unique, BorrowKind::Unique) => { + first_borrow_desc = "first "; + self.cannot_uniquely_borrow_by_two_closures(span, &desc_place, issued_span, None) + } + + (BorrowKind::Mut { .. } | BorrowKind::Unique, BorrowKind::Shallow) => { + if let Some(immutable_section_description) = + self.classify_immutable_section(issued_borrow.assigned_place) + { + let mut err = self.cannot_mutate_in_immutable_section( + span, + issued_span, + &desc_place, + immutable_section_description, + "mutably borrow", + ); + borrow_spans.var_span_label( + &mut err, + format!( + "borrow occurs due to use of {}{}", + desc_place, + borrow_spans.describe(), + ), + "immutable", + ); + + return err; + } else { + first_borrow_desc = "immutable "; + self.cannot_reborrow_already_borrowed( + span, + &desc_place, + &msg_place, + "mutable", + issued_span, + "it", + "immutable", + &msg_borrow, + None, + ) + } + } + + (BorrowKind::Unique, _) => { + first_borrow_desc = "first "; + self.cannot_uniquely_borrow_by_one_closure( + span, + container_name, + &desc_place, + "", + issued_span, + "it", + "", + None, + ) + } + + (BorrowKind::Shared, BorrowKind::Unique) => { + first_borrow_desc = "first "; + self.cannot_reborrow_already_uniquely_borrowed( + span, + container_name, + &desc_place, + "", + "immutable", + issued_span, + "", + None, + second_borrow_desc, + ) + } + + (BorrowKind::Mut { .. }, BorrowKind::Unique) => { + first_borrow_desc = "first "; + self.cannot_reborrow_already_uniquely_borrowed( + span, + container_name, + &desc_place, + "", + "mutable", + issued_span, + "", + None, + second_borrow_desc, + ) + } + + (BorrowKind::Shared, BorrowKind::Shared | BorrowKind::Shallow) + | ( + BorrowKind::Shallow, + BorrowKind::Mut { .. } + | BorrowKind::Unique + | BorrowKind::Shared + | BorrowKind::Shallow, + ) => unreachable!(), + }; + + if issued_spans == borrow_spans { + borrow_spans.var_span_label( + &mut err, + format!("borrows occur due to use of {}{}", desc_place, borrow_spans.describe(),), + gen_borrow_kind.describe_mutability(), + ); + } else { + let borrow_place = &issued_borrow.borrowed_place; + let borrow_place_desc = self.describe_any_place(borrow_place.as_ref()); + issued_spans.var_span_label( + &mut err, + format!( + "first borrow occurs due to use of {}{}", + borrow_place_desc, + issued_spans.describe(), + ), + issued_borrow.kind.describe_mutability(), + ); + + borrow_spans.var_span_label( + &mut err, + format!( + "second borrow occurs due to use of {}{}", + desc_place, + borrow_spans.describe(), + ), + gen_borrow_kind.describe_mutability(), + ); + } + + if union_type_name != "" { + err.note(&format!( + "{} is a field of the union `{}`, so it overlaps the field {}", + msg_place, union_type_name, msg_borrow, + )); + } + + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + first_borrow_desc, + None, + Some((issued_span, span)), + ); + + self.suggest_using_local_if_applicable(&mut err, location, issued_borrow, explanation); + + err + } + + #[instrument(level = "debug", skip(self, err))] + fn suggest_using_local_if_applicable( + &self, + err: &mut Diagnostic, + location: Location, + issued_borrow: &BorrowData<'tcx>, + explanation: BorrowExplanation<'tcx>, + ) { + let used_in_call = matches!( + explanation, + BorrowExplanation::UsedLater(LaterUseKind::Call | LaterUseKind::Other, _call_span, _) + ); + if !used_in_call { + debug!("not later used in call"); + return; + } + + let use_span = + if let BorrowExplanation::UsedLater(LaterUseKind::Other, use_span, _) = explanation { + Some(use_span) + } else { + None + }; + + let outer_call_loc = + if let TwoPhaseActivation::ActivatedAt(loc) = issued_borrow.activation_location { + loc + } else { + issued_borrow.reserve_location + }; + let outer_call_stmt = self.body.stmt_at(outer_call_loc); + + let inner_param_location = location; + let Some(inner_param_stmt) = self.body.stmt_at(inner_param_location).left() else { + debug!("`inner_param_location` {:?} is not for a statement", inner_param_location); + return; + }; + let Some(&inner_param) = inner_param_stmt.kind.as_assign().map(|(p, _)| p) else { + debug!( + "`inner_param_location` {:?} is not for an assignment: {:?}", + inner_param_location, inner_param_stmt + ); + return; + }; + let inner_param_uses = find_all_local_uses::find(self.body, inner_param.local); + let Some((inner_call_loc, inner_call_term)) = inner_param_uses.into_iter().find_map(|loc| { + let Either::Right(term) = self.body.stmt_at(loc) else { + debug!("{:?} is a statement, so it can't be a call", loc); + return None; + }; + let TerminatorKind::Call { args, .. } = &term.kind else { + debug!("not a call: {:?}", term); + return None; + }; + debug!("checking call args for uses of inner_param: {:?}", args); + if args.contains(&Operand::Move(inner_param)) { + Some((loc, term)) + } else { + None + } + }) else { + debug!("no uses of inner_param found as a by-move call arg"); + return; + }; + debug!("===> outer_call_loc = {:?}, inner_call_loc = {:?}", outer_call_loc, inner_call_loc); + + let inner_call_span = inner_call_term.source_info.span; + let outer_call_span = match use_span { + Some(span) => span, + None => outer_call_stmt.either(|s| s.source_info, |t| t.source_info).span, + }; + if outer_call_span == inner_call_span || !outer_call_span.contains(inner_call_span) { + // FIXME: This stops the suggestion in some cases where it should be emitted. + // Fix the spans for those cases so it's emitted correctly. + debug!( + "outer span {:?} does not strictly contain inner span {:?}", + outer_call_span, inner_call_span + ); + return; + } + err.span_help( + inner_call_span, + &format!( + "try adding a local storing this{}...", + if use_span.is_some() { "" } else { " argument" } + ), + ); + err.span_help( + outer_call_span, + &format!( + "...and then using that local {}", + if use_span.is_some() { "here" } else { "as the argument to this call" } + ), + ); + } + + fn suggest_split_at_mut_if_applicable( + &self, + err: &mut Diagnostic, + place: Place<'tcx>, + borrowed_place: Place<'tcx>, + ) { + if let ([ProjectionElem::Index(_)], [ProjectionElem::Index(_)]) = + (&place.projection[..], &borrowed_place.projection[..]) + { + err.help( + "consider using `.split_at_mut(position)` or similar method to obtain \ + two mutable non-overlapping sub-slices", + ); + } + } + + /// Returns the description of the root place for a conflicting borrow and the full + /// descriptions of the places that caused the conflict. + /// + /// In the simplest case, where there are no unions involved, if a mutable borrow of `x` is + /// attempted while a shared borrow is live, then this function will return: + /// ``` + /// ("x", "", "") + /// # ; + /// ``` + /// In the simple union case, if a mutable borrow of a union field `x.z` is attempted while + /// a shared borrow of another field `x.y`, then this function will return: + /// ``` + /// ("x", "x.z", "x.y") + /// # ; + /// ``` + /// In the more complex union case, where the union is a field of a struct, then if a mutable + /// borrow of a union field in a struct `x.u.z` is attempted while a shared borrow of + /// another field `x.u.y`, then this function will return: + /// ``` + /// ("x.u", "x.u.z", "x.u.y") + /// # ; + /// ``` + /// This is used when creating error messages like below: + /// + /// ```text + /// cannot borrow `a.u` (via `a.u.z.c`) as immutable because it is also borrowed as + /// mutable (via `a.u.s.b`) [E0502] + /// ``` + pub(crate) fn describe_place_for_conflicting_borrow( + &self, + first_borrowed_place: Place<'tcx>, + second_borrowed_place: Place<'tcx>, + ) -> (String, String, String, String) { + // Define a small closure that we can use to check if the type of a place + // is a union. + let union_ty = |place_base| { + // Need to use fn call syntax `PlaceRef::ty` to determine the type of `place_base`; + // using a type annotation in the closure argument instead leads to a lifetime error. + let ty = PlaceRef::ty(&place_base, self.body, self.infcx.tcx).ty; + ty.ty_adt_def().filter(|adt| adt.is_union()).map(|_| ty) + }; + + // Start with an empty tuple, so we can use the functions on `Option` to reduce some + // code duplication (particularly around returning an empty description in the failure + // case). + Some(()) + .filter(|_| { + // If we have a conflicting borrow of the same place, then we don't want to add + // an extraneous "via x.y" to our diagnostics, so filter out this case. + first_borrowed_place != second_borrowed_place + }) + .and_then(|_| { + // We're going to want to traverse the first borrowed place to see if we can find + // field access to a union. If we find that, then we will keep the place of the + // union being accessed and the field that was being accessed so we can check the + // second borrowed place for the same union and an access to a different field. + for (place_base, elem) in first_borrowed_place.iter_projections().rev() { + match elem { + ProjectionElem::Field(field, _) if union_ty(place_base).is_some() => { + return Some((place_base, field)); + } + _ => {} + } + } + None + }) + .and_then(|(target_base, target_field)| { + // With the place of a union and a field access into it, we traverse the second + // borrowed place and look for an access to a different field of the same union. + for (place_base, elem) in second_borrowed_place.iter_projections().rev() { + if let ProjectionElem::Field(field, _) = elem { + if let Some(union_ty) = union_ty(place_base) { + if field != target_field && place_base == target_base { + return Some(( + self.describe_any_place(place_base), + self.describe_any_place(first_borrowed_place.as_ref()), + self.describe_any_place(second_borrowed_place.as_ref()), + union_ty.to_string(), + )); + } + } + } + } + None + }) + .unwrap_or_else(|| { + // If we didn't find a field access into a union, or both places match, then + // only return the description of the first place. + ( + self.describe_any_place(first_borrowed_place.as_ref()), + "".to_string(), + "".to_string(), + "".to_string(), + ) + }) + } + + /// Reports StorageDeadOrDrop of `place` conflicts with `borrow`. + /// + /// This means that some data referenced by `borrow` needs to live + /// past the point where the StorageDeadOrDrop of `place` occurs. + /// This is usually interpreted as meaning that `place` has too + /// short a lifetime. (But sometimes it is more useful to report + /// it as a more direct conflict between the execution of a + /// `Drop::drop` with an aliasing borrow.) + pub(crate) fn report_borrowed_value_does_not_live_long_enough( + &mut self, + location: Location, + borrow: &BorrowData<'tcx>, + place_span: (Place<'tcx>, Span), + kind: Option<WriteKind>, + ) { + debug!( + "report_borrowed_value_does_not_live_long_enough(\ + {:?}, {:?}, {:?}, {:?}\ + )", + location, borrow, place_span, kind + ); + + let drop_span = place_span.1; + let root_place = + self.prefixes(borrow.borrowed_place.as_ref(), PrefixSet::All).last().unwrap(); + + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.var_or_use_path_span(); + + assert!(root_place.projection.is_empty()); + let proper_span = self.body.local_decls[root_place.local].source_info.span; + + let root_place_projection = self.infcx.tcx.intern_place_elems(root_place.projection); + + if self.access_place_error_reported.contains(&( + Place { local: root_place.local, projection: root_place_projection }, + borrow_span, + )) { + debug!( + "suppressing access_place error when borrow doesn't live long enough for {:?}", + borrow_span + ); + return; + } + + self.access_place_error_reported.insert(( + Place { local: root_place.local, projection: root_place_projection }, + borrow_span, + )); + + let borrowed_local = borrow.borrowed_place.local; + if self.body.local_decls[borrowed_local].is_ref_to_thread_local() { + let err = + self.report_thread_local_value_does_not_live_long_enough(drop_span, borrow_span); + self.buffer_error(err); + return; + } + + if let StorageDeadOrDrop::Destructor(dropped_ty) = + self.classify_drop_access_kind(borrow.borrowed_place.as_ref()) + { + // If a borrow of path `B` conflicts with drop of `D` (and + // we're not in the uninteresting case where `B` is a + // prefix of `D`), then report this as a more interesting + // destructor conflict. + if !borrow.borrowed_place.as_ref().is_prefix_of(place_span.0.as_ref()) { + self.report_borrow_conflicts_with_destructor( + location, borrow, place_span, kind, dropped_ty, + ); + return; + } + } + + let place_desc = self.describe_place(borrow.borrowed_place.as_ref()); + + let kind_place = kind.filter(|_| place_desc.is_some()).map(|k| (k, place_span.0)); + let explanation = self.explain_why_borrow_contains_point(location, &borrow, kind_place); + + debug!( + "report_borrowed_value_does_not_live_long_enough(place_desc: {:?}, explanation: {:?})", + place_desc, explanation + ); + let err = match (place_desc, explanation) { + // If the outlives constraint comes from inside the closure, + // for example: + // + // let x = 0; + // let y = &x; + // Box::new(|| y) as Box<Fn() -> &'static i32> + // + // then just use the normal error. The closure isn't escaping + // and `move` will not help here. + ( + Some(ref name), + BorrowExplanation::MustBeValidFor { + category: + category @ (ConstraintCategory::Return(_) + | ConstraintCategory::CallArgument(_) + | ConstraintCategory::OpaqueType), + from_closure: false, + ref region_name, + span, + .. + }, + ) if borrow_spans.for_generator() | borrow_spans.for_closure() => self + .report_escaping_closure_capture( + borrow_spans, + borrow_span, + region_name, + category, + span, + &format!("`{}`", name), + ), + ( + ref name, + BorrowExplanation::MustBeValidFor { + category: ConstraintCategory::Assignment, + from_closure: false, + region_name: + RegionName { + source: RegionNameSource::AnonRegionFromUpvar(upvar_span, upvar_name), + .. + }, + span, + .. + }, + ) => self.report_escaping_data(borrow_span, name, upvar_span, upvar_name, span), + (Some(name), explanation) => self.report_local_value_does_not_live_long_enough( + location, + &name, + &borrow, + drop_span, + borrow_spans, + explanation, + ), + (None, explanation) => self.report_temporary_value_does_not_live_long_enough( + location, + &borrow, + drop_span, + borrow_spans, + proper_span, + explanation, + ), + }; + + self.buffer_error(err); + } + + fn report_local_value_does_not_live_long_enough( + &mut self, + location: Location, + name: &str, + borrow: &BorrowData<'tcx>, + drop_span: Span, + borrow_spans: UseSpans<'tcx>, + explanation: BorrowExplanation<'tcx>, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + debug!( + "report_local_value_does_not_live_long_enough(\ + {:?}, {:?}, {:?}, {:?}, {:?}\ + )", + location, name, borrow, drop_span, borrow_spans + ); + + let borrow_span = borrow_spans.var_or_use_path_span(); + if let BorrowExplanation::MustBeValidFor { + category, + span, + ref opt_place_desc, + from_closure: false, + .. + } = explanation + { + if let Some(diag) = self.try_report_cannot_return_reference_to_local( + borrow, + borrow_span, + span, + category, + opt_place_desc.as_ref(), + ) { + return diag; + } + } + + let mut err = self.path_does_not_live_long_enough(borrow_span, &format!("`{}`", name)); + + if let Some(annotation) = self.annotate_argument_and_return_for_borrow(borrow) { + let region_name = annotation.emit(self, &mut err); + + err.span_label( + borrow_span, + format!("`{}` would have to be valid for `{}`...", name, region_name), + ); + + let fn_hir_id = self.mir_hir_id(); + err.span_label( + drop_span, + format!( + "...but `{}` will be dropped here, when the {} returns", + name, + self.infcx + .tcx + .hir() + .opt_name(fn_hir_id) + .map(|name| format!("function `{}`", name)) + .unwrap_or_else(|| { + match &self + .infcx + .tcx + .typeck(self.mir_def_id()) + .node_type(fn_hir_id) + .kind() + { + ty::Closure(..) => "enclosing closure", + ty::Generator(..) => "enclosing generator", + kind => bug!("expected closure or generator, found {:?}", kind), + } + .to_string() + }) + ), + ); + + err.note( + "functions cannot return a borrow to data owned within the function's scope, \ + functions can only return borrows to data passed as arguments", + ); + err.note( + "to learn more, visit <https://doc.rust-lang.org/book/ch04-02-\ + references-and-borrowing.html#dangling-references>", + ); + + if let BorrowExplanation::MustBeValidFor { .. } = explanation { + } else { + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + None, + ); + } + } else { + err.span_label(borrow_span, "borrowed value does not live long enough"); + err.span_label(drop_span, format!("`{}` dropped here while still borrowed", name)); + + let within = if borrow_spans.for_generator() { " by generator" } else { "" }; + + borrow_spans.args_span_label(&mut err, format!("value captured here{}", within)); + + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + None, + ); + } + + err + } + + fn report_borrow_conflicts_with_destructor( + &mut self, + location: Location, + borrow: &BorrowData<'tcx>, + (place, drop_span): (Place<'tcx>, Span), + kind: Option<WriteKind>, + dropped_ty: Ty<'tcx>, + ) { + debug!( + "report_borrow_conflicts_with_destructor(\ + {:?}, {:?}, ({:?}, {:?}), {:?}\ + )", + location, borrow, place, drop_span, kind, + ); + + let borrow_spans = self.retrieve_borrow_spans(borrow); + let borrow_span = borrow_spans.var_or_use(); + + let mut err = self.cannot_borrow_across_destructor(borrow_span); + + let what_was_dropped = match self.describe_place(place.as_ref()) { + Some(name) => format!("`{}`", name), + None => String::from("temporary value"), + }; + + let label = match self.describe_place(borrow.borrowed_place.as_ref()) { + Some(borrowed) => format!( + "here, drop of {D} needs exclusive access to `{B}`, \ + because the type `{T}` implements the `Drop` trait", + D = what_was_dropped, + T = dropped_ty, + B = borrowed + ), + None => format!( + "here is drop of {D}; whose type `{T}` implements the `Drop` trait", + D = what_was_dropped, + T = dropped_ty + ), + }; + err.span_label(drop_span, label); + + // Only give this note and suggestion if they could be relevant. + let explanation = + self.explain_why_borrow_contains_point(location, borrow, kind.map(|k| (k, place))); + match explanation { + BorrowExplanation::UsedLater { .. } + | BorrowExplanation::UsedLaterWhenDropped { .. } => { + err.note("consider using a `let` binding to create a longer lived value"); + } + _ => {} + } + + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + None, + ); + + self.buffer_error(err); + } + + fn report_thread_local_value_does_not_live_long_enough( + &mut self, + drop_span: Span, + borrow_span: Span, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + debug!( + "report_thread_local_value_does_not_live_long_enough(\ + {:?}, {:?}\ + )", + drop_span, borrow_span + ); + + let mut err = self.thread_local_value_does_not_live_long_enough(borrow_span); + + err.span_label( + borrow_span, + "thread-local variables cannot be borrowed beyond the end of the function", + ); + err.span_label(drop_span, "end of enclosing function is here"); + + err + } + + fn report_temporary_value_does_not_live_long_enough( + &mut self, + location: Location, + borrow: &BorrowData<'tcx>, + drop_span: Span, + borrow_spans: UseSpans<'tcx>, + proper_span: Span, + explanation: BorrowExplanation<'tcx>, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + debug!( + "report_temporary_value_does_not_live_long_enough(\ + {:?}, {:?}, {:?}, {:?}\ + )", + location, borrow, drop_span, proper_span + ); + + if let BorrowExplanation::MustBeValidFor { category, span, from_closure: false, .. } = + explanation + { + if let Some(diag) = self.try_report_cannot_return_reference_to_local( + borrow, + proper_span, + span, + category, + None, + ) { + return diag; + } + } + + let mut err = self.temporary_value_borrowed_for_too_long(proper_span); + err.span_label(proper_span, "creates a temporary which is freed while still in use"); + err.span_label(drop_span, "temporary value is freed at the end of this statement"); + + match explanation { + BorrowExplanation::UsedLater(..) + | BorrowExplanation::UsedLaterInLoop(..) + | BorrowExplanation::UsedLaterWhenDropped { .. } => { + // Only give this note and suggestion if it could be relevant. + let sm = self.infcx.tcx.sess.source_map(); + let mut suggested = false; + let msg = "consider using a `let` binding to create a longer lived value"; + + /// We check that there's a single level of block nesting to ensure always correct + /// suggestions. If we don't, then we only provide a free-form message to avoid + /// misleading users in cases like `src/test/ui/nll/borrowed-temporary-error.rs`. + /// We could expand the analysis to suggest hoising all of the relevant parts of + /// the users' code to make the code compile, but that could be too much. + struct NestedStatementVisitor { + span: Span, + current: usize, + found: usize, + } + + impl<'tcx> Visitor<'tcx> for NestedStatementVisitor { + fn visit_block(&mut self, block: &hir::Block<'tcx>) { + self.current += 1; + walk_block(self, block); + self.current -= 1; + } + fn visit_expr(&mut self, expr: &hir::Expr<'tcx>) { + if self.span == expr.span { + self.found = self.current; + } + walk_expr(self, expr); + } + } + let source_info = self.body.source_info(location); + if let Some(scope) = self.body.source_scopes.get(source_info.scope) + && let ClearCrossCrate::Set(scope_data) = &scope.local_data + && let Some(node) = self.infcx.tcx.hir().find(scope_data.lint_root) + && let Some(id) = node.body_id() + && let hir::ExprKind::Block(block, _) = self.infcx.tcx.hir().body(id).value.kind + { + for stmt in block.stmts { + let mut visitor = NestedStatementVisitor { + span: proper_span, + current: 0, + found: 0, + }; + visitor.visit_stmt(stmt); + if visitor.found == 0 + && stmt.span.contains(proper_span) + && let Some(p) = sm.span_to_margin(stmt.span) + && let Ok(s) = sm.span_to_snippet(proper_span) + { + let addition = format!("let binding = {};\n{}", s, " ".repeat(p)); + err.multipart_suggestion_verbose( + msg, + vec![ + (stmt.span.shrink_to_lo(), addition), + (proper_span, "binding".to_string()), + ], + Applicability::MaybeIncorrect, + ); + suggested = true; + break; + } + } + } + if !suggested { + err.note(msg); + } + } + _ => {} + } + explanation.add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + None, + ); + + let within = if borrow_spans.for_generator() { " by generator" } else { "" }; + + borrow_spans.args_span_label(&mut err, format!("value captured here{}", within)); + + err + } + + fn try_report_cannot_return_reference_to_local( + &self, + borrow: &BorrowData<'tcx>, + borrow_span: Span, + return_span: Span, + category: ConstraintCategory<'tcx>, + opt_place_desc: Option<&String>, + ) -> Option<DiagnosticBuilder<'cx, ErrorGuaranteed>> { + let return_kind = match category { + ConstraintCategory::Return(_) => "return", + ConstraintCategory::Yield => "yield", + _ => return None, + }; + + // FIXME use a better heuristic than Spans + let reference_desc = if return_span == self.body.source_info(borrow.reserve_location).span { + "reference to" + } else { + "value referencing" + }; + + let (place_desc, note) = if let Some(place_desc) = opt_place_desc { + let local_kind = if let Some(local) = borrow.borrowed_place.as_local() { + match self.body.local_kind(local) { + LocalKind::ReturnPointer | LocalKind::Temp => { + bug!("temporary or return pointer with a name") + } + LocalKind::Var => "local variable ", + LocalKind::Arg + if !self.upvars.is_empty() && local == ty::CAPTURE_STRUCT_LOCAL => + { + "variable captured by `move` " + } + LocalKind::Arg => "function parameter ", + } + } else { + "local data " + }; + ( + format!("{}`{}`", local_kind, place_desc), + format!("`{}` is borrowed here", place_desc), + ) + } else { + let root_place = + self.prefixes(borrow.borrowed_place.as_ref(), PrefixSet::All).last().unwrap(); + let local = root_place.local; + match self.body.local_kind(local) { + LocalKind::ReturnPointer | LocalKind::Temp => { + ("temporary value".to_string(), "temporary value created here".to_string()) + } + LocalKind::Arg => ( + "function parameter".to_string(), + "function parameter borrowed here".to_string(), + ), + LocalKind::Var => { + ("local binding".to_string(), "local binding introduced here".to_string()) + } + } + }; + + let mut err = self.cannot_return_reference_to_local( + return_span, + return_kind, + reference_desc, + &place_desc, + ); + + if return_span != borrow_span { + err.span_label(borrow_span, note); + + let tcx = self.infcx.tcx; + let ty_params = ty::List::empty(); + + let return_ty = self.regioncx.universal_regions().unnormalized_output_ty; + let return_ty = tcx.erase_regions(return_ty); + + // to avoid panics + if let Some(iter_trait) = tcx.get_diagnostic_item(sym::Iterator) + && self + .infcx + .type_implements_trait(iter_trait, return_ty, ty_params, self.param_env) + .must_apply_modulo_regions() + { + err.span_suggestion_hidden( + return_span.shrink_to_hi(), + "use `.collect()` to allocate the iterator", + ".collect::<Vec<_>>()", + Applicability::MaybeIncorrect, + ); + } + } + + Some(err) + } + + fn report_escaping_closure_capture( + &mut self, + use_span: UseSpans<'tcx>, + var_span: Span, + fr_name: &RegionName, + category: ConstraintCategory<'tcx>, + constraint_span: Span, + captured_var: &str, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + let tcx = self.infcx.tcx; + let args_span = use_span.args_or_use(); + + let (sugg_span, suggestion) = match tcx.sess.source_map().span_to_snippet(args_span) { + Ok(string) => { + if string.starts_with("async ") { + let pos = args_span.lo() + BytePos(6); + (args_span.with_lo(pos).with_hi(pos), "move ") + } else if string.starts_with("async|") { + let pos = args_span.lo() + BytePos(5); + (args_span.with_lo(pos).with_hi(pos), " move") + } else { + (args_span.shrink_to_lo(), "move ") + } + } + Err(_) => (args_span, "move |<args>| <body>"), + }; + let kind = match use_span.generator_kind() { + Some(generator_kind) => match generator_kind { + GeneratorKind::Async(async_kind) => match async_kind { + AsyncGeneratorKind::Block => "async block", + AsyncGeneratorKind::Closure => "async closure", + _ => bug!("async block/closure expected, but async function found."), + }, + GeneratorKind::Gen => "generator", + }, + None => "closure", + }; + + let mut err = + self.cannot_capture_in_long_lived_closure(args_span, kind, captured_var, var_span); + err.span_suggestion_verbose( + sugg_span, + &format!( + "to force the {} to take ownership of {} (and any \ + other referenced variables), use the `move` keyword", + kind, captured_var + ), + suggestion, + Applicability::MachineApplicable, + ); + + match category { + ConstraintCategory::Return(_) | ConstraintCategory::OpaqueType => { + let msg = format!("{} is returned here", kind); + err.span_note(constraint_span, &msg); + } + ConstraintCategory::CallArgument(_) => { + fr_name.highlight_region_name(&mut err); + if matches!(use_span.generator_kind(), Some(GeneratorKind::Async(_))) { + err.note( + "async blocks are not executed immediately and must either take a \ + reference or ownership of outside variables they use", + ); + } else { + let msg = format!("function requires argument type to outlive `{}`", fr_name); + err.span_note(constraint_span, &msg); + } + } + _ => bug!( + "report_escaping_closure_capture called with unexpected constraint \ + category: `{:?}`", + category + ), + } + + err + } + + fn report_escaping_data( + &mut self, + borrow_span: Span, + name: &Option<String>, + upvar_span: Span, + upvar_name: Symbol, + escape_span: Span, + ) -> DiagnosticBuilder<'cx, ErrorGuaranteed> { + let tcx = self.infcx.tcx; + + let (_, escapes_from) = tcx.article_and_description(self.mir_def_id().to_def_id()); + + let mut err = + borrowck_errors::borrowed_data_escapes_closure(tcx, escape_span, escapes_from); + + err.span_label( + upvar_span, + format!("`{}` declared here, outside of the {} body", upvar_name, escapes_from), + ); + + err.span_label(borrow_span, format!("borrow is only valid in the {} body", escapes_from)); + + if let Some(name) = name { + err.span_label( + escape_span, + format!("reference to `{}` escapes the {} body here", name, escapes_from), + ); + } else { + err.span_label( + escape_span, + format!("reference escapes the {} body here", escapes_from), + ); + } + + err + } + + fn get_moved_indexes( + &mut self, + location: Location, + mpi: MovePathIndex, + ) -> (Vec<MoveSite>, Vec<Location>) { + fn predecessor_locations<'tcx, 'a>( + body: &'a mir::Body<'tcx>, + location: Location, + ) -> impl Iterator<Item = Location> + Captures<'tcx> + 'a { + if location.statement_index == 0 { + let predecessors = body.basic_blocks.predecessors()[location.block].to_vec(); + Either::Left(predecessors.into_iter().map(move |bb| body.terminator_loc(bb))) + } else { + Either::Right(std::iter::once(Location { + statement_index: location.statement_index - 1, + ..location + })) + } + } + + let mut mpis = vec![mpi]; + let move_paths = &self.move_data.move_paths; + mpis.extend(move_paths[mpi].parents(move_paths).map(|(mpi, _)| mpi)); + + let mut stack = Vec::new(); + let mut back_edge_stack = Vec::new(); + + predecessor_locations(self.body, location).for_each(|predecessor| { + if location.dominates(predecessor, &self.dominators) { + back_edge_stack.push(predecessor) + } else { + stack.push(predecessor); + } + }); + + let mut reached_start = false; + + /* Check if the mpi is initialized as an argument */ + let mut is_argument = false; + for arg in self.body.args_iter() { + let path = self.move_data.rev_lookup.find_local(arg); + if mpis.contains(&path) { + is_argument = true; + } + } + + let mut visited = FxHashSet::default(); + let mut move_locations = FxHashSet::default(); + let mut reinits = vec![]; + let mut result = vec![]; + + let mut dfs_iter = |result: &mut Vec<MoveSite>, location: Location, is_back_edge: bool| { + debug!( + "report_use_of_moved_or_uninitialized: (current_location={:?}, back_edge={})", + location, is_back_edge + ); + + if !visited.insert(location) { + return true; + } + + // check for moves + let stmt_kind = + self.body[location.block].statements.get(location.statement_index).map(|s| &s.kind); + if let Some(StatementKind::StorageDead(..)) = stmt_kind { + // this analysis only tries to find moves explicitly + // written by the user, so we ignore the move-outs + // created by `StorageDead` and at the beginning + // of a function. + } else { + // If we are found a use of a.b.c which was in error, then we want to look for + // moves not only of a.b.c but also a.b and a. + // + // Note that the moves data already includes "parent" paths, so we don't have to + // worry about the other case: that is, if there is a move of a.b.c, it is already + // marked as a move of a.b and a as well, so we will generate the correct errors + // there. + for moi in &self.move_data.loc_map[location] { + debug!("report_use_of_moved_or_uninitialized: moi={:?}", moi); + let path = self.move_data.moves[*moi].path; + if mpis.contains(&path) { + debug!( + "report_use_of_moved_or_uninitialized: found {:?}", + move_paths[path].place + ); + result.push(MoveSite { moi: *moi, traversed_back_edge: is_back_edge }); + move_locations.insert(location); + + // Strictly speaking, we could continue our DFS here. There may be + // other moves that can reach the point of error. But it is kind of + // confusing to highlight them. + // + // Example: + // + // ``` + // let a = vec![]; + // let b = a; + // let c = a; + // drop(a); // <-- current point of error + // ``` + // + // Because we stop the DFS here, we only highlight `let c = a`, + // and not `let b = a`. We will of course also report an error at + // `let c = a` which highlights `let b = a` as the move. + return true; + } + } + } + + // check for inits + let mut any_match = false; + for ii in &self.move_data.init_loc_map[location] { + let init = self.move_data.inits[*ii]; + match init.kind { + InitKind::Deep | InitKind::NonPanicPathOnly => { + if mpis.contains(&init.path) { + any_match = true; + } + } + InitKind::Shallow => { + if mpi == init.path { + any_match = true; + } + } + } + } + if any_match { + reinits.push(location); + return true; + } + return false; + }; + + while let Some(location) = stack.pop() { + if dfs_iter(&mut result, location, false) { + continue; + } + + let mut has_predecessor = false; + predecessor_locations(self.body, location).for_each(|predecessor| { + if location.dominates(predecessor, &self.dominators) { + back_edge_stack.push(predecessor) + } else { + stack.push(predecessor); + } + has_predecessor = true; + }); + + if !has_predecessor { + reached_start = true; + } + } + if (is_argument || !reached_start) && result.is_empty() { + /* Process back edges (moves in future loop iterations) only if + the move path is definitely initialized upon loop entry, + to avoid spurious "in previous iteration" errors. + During DFS, if there's a path from the error back to the start + of the function with no intervening init or move, then the + move path may be uninitialized at loop entry. + */ + while let Some(location) = back_edge_stack.pop() { + if dfs_iter(&mut result, location, true) { + continue; + } + + predecessor_locations(self.body, location) + .for_each(|predecessor| back_edge_stack.push(predecessor)); + } + } + + // Check if we can reach these reinits from a move location. + let reinits_reachable = reinits + .into_iter() + .filter(|reinit| { + let mut visited = FxHashSet::default(); + let mut stack = vec![*reinit]; + while let Some(location) = stack.pop() { + if !visited.insert(location) { + continue; + } + if move_locations.contains(&location) { + return true; + } + stack.extend(predecessor_locations(self.body, location)); + } + false + }) + .collect::<Vec<Location>>(); + (result, reinits_reachable) + } + + pub(crate) fn report_illegal_mutation_of_borrowed( + &mut self, + location: Location, + (place, span): (Place<'tcx>, Span), + loan: &BorrowData<'tcx>, + ) { + let loan_spans = self.retrieve_borrow_spans(loan); + let loan_span = loan_spans.args_or_use(); + + let descr_place = self.describe_any_place(place.as_ref()); + if loan.kind == BorrowKind::Shallow { + if let Some(section) = self.classify_immutable_section(loan.assigned_place) { + let mut err = self.cannot_mutate_in_immutable_section( + span, + loan_span, + &descr_place, + section, + "assign", + ); + loan_spans.var_span_label( + &mut err, + format!("borrow occurs due to use{}", loan_spans.describe()), + loan.kind.describe_mutability(), + ); + + self.buffer_error(err); + + return; + } + } + + let mut err = self.cannot_assign_to_borrowed(span, loan_span, &descr_place); + + loan_spans.var_span_label( + &mut err, + format!("borrow occurs due to use{}", loan_spans.describe()), + loan.kind.describe_mutability(), + ); + + self.explain_why_borrow_contains_point(location, loan, None).add_explanation_to_diagnostic( + self.infcx.tcx, + &self.body, + &self.local_names, + &mut err, + "", + None, + None, + ); + + self.explain_deref_coercion(loan, &mut err); + + self.buffer_error(err); + } + + fn explain_deref_coercion(&mut self, loan: &BorrowData<'tcx>, err: &mut Diagnostic) { + let tcx = self.infcx.tcx; + if let ( + Some(Terminator { kind: TerminatorKind::Call { from_hir_call: false, .. }, .. }), + Some((method_did, method_substs)), + ) = ( + &self.body[loan.reserve_location.block].terminator, + rustc_const_eval::util::find_self_call( + tcx, + self.body, + loan.assigned_place.local, + loan.reserve_location.block, + ), + ) { + if tcx.is_diagnostic_item(sym::deref_method, method_did) { + let deref_target = + tcx.get_diagnostic_item(sym::deref_target).and_then(|deref_target| { + Instance::resolve(tcx, self.param_env, deref_target, method_substs) + .transpose() + }); + if let Some(Ok(instance)) = deref_target { + let deref_target_ty = instance.ty(tcx, self.param_env); + err.note(&format!( + "borrow occurs due to deref coercion to `{}`", + deref_target_ty + )); + err.span_note(tcx.def_span(instance.def_id()), "deref defined here"); + } + } + } + } + + /// Reports an illegal reassignment; for example, an assignment to + /// (part of) a non-`mut` local that occurs potentially after that + /// local has already been initialized. `place` is the path being + /// assigned; `err_place` is a place providing a reason why + /// `place` is not mutable (e.g., the non-`mut` local `x` in an + /// assignment to `x.f`). + pub(crate) fn report_illegal_reassignment( + &mut self, + _location: Location, + (place, span): (Place<'tcx>, Span), + assigned_span: Span, + err_place: Place<'tcx>, + ) { + let (from_arg, local_decl, local_name) = match err_place.as_local() { + Some(local) => ( + self.body.local_kind(local) == LocalKind::Arg, + Some(&self.body.local_decls[local]), + self.local_names[local], + ), + None => (false, None, None), + }; + + // If root local is initialized immediately (everything apart from let + // PATTERN;) then make the error refer to that local, rather than the + // place being assigned later. + let (place_description, assigned_span) = match local_decl { + Some(LocalDecl { + local_info: + Some(box LocalInfo::User( + ClearCrossCrate::Clear + | ClearCrossCrate::Set(BindingForm::Var(VarBindingForm { + opt_match_place: None, + .. + })), + )) + | Some(box LocalInfo::StaticRef { .. }) + | None, + .. + }) + | None => (self.describe_any_place(place.as_ref()), assigned_span), + Some(decl) => (self.describe_any_place(err_place.as_ref()), decl.source_info.span), + }; + + let mut err = self.cannot_reassign_immutable(span, &place_description, from_arg); + let msg = if from_arg { + "cannot assign to immutable argument" + } else { + "cannot assign twice to immutable variable" + }; + if span != assigned_span && !from_arg { + err.span_label(assigned_span, format!("first assignment to {}", place_description)); + } + if let Some(decl) = local_decl + && let Some(name) = local_name + && decl.can_be_made_mutable() + { + err.span_suggestion( + decl.source_info.span, + "consider making this binding mutable", + format!("mut {}", name), + Applicability::MachineApplicable, + ); + } + err.span_label(span, msg); + self.buffer_error(err); + } + + fn classify_drop_access_kind(&self, place: PlaceRef<'tcx>) -> StorageDeadOrDrop<'tcx> { + let tcx = self.infcx.tcx; + let (kind, _place_ty) = place.projection.iter().fold( + (LocalStorageDead, PlaceTy::from_ty(self.body.local_decls[place.local].ty)), + |(kind, place_ty), &elem| { + ( + match elem { + ProjectionElem::Deref => match kind { + StorageDeadOrDrop::LocalStorageDead + | StorageDeadOrDrop::BoxedStorageDead => { + assert!( + place_ty.ty.is_box(), + "Drop of value behind a reference or raw pointer" + ); + StorageDeadOrDrop::BoxedStorageDead + } + StorageDeadOrDrop::Destructor(_) => kind, + }, + ProjectionElem::Field(..) | ProjectionElem::Downcast(..) => { + match place_ty.ty.kind() { + ty::Adt(def, _) if def.has_dtor(tcx) => { + // Report the outermost adt with a destructor + match kind { + StorageDeadOrDrop::Destructor(_) => kind, + StorageDeadOrDrop::LocalStorageDead + | StorageDeadOrDrop::BoxedStorageDead => { + StorageDeadOrDrop::Destructor(place_ty.ty) + } + } + } + _ => kind, + } + } + ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::Index(_) => kind, + }, + place_ty.projection_ty(tcx, elem), + ) + }, + ); + kind + } + + /// Describe the reason for the fake borrow that was assigned to `place`. + fn classify_immutable_section(&self, place: Place<'tcx>) -> Option<&'static str> { + use rustc_middle::mir::visit::Visitor; + struct FakeReadCauseFinder<'tcx> { + place: Place<'tcx>, + cause: Option<FakeReadCause>, + } + impl<'tcx> Visitor<'tcx> for FakeReadCauseFinder<'tcx> { + fn visit_statement(&mut self, statement: &Statement<'tcx>, _: Location) { + match statement { + Statement { kind: StatementKind::FakeRead(box (cause, place)), .. } + if *place == self.place => + { + self.cause = Some(*cause); + } + _ => (), + } + } + } + let mut visitor = FakeReadCauseFinder { place, cause: None }; + visitor.visit_body(&self.body); + match visitor.cause { + Some(FakeReadCause::ForMatchGuard) => Some("match guard"), + Some(FakeReadCause::ForIndex) => Some("indexing expression"), + _ => None, + } + } + + /// Annotate argument and return type of function and closure with (synthesized) lifetime for + /// borrow of local value that does not live long enough. + fn annotate_argument_and_return_for_borrow( + &self, + borrow: &BorrowData<'tcx>, + ) -> Option<AnnotatedBorrowFnSignature<'tcx>> { + // Define a fallback for when we can't match a closure. + let fallback = || { + let is_closure = self.infcx.tcx.is_closure(self.mir_def_id().to_def_id()); + if is_closure { + None + } else { + let ty = self.infcx.tcx.type_of(self.mir_def_id()); + match ty.kind() { + ty::FnDef(_, _) | ty::FnPtr(_) => self.annotate_fn_sig( + self.mir_def_id(), + self.infcx.tcx.fn_sig(self.mir_def_id()), + ), + _ => None, + } + } + }; + + // In order to determine whether we need to annotate, we need to check whether the reserve + // place was an assignment into a temporary. + // + // If it was, we check whether or not that temporary is eventually assigned into the return + // place. If it was, we can add annotations about the function's return type and arguments + // and it'll make sense. + let location = borrow.reserve_location; + debug!("annotate_argument_and_return_for_borrow: location={:?}", location); + if let Some(&Statement { kind: StatementKind::Assign(box (ref reservation, _)), .. }) = + &self.body[location.block].statements.get(location.statement_index) + { + debug!("annotate_argument_and_return_for_borrow: reservation={:?}", reservation); + // Check that the initial assignment of the reserve location is into a temporary. + let mut target = match reservation.as_local() { + Some(local) if self.body.local_kind(local) == LocalKind::Temp => local, + _ => return None, + }; + + // Next, look through the rest of the block, checking if we are assigning the + // `target` (that is, the place that contains our borrow) to anything. + let mut annotated_closure = None; + for stmt in &self.body[location.block].statements[location.statement_index + 1..] { + debug!( + "annotate_argument_and_return_for_borrow: target={:?} stmt={:?}", + target, stmt + ); + if let StatementKind::Assign(box (place, rvalue)) = &stmt.kind { + if let Some(assigned_to) = place.as_local() { + debug!( + "annotate_argument_and_return_for_borrow: assigned_to={:?} \ + rvalue={:?}", + assigned_to, rvalue + ); + // Check if our `target` was captured by a closure. + if let Rvalue::Aggregate( + box AggregateKind::Closure(def_id, substs), + ref operands, + ) = *rvalue + { + for operand in operands { + let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = operand else { + continue; + }; + debug!( + "annotate_argument_and_return_for_borrow: assigned_from={:?}", + assigned_from + ); + + // Find the local from the operand. + let Some(assigned_from_local) = assigned_from.local_or_deref_local() else { + continue; + }; + + if assigned_from_local != target { + continue; + } + + // If a closure captured our `target` and then assigned + // into a place then we should annotate the closure in + // case it ends up being assigned into the return place. + annotated_closure = + self.annotate_fn_sig(def_id, substs.as_closure().sig()); + debug!( + "annotate_argument_and_return_for_borrow: \ + annotated_closure={:?} assigned_from_local={:?} \ + assigned_to={:?}", + annotated_closure, assigned_from_local, assigned_to + ); + + if assigned_to == mir::RETURN_PLACE { + // If it was assigned directly into the return place, then + // return now. + return annotated_closure; + } else { + // Otherwise, update the target. + target = assigned_to; + } + } + + // If none of our closure's operands matched, then skip to the next + // statement. + continue; + } + + // Otherwise, look at other types of assignment. + let assigned_from = match rvalue { + Rvalue::Ref(_, _, assigned_from) => assigned_from, + Rvalue::Use(operand) => match operand { + Operand::Copy(assigned_from) | Operand::Move(assigned_from) => { + assigned_from + } + _ => continue, + }, + _ => continue, + }; + debug!( + "annotate_argument_and_return_for_borrow: \ + assigned_from={:?}", + assigned_from, + ); + + // Find the local from the rvalue. + let Some(assigned_from_local) = assigned_from.local_or_deref_local() else { continue }; + debug!( + "annotate_argument_and_return_for_borrow: \ + assigned_from_local={:?}", + assigned_from_local, + ); + + // Check if our local matches the target - if so, we've assigned our + // borrow to a new place. + if assigned_from_local != target { + continue; + } + + // If we assigned our `target` into a new place, then we should + // check if it was the return place. + debug!( + "annotate_argument_and_return_for_borrow: \ + assigned_from_local={:?} assigned_to={:?}", + assigned_from_local, assigned_to + ); + if assigned_to == mir::RETURN_PLACE { + // If it was then return the annotated closure if there was one, + // else, annotate this function. + return annotated_closure.or_else(fallback); + } + + // If we didn't assign into the return place, then we just update + // the target. + target = assigned_to; + } + } + } + + // Check the terminator if we didn't find anything in the statements. + let terminator = &self.body[location.block].terminator(); + debug!( + "annotate_argument_and_return_for_borrow: target={:?} terminator={:?}", + target, terminator + ); + if let TerminatorKind::Call { destination, target: Some(_), args, .. } = + &terminator.kind + { + if let Some(assigned_to) = destination.as_local() { + debug!( + "annotate_argument_and_return_for_borrow: assigned_to={:?} args={:?}", + assigned_to, args + ); + for operand in args { + let (Operand::Copy(assigned_from) | Operand::Move(assigned_from)) = operand else { + continue; + }; + debug!( + "annotate_argument_and_return_for_borrow: assigned_from={:?}", + assigned_from, + ); + + if let Some(assigned_from_local) = assigned_from.local_or_deref_local() { + debug!( + "annotate_argument_and_return_for_borrow: assigned_from_local={:?}", + assigned_from_local, + ); + + if assigned_to == mir::RETURN_PLACE && assigned_from_local == target { + return annotated_closure.or_else(fallback); + } + } + } + } + } + } + + // If we haven't found an assignment into the return place, then we need not add + // any annotations. + debug!("annotate_argument_and_return_for_borrow: none found"); + None + } + + /// Annotate the first argument and return type of a function signature if they are + /// references. + fn annotate_fn_sig( + &self, + did: LocalDefId, + sig: ty::PolyFnSig<'tcx>, + ) -> Option<AnnotatedBorrowFnSignature<'tcx>> { + debug!("annotate_fn_sig: did={:?} sig={:?}", did, sig); + let is_closure = self.infcx.tcx.is_closure(did.to_def_id()); + let fn_hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(did); + let fn_decl = self.infcx.tcx.hir().fn_decl_by_hir_id(fn_hir_id)?; + + // We need to work out which arguments to highlight. We do this by looking + // at the return type, where there are three cases: + // + // 1. If there are named arguments, then we should highlight the return type and + // highlight any of the arguments that are also references with that lifetime. + // If there are no arguments that have the same lifetime as the return type, + // then don't highlight anything. + // 2. The return type is a reference with an anonymous lifetime. If this is + // the case, then we can take advantage of (and teach) the lifetime elision + // rules. + // + // We know that an error is being reported. So the arguments and return type + // must satisfy the elision rules. Therefore, if there is a single argument + // then that means the return type and first (and only) argument have the same + // lifetime and the borrow isn't meeting that, we can highlight the argument + // and return type. + // + // If there are multiple arguments then the first argument must be self (else + // it would not satisfy the elision rules), so we can highlight self and the + // return type. + // 3. The return type is not a reference. In this case, we don't highlight + // anything. + let return_ty = sig.output(); + match return_ty.skip_binder().kind() { + ty::Ref(return_region, _, _) if return_region.has_name() && !is_closure => { + // This is case 1 from above, return type is a named reference so we need to + // search for relevant arguments. + let mut arguments = Vec::new(); + for (index, argument) in sig.inputs().skip_binder().iter().enumerate() { + if let ty::Ref(argument_region, _, _) = argument.kind() { + if argument_region == return_region { + // Need to use the `rustc_middle::ty` types to compare against the + // `return_region`. Then use the `rustc_hir` type to get only + // the lifetime span. + if let hir::TyKind::Rptr(lifetime, _) = &fn_decl.inputs[index].kind { + // With access to the lifetime, we can get + // the span of it. + arguments.push((*argument, lifetime.span)); + } else { + bug!("ty type is a ref but hir type is not"); + } + } + } + } + + // We need to have arguments. This shouldn't happen, but it's worth checking. + if arguments.is_empty() { + return None; + } + + // We use a mix of the HIR and the Ty types to get information + // as the HIR doesn't have full types for closure arguments. + let return_ty = sig.output().skip_binder(); + let mut return_span = fn_decl.output.span(); + if let hir::FnRetTy::Return(ty) = &fn_decl.output { + if let hir::TyKind::Rptr(lifetime, _) = ty.kind { + return_span = lifetime.span; + } + } + + Some(AnnotatedBorrowFnSignature::NamedFunction { + arguments, + return_ty, + return_span, + }) + } + ty::Ref(_, _, _) if is_closure => { + // This is case 2 from above but only for closures, return type is anonymous + // reference so we select + // the first argument. + let argument_span = fn_decl.inputs.first()?.span; + let argument_ty = sig.inputs().skip_binder().first()?; + + // Closure arguments are wrapped in a tuple, so we need to get the first + // from that. + if let ty::Tuple(elems) = argument_ty.kind() { + let &argument_ty = elems.first()?; + if let ty::Ref(_, _, _) = argument_ty.kind() { + return Some(AnnotatedBorrowFnSignature::Closure { + argument_ty, + argument_span, + }); + } + } + + None + } + ty::Ref(_, _, _) => { + // This is also case 2 from above but for functions, return type is still an + // anonymous reference so we select the first argument. + let argument_span = fn_decl.inputs.first()?.span; + let argument_ty = *sig.inputs().skip_binder().first()?; + + let return_span = fn_decl.output.span(); + let return_ty = sig.output().skip_binder(); + + // We expect the first argument to be a reference. + match argument_ty.kind() { + ty::Ref(_, _, _) => {} + _ => return None, + } + + Some(AnnotatedBorrowFnSignature::AnonymousFunction { + argument_ty, + argument_span, + return_ty, + return_span, + }) + } + _ => { + // This is case 3 from above, return type is not a reference so don't highlight + // anything. + None + } + } + } +} + +#[derive(Debug)] +enum AnnotatedBorrowFnSignature<'tcx> { + NamedFunction { + arguments: Vec<(Ty<'tcx>, Span)>, + return_ty: Ty<'tcx>, + return_span: Span, + }, + AnonymousFunction { + argument_ty: Ty<'tcx>, + argument_span: Span, + return_ty: Ty<'tcx>, + return_span: Span, + }, + Closure { + argument_ty: Ty<'tcx>, + argument_span: Span, + }, +} + +impl<'tcx> AnnotatedBorrowFnSignature<'tcx> { + /// Annotate the provided diagnostic with information about borrow from the fn signature that + /// helps explain. + pub(crate) fn emit(&self, cx: &mut MirBorrowckCtxt<'_, 'tcx>, diag: &mut Diagnostic) -> String { + match self { + &AnnotatedBorrowFnSignature::Closure { argument_ty, argument_span } => { + diag.span_label( + argument_span, + format!("has type `{}`", cx.get_name_for_ty(argument_ty, 0)), + ); + + cx.get_region_name_for_ty(argument_ty, 0) + } + &AnnotatedBorrowFnSignature::AnonymousFunction { + argument_ty, + argument_span, + return_ty, + return_span, + } => { + let argument_ty_name = cx.get_name_for_ty(argument_ty, 0); + diag.span_label(argument_span, format!("has type `{}`", argument_ty_name)); + + let return_ty_name = cx.get_name_for_ty(return_ty, 0); + let types_equal = return_ty_name == argument_ty_name; + diag.span_label( + return_span, + format!( + "{}has type `{}`", + if types_equal { "also " } else { "" }, + return_ty_name, + ), + ); + + diag.note( + "argument and return type have the same lifetime due to lifetime elision rules", + ); + diag.note( + "to learn more, visit <https://doc.rust-lang.org/book/ch10-03-\ + lifetime-syntax.html#lifetime-elision>", + ); + + cx.get_region_name_for_ty(return_ty, 0) + } + AnnotatedBorrowFnSignature::NamedFunction { arguments, return_ty, return_span } => { + // Region of return type and arguments checked to be the same earlier. + let region_name = cx.get_region_name_for_ty(*return_ty, 0); + for (_, argument_span) in arguments { + diag.span_label(*argument_span, format!("has lifetime `{}`", region_name)); + } + + diag.span_label(*return_span, format!("also has lifetime `{}`", region_name,)); + + diag.help(&format!( + "use data from the highlighted arguments which match the `{}` lifetime of \ + the return type", + region_name, + )); + + region_name + } + } + } +} + +/// Detect whether one of the provided spans is a statement nested within the top-most visited expr +struct ReferencedStatementsVisitor<'a>(&'a [Span], bool); + +impl<'a, 'v> Visitor<'v> for ReferencedStatementsVisitor<'a> { + fn visit_stmt(&mut self, s: &'v hir::Stmt<'v>) { + match s.kind { + hir::StmtKind::Semi(expr) if self.0.contains(&expr.span) => { + self.1 = true; + } + _ => {} + } + } +} + +/// Given a set of spans representing statements initializing the relevant binding, visit all the +/// function expressions looking for branching code paths that *do not* initialize the binding. +struct ConditionVisitor<'b> { + spans: &'b [Span], + name: &'b str, + errors: Vec<(Span, String)>, +} + +impl<'b, 'v> Visitor<'v> for ConditionVisitor<'b> { + fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) { + match ex.kind { + hir::ExprKind::If(cond, body, None) => { + // `if` expressions with no `else` that initialize the binding might be missing an + // `else` arm. + let mut v = ReferencedStatementsVisitor(self.spans, false); + v.visit_expr(body); + if v.1 { + self.errors.push(( + cond.span, + format!( + "if this `if` condition is `false`, {} is not initialized", + self.name, + ), + )); + self.errors.push(( + ex.span.shrink_to_hi(), + format!("an `else` arm might be missing here, initializing {}", self.name), + )); + } + } + hir::ExprKind::If(cond, body, Some(other)) => { + // `if` expressions where the binding is only initialized in one of the two arms + // might be missing a binding initialization. + let mut a = ReferencedStatementsVisitor(self.spans, false); + a.visit_expr(body); + let mut b = ReferencedStatementsVisitor(self.spans, false); + b.visit_expr(other); + match (a.1, b.1) { + (true, true) | (false, false) => {} + (true, false) => { + if other.span.is_desugaring(DesugaringKind::WhileLoop) { + self.errors.push(( + cond.span, + format!( + "if this condition isn't met and the `while` loop runs 0 \ + times, {} is not initialized", + self.name + ), + )); + } else { + self.errors.push(( + body.span.shrink_to_hi().until(other.span), + format!( + "if the `if` condition is `false` and this `else` arm is \ + executed, {} is not initialized", + self.name + ), + )); + } + } + (false, true) => { + self.errors.push(( + cond.span, + format!( + "if this condition is `true`, {} is not initialized", + self.name + ), + )); + } + } + } + hir::ExprKind::Match(e, arms, loop_desugar) => { + // If the binding is initialized in one of the match arms, then the other match + // arms might be missing an initialization. + let results: Vec<bool> = arms + .iter() + .map(|arm| { + let mut v = ReferencedStatementsVisitor(self.spans, false); + v.visit_arm(arm); + v.1 + }) + .collect(); + if results.iter().any(|x| *x) && !results.iter().all(|x| *x) { + for (arm, seen) in arms.iter().zip(results) { + if !seen { + if loop_desugar == hir::MatchSource::ForLoopDesugar { + self.errors.push(( + e.span, + format!( + "if the `for` loop runs 0 times, {} is not initialized", + self.name + ), + )); + } else if let Some(guard) = &arm.guard { + self.errors.push(( + arm.pat.span.to(guard.body().span), + format!( + "if this pattern and condition are matched, {} is not \ + initialized", + self.name + ), + )); + } else { + self.errors.push(( + arm.pat.span, + format!( + "if this pattern is matched, {} is not initialized", + self.name + ), + )); + } + } + } + } + } + // FIXME: should we also account for binops, particularly `&&` and `||`? `try` should + // also be accounted for. For now it is fine, as if we don't find *any* relevant + // branching code paths, we point at the places where the binding *is* initialized for + // *some* context. + _ => {} + } + walk_expr(self, ex); + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs new file mode 100644 index 000000000..72aee0267 --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs @@ -0,0 +1,744 @@ +//! Print diagnostics to explain why values are borrowed. + +use std::collections::VecDeque; + +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_index::vec::IndexVec; +use rustc_infer::infer::NllRegionVariableOrigin; +use rustc_middle::mir::{ + Body, CastKind, ConstraintCategory, FakeReadCause, Local, Location, Operand, Place, Rvalue, + Statement, StatementKind, TerminatorKind, +}; +use rustc_middle::ty::adjustment::PointerCast; +use rustc_middle::ty::{self, RegionVid, TyCtxt}; +use rustc_span::symbol::{kw, Symbol}; +use rustc_span::{sym, DesugaringKind, Span}; + +use crate::region_infer::BlameConstraint; +use crate::{ + borrow_set::BorrowData, nll::ConstraintDescription, region_infer::Cause, MirBorrowckCtxt, + WriteKind, +}; + +use super::{find_use, RegionName, UseSpans}; + +#[derive(Debug)] +pub(crate) enum BorrowExplanation<'tcx> { + UsedLater(LaterUseKind, Span, Option<Span>), + UsedLaterInLoop(LaterUseKind, Span, Option<Span>), + UsedLaterWhenDropped { + drop_loc: Location, + dropped_local: Local, + should_note_order: bool, + }, + MustBeValidFor { + category: ConstraintCategory<'tcx>, + from_closure: bool, + span: Span, + region_name: RegionName, + opt_place_desc: Option<String>, + }, + Unexplained, +} + +#[derive(Clone, Copy, Debug)] +pub(crate) enum LaterUseKind { + TraitCapture, + ClosureCapture, + Call, + FakeLetRead, + Other, +} + +impl<'tcx> BorrowExplanation<'tcx> { + pub(crate) fn is_explained(&self) -> bool { + !matches!(self, BorrowExplanation::Unexplained) + } + pub(crate) fn add_explanation_to_diagnostic( + &self, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + local_names: &IndexVec<Local, Option<Symbol>>, + err: &mut Diagnostic, + borrow_desc: &str, + borrow_span: Option<Span>, + multiple_borrow_span: Option<(Span, Span)>, + ) { + match *self { + BorrowExplanation::UsedLater(later_use_kind, var_or_use_span, path_span) => { + let message = match later_use_kind { + LaterUseKind::TraitCapture => "captured here by trait object", + LaterUseKind::ClosureCapture => "captured here by closure", + LaterUseKind::Call => "used by call", + LaterUseKind::FakeLetRead => "stored here", + LaterUseKind::Other => "used here", + }; + // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same + if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) { + if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) { + err.span_label( + var_or_use_span, + format!("{}borrow later {}", borrow_desc, message), + ); + } + } else { + // path_span must be `Some` as otherwise the if condition is true + let path_span = path_span.unwrap(); + // path_span is only present in the case of closure capture + assert!(matches!(later_use_kind, LaterUseKind::ClosureCapture)); + if !borrow_span.map_or(false, |sp| sp.overlaps(var_or_use_span)) { + let path_label = "used here by closure"; + let capture_kind_label = message; + err.span_label( + var_or_use_span, + format!("{}borrow later {}", borrow_desc, capture_kind_label), + ); + err.span_label(path_span, path_label); + } + } + } + BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span) => { + let message = match later_use_kind { + LaterUseKind::TraitCapture => { + "borrow captured here by trait object, in later iteration of loop" + } + LaterUseKind::ClosureCapture => { + "borrow captured here by closure, in later iteration of loop" + } + LaterUseKind::Call => "borrow used by call, in later iteration of loop", + LaterUseKind::FakeLetRead => "borrow later stored here", + LaterUseKind::Other => "borrow used here, in later iteration of loop", + }; + // We can use `var_or_use_span` if either `path_span` is not present, or both spans are the same + if path_span.map(|path_span| path_span == var_or_use_span).unwrap_or(true) { + err.span_label(var_or_use_span, format!("{}{}", borrow_desc, message)); + } else { + // path_span must be `Some` as otherwise the if condition is true + let path_span = path_span.unwrap(); + // path_span is only present in the case of closure capture + assert!(matches!(later_use_kind, LaterUseKind::ClosureCapture)); + if borrow_span.map(|sp| !sp.overlaps(var_or_use_span)).unwrap_or(true) { + let path_label = "used here by closure"; + let capture_kind_label = message; + err.span_label( + var_or_use_span, + format!("{}borrow later {}", borrow_desc, capture_kind_label), + ); + err.span_label(path_span, path_label); + } + } + } + BorrowExplanation::UsedLaterWhenDropped { + drop_loc, + dropped_local, + should_note_order, + } => { + let local_decl = &body.local_decls[dropped_local]; + let mut ty = local_decl.ty; + if local_decl.source_info.span.desugaring_kind() == Some(DesugaringKind::ForLoop) { + if let ty::Adt(adt, substs) = local_decl.ty.kind() { + if tcx.is_diagnostic_item(sym::Option, adt.did()) { + // in for loop desugaring, only look at the `Some(..)` inner type + ty = substs.type_at(0); + } + } + } + let (dtor_desc, type_desc) = match ty.kind() { + // If type is an ADT that implements Drop, then + // simplify output by reporting just the ADT name. + ty::Adt(adt, _substs) if adt.has_dtor(tcx) && !adt.is_box() => { + ("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did()))) + } + + // Otherwise, just report the whole type (and use + // the intentionally fuzzy phrase "destructor") + ty::Closure(..) => ("destructor", "closure".to_owned()), + ty::Generator(..) => ("destructor", "generator".to_owned()), + + _ => ("destructor", format!("type `{}`", local_decl.ty)), + }; + + match local_names[dropped_local] { + Some(local_name) if !local_decl.from_compiler_desugaring() => { + let message = format!( + "{B}borrow might be used here, when `{LOC}` is dropped \ + and runs the {DTOR} for {TYPE}", + B = borrow_desc, + LOC = local_name, + TYPE = type_desc, + DTOR = dtor_desc + ); + err.span_label(body.source_info(drop_loc).span, message); + + if should_note_order { + err.note( + "values in a scope are dropped \ + in the opposite order they are defined", + ); + } + } + _ => { + err.span_label( + local_decl.source_info.span, + format!( + "a temporary with access to the {B}borrow \ + is created here ...", + B = borrow_desc + ), + ); + let message = format!( + "... and the {B}borrow might be used here, \ + when that temporary is dropped \ + and runs the {DTOR} for {TYPE}", + B = borrow_desc, + TYPE = type_desc, + DTOR = dtor_desc + ); + err.span_label(body.source_info(drop_loc).span, message); + + if let Some(info) = &local_decl.is_block_tail { + if info.tail_result_is_ignored { + // #85581: If the first mutable borrow's scope contains + // the second borrow, this suggestion isn't helpful. + if !multiple_borrow_span + .map(|(old, new)| { + old.to(info.span.shrink_to_hi()).contains(new) + }) + .unwrap_or(false) + { + err.span_suggestion_verbose( + info.span.shrink_to_hi(), + "consider adding semicolon after the expression so its \ + temporaries are dropped sooner, before the local variables \ + declared by the block are dropped", + ";", + Applicability::MaybeIncorrect, + ); + } + } else { + err.note( + "the temporary is part of an expression at the end of a \ + block;\nconsider forcing this temporary to be dropped sooner, \ + before the block's local variables are dropped", + ); + err.multipart_suggestion( + "for example, you could save the expression's value in a new \ + local variable `x` and then make `x` be the expression at the \ + end of the block", + vec![ + (info.span.shrink_to_lo(), "let x = ".to_string()), + (info.span.shrink_to_hi(), "; x".to_string()), + ], + Applicability::MaybeIncorrect, + ); + }; + } + } + } + } + BorrowExplanation::MustBeValidFor { + category, + span, + ref region_name, + ref opt_place_desc, + from_closure: _, + } => { + region_name.highlight_region_name(err); + + if let Some(desc) = opt_place_desc { + err.span_label( + span, + format!( + "{}requires that `{}` is borrowed for `{}`", + category.description(), + desc, + region_name, + ), + ); + } else { + err.span_label( + span, + format!( + "{}requires that {}borrow lasts for `{}`", + category.description(), + borrow_desc, + region_name, + ), + ); + }; + + self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name); + } + _ => {} + } + } + pub(crate) fn add_lifetime_bound_suggestion_to_diagnostic( + &self, + err: &mut Diagnostic, + category: &ConstraintCategory<'tcx>, + span: Span, + region_name: &RegionName, + ) { + if let ConstraintCategory::OpaqueType = category { + let suggestable_name = + if region_name.was_named() { region_name.name } else { kw::UnderscoreLifetime }; + + let msg = format!( + "you can add a bound to the {}to make it last less than `'static` and match `{}`", + category.description(), + region_name, + ); + + err.span_suggestion_verbose( + span.shrink_to_hi(), + &msg, + format!(" + {}", suggestable_name), + Applicability::Unspecified, + ); + } + } +} + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + fn free_region_constraint_info( + &self, + borrow_region: RegionVid, + outlived_region: RegionVid, + ) -> (ConstraintCategory<'tcx>, bool, Span, Option<RegionName>) { + let BlameConstraint { category, from_closure, cause, variance_info: _ } = + self.regioncx.best_blame_constraint( + &self.body, + borrow_region, + NllRegionVariableOrigin::FreeRegion, + |r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region), + ); + + let outlived_fr_name = self.give_region_a_name(outlived_region); + + (category, from_closure, cause.span, outlived_fr_name) + } + + /// Returns structured explanation for *why* the borrow contains the + /// point from `location`. This is key for the "3-point errors" + /// [described in the NLL RFC][d]. + /// + /// # Parameters + /// + /// - `borrow`: the borrow in question + /// - `location`: where the borrow occurs + /// - `kind_place`: if Some, this describes the statement that triggered the error. + /// - first half is the kind of write, if any, being performed + /// - second half is the place being accessed + /// + /// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points + pub(crate) fn explain_why_borrow_contains_point( + &self, + location: Location, + borrow: &BorrowData<'tcx>, + kind_place: Option<(WriteKind, Place<'tcx>)>, + ) -> BorrowExplanation<'tcx> { + debug!( + "explain_why_borrow_contains_point(location={:?}, borrow={:?}, kind_place={:?})", + location, borrow, kind_place + ); + + let regioncx = &self.regioncx; + let body: &Body<'_> = &self.body; + let tcx = self.infcx.tcx; + + let borrow_region_vid = borrow.region; + debug!("explain_why_borrow_contains_point: borrow_region_vid={:?}", borrow_region_vid); + + let region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location); + debug!("explain_why_borrow_contains_point: region_sub={:?}", region_sub); + + match find_use::find(body, regioncx, tcx, region_sub, location) { + Some(Cause::LiveVar(local, location)) => { + let span = body.source_info(location).span; + let spans = self + .move_spans(Place::from(local).as_ref(), location) + .or_else(|| self.borrow_spans(span, location)); + + let borrow_location = location; + if self.is_use_in_later_iteration_of_loop(borrow_location, location) { + let later_use = self.later_use_kind(borrow, spans, location); + BorrowExplanation::UsedLaterInLoop(later_use.0, later_use.1, later_use.2) + } else { + // Check if the location represents a `FakeRead`, and adapt the error + // message to the `FakeReadCause` it is from: in particular, + // the ones inserted in optimized `let var = <expr>` patterns. + let later_use = self.later_use_kind(borrow, spans, location); + BorrowExplanation::UsedLater(later_use.0, later_use.1, later_use.2) + } + } + + Some(Cause::DropVar(local, location)) => { + let mut should_note_order = false; + if self.local_names[local].is_some() + && let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place + && let Some(borrowed_local) = place.as_local() + && self.local_names[borrowed_local].is_some() && local != borrowed_local + { + should_note_order = true; + } + + BorrowExplanation::UsedLaterWhenDropped { + drop_loc: location, + dropped_local: local, + should_note_order, + } + } + + None => { + if let Some(region) = self.to_error_region_vid(borrow_region_vid) { + let (category, from_closure, span, region_name) = + self.free_region_constraint_info(borrow_region_vid, region); + if let Some(region_name) = region_name { + let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref()); + BorrowExplanation::MustBeValidFor { + category, + from_closure, + span, + region_name, + opt_place_desc, + } + } else { + debug!( + "explain_why_borrow_contains_point: \ + Could not generate a region name" + ); + BorrowExplanation::Unexplained + } + } else { + debug!( + "explain_why_borrow_contains_point: \ + Could not generate an error region vid" + ); + BorrowExplanation::Unexplained + } + } + } + } + + /// true if `borrow_location` can reach `use_location` by going through a loop and + /// `use_location` is also inside of that loop + fn is_use_in_later_iteration_of_loop( + &self, + borrow_location: Location, + use_location: Location, + ) -> bool { + let back_edge = self.reach_through_backedge(borrow_location, use_location); + back_edge.map_or(false, |back_edge| self.can_reach_head_of_loop(use_location, back_edge)) + } + + /// Returns the outmost back edge if `from` location can reach `to` location passing through + /// that back edge + fn reach_through_backedge(&self, from: Location, to: Location) -> Option<Location> { + let mut visited_locations = FxHashSet::default(); + let mut pending_locations = VecDeque::new(); + visited_locations.insert(from); + pending_locations.push_back(from); + debug!("reach_through_backedge: from={:?} to={:?}", from, to,); + + let mut outmost_back_edge = None; + while let Some(location) = pending_locations.pop_front() { + debug!( + "reach_through_backedge: location={:?} outmost_back_edge={:?} + pending_locations={:?} visited_locations={:?}", + location, outmost_back_edge, pending_locations, visited_locations + ); + + if location == to && outmost_back_edge.is_some() { + // We've managed to reach the use location + debug!("reach_through_backedge: found!"); + return outmost_back_edge; + } + + let block = &self.body.basic_blocks()[location.block]; + + if location.statement_index < block.statements.len() { + let successor = location.successor_within_block(); + if visited_locations.insert(successor) { + pending_locations.push_back(successor); + } + } else { + pending_locations.extend( + block + .terminator() + .successors() + .map(|bb| Location { statement_index: 0, block: bb }) + .filter(|s| visited_locations.insert(*s)) + .map(|s| { + if self.is_back_edge(location, s) { + match outmost_back_edge { + None => { + outmost_back_edge = Some(location); + } + + Some(back_edge) + if location.dominates(back_edge, &self.dominators) => + { + outmost_back_edge = Some(location); + } + + Some(_) => {} + } + } + + s + }), + ); + } + } + + None + } + + /// true if `from` location can reach `loop_head` location and `loop_head` dominates all the + /// intermediate nodes + fn can_reach_head_of_loop(&self, from: Location, loop_head: Location) -> bool { + self.find_loop_head_dfs(from, loop_head, &mut FxHashSet::default()) + } + + fn find_loop_head_dfs( + &self, + from: Location, + loop_head: Location, + visited_locations: &mut FxHashSet<Location>, + ) -> bool { + visited_locations.insert(from); + + if from == loop_head { + return true; + } + + if loop_head.dominates(from, &self.dominators) { + let block = &self.body.basic_blocks()[from.block]; + + if from.statement_index < block.statements.len() { + let successor = from.successor_within_block(); + + if !visited_locations.contains(&successor) + && self.find_loop_head_dfs(successor, loop_head, visited_locations) + { + return true; + } + } else { + for bb in block.terminator().successors() { + let successor = Location { statement_index: 0, block: bb }; + + if !visited_locations.contains(&successor) + && self.find_loop_head_dfs(successor, loop_head, visited_locations) + { + return true; + } + } + } + } + + false + } + + /// True if an edge `source -> target` is a backedge -- in other words, if the target + /// dominates the source. + fn is_back_edge(&self, source: Location, target: Location) -> bool { + target.dominates(source, &self.dominators) + } + + /// Determine how the borrow was later used. + /// First span returned points to the location of the conflicting use + /// Second span if `Some` is returned in the case of closures and points + /// to the use of the path + fn later_use_kind( + &self, + borrow: &BorrowData<'tcx>, + use_spans: UseSpans<'tcx>, + location: Location, + ) -> (LaterUseKind, Span, Option<Span>) { + match use_spans { + UseSpans::ClosureUse { capture_kind_span, path_span, .. } => { + // Used in a closure. + (LaterUseKind::ClosureCapture, capture_kind_span, Some(path_span)) + } + UseSpans::PatUse(span) + | UseSpans::OtherUse(span) + | UseSpans::FnSelfUse { var_span: span, .. } => { + let block = &self.body.basic_blocks()[location.block]; + + let kind = if let Some(&Statement { + kind: StatementKind::FakeRead(box (FakeReadCause::ForLet(_), _)), + .. + }) = block.statements.get(location.statement_index) + { + LaterUseKind::FakeLetRead + } else if self.was_captured_by_trait_object(borrow) { + LaterUseKind::TraitCapture + } else if location.statement_index == block.statements.len() { + if let TerminatorKind::Call { ref func, from_hir_call: true, .. } = + block.terminator().kind + { + // Just point to the function, to reduce the chance of overlapping spans. + let function_span = match func { + Operand::Constant(c) => c.span, + Operand::Copy(place) | Operand::Move(place) => { + if let Some(l) = place.as_local() { + let local_decl = &self.body.local_decls[l]; + if self.local_names[l].is_none() { + local_decl.source_info.span + } else { + span + } + } else { + span + } + } + }; + return (LaterUseKind::Call, function_span, None); + } else { + LaterUseKind::Other + } + } else { + LaterUseKind::Other + }; + + (kind, span, None) + } + } + } + + /// Checks if a borrowed value was captured by a trait object. We do this by + /// looking forward in the MIR from the reserve location and checking if we see + /// an unsized cast to a trait object on our data. + fn was_captured_by_trait_object(&self, borrow: &BorrowData<'tcx>) -> bool { + // Start at the reserve location, find the place that we want to see cast to a trait object. + let location = borrow.reserve_location; + let block = &self.body[location.block]; + let stmt = block.statements.get(location.statement_index); + debug!("was_captured_by_trait_object: location={:?} stmt={:?}", location, stmt); + + // We make a `queue` vector that has the locations we want to visit. As of writing, this + // will only ever have one item at any given time, but by using a vector, we can pop from + // it which simplifies the termination logic. + let mut queue = vec![location]; + let mut target = if let Some(&Statement { + kind: StatementKind::Assign(box (ref place, _)), + .. + }) = stmt + { + if let Some(local) = place.as_local() { + local + } else { + return false; + } + } else { + return false; + }; + + debug!("was_captured_by_trait: target={:?} queue={:?}", target, queue); + while let Some(current_location) = queue.pop() { + debug!("was_captured_by_trait: target={:?}", target); + let block = &self.body[current_location.block]; + // We need to check the current location to find out if it is a terminator. + let is_terminator = current_location.statement_index == block.statements.len(); + if !is_terminator { + let stmt = &block.statements[current_location.statement_index]; + debug!("was_captured_by_trait_object: stmt={:?}", stmt); + + // The only kind of statement that we care about is assignments... + if let StatementKind::Assign(box (place, rvalue)) = &stmt.kind { + let Some(into) = place.local_or_deref_local() else { + // Continue at the next location. + queue.push(current_location.successor_within_block()); + continue; + }; + + match rvalue { + // If we see a use, we should check whether it is our data, and if so + // update the place that we're looking for to that new place. + Rvalue::Use(operand) => match operand { + Operand::Copy(place) | Operand::Move(place) => { + if let Some(from) = place.as_local() { + if from == target { + target = into; + } + } + } + _ => {} + }, + // If we see an unsized cast, then if it is our data we should check + // whether it is being cast to a trait object. + Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), operand, ty) => { + match operand { + Operand::Copy(place) | Operand::Move(place) => { + if let Some(from) = place.as_local() { + if from == target { + debug!("was_captured_by_trait_object: ty={:?}", ty); + // Check the type for a trait object. + return match ty.kind() { + // `&dyn Trait` + ty::Ref(_, ty, _) if ty.is_trait() => true, + // `Box<dyn Trait>` + _ if ty.is_box() && ty.boxed_ty().is_trait() => { + true + } + // `dyn Trait` + _ if ty.is_trait() => true, + // Anything else. + _ => false, + }; + } + } + return false; + } + _ => return false, + } + } + _ => {} + } + } + + // Continue at the next location. + queue.push(current_location.successor_within_block()); + } else { + // The only thing we need to do for terminators is progress to the next block. + let terminator = block.terminator(); + debug!("was_captured_by_trait_object: terminator={:?}", terminator); + + if let TerminatorKind::Call { destination, target: Some(block), args, .. } = + &terminator.kind + { + if let Some(dest) = destination.as_local() { + debug!( + "was_captured_by_trait_object: target={:?} dest={:?} args={:?}", + target, dest, args + ); + // Check if one of the arguments to this function is the target place. + let found_target = args.iter().any(|arg| { + if let Operand::Move(place) = arg { + if let Some(potential) = place.as_local() { + potential == target + } else { + false + } + } else { + false + } + }); + + // If it is, follow this to the next block and update the target. + if found_target { + target = dest; + queue.push(block.start_location()); + } + } + } + } + + debug!("was_captured_by_trait: queue={:?}", queue); + } + + // We didn't find anything and ran out of locations to check. + false + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs b/compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs new file mode 100644 index 000000000..b3edc35dc --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs @@ -0,0 +1,26 @@ +use std::collections::BTreeSet; + +use rustc_middle::mir::visit::{PlaceContext, Visitor}; +use rustc_middle::mir::{Body, Local, Location}; + +/// Find all uses of (including assignments to) a [`Local`]. +/// +/// Uses `BTreeSet` so output is deterministic. +pub(super) fn find<'tcx>(body: &Body<'tcx>, local: Local) -> BTreeSet<Location> { + let mut visitor = AllLocalUsesVisitor { for_local: local, uses: BTreeSet::default() }; + visitor.visit_body(body); + visitor.uses +} + +struct AllLocalUsesVisitor { + for_local: Local, + uses: BTreeSet<Location>, +} + +impl<'tcx> Visitor<'tcx> for AllLocalUsesVisitor { + fn visit_local(&mut self, local: Local, _context: PlaceContext, location: Location) { + if local == self.for_local { + self.uses.insert(location); + } + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/find_use.rs b/compiler/rustc_borrowck/src/diagnostics/find_use.rs new file mode 100644 index 000000000..b5a3081e5 --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/find_use.rs @@ -0,0 +1,128 @@ +use std::collections::VecDeque; +use std::rc::Rc; + +use crate::{ + def_use::{self, DefUse}, + nll::ToRegionVid, + region_infer::{Cause, RegionInferenceContext}, +}; +use rustc_data_structures::fx::FxHashSet; +use rustc_middle::mir::visit::{MirVisitable, PlaceContext, Visitor}; +use rustc_middle::mir::{Body, Local, Location}; +use rustc_middle::ty::{RegionVid, TyCtxt}; + +pub(crate) fn find<'tcx>( + body: &Body<'tcx>, + regioncx: &Rc<RegionInferenceContext<'tcx>>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + start_point: Location, +) -> Option<Cause> { + let mut uf = UseFinder { body, regioncx, tcx, region_vid, start_point }; + + uf.find() +} + +struct UseFinder<'cx, 'tcx> { + body: &'cx Body<'tcx>, + regioncx: &'cx Rc<RegionInferenceContext<'tcx>>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + start_point: Location, +} + +impl<'cx, 'tcx> UseFinder<'cx, 'tcx> { + fn find(&mut self) -> Option<Cause> { + let mut queue = VecDeque::new(); + let mut visited = FxHashSet::default(); + + queue.push_back(self.start_point); + while let Some(p) = queue.pop_front() { + if !self.regioncx.region_contains(self.region_vid, p) { + continue; + } + + if !visited.insert(p) { + continue; + } + + let block_data = &self.body[p.block]; + + match self.def_use(p, block_data.visitable(p.statement_index)) { + Some(DefUseResult::Def) => {} + + Some(DefUseResult::UseLive { local }) => { + return Some(Cause::LiveVar(local, p)); + } + + Some(DefUseResult::UseDrop { local }) => { + return Some(Cause::DropVar(local, p)); + } + + None => { + if p.statement_index < block_data.statements.len() { + queue.push_back(p.successor_within_block()); + } else { + queue.extend( + block_data + .terminator() + .successors() + .filter(|&bb| Some(&Some(bb)) != block_data.terminator().unwind()) + .map(|bb| Location { statement_index: 0, block: bb }), + ); + } + } + } + } + + None + } + + fn def_use(&self, location: Location, thing: &dyn MirVisitable<'tcx>) -> Option<DefUseResult> { + let mut visitor = DefUseVisitor { + body: self.body, + tcx: self.tcx, + region_vid: self.region_vid, + def_use_result: None, + }; + + thing.apply(location, &mut visitor); + + visitor.def_use_result + } +} + +struct DefUseVisitor<'cx, 'tcx> { + body: &'cx Body<'tcx>, + tcx: TyCtxt<'tcx>, + region_vid: RegionVid, + def_use_result: Option<DefUseResult>, +} + +enum DefUseResult { + Def, + UseLive { local: Local }, + UseDrop { local: Local }, +} + +impl<'cx, 'tcx> Visitor<'tcx> for DefUseVisitor<'cx, 'tcx> { + fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) { + let local_ty = self.body.local_decls[local].ty; + + let mut found_it = false; + self.tcx.for_each_free_region(&local_ty, |r| { + if r.to_region_vid() == self.region_vid { + found_it = true; + } + }); + + if found_it { + self.def_use_result = match def_use::categorize(context) { + Some(DefUse::Def) => Some(DefUseResult::Def), + Some(DefUse::Use) => Some(DefUseResult::UseLive { local }), + Some(DefUse::Drop) => Some(DefUseResult::UseDrop { local }), + None => None, + }; + } + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs new file mode 100644 index 000000000..098e8de94 --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs @@ -0,0 +1,1127 @@ +//! Borrow checker diagnostics. + +use itertools::Itertools; +use rustc_const_eval::util::{call_kind, CallDesugaringKind}; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir as hir; +use rustc_hir::def::{CtorKind, Namespace}; +use rustc_hir::GeneratorKind; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_middle::mir::tcx::PlaceTy; +use rustc_middle::mir::{ + AggregateKind, Constant, FakeReadCause, Field, Local, LocalInfo, LocalKind, Location, Operand, + Place, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator, TerminatorKind, +}; +use rustc_middle::ty::print::Print; +use rustc_middle::ty::{self, DefIdTree, Instance, Ty, TyCtxt}; +use rustc_mir_dataflow::move_paths::{InitLocation, LookupResult}; +use rustc_span::def_id::LocalDefId; +use rustc_span::{symbol::sym, Span, Symbol, DUMMY_SP}; +use rustc_target::abi::VariantIdx; +use rustc_trait_selection::traits::type_known_to_meet_bound_modulo_regions; + +use super::borrow_set::BorrowData; +use super::MirBorrowckCtxt; + +mod find_all_local_uses; +mod find_use; +mod outlives_suggestion; +mod region_name; +mod var_name; + +mod bound_region_errors; +mod conflict_errors; +mod explain_borrow; +mod move_errors; +mod mutability_errors; +mod region_errors; + +pub(crate) use bound_region_errors::{ToUniverseInfo, UniverseInfo}; +pub(crate) use mutability_errors::AccessKind; +pub(crate) use outlives_suggestion::OutlivesSuggestionBuilder; +pub(crate) use region_errors::{ErrorConstraintInfo, RegionErrorKind, RegionErrors}; +pub(crate) use region_name::{RegionName, RegionNameSource}; +pub(crate) use rustc_const_eval::util::CallKind; + +pub(super) struct DescribePlaceOpt { + pub including_downcast: bool, + + /// Enable/Disable tuple fields. + /// For example `x` tuple. if it's `true` `x.0`. Otherwise `x` + pub including_tuple_field: bool, +} + +pub(super) struct IncludingTupleField(pub(super) bool); + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + /// Adds a suggestion when a closure is invoked twice with a moved variable or when a closure + /// is moved after being invoked. + /// + /// ```text + /// note: closure cannot be invoked more than once because it moves the variable `dict` out of + /// its environment + /// --> $DIR/issue-42065.rs:16:29 + /// | + /// LL | for (key, value) in dict { + /// | ^^^^ + /// ``` + pub(super) fn add_moved_or_invoked_closure_note( + &self, + location: Location, + place: PlaceRef<'tcx>, + diag: &mut Diagnostic, + ) { + debug!("add_moved_or_invoked_closure_note: location={:?} place={:?}", location, place); + let mut target = place.local_or_deref_local(); + for stmt in &self.body[location.block].statements[location.statement_index..] { + debug!("add_moved_or_invoked_closure_note: stmt={:?} target={:?}", stmt, target); + if let StatementKind::Assign(box (into, Rvalue::Use(from))) = &stmt.kind { + debug!("add_fnonce_closure_note: into={:?} from={:?}", into, from); + match from { + Operand::Copy(ref place) | Operand::Move(ref place) + if target == place.local_or_deref_local() => + { + target = into.local_or_deref_local() + } + _ => {} + } + } + } + + // Check if we are attempting to call a closure after it has been invoked. + let terminator = self.body[location.block].terminator(); + debug!("add_moved_or_invoked_closure_note: terminator={:?}", terminator); + if let TerminatorKind::Call { + func: Operand::Constant(box Constant { literal, .. }), + args, + .. + } = &terminator.kind + { + if let ty::FnDef(id, _) = *literal.ty().kind() { + debug!("add_moved_or_invoked_closure_note: id={:?}", id); + if Some(self.infcx.tcx.parent(id)) == self.infcx.tcx.lang_items().fn_once_trait() { + let closure = match args.first() { + Some(Operand::Copy(ref place)) | Some(Operand::Move(ref place)) + if target == place.local_or_deref_local() => + { + place.local_or_deref_local().unwrap() + } + _ => return, + }; + + debug!("add_moved_or_invoked_closure_note: closure={:?}", closure); + if let ty::Closure(did, _) = self.body.local_decls[closure].ty.kind() { + let did = did.expect_local(); + let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(did); + + if let Some((span, hir_place)) = + self.infcx.tcx.typeck(did).closure_kind_origins().get(hir_id) + { + diag.span_note( + *span, + &format!( + "closure cannot be invoked more than once because it moves the \ + variable `{}` out of its environment", + ty::place_to_string_for_capture(self.infcx.tcx, hir_place) + ), + ); + return; + } + } + } + } + } + + // Check if we are just moving a closure after it has been invoked. + if let Some(target) = target { + if let ty::Closure(did, _) = self.body.local_decls[target].ty.kind() { + let did = did.expect_local(); + let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(did); + + if let Some((span, hir_place)) = + self.infcx.tcx.typeck(did).closure_kind_origins().get(hir_id) + { + diag.span_note( + *span, + &format!( + "closure cannot be moved more than once as it is not `Copy` due to \ + moving the variable `{}` out of its environment", + ty::place_to_string_for_capture(self.infcx.tcx, hir_place) + ), + ); + } + } + } + } + + /// End-user visible description of `place` if one can be found. + /// If the place is a temporary for instance, `"value"` will be returned. + pub(super) fn describe_any_place(&self, place_ref: PlaceRef<'tcx>) -> String { + match self.describe_place(place_ref) { + Some(mut descr) => { + // Surround descr with `backticks`. + descr.reserve(2); + descr.insert(0, '`'); + descr.push('`'); + descr + } + None => "value".to_string(), + } + } + + /// End-user visible description of `place` if one can be found. + /// If the place is a temporary for instance, `None` will be returned. + pub(super) fn describe_place(&self, place_ref: PlaceRef<'tcx>) -> Option<String> { + self.describe_place_with_options( + place_ref, + DescribePlaceOpt { including_downcast: false, including_tuple_field: true }, + ) + } + + /// End-user visible description of `place` if one can be found. If the place is a temporary + /// for instance, `None` will be returned. + /// `IncludingDowncast` parameter makes the function return `None` if `ProjectionElem` is + /// `Downcast` and `IncludingDowncast` is true + pub(super) fn describe_place_with_options( + &self, + place: PlaceRef<'tcx>, + opt: DescribePlaceOpt, + ) -> Option<String> { + let local = place.local; + let mut autoderef_index = None; + let mut buf = String::new(); + let mut ok = self.append_local_to_string(local, &mut buf); + + for (index, elem) in place.projection.into_iter().enumerate() { + match elem { + ProjectionElem::Deref => { + if index == 0 { + if self.body.local_decls[local].is_ref_for_guard() { + continue; + } + if let Some(box LocalInfo::StaticRef { def_id, .. }) = + &self.body.local_decls[local].local_info + { + buf.push_str(self.infcx.tcx.item_name(*def_id).as_str()); + ok = Ok(()); + continue; + } + } + if let Some(field) = self.is_upvar_field_projection(PlaceRef { + local, + projection: place.projection.split_at(index + 1).0, + }) { + let var_index = field.index(); + buf = self.upvars[var_index].place.to_string(self.infcx.tcx); + ok = Ok(()); + if !self.upvars[var_index].by_ref { + buf.insert(0, '*'); + } + } else { + if autoderef_index.is_none() { + autoderef_index = + match place.projection.into_iter().rev().find_position(|elem| { + !matches!( + elem, + ProjectionElem::Deref | ProjectionElem::Downcast(..) + ) + }) { + Some((index, _)) => Some(place.projection.len() - index), + None => Some(0), + }; + } + if index >= autoderef_index.unwrap() { + buf.insert(0, '*'); + } + } + } + ProjectionElem::Downcast(..) if opt.including_downcast => return None, + ProjectionElem::Downcast(..) => (), + ProjectionElem::Field(field, _ty) => { + // FIXME(project-rfc_2229#36): print capture precisely here. + if let Some(field) = self.is_upvar_field_projection(PlaceRef { + local, + projection: place.projection.split_at(index + 1).0, + }) { + buf = self.upvars[field.index()].place.to_string(self.infcx.tcx); + ok = Ok(()); + } else { + let field_name = self.describe_field( + PlaceRef { local, projection: place.projection.split_at(index).0 }, + *field, + IncludingTupleField(opt.including_tuple_field), + ); + if let Some(field_name_str) = field_name { + buf.push('.'); + buf.push_str(&field_name_str); + } + } + } + ProjectionElem::Index(index) => { + buf.push('['); + if self.append_local_to_string(*index, &mut buf).is_err() { + buf.push('_'); + } + buf.push(']'); + } + ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => { + // Since it isn't possible to borrow an element on a particular index and + // then use another while the borrow is held, don't output indices details + // to avoid confusing the end-user + buf.push_str("[..]"); + } + } + } + ok.ok().map(|_| buf) + } + + fn describe_name(&self, place: PlaceRef<'tcx>) -> Option<Symbol> { + for elem in place.projection.into_iter() { + match elem { + ProjectionElem::Downcast(Some(name), _) => { + return Some(*name); + } + _ => {} + } + } + None + } + + /// Appends end-user visible description of the `local` place to `buf`. If `local` doesn't have + /// a name, or its name was generated by the compiler, then `Err` is returned + fn append_local_to_string(&self, local: Local, buf: &mut String) -> Result<(), ()> { + let decl = &self.body.local_decls[local]; + match self.local_names[local] { + Some(name) if !decl.from_compiler_desugaring() => { + buf.push_str(name.as_str()); + Ok(()) + } + _ => Err(()), + } + } + + /// End-user visible description of the `field`nth field of `base` + fn describe_field( + &self, + place: PlaceRef<'tcx>, + field: Field, + including_tuple_field: IncludingTupleField, + ) -> Option<String> { + let place_ty = match place { + PlaceRef { local, projection: [] } => PlaceTy::from_ty(self.body.local_decls[local].ty), + PlaceRef { local, projection: [proj_base @ .., elem] } => match elem { + ProjectionElem::Deref + | ProjectionElem::Index(..) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } => { + PlaceRef { local, projection: proj_base }.ty(self.body, self.infcx.tcx) + } + ProjectionElem::Downcast(..) => place.ty(self.body, self.infcx.tcx), + ProjectionElem::Field(_, field_type) => PlaceTy::from_ty(*field_type), + }, + }; + self.describe_field_from_ty( + place_ty.ty, + field, + place_ty.variant_index, + including_tuple_field, + ) + } + + /// End-user visible description of the `field_index`nth field of `ty` + fn describe_field_from_ty( + &self, + ty: Ty<'_>, + field: Field, + variant_index: Option<VariantIdx>, + including_tuple_field: IncludingTupleField, + ) -> Option<String> { + if ty.is_box() { + // If the type is a box, the field is described from the boxed type + self.describe_field_from_ty(ty.boxed_ty(), field, variant_index, including_tuple_field) + } else { + match *ty.kind() { + ty::Adt(def, _) => { + let variant = if let Some(idx) = variant_index { + assert!(def.is_enum()); + &def.variant(idx) + } else { + def.non_enum_variant() + }; + if !including_tuple_field.0 && variant.ctor_kind == CtorKind::Fn { + return None; + } + Some(variant.fields[field.index()].name.to_string()) + } + ty::Tuple(_) => Some(field.index().to_string()), + ty::Ref(_, ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) => { + self.describe_field_from_ty(ty, field, variant_index, including_tuple_field) + } + ty::Array(ty, _) | ty::Slice(ty) => { + self.describe_field_from_ty(ty, field, variant_index, including_tuple_field) + } + ty::Closure(def_id, _) | ty::Generator(def_id, _, _) => { + // We won't be borrowck'ing here if the closure came from another crate, + // so it's safe to call `expect_local`. + // + // We know the field exists so it's safe to call operator[] and `unwrap` here. + let def_id = def_id.expect_local(); + let var_id = self + .infcx + .tcx + .typeck(def_id) + .closure_min_captures_flattened(def_id) + .nth(field.index()) + .unwrap() + .get_root_variable(); + + Some(self.infcx.tcx.hir().name(var_id).to_string()) + } + _ => { + // Might need a revision when the fields in trait RFC is implemented + // (https://github.com/rust-lang/rfcs/pull/1546) + bug!("End-user description not implemented for field access on `{:?}`", ty); + } + } + } + } + + /// Add a note that a type does not implement `Copy` + pub(super) fn note_type_does_not_implement_copy( + &self, + err: &mut Diagnostic, + place_desc: &str, + ty: Ty<'tcx>, + span: Option<Span>, + move_prefix: &str, + ) { + let message = format!( + "{}move occurs because {} has type `{}`, which does not implement the `Copy` trait", + move_prefix, place_desc, ty, + ); + if let Some(span) = span { + err.span_label(span, message); + } else { + err.note(&message); + } + } + + pub(super) fn borrowed_content_source( + &self, + deref_base: PlaceRef<'tcx>, + ) -> BorrowedContentSource<'tcx> { + let tcx = self.infcx.tcx; + + // Look up the provided place and work out the move path index for it, + // we'll use this to check whether it was originally from an overloaded + // operator. + match self.move_data.rev_lookup.find(deref_base) { + LookupResult::Exact(mpi) | LookupResult::Parent(Some(mpi)) => { + debug!("borrowed_content_source: mpi={:?}", mpi); + + for i in &self.move_data.init_path_map[mpi] { + let init = &self.move_data.inits[*i]; + debug!("borrowed_content_source: init={:?}", init); + // We're only interested in statements that initialized a value, not the + // initializations from arguments. + let InitLocation::Statement(loc) = init.location else { continue }; + + let bbd = &self.body[loc.block]; + let is_terminator = bbd.statements.len() == loc.statement_index; + debug!( + "borrowed_content_source: loc={:?} is_terminator={:?}", + loc, is_terminator, + ); + if !is_terminator { + continue; + } else if let Some(Terminator { + kind: TerminatorKind::Call { ref func, from_hir_call: false, .. }, + .. + }) = bbd.terminator + { + if let Some(source) = + BorrowedContentSource::from_call(func.ty(self.body, tcx), tcx) + { + return source; + } + } + } + } + // Base is a `static` so won't be from an overloaded operator + _ => (), + }; + + // If we didn't find an overloaded deref or index, then assume it's a + // built in deref and check the type of the base. + let base_ty = deref_base.ty(self.body, tcx).ty; + if base_ty.is_unsafe_ptr() { + BorrowedContentSource::DerefRawPointer + } else if base_ty.is_mutable_ptr() { + BorrowedContentSource::DerefMutableRef + } else { + BorrowedContentSource::DerefSharedRef + } + } + + /// Return the name of the provided `Ty` (that must be a reference) with a synthesized lifetime + /// name where required. + pub(super) fn get_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { + let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, Namespace::TypeNS); + + // We need to add synthesized lifetimes where appropriate. We do + // this by hooking into the pretty printer and telling it to label the + // lifetimes without names with the value `'0`. + if let ty::Ref(region, ..) = ty.kind() { + match **region { + ty::ReLateBound(_, ty::BoundRegion { kind: br, .. }) + | ty::RePlaceholder(ty::PlaceholderRegion { name: br, .. }) => { + printer.region_highlight_mode.highlighting_bound_region(br, counter) + } + _ => {} + } + } + + ty.print(printer).unwrap().into_buffer() + } + + /// Returns the name of the provided `Ty` (that must be a reference)'s region with a + /// synthesized lifetime name where required. + pub(super) fn get_region_name_for_ty(&self, ty: Ty<'tcx>, counter: usize) -> String { + let mut printer = ty::print::FmtPrinter::new(self.infcx.tcx, Namespace::TypeNS); + + let region = if let ty::Ref(region, ..) = ty.kind() { + match **region { + ty::ReLateBound(_, ty::BoundRegion { kind: br, .. }) + | ty::RePlaceholder(ty::PlaceholderRegion { name: br, .. }) => { + printer.region_highlight_mode.highlighting_bound_region(br, counter) + } + _ => {} + } + region + } else { + bug!("ty for annotation of borrow region is not a reference"); + }; + + region.print(printer).unwrap().into_buffer() + } +} + +/// The span(s) associated to a use of a place. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub(super) enum UseSpans<'tcx> { + /// The access is caused by capturing a variable for a closure. + ClosureUse { + /// This is true if the captured variable was from a generator. + generator_kind: Option<GeneratorKind>, + /// The span of the args of the closure, including the `move` keyword if + /// it's present. + args_span: Span, + /// The span of the use resulting in capture kind + /// Check `ty::CaptureInfo` for more details + capture_kind_span: Span, + /// The span of the use resulting in the captured path + /// Check `ty::CaptureInfo` for more details + path_span: Span, + }, + /// The access is caused by using a variable as the receiver of a method + /// that takes 'self' + FnSelfUse { + /// The span of the variable being moved + var_span: Span, + /// The span of the method call on the variable + fn_call_span: Span, + /// The definition span of the method being called + fn_span: Span, + kind: CallKind<'tcx>, + }, + /// This access is caused by a `match` or `if let` pattern. + PatUse(Span), + /// This access has a single span associated to it: common case. + OtherUse(Span), +} + +impl UseSpans<'_> { + pub(super) fn args_or_use(self) -> Span { + match self { + UseSpans::ClosureUse { args_span: span, .. } + | UseSpans::PatUse(span) + | UseSpans::OtherUse(span) => span, + UseSpans::FnSelfUse { fn_call_span, kind: CallKind::DerefCoercion { .. }, .. } => { + fn_call_span + } + UseSpans::FnSelfUse { var_span, .. } => var_span, + } + } + + /// Returns the span of `self`, in the case of a `ClosureUse` returns the `path_span` + pub(super) fn var_or_use_path_span(self) -> Span { + match self { + UseSpans::ClosureUse { path_span: span, .. } + | UseSpans::PatUse(span) + | UseSpans::OtherUse(span) => span, + UseSpans::FnSelfUse { fn_call_span, kind: CallKind::DerefCoercion { .. }, .. } => { + fn_call_span + } + UseSpans::FnSelfUse { var_span, .. } => var_span, + } + } + + /// Returns the span of `self`, in the case of a `ClosureUse` returns the `capture_kind_span` + pub(super) fn var_or_use(self) -> Span { + match self { + UseSpans::ClosureUse { capture_kind_span: span, .. } + | UseSpans::PatUse(span) + | UseSpans::OtherUse(span) => span, + UseSpans::FnSelfUse { fn_call_span, kind: CallKind::DerefCoercion { .. }, .. } => { + fn_call_span + } + UseSpans::FnSelfUse { var_span, .. } => var_span, + } + } + + pub(super) fn generator_kind(self) -> Option<GeneratorKind> { + match self { + UseSpans::ClosureUse { generator_kind, .. } => generator_kind, + _ => None, + } + } + + // Add a span label to the arguments of the closure, if it exists. + pub(super) fn args_span_label(self, err: &mut Diagnostic, message: impl Into<String>) { + if let UseSpans::ClosureUse { args_span, .. } = self { + err.span_label(args_span, message); + } + } + + // Add a span label to the use of the captured variable, if it exists. + // only adds label to the `path_span` + pub(super) fn var_span_label_path_only(self, err: &mut Diagnostic, message: impl Into<String>) { + if let UseSpans::ClosureUse { path_span, .. } = self { + err.span_label(path_span, message); + } + } + + // Add a span label to the use of the captured variable, if it exists. + pub(super) fn var_span_label( + self, + err: &mut Diagnostic, + message: impl Into<String>, + kind_desc: impl Into<String>, + ) { + if let UseSpans::ClosureUse { capture_kind_span, path_span, .. } = self { + if capture_kind_span == path_span { + err.span_label(capture_kind_span, message); + } else { + let capture_kind_label = + format!("capture is {} because of use here", kind_desc.into()); + let path_label = message; + err.span_label(capture_kind_span, capture_kind_label); + err.span_label(path_span, path_label); + } + } + } + + /// Returns `false` if this place is not used in a closure. + pub(super) fn for_closure(&self) -> bool { + match *self { + UseSpans::ClosureUse { generator_kind, .. } => generator_kind.is_none(), + _ => false, + } + } + + /// Returns `false` if this place is not used in a generator. + pub(super) fn for_generator(&self) -> bool { + match *self { + UseSpans::ClosureUse { generator_kind, .. } => generator_kind.is_some(), + _ => false, + } + } + + /// Describe the span associated with a use of a place. + pub(super) fn describe(&self) -> &str { + match *self { + UseSpans::ClosureUse { generator_kind, .. } => { + if generator_kind.is_some() { + " in generator" + } else { + " in closure" + } + } + _ => "", + } + } + + pub(super) fn or_else<F>(self, if_other: F) -> Self + where + F: FnOnce() -> Self, + { + match self { + closure @ UseSpans::ClosureUse { .. } => closure, + UseSpans::PatUse(_) | UseSpans::OtherUse(_) => if_other(), + fn_self @ UseSpans::FnSelfUse { .. } => fn_self, + } + } +} + +pub(super) enum BorrowedContentSource<'tcx> { + DerefRawPointer, + DerefMutableRef, + DerefSharedRef, + OverloadedDeref(Ty<'tcx>), + OverloadedIndex(Ty<'tcx>), +} + +impl<'tcx> BorrowedContentSource<'tcx> { + pub(super) fn describe_for_unnamed_place(&self, tcx: TyCtxt<'_>) -> String { + match *self { + BorrowedContentSource::DerefRawPointer => "a raw pointer".to_string(), + BorrowedContentSource::DerefSharedRef => "a shared reference".to_string(), + BorrowedContentSource::DerefMutableRef => "a mutable reference".to_string(), + BorrowedContentSource::OverloadedDeref(ty) => ty + .ty_adt_def() + .and_then(|adt| match tcx.get_diagnostic_name(adt.did())? { + name @ (sym::Rc | sym::Arc) => Some(format!("an `{}`", name)), + _ => None, + }) + .unwrap_or_else(|| format!("dereference of `{}`", ty)), + BorrowedContentSource::OverloadedIndex(ty) => format!("index of `{}`", ty), + } + } + + pub(super) fn describe_for_named_place(&self) -> Option<&'static str> { + match *self { + BorrowedContentSource::DerefRawPointer => Some("raw pointer"), + BorrowedContentSource::DerefSharedRef => Some("shared reference"), + BorrowedContentSource::DerefMutableRef => Some("mutable reference"), + // Overloaded deref and index operators should be evaluated into a + // temporary. So we don't need a description here. + BorrowedContentSource::OverloadedDeref(_) + | BorrowedContentSource::OverloadedIndex(_) => None, + } + } + + pub(super) fn describe_for_immutable_place(&self, tcx: TyCtxt<'_>) -> String { + match *self { + BorrowedContentSource::DerefRawPointer => "a `*const` pointer".to_string(), + BorrowedContentSource::DerefSharedRef => "a `&` reference".to_string(), + BorrowedContentSource::DerefMutableRef => { + bug!("describe_for_immutable_place: DerefMutableRef isn't immutable") + } + BorrowedContentSource::OverloadedDeref(ty) => ty + .ty_adt_def() + .and_then(|adt| match tcx.get_diagnostic_name(adt.did())? { + name @ (sym::Rc | sym::Arc) => Some(format!("an `{}`", name)), + _ => None, + }) + .unwrap_or_else(|| format!("dereference of `{}`", ty)), + BorrowedContentSource::OverloadedIndex(ty) => format!("an index of `{}`", ty), + } + } + + fn from_call(func: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Option<Self> { + match *func.kind() { + ty::FnDef(def_id, substs) => { + let trait_id = tcx.trait_of_item(def_id)?; + + let lang_items = tcx.lang_items(); + if Some(trait_id) == lang_items.deref_trait() + || Some(trait_id) == lang_items.deref_mut_trait() + { + Some(BorrowedContentSource::OverloadedDeref(substs.type_at(0))) + } else if Some(trait_id) == lang_items.index_trait() + || Some(trait_id) == lang_items.index_mut_trait() + { + Some(BorrowedContentSource::OverloadedIndex(substs.type_at(0))) + } else { + None + } + } + _ => None, + } + } +} + +impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> { + /// Finds the spans associated to a move or copy of move_place at location. + pub(super) fn move_spans( + &self, + moved_place: PlaceRef<'tcx>, // Could also be an upvar. + location: Location, + ) -> UseSpans<'tcx> { + use self::UseSpans::*; + + let Some(stmt) = self.body[location.block].statements.get(location.statement_index) else { + return OtherUse(self.body.source_info(location).span); + }; + + debug!("move_spans: moved_place={:?} location={:?} stmt={:?}", moved_place, location, stmt); + if let StatementKind::Assign(box (_, Rvalue::Aggregate(ref kind, ref places))) = stmt.kind { + match **kind { + AggregateKind::Closure(def_id, _) | AggregateKind::Generator(def_id, _, _) => { + debug!("move_spans: def_id={:?} places={:?}", def_id, places); + if let Some((args_span, generator_kind, capture_kind_span, path_span)) = + self.closure_span(def_id, moved_place, places) + { + return ClosureUse { + generator_kind, + args_span, + capture_kind_span, + path_span, + }; + } + } + _ => {} + } + } + + // StatementKind::FakeRead only contains a def_id if they are introduced as a result + // of pattern matching within a closure. + if let StatementKind::FakeRead(box (cause, ref place)) = stmt.kind { + match cause { + FakeReadCause::ForMatchedPlace(Some(closure_def_id)) + | FakeReadCause::ForLet(Some(closure_def_id)) => { + debug!("move_spans: def_id={:?} place={:?}", closure_def_id, place); + let places = &[Operand::Move(*place)]; + if let Some((args_span, generator_kind, capture_kind_span, path_span)) = + self.closure_span(closure_def_id, moved_place, places) + { + return ClosureUse { + generator_kind, + args_span, + capture_kind_span, + path_span, + }; + } + } + _ => {} + } + } + + let normal_ret = + if moved_place.projection.iter().any(|p| matches!(p, ProjectionElem::Downcast(..))) { + PatUse(stmt.source_info.span) + } else { + OtherUse(stmt.source_info.span) + }; + + // We are trying to find MIR of the form: + // ``` + // _temp = _moved_val; + // ... + // FnSelfCall(_temp, ...) + // ``` + // + // where `_moved_val` is the place we generated the move error for, + // `_temp` is some other local, and `FnSelfCall` is a function + // that has a `self` parameter. + + let target_temp = match stmt.kind { + StatementKind::Assign(box (temp, _)) if temp.as_local().is_some() => { + temp.as_local().unwrap() + } + _ => return normal_ret, + }; + + debug!("move_spans: target_temp = {:?}", target_temp); + + if let Some(Terminator { + kind: TerminatorKind::Call { fn_span, from_hir_call, .. }, .. + }) = &self.body[location.block].terminator + { + let Some((method_did, method_substs)) = + rustc_const_eval::util::find_self_call( + self.infcx.tcx, + &self.body, + target_temp, + location.block, + ) + else { + return normal_ret; + }; + + let kind = call_kind( + self.infcx.tcx, + self.param_env, + method_did, + method_substs, + *fn_span, + *from_hir_call, + Some(self.infcx.tcx.fn_arg_names(method_did)[0]), + ); + + return FnSelfUse { + var_span: stmt.source_info.span, + fn_call_span: *fn_span, + fn_span: self.infcx.tcx.def_span(method_did), + kind, + }; + } + normal_ret + } + + /// Finds the span of arguments of a closure (within `maybe_closure_span`) + /// and its usage of the local assigned at `location`. + /// This is done by searching in statements succeeding `location` + /// and originating from `maybe_closure_span`. + pub(super) fn borrow_spans(&self, use_span: Span, location: Location) -> UseSpans<'tcx> { + use self::UseSpans::*; + debug!("borrow_spans: use_span={:?} location={:?}", use_span, location); + + let target = match self.body[location.block].statements.get(location.statement_index) { + Some(&Statement { kind: StatementKind::Assign(box (ref place, _)), .. }) => { + if let Some(local) = place.as_local() { + local + } else { + return OtherUse(use_span); + } + } + _ => return OtherUse(use_span), + }; + + if self.body.local_kind(target) != LocalKind::Temp { + // operands are always temporaries. + return OtherUse(use_span); + } + + for stmt in &self.body[location.block].statements[location.statement_index + 1..] { + if let StatementKind::Assign(box (_, Rvalue::Aggregate(ref kind, ref places))) = + stmt.kind + { + let (&def_id, is_generator) = match kind { + box AggregateKind::Closure(def_id, _) => (def_id, false), + box AggregateKind::Generator(def_id, _, _) => (def_id, true), + _ => continue, + }; + + debug!( + "borrow_spans: def_id={:?} is_generator={:?} places={:?}", + def_id, is_generator, places + ); + if let Some((args_span, generator_kind, capture_kind_span, path_span)) = + self.closure_span(def_id, Place::from(target).as_ref(), places) + { + return ClosureUse { generator_kind, args_span, capture_kind_span, path_span }; + } else { + return OtherUse(use_span); + } + } + + if use_span != stmt.source_info.span { + break; + } + } + + OtherUse(use_span) + } + + /// Finds the spans of a captured place within a closure or generator. + /// The first span is the location of the use resulting in the capture kind of the capture + /// The second span is the location the use resulting in the captured path of the capture + fn closure_span( + &self, + def_id: LocalDefId, + target_place: PlaceRef<'tcx>, + places: &[Operand<'tcx>], + ) -> Option<(Span, Option<GeneratorKind>, Span, Span)> { + debug!( + "closure_span: def_id={:?} target_place={:?} places={:?}", + def_id, target_place, places + ); + let hir_id = self.infcx.tcx.hir().local_def_id_to_hir_id(def_id); + let expr = &self.infcx.tcx.hir().expect_expr(hir_id).kind; + debug!("closure_span: hir_id={:?} expr={:?}", hir_id, expr); + if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl_span, .. }) = expr { + for (captured_place, place) in + self.infcx.tcx.typeck(def_id).closure_min_captures_flattened(def_id).zip(places) + { + match place { + Operand::Copy(place) | Operand::Move(place) + if target_place == place.as_ref() => + { + debug!("closure_span: found captured local {:?}", place); + let body = self.infcx.tcx.hir().body(body); + let generator_kind = body.generator_kind(); + + return Some(( + fn_decl_span, + generator_kind, + captured_place.get_capture_kind_span(self.infcx.tcx), + captured_place.get_path_span(self.infcx.tcx), + )); + } + _ => {} + } + } + } + None + } + + /// Helper to retrieve span(s) of given borrow from the current MIR + /// representation + pub(super) fn retrieve_borrow_spans(&self, borrow: &BorrowData<'_>) -> UseSpans<'tcx> { + let span = self.body.source_info(borrow.reserve_location).span; + self.borrow_spans(span, borrow.reserve_location) + } + + fn explain_captures( + &mut self, + err: &mut Diagnostic, + span: Span, + move_span: Span, + move_spans: UseSpans<'tcx>, + moved_place: Place<'tcx>, + used_place: Option<PlaceRef<'tcx>>, + partially_str: &str, + loop_message: &str, + move_msg: &str, + is_loop_move: bool, + maybe_reinitialized_locations_is_empty: bool, + ) { + if let UseSpans::FnSelfUse { var_span, fn_call_span, fn_span, kind } = move_spans { + let place_name = self + .describe_place(moved_place.as_ref()) + .map(|n| format!("`{}`", n)) + .unwrap_or_else(|| "value".to_owned()); + match kind { + CallKind::FnCall { fn_trait_id, .. } + if Some(fn_trait_id) == self.infcx.tcx.lang_items().fn_once_trait() => + { + err.span_label( + fn_call_span, + &format!( + "{} {}moved due to this call{}", + place_name, partially_str, loop_message + ), + ); + err.span_note( + var_span, + "this value implements `FnOnce`, which causes it to be moved when called", + ); + } + CallKind::Operator { self_arg, .. } => { + let self_arg = self_arg.unwrap(); + err.span_label( + fn_call_span, + &format!( + "{} {}moved due to usage in operator{}", + place_name, partially_str, loop_message + ), + ); + if self.fn_self_span_reported.insert(fn_span) { + err.span_note( + // Check whether the source is accessible + if self.infcx.tcx.sess.source_map().is_span_accessible(self_arg.span) { + self_arg.span + } else { + fn_call_span + }, + "calling this operator moves the left-hand side", + ); + } + } + CallKind::Normal { self_arg, desugaring, is_option_or_result } => { + let self_arg = self_arg.unwrap(); + if let Some((CallDesugaringKind::ForLoopIntoIter, _)) = desugaring { + let ty = moved_place.ty(self.body, self.infcx.tcx).ty; + let suggest = match self.infcx.tcx.get_diagnostic_item(sym::IntoIterator) { + Some(def_id) => self.infcx.tcx.infer_ctxt().enter(|infcx| { + type_known_to_meet_bound_modulo_regions( + &infcx, + self.param_env, + infcx.tcx.mk_imm_ref( + infcx.tcx.lifetimes.re_erased, + infcx.tcx.erase_regions(ty), + ), + def_id, + DUMMY_SP, + ) + }), + _ => false, + }; + if suggest { + err.span_suggestion_verbose( + move_span.shrink_to_lo(), + &format!( + "consider iterating over a slice of the `{}`'s content to \ + avoid moving into the `for` loop", + ty, + ), + "&", + Applicability::MaybeIncorrect, + ); + } + + err.span_label( + fn_call_span, + &format!( + "{} {}moved due to this implicit call to `.into_iter()`{}", + place_name, partially_str, loop_message + ), + ); + // If we have a `&mut` ref, we need to reborrow. + if let Some(ty::Ref(_, _, hir::Mutability::Mut)) = used_place + .map(|used_place| used_place.ty(self.body, self.infcx.tcx).ty.kind()) + { + // If we are in a loop this will be suggested later. + if !is_loop_move { + err.span_suggestion_verbose( + move_span.shrink_to_lo(), + &format!( + "consider creating a fresh reborrow of {} here", + self.describe_place(moved_place.as_ref()) + .map(|n| format!("`{}`", n)) + .unwrap_or_else(|| "the mutable reference".to_string()), + ), + "&mut *", + Applicability::MachineApplicable, + ); + } + } + } else { + err.span_label( + fn_call_span, + &format!( + "{} {}moved due to this method call{}", + place_name, partially_str, loop_message + ), + ); + } + if is_option_or_result && maybe_reinitialized_locations_is_empty { + err.span_suggestion_verbose( + fn_call_span.shrink_to_lo(), + "consider calling `.as_ref()` to borrow the type's contents", + "as_ref().", + Applicability::MachineApplicable, + ); + } + // Avoid pointing to the same function in multiple different + // error messages. + if span != DUMMY_SP && self.fn_self_span_reported.insert(self_arg.span) { + err.span_note( + self_arg.span, + &format!("this function takes ownership of the receiver `self`, which moves {}", place_name) + ); + } + } + // Other desugarings takes &self, which cannot cause a move + _ => {} + } + } else { + if move_span != span || !loop_message.is_empty() { + err.span_label( + move_span, + format!("value {}moved{} here{}", partially_str, move_msg, loop_message), + ); + } + // If the move error occurs due to a loop, don't show + // another message for the same span + if loop_message.is_empty() { + move_spans.var_span_label( + err, + format!("variable {}moved due to use{}", partially_str, move_spans.describe()), + "moved", + ); + } + } + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/move_errors.rs b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs new file mode 100644 index 000000000..cb3cd479a --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/move_errors.rs @@ -0,0 +1,529 @@ +use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed}; +use rustc_middle::mir::*; +use rustc_middle::ty; +use rustc_mir_dataflow::move_paths::{ + IllegalMoveOrigin, IllegalMoveOriginKind, LookupResult, MoveError, MovePathIndex, +}; +use rustc_span::Span; + +use crate::diagnostics::{DescribePlaceOpt, UseSpans}; +use crate::prefixes::PrefixSet; +use crate::MirBorrowckCtxt; + +// 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>, + }, +} + +impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { + pub(crate) fn report_move_errors(&mut self, move_errors: Vec<(Place<'tcx>, MoveError<'tcx>)>) { + let grouped_errors = self.group_move_errors(move_errors); + for error in grouped_errors { + self.report(error); + } + } + + fn group_move_errors( + &self, + errors: Vec<(Place<'tcx>, MoveError<'tcx>)>, + ) -> Vec<GroupedMoveError<'tcx>> { + let mut grouped_errors = Vec::new(); + for (original_path, error) in errors { + self.append_to_grouped_errors(&mut grouped_errors, original_path, error); + } + grouped_errors + } + + fn append_to_grouped_errors( + &self, + grouped_errors: &mut Vec<GroupedMoveError<'tcx>>, + original_path: Place<'tcx>, + error: MoveError<'tcx>, + ) { + match error { + MoveError::UnionMove { .. } => { + unimplemented!("don't know how to report union move errors yet.") + } + MoveError::IllegalMove { cannot_move_out_of: IllegalMoveOrigin { location, kind } } => { + // 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(box ( + place, + Rvalue::Use(Operand::Move(move_from)), + ))) = self.body.basic_blocks()[location.block] + .statements + .get(location.statement_index) + .map(|stmt| &stmt.kind) + { + if 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 Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + VarBindingForm { + opt_match_place: Some((opt_match_place, match_span)), + binding_mode: _, + opt_ty_info: _, + pat_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!("append_binding_error(match_place={:?}, match_span={:?})", match_place, match_span); + + 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({:?}) to list", bind_to); + 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({:?}) to list", bind_to); + 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 (mut err, err_span) = { + 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()) + ); + ( + match kind { + &IllegalMoveOriginKind::BorrowedContent { target_place } => self + .report_cannot_move_from_borrowed_content( + original_path, + target_place, + span, + use_spans, + ), + &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)) + } + }, + span, + ) + }; + + self.add_move_hints(error, &mut err, err_span); + self.buffer_error(err); + } + + fn report_cannot_move_from_static( + &mut self, + place: Place<'tcx>, + span: Span, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + 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) + } + + 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>>, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + // 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, self.infcx.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]; + if decl.is_ref_for_guard() { + let mut err = self.cannot_move_out_of( + span, + &format!("`{}` in pattern guard", self.local_names[local].unwrap()), + ); + err.note( + "variables bound in patterns cannot be moved from \ + until after the end of the pattern guard", + ); + return err; + } else if decl.is_ref_to_static() { + return self.report_cannot_move_from_static(move_place, span); + } + } + + 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_substs) + if def_id.as_local() == Some(self.mir_def_id()) && upvar_field.is_some() => + { + let closure_kind_ty = closure_substs.as_closure().kind_ty(); + 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 capture_description = + format!("captured variable in an `{closure_kind}` closure"); + + let upvar = &self.upvars[upvar_field.unwrap().index()]; + let upvar_hir_id = upvar.place.get_root_variable(); + let upvar_name = upvar.place.to_string(self.infcx.tcx); + let upvar_span = self.infcx.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!( + "report: closure_kind_ty={:?} closure_kind={:?} place_description={:?}", + closure_kind_ty, closure_kind, place_description, + ); + + let mut diag = self.cannot_move_out_of(span, &place_description); + + diag.span_label(upvar_span, "captured outer variable"); + diag.span_label( + self.body.span, + format!("captured by this `{closure_kind}` closure"), + ); + + diag + } + _ => { + 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(self.infcx.tcx), + ), + } + } + }; + if let Some(use_spans) = use_spans { + self.explain_captures( + &mut err, span, span, use_spans, move_place, None, "", "", "", false, true, + ); + } + err + } + + fn add_move_hints(&self, error: GroupedMoveError<'tcx>, err: &mut Diagnostic, span: Span) { + match error { + GroupedMoveError::MovesFromPlace { mut binds_to, move_from, .. } => { + if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { + err.span_suggestion( + span, + "consider borrowing here", + format!("&{snippet}"), + Applicability::Unspecified, + ); + } + + if binds_to.is_empty() { + 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(), + }; + + self.note_type_does_not_implement_copy( + err, + &place_desc, + place_ty, + Some(span), + "", + ); + } else { + binds_to.sort(); + binds_to.dedup(); + + self.add_move_error_details(err, &binds_to); + } + } + GroupedMoveError::MovesFromValue { mut binds_to, .. } => { + binds_to.sort(); + binds_to.dedup(); + self.add_move_error_suggestions(err, &binds_to); + self.add_move_error_details(err, &binds_to); + } + // No binding. Nothing to suggest. + GroupedMoveError::OtherIllegalMove { ref original_path, use_spans, .. } => { + let 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(), + }; + self.note_type_does_not_implement_copy(err, &place_desc, place_ty, Some(span), ""); + + use_spans.args_span_label(err, format!("move out of {place_desc} occurs here")); + } + } + } + + fn add_move_error_suggestions(&self, err: &mut Diagnostic, binds_to: &[Local]) { + let mut suggestions: Vec<(Span, &str, String)> = Vec::new(); + for local in binds_to { + let bind_to = &self.body.local_decls[*local]; + if let Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + VarBindingForm { pat_span, .. }, + )))) = bind_to.local_info + { + if let Ok(pat_snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(pat_span) + { + if let Some(stripped) = pat_snippet.strip_prefix('&') { + let pat_snippet = stripped.trim_start(); + let (suggestion, to_remove) = if pat_snippet.starts_with("mut") + && pat_snippet["mut".len()..].starts_with(rustc_lexer::is_whitespace) + { + (pat_snippet["mut".len()..].trim_start(), "&mut") + } else { + (pat_snippet, "&") + }; + suggestions.push((pat_span, to_remove, suggestion.to_owned())); + } + } + } + } + suggestions.sort_unstable_by_key(|&(span, _, _)| span); + suggestions.dedup_by_key(|&mut (span, _, _)| span); + for (span, to_remove, suggestion) in suggestions { + err.span_suggestion( + span, + &format!("consider removing the `{to_remove}`"), + suggestion, + Applicability::MachineApplicable, + ); + } + } + + fn add_move_error_details(&self, err: &mut Diagnostic, binds_to: &[Local]) { + 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 j == 0 { + err.span_label(binding_span, "data moved here"); + } else { + err.span_label(binding_span, "...and here"); + } + + if binds_to.len() == 1 { + self.note_type_does_not_implement_copy( + err, + &format!("`{}`", self.local_names[*local].unwrap()), + bind_to.ty, + Some(binding_span), + "", + ); + } + } + + if binds_to.len() > 1 { + err.note( + "move occurs because these variables have types that \ + don't implement the `Copy` trait", + ); + } + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs new file mode 100644 index 000000000..0ad4abbce --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs @@ -0,0 +1,1115 @@ +use rustc_hir as hir; +use rustc_hir::Node; +use rustc_middle::hir::map::Map; +use rustc_middle::mir::{Mutability, Place, PlaceRef, ProjectionElem}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::{ + hir::place::PlaceBase, + mir::{ + self, BindingForm, ClearCrossCrate, ImplicitSelfKind, Local, LocalDecl, LocalInfo, + LocalKind, Location, + }, +}; +use rustc_span::source_map::DesugaringKind; +use rustc_span::symbol::{kw, Symbol}; +use rustc_span::{BytePos, Span}; + +use crate::diagnostics::BorrowedContentSource; +use crate::MirBorrowckCtxt; +use rustc_const_eval::util::collect_writes::FindAssignments; +use rustc_errors::{Applicability, Diagnostic}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub(crate) enum AccessKind { + MutableBorrow, + Mutate, +} + +impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { + pub(crate) fn report_mutability_error( + &mut self, + access_place: Place<'tcx>, + span: Span, + the_place_err: PlaceRef<'tcx>, + error_access: AccessKind, + location: Location, + ) { + debug!( + "report_mutability_error(\ + access_place={:?}, span={:?}, the_place_err={:?}, error_access={:?}, location={:?},\ + )", + access_place, span, the_place_err, error_access, location, + ); + + let mut err; + let item_msg; + let reason; + let mut opt_source = None; + let access_place_desc = self.describe_any_place(access_place.as_ref()); + debug!("report_mutability_error: access_place_desc={:?}", access_place_desc); + + match the_place_err { + PlaceRef { local, projection: [] } => { + item_msg = access_place_desc; + if access_place.as_local().is_some() { + reason = ", as it is not declared as mutable".to_string(); + } else { + let name = self.local_names[local].expect("immutable unnamed local"); + reason = format!(", as `{name}` is not declared as mutable"); + } + } + + PlaceRef { + local, + projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], + } => { + debug_assert!(is_closure_or_generator( + Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty + )); + + let imm_borrow_derefed = self.upvars[upvar_index.index()] + .place + .place + .deref_tys() + .any(|ty| matches!(ty.kind(), ty::Ref(.., hir::Mutability::Not))); + + // If the place is immutable then: + // + // - Either we deref an immutable ref to get to our final place. + // - We don't capture derefs of raw ptrs + // - Or the final place is immut because the root variable of the capture + // isn't marked mut and we should suggest that to the user. + if imm_borrow_derefed { + // If we deref an immutable ref then the suggestion here doesn't help. + return; + } else { + item_msg = access_place_desc; + if self.is_upvar_field_projection(access_place.as_ref()).is_some() { + reason = ", as it is not declared as mutable".to_string(); + } else { + let name = self.upvars[upvar_index.index()].place.to_string(self.infcx.tcx); + reason = format!(", as `{name}` is not declared as mutable"); + } + } + } + + PlaceRef { local, projection: [ProjectionElem::Deref] } + if self.body.local_decls[local].is_ref_for_guard() => + { + item_msg = access_place_desc; + reason = ", as it is immutable for the pattern guard".to_string(); + } + PlaceRef { local, projection: [ProjectionElem::Deref] } + if self.body.local_decls[local].is_ref_to_static() => + { + if access_place.projection.len() == 1 { + item_msg = format!("immutable static item {access_place_desc}"); + reason = String::new(); + } else { + item_msg = access_place_desc; + let local_info = &self.body.local_decls[local].local_info; + if let Some(box LocalInfo::StaticRef { def_id, .. }) = *local_info { + let static_name = &self.infcx.tcx.item_name(def_id); + reason = format!(", as `{static_name}` is an immutable static item"); + } else { + bug!("is_ref_to_static return true, but not ref to static?"); + } + } + } + PlaceRef { local: _, projection: [proj_base @ .., ProjectionElem::Deref] } => { + if the_place_err.local == ty::CAPTURE_STRUCT_LOCAL + && proj_base.is_empty() + && !self.upvars.is_empty() + { + item_msg = access_place_desc; + debug_assert!( + self.body.local_decls[ty::CAPTURE_STRUCT_LOCAL].ty.is_region_ptr() + ); + debug_assert!(is_closure_or_generator( + Place::ty_from( + the_place_err.local, + the_place_err.projection, + self.body, + self.infcx.tcx + ) + .ty + )); + + reason = if self.is_upvar_field_projection(access_place.as_ref()).is_some() { + ", as it is a captured variable in a `Fn` closure".to_string() + } else { + ", as `Fn` closures cannot mutate their captured variables".to_string() + } + } else { + let source = self.borrowed_content_source(PlaceRef { + local: the_place_err.local, + projection: proj_base, + }); + let pointer_type = source.describe_for_immutable_place(self.infcx.tcx); + opt_source = Some(source); + if let Some(desc) = self.describe_place(access_place.as_ref()) { + item_msg = format!("`{desc}`"); + reason = match error_access { + AccessKind::Mutate => format!(", which is behind {pointer_type}"), + AccessKind::MutableBorrow => { + format!(", as it is behind {pointer_type}") + } + } + } else { + item_msg = format!("data in {pointer_type}"); + reason = String::new(); + } + } + } + + PlaceRef { + local: _, + projection: + [ + .., + ProjectionElem::Index(_) + | ProjectionElem::ConstantIndex { .. } + | ProjectionElem::Subslice { .. } + | ProjectionElem::Downcast(..), + ], + } => bug!("Unexpected immutable place."), + } + + debug!("report_mutability_error: item_msg={:?}, reason={:?}", item_msg, reason); + + // `act` and `acted_on` are strings that let us abstract over + // the verbs used in some diagnostic messages. + let act; + let acted_on; + + let span = match error_access { + AccessKind::Mutate => { + err = self.cannot_assign(span, &(item_msg + &reason)); + act = "assign"; + acted_on = "written"; + span + } + AccessKind::MutableBorrow => { + act = "borrow as mutable"; + acted_on = "borrowed as mutable"; + + let borrow_spans = self.borrow_spans(span, location); + let borrow_span = borrow_spans.args_or_use(); + err = self.cannot_borrow_path_as_mutable_because(borrow_span, &item_msg, &reason); + borrow_spans.var_span_label( + &mut err, + format!( + "mutable borrow occurs due to use of {} in closure", + self.describe_any_place(access_place.as_ref()), + ), + "mutable", + ); + borrow_span + } + }; + + debug!("report_mutability_error: act={:?}, acted_on={:?}", act, acted_on); + + match the_place_err { + // Suggest making an existing shared borrow in a struct definition a mutable borrow. + // + // This is applicable when we have a deref of a field access to a deref of a local - + // something like `*((*_1).0`. The local that we get will be a reference to the + // struct we've got a field access of (it must be a reference since there's a deref + // after the field access). + PlaceRef { + local, + projection: + &[ + ref proj_base @ .., + ProjectionElem::Deref, + ProjectionElem::Field(field, _), + ProjectionElem::Deref, + ], + } => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + if let Some(span) = get_mut_span_in_struct_field( + self.infcx.tcx, + Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty, + field, + ) { + err.span_suggestion_verbose( + span, + "consider changing this to be mutable", + " mut ", + Applicability::MaybeIncorrect, + ); + } + } + + // Suggest removing a `&mut` from the use of a mutable reference. + PlaceRef { local, projection: [] } + if self + .body + .local_decls + .get(local) + .map(|l| mut_borrow_of_mutable_ref(l, self.local_names[local])) + .unwrap_or(false) => + { + let decl = &self.body.local_decls[local]; + err.span_label(span, format!("cannot {ACT}", ACT = act)); + if let Some(mir::Statement { + source_info, + kind: + mir::StatementKind::Assign(box ( + _, + mir::Rvalue::Ref( + _, + mir::BorrowKind::Mut { allow_two_phase_borrow: false }, + _, + ), + )), + .. + }) = &self.body[location.block].statements.get(location.statement_index) + { + match decl.local_info { + Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByValue(Mutability::Not), + opt_ty_info: Some(sp), + opt_match_place: _, + pat_span: _, + }, + )))) => { + err.span_note(sp, "the binding is already a mutable borrow"); + } + _ => { + err.span_note( + decl.source_info.span, + "the binding is already a mutable borrow", + ); + } + } + if let Ok(snippet) = + self.infcx.tcx.sess.source_map().span_to_snippet(source_info.span) + { + if snippet.starts_with("&mut ") { + // We don't have access to the HIR to get accurate spans, but we can + // give a best effort structured suggestion. + err.span_suggestion_verbose( + source_info.span.with_hi(source_info.span.lo() + BytePos(5)), + "try removing `&mut` here", + "", + Applicability::MachineApplicable, + ); + } else { + // This can occur with things like `(&mut self).foo()`. + err.span_help(source_info.span, "try removing `&mut` here"); + } + } else { + err.span_help(source_info.span, "try removing `&mut` here"); + } + } else if decl.mutability == Mutability::Not + && !matches!( + decl.local_info, + Some(box LocalInfo::User(ClearCrossCrate::Set(BindingForm::ImplicitSelf( + ImplicitSelfKind::MutRef + )))) + ) + { + err.span_suggestion_verbose( + decl.source_info.span.shrink_to_lo(), + "consider making the binding mutable", + "mut ", + Applicability::MachineApplicable, + ); + } + } + + // We want to suggest users use `let mut` for local (user + // variable) mutations... + PlaceRef { local, projection: [] } + if self.body.local_decls[local].can_be_made_mutable() => + { + // ... but it doesn't make sense to suggest it on + // variables that are `ref x`, `ref mut x`, `&self`, + // or `&mut self` (such variables are simply not + // mutable). + let local_decl = &self.body.local_decls[local]; + assert_eq!(local_decl.mutability, Mutability::Not); + + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.span_suggestion( + local_decl.source_info.span, + "consider changing this to be mutable", + format!("mut {}", self.local_names[local].unwrap()), + Applicability::MachineApplicable, + ); + let tcx = self.infcx.tcx; + if let ty::Closure(id, _) = *the_place_err.ty(self.body, tcx).ty.kind() { + self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err); + } + } + + // Also suggest adding mut for upvars + PlaceRef { + local, + projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)], + } => { + debug_assert!(is_closure_or_generator( + Place::ty_from(local, proj_base, self.body, self.infcx.tcx).ty + )); + + let captured_place = &self.upvars[upvar_index.index()].place; + + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + let upvar_hir_id = captured_place.get_root_variable(); + + if let Some(Node::Pat(pat)) = self.infcx.tcx.hir().find(upvar_hir_id) + && let hir::PatKind::Binding( + hir::BindingAnnotation::Unannotated, + _, + upvar_ident, + _, + ) = pat.kind + { + err.span_suggestion( + upvar_ident.span, + "consider changing this to be mutable", + format!("mut {}", upvar_ident.name), + Applicability::MachineApplicable, + ); + } + + let tcx = self.infcx.tcx; + if let ty::Ref(_, ty, Mutability::Mut) = the_place_err.ty(self.body, tcx).ty.kind() + && let ty::Closure(id, _) = *ty.kind() + { + self.show_mutating_upvar(tcx, id.expect_local(), the_place_err, &mut err); + } + } + + // complete hack to approximate old AST-borrowck + // diagnostic: if the span starts with a mutable borrow of + // a local variable, then just suggest the user remove it. + PlaceRef { local: _, projection: [] } + if { + if let Ok(snippet) = self.infcx.tcx.sess.source_map().span_to_snippet(span) { + snippet.starts_with("&mut ") + } else { + false + } + } => + { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.span_suggestion( + span, + "try removing `&mut` here", + "", + Applicability::MaybeIncorrect, + ); + } + + PlaceRef { local, projection: [ProjectionElem::Deref] } + if self.body.local_decls[local].is_ref_for_guard() => + { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + err.note( + "variables bound in patterns are immutable until the end of the pattern guard", + ); + } + + // We want to point out when a `&` can be readily replaced + // with an `&mut`. + // + // FIXME: can this case be generalized to work for an + // arbitrary base for the projection? + PlaceRef { local, projection: [ProjectionElem::Deref] } + if self.body.local_decls[local].is_user_variable() => + { + let local_decl = &self.body.local_decls[local]; + + let (pointer_sigil, pointer_desc) = if local_decl.ty.is_region_ptr() { + ("&", "reference") + } else { + ("*const", "pointer") + }; + + match self.local_names[local] { + Some(name) if !local_decl.from_compiler_desugaring() => { + let label = match local_decl.local_info.as_deref().unwrap() { + LocalInfo::User(ClearCrossCrate::Set( + mir::BindingForm::ImplicitSelf(_), + )) => { + let (span, suggestion) = + suggest_ampmut_self(self.infcx.tcx, local_decl); + Some((true, span, suggestion)) + } + + LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByValue(_), + opt_ty_info, + .. + }, + ))) => { + // check if the RHS is from desugaring + let opt_assignment_rhs_span = + self.body.find_assignments(local).first().map(|&location| { + if let Some(mir::Statement { + source_info: _, + kind: + mir::StatementKind::Assign(box ( + _, + mir::Rvalue::Use(mir::Operand::Copy(place)), + )), + }) = self.body[location.block] + .statements + .get(location.statement_index) + { + self.body.local_decls[place.local].source_info.span + } else { + self.body.source_info(location).span + } + }); + match opt_assignment_rhs_span.and_then(|s| s.desugaring_kind()) { + // on for loops, RHS points to the iterator part + Some(DesugaringKind::ForLoop) => { + self.suggest_similar_mut_method_for_for_loop(&mut err); + err.span_label(opt_assignment_rhs_span.unwrap(), format!( + "this iterator yields `{pointer_sigil}` {pointer_desc}s", + )); + None + } + // don't create labels for compiler-generated spans + Some(_) => None, + None => { + let label = if name != kw::SelfLower { + suggest_ampmut( + self.infcx.tcx, + local_decl, + opt_assignment_rhs_span, + *opt_ty_info, + ) + } else { + match local_decl.local_info.as_deref() { + Some(LocalInfo::User(ClearCrossCrate::Set( + mir::BindingForm::Var(mir::VarBindingForm { + opt_ty_info: None, + .. + }), + ))) => { + let (span, sugg) = suggest_ampmut_self( + self.infcx.tcx, + local_decl, + ); + (true, span, sugg) + } + // explicit self (eg `self: &'a Self`) + _ => suggest_ampmut( + self.infcx.tcx, + local_decl, + opt_assignment_rhs_span, + *opt_ty_info, + ), + } + }; + Some(label) + } + } + } + + LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByReference(_), + .. + }, + ))) => { + let pattern_span = local_decl.source_info.span; + suggest_ref_mut(self.infcx.tcx, pattern_span) + .map(|replacement| (true, pattern_span, replacement)) + } + + LocalInfo::User(ClearCrossCrate::Clear) => { + bug!("saw cleared local state") + } + + _ => unreachable!(), + }; + + match label { + Some((true, err_help_span, suggested_code)) => { + let (is_trait_sig, local_trait) = self.is_error_in_trait(local); + if !is_trait_sig { + err.span_suggestion( + err_help_span, + &format!( + "consider changing this to be a mutable {pointer_desc}" + ), + suggested_code, + Applicability::MachineApplicable, + ); + } else if let Some(x) = local_trait { + err.span_suggestion( + x, + &format!( + "consider changing that to be a mutable {pointer_desc}" + ), + suggested_code, + Applicability::MachineApplicable, + ); + } + } + Some((false, err_label_span, message)) => { + err.span_label( + err_label_span, + &format!( + "consider changing this binding's type to be: `{message}`" + ), + ); + } + None => {} + } + err.span_label( + span, + format!( + "`{NAME}` is a `{SIGIL}` {DESC}, \ + so the data it refers to cannot be {ACTED_ON}", + NAME = name, + SIGIL = pointer_sigil, + DESC = pointer_desc, + ACTED_ON = acted_on + ), + ); + } + _ => { + err.span_label( + span, + format!( + "cannot {ACT} through `{SIGIL}` {DESC}", + ACT = act, + SIGIL = pointer_sigil, + DESC = pointer_desc + ), + ); + } + } + } + + PlaceRef { local, projection: [ProjectionElem::Deref] } + if local == ty::CAPTURE_STRUCT_LOCAL && !self.upvars.is_empty() => + { + self.expected_fn_found_fn_mut_call(&mut err, span, act); + } + + PlaceRef { local: _, projection: [.., ProjectionElem::Deref] } => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + + match opt_source { + Some(BorrowedContentSource::OverloadedDeref(ty)) => { + err.help(&format!( + "trait `DerefMut` is required to modify through a dereference, \ + but it is not implemented for `{ty}`", + )); + } + Some(BorrowedContentSource::OverloadedIndex(ty)) => { + err.help(&format!( + "trait `IndexMut` is required to modify indexed content, \ + but it is not implemented for `{ty}`", + )); + } + _ => (), + } + } + + _ => { + err.span_label(span, format!("cannot {ACT}", ACT = act)); + } + } + + self.buffer_error(err); + } + + /// User cannot make signature of a trait mutable without changing the + /// trait. So we find if this error belongs to a trait and if so we move + /// suggestion to the trait or disable it if it is out of scope of this crate + fn is_error_in_trait(&self, local: Local) -> (bool, Option<Span>) { + if self.body.local_kind(local) != LocalKind::Arg { + return (false, None); + } + let hir_map = self.infcx.tcx.hir(); + let my_def = self.body.source.def_id(); + let my_hir = hir_map.local_def_id_to_hir_id(my_def.as_local().unwrap()); + let Some(td) = + self.infcx.tcx.impl_of_method(my_def).and_then(|x| self.infcx.tcx.trait_id_of_impl(x)) + else { + return (false, None); + }; + ( + true, + td.as_local().and_then(|tld| match hir_map.find_by_def_id(tld) { + Some(Node::Item(hir::Item { + kind: hir::ItemKind::Trait(_, _, _, _, items), + .. + })) => { + let mut f_in_trait_opt = None; + for hir::TraitItemRef { id: fi, kind: k, .. } in *items { + let hi = fi.hir_id(); + if !matches!(k, hir::AssocItemKind::Fn { .. }) { + continue; + } + if hir_map.name(hi) != hir_map.name(my_hir) { + continue; + } + f_in_trait_opt = Some(hi); + break; + } + f_in_trait_opt.and_then(|f_in_trait| match hir_map.find(f_in_trait) { + Some(Node::TraitItem(hir::TraitItem { + kind: + hir::TraitItemKind::Fn( + hir::FnSig { decl: hir::FnDecl { inputs, .. }, .. }, + _, + ), + .. + })) => { + let hir::Ty { span, .. } = inputs[local.index() - 1]; + Some(span) + } + _ => None, + }) + } + _ => None, + }), + ) + } + + // point to span of upvar making closure call require mutable borrow + fn show_mutating_upvar( + &self, + tcx: TyCtxt<'_>, + closure_local_def_id: hir::def_id::LocalDefId, + the_place_err: PlaceRef<'tcx>, + err: &mut Diagnostic, + ) { + let tables = tcx.typeck(closure_local_def_id); + let closure_hir_id = tcx.hir().local_def_id_to_hir_id(closure_local_def_id); + if let Some((span, closure_kind_origin)) = + &tables.closure_kind_origins().get(closure_hir_id) + { + let reason = if let PlaceBase::Upvar(upvar_id) = closure_kind_origin.base { + let upvar = ty::place_to_string_for_capture(tcx, closure_kind_origin); + let root_hir_id = upvar_id.var_path.hir_id; + // we have an origin for this closure kind starting at this root variable so it's safe to unwrap here + let captured_places = + tables.closure_min_captures[&closure_local_def_id].get(&root_hir_id).unwrap(); + + let origin_projection = closure_kind_origin + .projections + .iter() + .map(|proj| proj.kind) + .collect::<Vec<_>>(); + let mut capture_reason = String::new(); + for captured_place in captured_places { + let captured_place_kinds = captured_place + .place + .projections + .iter() + .map(|proj| proj.kind) + .collect::<Vec<_>>(); + if rustc_middle::ty::is_ancestor_or_same_capture( + &captured_place_kinds, + &origin_projection, + ) { + match captured_place.info.capture_kind { + ty::UpvarCapture::ByRef( + ty::BorrowKind::MutBorrow | ty::BorrowKind::UniqueImmBorrow, + ) => { + capture_reason = format!("mutable borrow of `{upvar}`"); + } + ty::UpvarCapture::ByValue => { + capture_reason = format!("possible mutation of `{upvar}`"); + } + _ => bug!("upvar `{upvar}` borrowed, but not mutably"), + } + break; + } + } + if capture_reason.is_empty() { + bug!("upvar `{upvar}` borrowed, but cannot find reason"); + } + capture_reason + } else { + bug!("not an upvar") + }; + err.span_label( + *span, + format!( + "calling `{}` requires mutable binding due to {}", + self.describe_place(the_place_err).unwrap(), + reason + ), + ); + } + } + + // Attempt to search similar mutable associated items for suggestion. + // In the future, attempt in all path but initially for RHS of for_loop + fn suggest_similar_mut_method_for_for_loop(&self, err: &mut Diagnostic) { + use hir::{ + BodyId, Expr, + ExprKind::{Block, Call, DropTemps, Match, MethodCall}, + HirId, ImplItem, ImplItemKind, Item, ItemKind, + }; + + fn maybe_body_id_of_fn(hir_map: Map<'_>, id: HirId) -> Option<BodyId> { + match hir_map.find(id) { + Some(Node::Item(Item { kind: ItemKind::Fn(_, _, body_id), .. })) + | Some(Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(_, body_id), .. })) => { + Some(*body_id) + } + _ => None, + } + } + let hir_map = self.infcx.tcx.hir(); + let mir_body_hir_id = self.mir_hir_id(); + if let Some(fn_body_id) = maybe_body_id_of_fn(hir_map, mir_body_hir_id) { + if let Block( + hir::Block { + expr: + Some(Expr { + kind: + DropTemps(Expr { + kind: + Match( + Expr { + kind: + Call( + _, + [ + Expr { + kind: + MethodCall( + path_segment, + _args, + span, + ), + hir_id, + .. + }, + .., + ], + ), + .. + }, + .., + ), + .. + }), + .. + }), + .. + }, + _, + ) = hir_map.body(fn_body_id).value.kind + { + let opt_suggestions = path_segment + .hir_id + .map(|path_hir_id| self.infcx.tcx.typeck(path_hir_id.owner)) + .and_then(|typeck| typeck.type_dependent_def_id(*hir_id)) + .and_then(|def_id| self.infcx.tcx.impl_of_method(def_id)) + .map(|def_id| self.infcx.tcx.associated_items(def_id)) + .map(|assoc_items| { + assoc_items + .in_definition_order() + .map(|assoc_item_def| assoc_item_def.ident(self.infcx.tcx)) + .filter(|&ident| { + let original_method_ident = path_segment.ident; + original_method_ident != ident + && ident + .as_str() + .starts_with(&original_method_ident.name.to_string()) + }) + .map(|ident| format!("{ident}()")) + .peekable() + }); + + if let Some(mut suggestions) = opt_suggestions + && suggestions.peek().is_some() + { + err.span_suggestions( + *span, + "use mutable method", + suggestions, + Applicability::MaybeIncorrect, + ); + } + } + }; + } + + /// Targeted error when encountering an `FnMut` closure where an `Fn` closure was expected. + fn expected_fn_found_fn_mut_call(&self, err: &mut Diagnostic, sp: Span, act: &str) { + err.span_label(sp, format!("cannot {act}")); + + let hir = self.infcx.tcx.hir(); + let closure_id = self.mir_hir_id(); + let fn_call_id = hir.get_parent_node(closure_id); + let node = hir.get(fn_call_id); + let def_id = hir.enclosing_body_owner(fn_call_id); + let mut look_at_return = true; + // If we can detect the expression to be an `fn` call where the closure was an argument, + // we point at the `fn` definition argument... + if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::Call(func, args), .. }) = node { + let arg_pos = args + .iter() + .enumerate() + .filter(|(_, arg)| arg.hir_id == closure_id) + .map(|(pos, _)| pos) + .next(); + let tables = self.infcx.tcx.typeck(def_id); + if let Some(ty::FnDef(def_id, _)) = + tables.node_type_opt(func.hir_id).as_ref().map(|ty| ty.kind()) + { + let arg = match hir.get_if_local(*def_id) { + Some( + hir::Node::Item(hir::Item { + ident, kind: hir::ItemKind::Fn(sig, ..), .. + }) + | hir::Node::TraitItem(hir::TraitItem { + ident, + kind: hir::TraitItemKind::Fn(sig, _), + .. + }) + | hir::Node::ImplItem(hir::ImplItem { + ident, + kind: hir::ImplItemKind::Fn(sig, _), + .. + }), + ) => Some( + arg_pos + .and_then(|pos| { + sig.decl.inputs.get( + pos + if sig.decl.implicit_self.has_implicit_self() { + 1 + } else { + 0 + }, + ) + }) + .map(|arg| arg.span) + .unwrap_or(ident.span), + ), + _ => None, + }; + if let Some(span) = arg { + err.span_label(span, "change this to accept `FnMut` instead of `Fn`"); + err.span_label(func.span, "expects `Fn` instead of `FnMut`"); + err.span_label(self.body.span, "in this closure"); + look_at_return = false; + } + } + } + + if look_at_return && hir.get_return_block(closure_id).is_some() { + // ...otherwise we are probably in the tail expression of the function, point at the + // return type. + match hir.get_by_def_id(hir.get_parent_item(fn_call_id)) { + hir::Node::Item(hir::Item { ident, kind: hir::ItemKind::Fn(sig, ..), .. }) + | hir::Node::TraitItem(hir::TraitItem { + ident, + kind: hir::TraitItemKind::Fn(sig, _), + .. + }) + | hir::Node::ImplItem(hir::ImplItem { + ident, + kind: hir::ImplItemKind::Fn(sig, _), + .. + }) => { + err.span_label(ident.span, ""); + err.span_label( + sig.decl.output.span(), + "change this to return `FnMut` instead of `Fn`", + ); + err.span_label(self.body.span, "in this closure"); + } + _ => {} + } + } + } +} + +fn mut_borrow_of_mutable_ref(local_decl: &LocalDecl<'_>, local_name: Option<Symbol>) -> bool { + debug!("local_info: {:?}, ty.kind(): {:?}", local_decl.local_info, local_decl.ty.kind()); + + match local_decl.local_info.as_deref() { + // Check if mutably borrowing a mutable reference. + Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::Var( + mir::VarBindingForm { + binding_mode: ty::BindingMode::BindByValue(Mutability::Not), .. + }, + )))) => matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)), + Some(LocalInfo::User(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf(kind)))) => { + // Check if the user variable is a `&mut self` and we can therefore + // suggest removing the `&mut`. + // + // Deliberately fall into this case for all implicit self types, + // so that we don't fall in to the next case with them. + *kind == mir::ImplicitSelfKind::MutRef + } + _ if Some(kw::SelfLower) == local_name => { + // Otherwise, check if the name is the `self` keyword - in which case + // we have an explicit self. Do the same thing in this case and check + // for a `self: &mut Self` to suggest removing the `&mut`. + matches!(local_decl.ty.kind(), ty::Ref(_, _, hir::Mutability::Mut)) + } + _ => false, + } +} + +fn suggest_ampmut_self<'tcx>( + tcx: TyCtxt<'tcx>, + local_decl: &mir::LocalDecl<'tcx>, +) -> (Span, String) { + let sp = local_decl.source_info.span; + ( + sp, + match tcx.sess.source_map().span_to_snippet(sp) { + Ok(snippet) => { + let lt_pos = snippet.find('\''); + if let Some(lt_pos) = lt_pos { + format!("&{}mut self", &snippet[lt_pos..snippet.len() - 4]) + } else { + "&mut self".to_string() + } + } + _ => "&mut self".to_string(), + }, + ) +} + +// When we want to suggest a user change a local variable to be a `&mut`, there +// are three potential "obvious" things to highlight: +// +// let ident [: Type] [= RightHandSideExpression]; +// ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ +// (1.) (2.) (3.) +// +// We can always fallback on highlighting the first. But chances are good that +// the user experience will be better if we highlight one of the others if possible; +// for example, if the RHS is present and the Type is not, then the type is going to +// be inferred *from* the RHS, which means we should highlight that (and suggest +// that they borrow the RHS mutably). +// +// This implementation attempts to emulate AST-borrowck prioritization +// by trying (3.), then (2.) and finally falling back on (1.). +fn suggest_ampmut<'tcx>( + tcx: TyCtxt<'tcx>, + local_decl: &mir::LocalDecl<'tcx>, + opt_assignment_rhs_span: Option<Span>, + opt_ty_info: Option<Span>, +) -> (bool, Span, String) { + if let Some(assignment_rhs_span) = opt_assignment_rhs_span + && let Ok(src) = tcx.sess.source_map().span_to_snippet(assignment_rhs_span) + { + let is_mutbl = |ty: &str| -> bool { + if let Some(rest) = ty.strip_prefix("mut") { + match rest.chars().next() { + // e.g. `&mut x` + Some(c) if c.is_whitespace() => true, + // e.g. `&mut(x)` + Some('(') => true, + // e.g. `&mut{x}` + Some('{') => true, + // e.g. `&mutablevar` + _ => false, + } + } else { + false + } + }; + if let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace)) { + let lt_name = &src[1..ws_pos]; + let ty = src[ws_pos..].trim_start(); + if !is_mutbl(ty) { + return (true, assignment_rhs_span, format!("&{lt_name} mut {ty}")); + } + } else if let Some(stripped) = src.strip_prefix('&') { + let stripped = stripped.trim_start(); + if !is_mutbl(stripped) { + return (true, assignment_rhs_span, format!("&mut {stripped}")); + } + } + } + + let (suggestability, highlight_span) = match opt_ty_info { + // if this is a variable binding with an explicit type, + // try to highlight that for the suggestion. + Some(ty_span) => (true, ty_span), + + // otherwise, just highlight the span associated with + // the (MIR) LocalDecl. + None => (false, local_decl.source_info.span), + }; + + if let Ok(src) = tcx.sess.source_map().span_to_snippet(highlight_span) + && let (true, Some(ws_pos)) = (src.starts_with("&'"), src.find(char::is_whitespace)) + { + let lt_name = &src[1..ws_pos]; + let ty = &src[ws_pos..]; + return (true, highlight_span, format!("&{} mut{}", lt_name, ty)); + } + + let ty_mut = local_decl.ty.builtin_deref(true).unwrap(); + assert_eq!(ty_mut.mutbl, hir::Mutability::Not); + ( + suggestability, + highlight_span, + if local_decl.ty.is_region_ptr() { + format!("&mut {}", ty_mut.ty) + } else { + format!("*mut {}", ty_mut.ty) + }, + ) +} + +fn is_closure_or_generator(ty: Ty<'_>) -> bool { + ty.is_closure() || ty.is_generator() +} + +/// Given a field that needs to be mutable, returns a span where the " mut " could go. +/// This function expects the local to be a reference to a struct in order to produce a span. +/// +/// ```text +/// LL | s: &'a String +/// | ^^^ returns a span taking up the space here +/// ``` +fn get_mut_span_in_struct_field<'tcx>( + tcx: TyCtxt<'tcx>, + ty: Ty<'tcx>, + field: mir::Field, +) -> Option<Span> { + // Expect our local to be a reference to a struct of some kind. + if let ty::Ref(_, ty, _) = ty.kind() + && let ty::Adt(def, _) = ty.kind() + && let field = def.all_fields().nth(field.index())? + // Use the HIR types to construct the diagnostic message. + && let node = tcx.hir().find_by_def_id(field.did.as_local()?)? + // Now we're dealing with the actual struct that we're going to suggest a change to, + // we can expect a field that is an immutable reference to a type. + && let hir::Node::Field(field) = node + && let hir::TyKind::Rptr(lt, hir::MutTy { mutbl: hir::Mutability::Not, ty }) = field.ty.kind + { + return Some(lt.span.between(ty.span)); + } + + None +} + +/// If possible, suggest replacing `ref` with `ref mut`. +fn suggest_ref_mut(tcx: TyCtxt<'_>, binding_span: Span) -> Option<String> { + let hi_src = tcx.sess.source_map().span_to_snippet(binding_span).ok()?; + if hi_src.starts_with("ref") && hi_src["ref".len()..].starts_with(rustc_lexer::is_whitespace) { + let replacement = format!("ref mut{}", &hi_src["ref".len()..]); + Some(replacement) + } else { + None + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs b/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs new file mode 100644 index 000000000..d359d7efb --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs @@ -0,0 +1,261 @@ +//! Contains utilities for generating suggestions for borrowck errors related to unsatisfied +//! outlives constraints. + +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Diagnostic; +use rustc_middle::ty::RegionVid; +use smallvec::SmallVec; +use std::collections::BTreeMap; +use tracing::debug; + +use crate::MirBorrowckCtxt; + +use super::{ErrorConstraintInfo, RegionName, RegionNameSource}; + +/// The different things we could suggest. +enum SuggestedConstraint { + /// Outlives(a, [b, c, d, ...]) => 'a: 'b + 'c + 'd + ... + Outlives(RegionName, SmallVec<[RegionName; 2]>), + + /// 'a = 'b + Equal(RegionName, RegionName), + + /// 'a: 'static i.e. 'a = 'static and the user should just use 'static + Static(RegionName), +} + +/// Collects information about outlives constraints that needed to be added for a given MIR node +/// corresponding to a function definition. +/// +/// Adds a help note suggesting adding a where clause with the needed constraints. +#[derive(Default)] +pub struct OutlivesSuggestionBuilder { + /// The list of outlives constraints that need to be added. Specifically, we map each free + /// region to all other regions that it must outlive. I will use the shorthand `fr: + /// outlived_frs`. Not all of these regions will already have names necessarily. Some could be + /// implicit free regions that we inferred. These will need to be given names in the final + /// suggestion message. + constraints_to_add: BTreeMap<RegionVid, Vec<RegionVid>>, +} + +impl OutlivesSuggestionBuilder { + /// Returns `true` iff the `RegionNameSource` is a valid source for an outlives + /// suggestion. + // + // FIXME: Currently, we only report suggestions if the `RegionNameSource` is an early-bound + // region or a named region, avoiding using regions with synthetic names altogether. This + // allows us to avoid giving impossible suggestions (e.g. adding bounds to closure args). + // We can probably be less conservative, since some inferred free regions are namable (e.g. + // the user can explicitly name them. To do this, we would allow some regions whose names + // come from `MatchedAdtAndSegment`, being careful to filter out bad suggestions, such as + // naming the `'self` lifetime in methods, etc. + fn region_name_is_suggestable(name: &RegionName) -> bool { + match name.source { + RegionNameSource::NamedEarlyBoundRegion(..) + | RegionNameSource::NamedFreeRegion(..) + | RegionNameSource::Static => true, + + // Don't give suggestions for upvars, closure return types, or other unnameable + // regions. + RegionNameSource::SynthesizedFreeEnvRegion(..) + | RegionNameSource::AnonRegionFromArgument(..) + | RegionNameSource::AnonRegionFromUpvar(..) + | RegionNameSource::AnonRegionFromOutput(..) + | RegionNameSource::AnonRegionFromYieldTy(..) + | RegionNameSource::AnonRegionFromAsyncFn(..) + | RegionNameSource::AnonRegionFromImplSignature(..) => { + debug!("Region {:?} is NOT suggestable", name); + false + } + } + } + + /// Returns a name for the region if it is suggestable. See `region_name_is_suggestable`. + fn region_vid_to_name( + &self, + mbcx: &MirBorrowckCtxt<'_, '_>, + region: RegionVid, + ) -> Option<RegionName> { + mbcx.give_region_a_name(region).filter(Self::region_name_is_suggestable) + } + + /// Compiles a list of all suggestions to be printed in the final big suggestion. + fn compile_all_suggestions( + &self, + mbcx: &MirBorrowckCtxt<'_, '_>, + ) -> SmallVec<[SuggestedConstraint; 2]> { + let mut suggested = SmallVec::new(); + + // Keep track of variables that we have already suggested unifying so that we don't print + // out silly duplicate messages. + let mut unified_already = FxHashSet::default(); + + for (fr, outlived) in &self.constraints_to_add { + let Some(fr_name) = self.region_vid_to_name(mbcx, *fr) else { + continue; + }; + + let outlived = outlived + .iter() + // if there is a `None`, we will just omit that constraint + .filter_map(|fr| self.region_vid_to_name(mbcx, *fr).map(|rname| (fr, rname))) + .collect::<Vec<_>>(); + + // No suggestable outlived lifetimes. + if outlived.is_empty() { + continue; + } + + // There are three types of suggestions we can make: + // 1) Suggest a bound: 'a: 'b + // 2) Suggest replacing 'a with 'static. If any of `outlived` is `'static`, then we + // should just replace 'a with 'static. + // 3) Suggest unifying 'a with 'b if we have both 'a: 'b and 'b: 'a + + if outlived + .iter() + .any(|(_, outlived_name)| matches!(outlived_name.source, RegionNameSource::Static)) + { + suggested.push(SuggestedConstraint::Static(fr_name)); + } else { + // We want to isolate out all lifetimes that should be unified and print out + // separate messages for them. + + let (unified, other): (Vec<_>, Vec<_>) = outlived.into_iter().partition( + // Do we have both 'fr: 'r and 'r: 'fr? + |(r, _)| { + self.constraints_to_add + .get(r) + .map(|r_outlived| r_outlived.as_slice().contains(fr)) + .unwrap_or(false) + }, + ); + + for (r, bound) in unified.into_iter() { + if !unified_already.contains(fr) { + suggested.push(SuggestedConstraint::Equal(fr_name.clone(), bound)); + unified_already.insert(r); + } + } + + if !other.is_empty() { + let other = + other.iter().map(|(_, rname)| rname.clone()).collect::<SmallVec<_>>(); + suggested.push(SuggestedConstraint::Outlives(fr_name, other)) + } + } + } + + suggested + } + + /// Add the outlives constraint `fr: outlived_fr` to the set of constraints we need to suggest. + pub(crate) fn collect_constraint(&mut self, fr: RegionVid, outlived_fr: RegionVid) { + debug!("Collected {:?}: {:?}", fr, outlived_fr); + + // Add to set of constraints for final help note. + self.constraints_to_add.entry(fr).or_default().push(outlived_fr); + } + + /// Emit an intermediate note on the given `Diagnostic` if the involved regions are + /// suggestable. + pub(crate) fn intermediate_suggestion( + &mut self, + mbcx: &MirBorrowckCtxt<'_, '_>, + errci: &ErrorConstraintInfo<'_>, + diag: &mut Diagnostic, + ) { + // Emit an intermediate note. + let fr_name = self.region_vid_to_name(mbcx, errci.fr); + let outlived_fr_name = self.region_vid_to_name(mbcx, errci.outlived_fr); + + if let (Some(fr_name), Some(outlived_fr_name)) = (fr_name, outlived_fr_name) + && !matches!(outlived_fr_name.source, RegionNameSource::Static) + { + diag.help(&format!( + "consider adding the following bound: `{fr_name}: {outlived_fr_name}`", + )); + } + } + + /// If there is a suggestion to emit, add a diagnostic to the buffer. This is the final + /// suggestion including all collected constraints. + pub(crate) fn add_suggestion(&self, mbcx: &mut MirBorrowckCtxt<'_, '_>) { + // No constraints to add? Done. + if self.constraints_to_add.is_empty() { + debug!("No constraints to suggest."); + return; + } + + // If there is only one constraint to suggest, then we already suggested it in the + // intermediate suggestion above. + if self.constraints_to_add.len() == 1 + && self.constraints_to_add.values().next().unwrap().len() == 1 + { + debug!("Only 1 suggestion. Skipping."); + return; + } + + // Get all suggestable constraints. + let suggested = self.compile_all_suggestions(mbcx); + + // If there are no suggestable constraints... + if suggested.is_empty() { + debug!("Only 1 suggestable constraint. Skipping."); + return; + } + + // If there is exactly one suggestable constraints, then just suggest it. Otherwise, emit a + // list of diagnostics. + let mut diag = if suggested.len() == 1 { + mbcx.infcx.tcx.sess.diagnostic().struct_help(&match suggested.last().unwrap() { + SuggestedConstraint::Outlives(a, bs) => { + let bs: SmallVec<[String; 2]> = bs.iter().map(|r| format!("{}", r)).collect(); + format!("add bound `{}: {}`", a, bs.join(" + ")) + } + + SuggestedConstraint::Equal(a, b) => { + format!("`{}` and `{}` must be the same: replace one with the other", a, b) + } + SuggestedConstraint::Static(a) => format!("replace `{}` with `'static`", a), + }) + } else { + // Create a new diagnostic. + let mut diag = mbcx + .infcx + .tcx + .sess + .diagnostic() + .struct_help("the following changes may resolve your lifetime errors"); + + // Add suggestions. + for constraint in suggested { + match constraint { + SuggestedConstraint::Outlives(a, bs) => { + let bs: SmallVec<[String; 2]> = + bs.iter().map(|r| format!("{}", r)).collect(); + diag.help(&format!("add bound `{}: {}`", a, bs.join(" + "))); + } + SuggestedConstraint::Equal(a, b) => { + diag.help(&format!( + "`{}` and `{}` must be the same: replace one with the other", + a, b + )); + } + SuggestedConstraint::Static(a) => { + diag.help(&format!("replace `{}` with `'static`", a)); + } + } + } + + diag + }; + + // We want this message to appear after other messages on the mir def. + let mir_span = mbcx.body.span; + diag.sort_span = mir_span.shrink_to_hi(); + + // Buffer the diagnostic + mbcx.buffer_non_error_diag(diag); + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/region_errors.rs b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs new file mode 100644 index 000000000..176090c3b --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/region_errors.rs @@ -0,0 +1,904 @@ +//! Error reporting machinery for lifetime errors. + +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed, MultiSpan}; +use rustc_hir::def_id::DefId; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{self as hir, Item, ItemKind, Node}; +use rustc_infer::infer::{ + error_reporting::nice_region_error::{ + self, find_anon_type, find_param_with_region, suggest_adding_lifetime_params, + HirTraitObjectVisitor, NiceRegionError, TraitObjectVisitor, + }, + error_reporting::unexpected_hidden_region_diagnostic, + NllRegionVariableOrigin, RelateParamBound, +}; +use rustc_middle::hir::place::PlaceBase; +use rustc_middle::mir::{ConstraintCategory, ReturnConstraint}; +use rustc_middle::ty::subst::InternalSubsts; +use rustc_middle::ty::Region; +use rustc_middle::ty::TypeVisitor; +use rustc_middle::ty::{self, RegionVid, Ty}; +use rustc_span::symbol::{kw, sym, Ident}; +use rustc_span::Span; + +use crate::borrowck_errors; +use crate::session_diagnostics::GenericDoesNotLiveLongEnough; + +use super::{OutlivesSuggestionBuilder, RegionName}; +use crate::region_infer::BlameConstraint; +use crate::{ + nll::ConstraintDescription, + region_infer::{values::RegionElement, TypeTest}, + universal_regions::DefiningTy, + MirBorrowckCtxt, +}; + +impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> { + fn description(&self) -> &'static str { + // Must end with a space. Allows for empty names to be provided. + match self { + ConstraintCategory::Assignment => "assignment ", + ConstraintCategory::Return(_) => "returning this value ", + ConstraintCategory::Yield => "yielding this value ", + ConstraintCategory::UseAsConst => "using this value as a constant ", + ConstraintCategory::UseAsStatic => "using this value as a static ", + ConstraintCategory::Cast => "cast ", + ConstraintCategory::CallArgument(_) => "argument ", + ConstraintCategory::TypeAnnotation => "type annotation ", + ConstraintCategory::ClosureBounds => "closure body ", + ConstraintCategory::SizedBound => "proving this value is `Sized` ", + ConstraintCategory::CopyBound => "copying this value ", + ConstraintCategory::OpaqueType => "opaque type ", + ConstraintCategory::ClosureUpvar(_) => "closure capture ", + ConstraintCategory::Usage => "this usage ", + ConstraintCategory::Predicate(_) + | ConstraintCategory::Boring + | ConstraintCategory::BoringNoLocation + | ConstraintCategory::Internal => "", + } + } +} + +/// A collection of errors encountered during region inference. This is needed to efficiently +/// report errors after borrow checking. +/// +/// Usually we expect this to either be empty or contain a small number of items, so we can avoid +/// allocation most of the time. +pub(crate) type RegionErrors<'tcx> = Vec<RegionErrorKind<'tcx>>; + +#[derive(Clone, Debug)] +pub(crate) enum RegionErrorKind<'tcx> { + /// A generic bound failure for a type test (`T: 'a`). + TypeTestError { type_test: TypeTest<'tcx> }, + + /// An unexpected hidden region for an opaque type. + UnexpectedHiddenRegion { + /// The span for the member constraint. + span: Span, + /// The hidden type. + hidden_ty: Ty<'tcx>, + /// The opaque type. + key: ty::OpaqueTypeKey<'tcx>, + /// The unexpected region. + member_region: ty::Region<'tcx>, + }, + + /// Higher-ranked subtyping error. + BoundUniversalRegionError { + /// The placeholder free region. + longer_fr: RegionVid, + /// The region element that erroneously must be outlived by `longer_fr`. + error_element: RegionElement, + /// The placeholder region. + placeholder: ty::PlaceholderRegion, + }, + + /// Any other lifetime error. + RegionError { + /// The origin of the region. + fr_origin: NllRegionVariableOrigin, + /// The region that should outlive `shorter_fr`. + longer_fr: RegionVid, + /// The region that should be shorter, but we can't prove it. + shorter_fr: RegionVid, + /// Indicates whether this is a reported error. We currently only report the first error + /// encountered and leave the rest unreported so as not to overwhelm the user. + is_reported: bool, + }, +} + +/// Information about the various region constraints involved in a borrow checker error. +#[derive(Clone, Debug)] +pub struct ErrorConstraintInfo<'tcx> { + // fr: outlived_fr + pub(super) fr: RegionVid, + pub(super) fr_is_local: bool, + pub(super) outlived_fr: RegionVid, + pub(super) outlived_fr_is_local: bool, + + // Category and span for best blame constraint + pub(super) category: ConstraintCategory<'tcx>, + pub(super) span: Span, +} + +impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> { + /// Converts a region inference variable into a `ty::Region` that + /// we can use for error reporting. If `r` is universally bound, + /// then we use the name that we have on record for it. If `r` is + /// existentially bound, then we check its inferred value and try + /// to find a good name from that. Returns `None` if we can't find + /// one (e.g., this is just some random part of the CFG). + pub(super) fn to_error_region(&self, r: RegionVid) -> Option<ty::Region<'tcx>> { + self.to_error_region_vid(r).and_then(|r| self.regioncx.region_definition(r).external_name) + } + + /// Returns the `RegionVid` corresponding to the region returned by + /// `to_error_region`. + pub(super) fn to_error_region_vid(&self, r: RegionVid) -> Option<RegionVid> { + if self.regioncx.universal_regions().is_universal_region(r) { + Some(r) + } else { + // We just want something nameable, even if it's not + // actually an upper bound. + let upper_bound = self.regioncx.approx_universal_upper_bound(r); + + if self.regioncx.upper_bound_in_region_scc(r, upper_bound) { + self.to_error_region_vid(upper_bound) + } else { + None + } + } + } + + /// Returns `true` if a closure is inferred to be an `FnMut` closure. + fn is_closure_fn_mut(&self, fr: RegionVid) -> bool { + if let Some(ty::ReFree(free_region)) = self.to_error_region(fr).as_deref() + && let ty::BoundRegionKind::BrEnv = free_region.bound_region + && let DefiningTy::Closure(_, substs) = self.regioncx.universal_regions().defining_ty + { + return substs.as_closure().kind() == ty::ClosureKind::FnMut; + } + + false + } + + /// Produces nice borrowck error diagnostics for all the errors collected in `nll_errors`. + pub(crate) fn report_region_errors(&mut self, nll_errors: RegionErrors<'tcx>) { + // Iterate through all the errors, producing a diagnostic for each one. The diagnostics are + // buffered in the `MirBorrowckCtxt`. + + let mut outlives_suggestion = OutlivesSuggestionBuilder::default(); + + for nll_error in nll_errors.into_iter() { + match nll_error { + RegionErrorKind::TypeTestError { type_test } => { + // Try to convert the lower-bound region into something named we can print for the user. + let lower_bound_region = self.to_error_region(type_test.lower_bound); + + let type_test_span = type_test.locations.span(&self.body); + + if let Some(lower_bound_region) = lower_bound_region { + let generic_ty = type_test.generic_kind.to_ty(self.infcx.tcx); + let origin = RelateParamBound(type_test_span, generic_ty, None); + self.buffer_error(self.infcx.construct_generic_bound_failure( + self.body.source.def_id().expect_local(), + type_test_span, + Some(origin), + type_test.generic_kind, + lower_bound_region, + )); + } else { + // FIXME. We should handle this case better. It + // indicates that we have e.g., some region variable + // whose value is like `'a+'b` where `'a` and `'b` are + // distinct unrelated universal regions that are not + // known to outlive one another. It'd be nice to have + // some examples where this arises to decide how best + // to report it; we could probably handle it by + // iterating over the universal regions and reporting + // an error that multiple bounds are required. + self.buffer_error(self.infcx.tcx.sess.create_err( + GenericDoesNotLiveLongEnough { + kind: type_test.generic_kind.to_string(), + span: type_test_span, + }, + )); + } + } + + RegionErrorKind::UnexpectedHiddenRegion { span, hidden_ty, key, member_region } => { + let named_ty = self.regioncx.name_regions(self.infcx.tcx, hidden_ty); + let named_key = self.regioncx.name_regions(self.infcx.tcx, key); + let named_region = self.regioncx.name_regions(self.infcx.tcx, member_region); + self.buffer_error(unexpected_hidden_region_diagnostic( + self.infcx.tcx, + span, + named_ty, + named_region, + named_key, + )); + } + + RegionErrorKind::BoundUniversalRegionError { + longer_fr, + placeholder, + error_element, + } => { + let error_vid = self.regioncx.region_from_element(longer_fr, &error_element); + + // Find the code to blame for the fact that `longer_fr` outlives `error_fr`. + let (_, cause) = self.regioncx.find_outlives_blame_span( + &self.body, + longer_fr, + NllRegionVariableOrigin::Placeholder(placeholder), + error_vid, + ); + + let universe = placeholder.universe; + let universe_info = self.regioncx.universe_info(universe); + + universe_info.report_error(self, placeholder, error_element, cause); + } + + RegionErrorKind::RegionError { fr_origin, longer_fr, shorter_fr, is_reported } => { + if is_reported { + self.report_region_error( + longer_fr, + fr_origin, + shorter_fr, + &mut outlives_suggestion, + ); + } else { + // We only report the first error, so as not to overwhelm the user. See + // `RegRegionErrorKind` docs. + // + // FIXME: currently we do nothing with these, but perhaps we can do better? + // FIXME: try collecting these constraints on the outlives suggestion + // builder. Does it make the suggestions any better? + debug!( + "Unreported region error: can't prove that {:?}: {:?}", + longer_fr, shorter_fr + ); + } + } + } + } + + // Emit one outlives suggestions for each MIR def we borrowck + outlives_suggestion.add_suggestion(self); + } + + fn get_impl_ident_and_self_ty_from_trait( + &self, + def_id: DefId, + trait_objects: &FxHashSet<DefId>, + ) -> Option<(Ident, &'tcx hir::Ty<'tcx>)> { + let tcx = self.infcx.tcx; + match tcx.hir().get_if_local(def_id) { + Some(Node::ImplItem(impl_item)) => { + match tcx.hir().find_by_def_id(tcx.hir().get_parent_item(impl_item.hir_id())) { + Some(Node::Item(Item { + kind: ItemKind::Impl(hir::Impl { self_ty, .. }), + .. + })) => Some((impl_item.ident, self_ty)), + _ => None, + } + } + Some(Node::TraitItem(trait_item)) => { + let trait_did = tcx.hir().get_parent_item(trait_item.hir_id()); + match tcx.hir().find_by_def_id(trait_did) { + Some(Node::Item(Item { kind: ItemKind::Trait(..), .. })) => { + // The method being called is defined in the `trait`, but the `'static` + // obligation comes from the `impl`. Find that `impl` so that we can point + // at it in the suggestion. + let trait_did = trait_did.to_def_id(); + match tcx + .hir() + .trait_impls(trait_did) + .iter() + .filter_map(|&impl_did| { + match tcx.hir().get_if_local(impl_did.to_def_id()) { + Some(Node::Item(Item { + kind: ItemKind::Impl(hir::Impl { self_ty, .. }), + .. + })) if trait_objects.iter().all(|did| { + // FIXME: we should check `self_ty` against the receiver + // type in the `UnifyReceiver` context, but for now, use + // this imperfect proxy. This will fail if there are + // multiple `impl`s for the same trait like + // `impl Foo for Box<dyn Bar>` and `impl Foo for dyn Bar`. + // In that case, only the first one will get suggestions. + let mut traits = vec![]; + let mut hir_v = HirTraitObjectVisitor(&mut traits, *did); + hir_v.visit_ty(self_ty); + !traits.is_empty() + }) => + { + Some(self_ty) + } + _ => None, + } + }) + .next() + { + Some(self_ty) => Some((trait_item.ident, self_ty)), + _ => None, + } + } + _ => None, + } + } + _ => None, + } + } + + /// Report an error because the universal region `fr` was required to outlive + /// `outlived_fr` but it is not known to do so. For example: + /// + /// ```compile_fail,E0312 + /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x } + /// ``` + /// + /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`. + pub(crate) fn report_region_error( + &mut self, + fr: RegionVid, + fr_origin: NllRegionVariableOrigin, + outlived_fr: RegionVid, + outlives_suggestion: &mut OutlivesSuggestionBuilder, + ) { + debug!("report_region_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr); + + let BlameConstraint { category, cause, variance_info, from_closure: _ } = + self.regioncx.best_blame_constraint(&self.body, fr, fr_origin, |r| { + self.regioncx.provides_universal_region(r, fr, outlived_fr) + }); + + debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info); + + // Check if we can use one of the "nice region errors". + if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) { + let nice = NiceRegionError::new_from_span(self.infcx, cause.span, o, f); + if let Some(diag) = nice.try_report_from_nll() { + self.buffer_error(diag); + return; + } + } + + let (fr_is_local, outlived_fr_is_local): (bool, bool) = ( + self.regioncx.universal_regions().is_local_free_region(fr), + self.regioncx.universal_regions().is_local_free_region(outlived_fr), + ); + + debug!( + "report_region_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}", + fr_is_local, outlived_fr_is_local, category + ); + + let errci = ErrorConstraintInfo { + fr, + outlived_fr, + fr_is_local, + outlived_fr_is_local, + category, + span: cause.span, + }; + + let mut diag = match (category, fr_is_local, outlived_fr_is_local) { + (ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => { + self.report_fnmut_error(&errci, kind) + } + (ConstraintCategory::Assignment, true, false) + | (ConstraintCategory::CallArgument(_), true, false) => { + let mut db = self.report_escaping_data_error(&errci); + + outlives_suggestion.intermediate_suggestion(self, &errci, &mut db); + outlives_suggestion.collect_constraint(fr, outlived_fr); + + db + } + _ => { + let mut db = self.report_general_error(&errci); + + outlives_suggestion.intermediate_suggestion(self, &errci, &mut db); + outlives_suggestion.collect_constraint(fr, outlived_fr); + + db + } + }; + + match variance_info { + ty::VarianceDiagInfo::None => {} + ty::VarianceDiagInfo::Invariant { ty, param_index } => { + let (desc, note) = match ty.kind() { + ty::RawPtr(ty_mut) => { + assert_eq!(ty_mut.mutbl, rustc_hir::Mutability::Mut); + ( + format!("a mutable pointer to `{}`", ty_mut.ty), + "mutable pointers are invariant over their type parameter".to_string(), + ) + } + ty::Ref(_, inner_ty, mutbl) => { + assert_eq!(*mutbl, rustc_hir::Mutability::Mut); + ( + format!("a mutable reference to `{inner_ty}`"), + "mutable references are invariant over their type parameter" + .to_string(), + ) + } + ty::Adt(adt, substs) => { + let generic_arg = substs[param_index as usize]; + let identity_substs = + InternalSubsts::identity_for_item(self.infcx.tcx, adt.did()); + let base_ty = self.infcx.tcx.mk_adt(*adt, identity_substs); + let base_generic_arg = identity_substs[param_index as usize]; + let adt_desc = adt.descr(); + + let desc = format!( + "the type `{ty}`, which makes the generic argument `{generic_arg}` invariant" + ); + let note = format!( + "the {adt_desc} `{base_ty}` is invariant over the parameter `{base_generic_arg}`" + ); + (desc, note) + } + ty::FnDef(def_id, _) => { + let name = self.infcx.tcx.item_name(*def_id); + let identity_substs = + InternalSubsts::identity_for_item(self.infcx.tcx, *def_id); + let desc = format!("a function pointer to `{name}`"); + let note = format!( + "the function `{name}` is invariant over the parameter `{}`", + identity_substs[param_index as usize] + ); + (desc, note) + } + _ => panic!("Unexpected type {:?}", ty), + }; + diag.note(&format!("requirement occurs because of {desc}",)); + diag.note(¬e); + diag.help("see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance"); + } + } + + self.buffer_error(diag); + } + + /// Report a specialized error when `FnMut` closures return a reference to a captured variable. + /// This function expects `fr` to be local and `outlived_fr` to not be local. + /// + /// ```text + /// error: captured variable cannot escape `FnMut` closure body + /// --> $DIR/issue-53040.rs:15:8 + /// | + /// LL | || &mut v; + /// | -- ^^^^^^ creates a reference to a captured variable which escapes the closure body + /// | | + /// | inferred to be a `FnMut` closure + /// | + /// = note: `FnMut` closures only have access to their captured variables while they are + /// executing... + /// = note: ...therefore, returned references to captured variables will escape the closure + /// ``` + fn report_fnmut_error( + &self, + errci: &ErrorConstraintInfo<'tcx>, + kind: ReturnConstraint, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + let ErrorConstraintInfo { outlived_fr, span, .. } = errci; + + let mut diag = self + .infcx + .tcx + .sess + .struct_span_err(*span, "captured variable cannot escape `FnMut` closure body"); + + let mut output_ty = self.regioncx.universal_regions().unnormalized_output_ty; + if let ty::Opaque(def_id, _) = *output_ty.kind() { + output_ty = self.infcx.tcx.type_of(def_id) + }; + + debug!("report_fnmut_error: output_ty={:?}", output_ty); + + let message = match output_ty.kind() { + ty::Closure(_, _) => { + "returns a closure that contains a reference to a captured variable, which then \ + escapes the closure body" + } + ty::Adt(def, _) if self.infcx.tcx.is_diagnostic_item(sym::gen_future, def.did()) => { + "returns an `async` block that contains a reference to a captured variable, which then \ + escapes the closure body" + } + _ => "returns a reference to a captured variable which escapes the closure body", + }; + + diag.span_label(*span, message); + + if let ReturnConstraint::ClosureUpvar(upvar_field) = kind { + let def_id = match self.regioncx.universal_regions().defining_ty { + DefiningTy::Closure(def_id, _) => def_id, + ty => bug!("unexpected DefiningTy {:?}", ty), + }; + + let captured_place = &self.upvars[upvar_field.index()].place; + let defined_hir = match captured_place.place.base { + PlaceBase::Local(hirid) => Some(hirid), + PlaceBase::Upvar(upvar) => Some(upvar.var_path.hir_id), + _ => None, + }; + + if let Some(def_hir) = defined_hir { + let upvars_map = self.infcx.tcx.upvars_mentioned(def_id).unwrap(); + let upvar_def_span = self.infcx.tcx.hir().span(def_hir); + let upvar_span = upvars_map.get(&def_hir).unwrap().span; + diag.span_label(upvar_def_span, "variable defined here"); + diag.span_label(upvar_span, "variable captured here"); + } + } + + if let Some(fr_span) = self.give_region_a_name(*outlived_fr).unwrap().span() { + diag.span_label(fr_span, "inferred to be a `FnMut` closure"); + } + + diag.note( + "`FnMut` closures only have access to their captured variables while they are \ + executing...", + ); + diag.note("...therefore, they cannot allow references to captured variables to escape"); + + diag + } + + /// Reports an error specifically for when data is escaping a closure. + /// + /// ```text + /// error: borrowed data escapes outside of function + /// --> $DIR/lifetime-bound-will-change-warning.rs:44:5 + /// | + /// LL | fn test2<'a>(x: &'a Box<Fn()+'a>) { + /// | - `x` is a reference that is only valid in the function body + /// LL | // but ref_obj will not, so warn. + /// LL | ref_obj(x) + /// | ^^^^^^^^^^ `x` escapes the function body here + /// ``` + fn report_escaping_data_error( + &self, + errci: &ErrorConstraintInfo<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + let ErrorConstraintInfo { span, category, .. } = errci; + + let fr_name_and_span = self.regioncx.get_var_name_and_span_for_region( + self.infcx.tcx, + &self.body, + &self.local_names, + &self.upvars, + errci.fr, + ); + let outlived_fr_name_and_span = self.regioncx.get_var_name_and_span_for_region( + self.infcx.tcx, + &self.body, + &self.local_names, + &self.upvars, + errci.outlived_fr, + ); + + let (_, escapes_from) = self + .infcx + .tcx + .article_and_description(self.regioncx.universal_regions().defining_ty.def_id()); + + // Revert to the normal error in these cases. + // Assignments aren't "escapes" in function items. + if (fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none()) + || (*category == ConstraintCategory::Assignment + && self.regioncx.universal_regions().defining_ty.is_fn_def()) + || self.regioncx.universal_regions().defining_ty.is_const() + { + return self.report_general_error(&ErrorConstraintInfo { + fr_is_local: true, + outlived_fr_is_local: false, + ..*errci + }); + } + + let mut diag = + borrowck_errors::borrowed_data_escapes_closure(self.infcx.tcx, *span, escapes_from); + + if let Some((Some(outlived_fr_name), outlived_fr_span)) = outlived_fr_name_and_span { + diag.span_label( + outlived_fr_span, + format!("`{outlived_fr_name}` declared here, outside of the {escapes_from} body",), + ); + } + + if let Some((Some(fr_name), fr_span)) = fr_name_and_span { + diag.span_label( + fr_span, + format!( + "`{fr_name}` is a reference that is only valid in the {escapes_from} body", + ), + ); + + diag.span_label(*span, format!("`{fr_name}` escapes the {escapes_from} body here")); + } + + // Only show an extra note if we can find an 'error region' for both of the region + // variables. This avoids showing a noisy note that just mentions 'synthetic' regions + // that don't help the user understand the error. + match (self.to_error_region(errci.fr), self.to_error_region(errci.outlived_fr)) { + (Some(f), Some(o)) => { + self.maybe_suggest_constrain_dyn_trait_impl(&mut diag, f, o, category); + + let fr_region_name = self.give_region_a_name(errci.fr).unwrap(); + fr_region_name.highlight_region_name(&mut diag); + let outlived_fr_region_name = self.give_region_a_name(errci.outlived_fr).unwrap(); + outlived_fr_region_name.highlight_region_name(&mut diag); + + diag.span_label( + *span, + format!( + "{}requires that `{}` must outlive `{}`", + category.description(), + fr_region_name, + outlived_fr_region_name, + ), + ); + } + _ => {} + } + + diag + } + + /// Reports a region inference error for the general case with named/synthesized lifetimes to + /// explain what is happening. + /// + /// ```text + /// error: unsatisfied lifetime constraints + /// --> $DIR/regions-creating-enums3.rs:17:5 + /// | + /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> { + /// | -- -- lifetime `'b` defined here + /// | | + /// | lifetime `'a` defined here + /// LL | ast::add(x, y) + /// | ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it + /// | is returning data with lifetime `'b` + /// ``` + fn report_general_error( + &self, + errci: &ErrorConstraintInfo<'tcx>, + ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { + let ErrorConstraintInfo { + fr, + fr_is_local, + outlived_fr, + outlived_fr_is_local, + span, + category, + .. + } = errci; + + let mut diag = + self.infcx.tcx.sess.struct_span_err(*span, "lifetime may not live long enough"); + + let (_, mir_def_name) = + self.infcx.tcx.article_and_description(self.mir_def_id().to_def_id()); + + let fr_name = self.give_region_a_name(*fr).unwrap(); + fr_name.highlight_region_name(&mut diag); + let outlived_fr_name = self.give_region_a_name(*outlived_fr).unwrap(); + outlived_fr_name.highlight_region_name(&mut diag); + + match (category, outlived_fr_is_local, fr_is_local) { + (ConstraintCategory::Return(_), true, _) => { + diag.span_label( + *span, + format!( + "{mir_def_name} was supposed to return data with lifetime `{outlived_fr_name}` but it is returning \ + data with lifetime `{fr_name}`", + ), + ); + } + _ => { + diag.span_label( + *span, + format!( + "{}requires that `{}` must outlive `{}`", + category.description(), + fr_name, + outlived_fr_name, + ), + ); + } + } + + self.add_static_impl_trait_suggestion(&mut diag, *fr, fr_name, *outlived_fr); + self.suggest_adding_lifetime_params(&mut diag, *fr, *outlived_fr); + + diag + } + + /// Adds a suggestion to errors where an `impl Trait` is returned. + /// + /// ```text + /// help: to allow this `impl Trait` to capture borrowed data with lifetime `'1`, add `'_` as + /// a constraint + /// | + /// LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> + 'a { + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + /// ``` + fn add_static_impl_trait_suggestion( + &self, + diag: &mut Diagnostic, + fr: RegionVid, + // We need to pass `fr_name` - computing it again will label it twice. + fr_name: RegionName, + outlived_fr: RegionVid, + ) { + if let (Some(f), Some(outlived_f)) = + (self.to_error_region(fr), self.to_error_region(outlived_fr)) + { + if *outlived_f != ty::ReStatic { + return; + } + + let fn_returns = self + .infcx + .tcx + .is_suitable_region(f) + .map(|r| self.infcx.tcx.return_type_impl_or_dyn_traits(r.def_id)) + .unwrap_or_default(); + + if fn_returns.is_empty() { + return; + } + + let param = if let Some(param) = find_param_with_region(self.infcx.tcx, f, outlived_f) { + param + } else { + return; + }; + + let lifetime = if f.has_name() { fr_name.name } else { kw::UnderscoreLifetime }; + + let arg = match param.param.pat.simple_ident() { + Some(simple_ident) => format!("argument `{}`", simple_ident), + None => "the argument".to_string(), + }; + let captures = format!("captures data from {}", arg); + + return nice_region_error::suggest_new_region_bound( + self.infcx.tcx, + diag, + fn_returns, + lifetime.to_string(), + Some(arg), + captures, + Some((param.param_ty_span, param.param_ty.to_string())), + ); + } + } + + fn maybe_suggest_constrain_dyn_trait_impl( + &self, + diag: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + f: Region<'tcx>, + o: Region<'tcx>, + category: &ConstraintCategory<'tcx>, + ) { + if !o.is_static() { + return; + } + + let tcx = self.infcx.tcx; + + let instance = if let ConstraintCategory::CallArgument(Some(func_ty)) = category { + let (fn_did, substs) = match func_ty.kind() { + ty::FnDef(fn_did, substs) => (fn_did, substs), + _ => return, + }; + debug!(?fn_did, ?substs); + + // Only suggest this on function calls, not closures + let ty = tcx.type_of(fn_did); + debug!("ty: {:?}, ty.kind: {:?}", ty, ty.kind()); + if let ty::Closure(_, _) = ty.kind() { + return; + } + + if let Ok(Some(instance)) = ty::Instance::resolve( + tcx, + self.param_env, + *fn_did, + self.infcx.resolve_vars_if_possible(substs), + ) { + instance + } else { + return; + } + } else { + return; + }; + + let param = match find_param_with_region(tcx, f, o) { + Some(param) => param, + None => return, + }; + debug!(?param); + + let mut visitor = TraitObjectVisitor(FxHashSet::default()); + visitor.visit_ty(param.param_ty); + + let Some((ident, self_ty)) = + self.get_impl_ident_and_self_ty_from_trait(instance.def_id(), &visitor.0) else {return}; + + self.suggest_constrain_dyn_trait_in_impl(diag, &visitor.0, ident, self_ty); + } + + #[instrument(skip(self, err), level = "debug")] + fn suggest_constrain_dyn_trait_in_impl( + &self, + err: &mut Diagnostic, + found_dids: &FxHashSet<DefId>, + ident: Ident, + self_ty: &hir::Ty<'_>, + ) -> bool { + debug!("err: {:#?}", err); + let mut suggested = false; + for found_did in found_dids { + let mut traits = vec![]; + let mut hir_v = HirTraitObjectVisitor(&mut traits, *found_did); + hir_v.visit_ty(&self_ty); + debug!("trait spans found: {:?}", traits); + for span in &traits { + let mut multi_span: MultiSpan = vec![*span].into(); + multi_span + .push_span_label(*span, "this has an implicit `'static` lifetime requirement"); + multi_span.push_span_label( + ident.span, + "calling this method introduces the `impl`'s 'static` requirement", + ); + err.span_note(multi_span, "the used `impl` has a `'static` requirement"); + err.span_suggestion_verbose( + span.shrink_to_hi(), + "consider relaxing the implicit `'static` requirement", + " + '_", + Applicability::MaybeIncorrect, + ); + suggested = true; + } + } + suggested + } + + fn suggest_adding_lifetime_params( + &self, + diag: &mut Diagnostic, + sub: RegionVid, + sup: RegionVid, + ) { + let (Some(sub), Some(sup)) = (self.to_error_region(sub), self.to_error_region(sup)) else { + return + }; + + let Some((ty_sub, _)) = self + .infcx + .tcx + .is_suitable_region(sub) + .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sub, &anon_reg.boundregion)) else { + return + }; + + let Some((ty_sup, _)) = self + .infcx + .tcx + .is_suitable_region(sup) + .and_then(|anon_reg| find_anon_type(self.infcx.tcx, sup, &anon_reg.boundregion)) else { + return + }; + + suggest_adding_lifetime_params(self.infcx.tcx, sub, ty_sup, ty_sub, diag); + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs new file mode 100644 index 000000000..a87e8bd5b --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -0,0 +1,896 @@ +use std::fmt::{self, Display}; +use std::iter; + +use rustc_errors::Diagnostic; +use rustc_hir as hir; +use rustc_hir::def::{DefKind, Res}; +use rustc_middle::ty::print::RegionHighlightMode; +use rustc_middle::ty::subst::{GenericArgKind, SubstsRef}; +use rustc_middle::ty::{self, DefIdTree, RegionVid, Ty}; +use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_span::{Span, DUMMY_SP}; + +use crate::{nll::ToRegionVid, universal_regions::DefiningTy, MirBorrowckCtxt}; + +/// A name for a particular region used in emitting diagnostics. This name could be a generated +/// name like `'1`, a name used by the user like `'a`, or a name like `'static`. +#[derive(Debug, Clone)] +pub(crate) struct RegionName { + /// The name of the region (interned). + pub(crate) name: Symbol, + /// Where the region comes from. + pub(crate) source: RegionNameSource, +} + +/// Denotes the source of a region that is named by a `RegionName`. For example, a free region that +/// was named by the user would get `NamedFreeRegion` and `'static` lifetime would get `Static`. +/// This helps to print the right kinds of diagnostics. +#[derive(Debug, Clone)] +pub(crate) enum RegionNameSource { + /// A bound (not free) region that was substituted at the def site (not an HRTB). + NamedEarlyBoundRegion(Span), + /// A free region that the user has a name (`'a`) for. + NamedFreeRegion(Span), + /// The `'static` region. + Static, + /// The free region corresponding to the environment of a closure. + SynthesizedFreeEnvRegion(Span, &'static str), + /// The region corresponding to an argument. + AnonRegionFromArgument(RegionNameHighlight), + /// The region corresponding to a closure upvar. + AnonRegionFromUpvar(Span, Symbol), + /// The region corresponding to the return type of a closure. + AnonRegionFromOutput(RegionNameHighlight, &'static str), + /// The region from a type yielded by a generator. + AnonRegionFromYieldTy(Span, String), + /// An anonymous region from an async fn. + AnonRegionFromAsyncFn(Span), + /// An anonymous region from an impl self type or trait + AnonRegionFromImplSignature(Span, &'static str), +} + +/// Describes what to highlight to explain to the user that we're giving an anonymous region a +/// synthesized name, and how to highlight it. +#[derive(Debug, Clone)] +pub(crate) enum RegionNameHighlight { + /// The anonymous region corresponds to a reference that was found by traversing the type in the HIR. + MatchedHirTy(Span), + /// The anonymous region corresponds to a `'_` in the generics list of a struct/enum/union. + MatchedAdtAndSegment(Span), + /// The anonymous region corresponds to a region where the type annotation is completely missing + /// from the code, e.g. in a closure arguments `|x| { ... }`, where `x` is a reference. + CannotMatchHirTy(Span, String), + /// The anonymous region corresponds to a region where the type annotation is completely missing + /// from the code, and *even if* we print out the full name of the type, the region name won't + /// be included. This currently occurs for opaque types like `impl Future`. + Occluded(Span, String), +} + +impl RegionName { + pub(crate) fn was_named(&self) -> bool { + match self.source { + RegionNameSource::NamedEarlyBoundRegion(..) + | RegionNameSource::NamedFreeRegion(..) + | RegionNameSource::Static => true, + RegionNameSource::SynthesizedFreeEnvRegion(..) + | RegionNameSource::AnonRegionFromArgument(..) + | RegionNameSource::AnonRegionFromUpvar(..) + | RegionNameSource::AnonRegionFromOutput(..) + | RegionNameSource::AnonRegionFromYieldTy(..) + | RegionNameSource::AnonRegionFromAsyncFn(..) + | RegionNameSource::AnonRegionFromImplSignature(..) => false, + } + } + + pub(crate) fn span(&self) -> Option<Span> { + match self.source { + RegionNameSource::Static => None, + RegionNameSource::NamedEarlyBoundRegion(span) + | RegionNameSource::NamedFreeRegion(span) + | RegionNameSource::SynthesizedFreeEnvRegion(span, _) + | RegionNameSource::AnonRegionFromUpvar(span, _) + | RegionNameSource::AnonRegionFromYieldTy(span, _) + | RegionNameSource::AnonRegionFromAsyncFn(span) + | RegionNameSource::AnonRegionFromImplSignature(span, _) => Some(span), + RegionNameSource::AnonRegionFromArgument(ref highlight) + | RegionNameSource::AnonRegionFromOutput(ref highlight, _) => match *highlight { + RegionNameHighlight::MatchedHirTy(span) + | RegionNameHighlight::MatchedAdtAndSegment(span) + | RegionNameHighlight::CannotMatchHirTy(span, _) + | RegionNameHighlight::Occluded(span, _) => Some(span), + }, + } + } + + pub(crate) fn highlight_region_name(&self, diag: &mut Diagnostic) { + match &self.source { + RegionNameSource::NamedFreeRegion(span) + | RegionNameSource::NamedEarlyBoundRegion(span) => { + diag.span_label(*span, format!("lifetime `{self}` defined here")); + } + RegionNameSource::SynthesizedFreeEnvRegion(span, note) => { + diag.span_label(*span, format!("lifetime `{self}` represents this closure's body")); + diag.note(*note); + } + RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::CannotMatchHirTy( + span, + type_name, + )) => { + diag.span_label(*span, format!("has type `{type_name}`")); + } + RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::MatchedHirTy(span)) + | RegionNameSource::AnonRegionFromOutput(RegionNameHighlight::MatchedHirTy(span), _) + | RegionNameSource::AnonRegionFromAsyncFn(span) => { + diag.span_label( + *span, + format!("let's call the lifetime of this reference `{self}`"), + ); + } + RegionNameSource::AnonRegionFromArgument( + RegionNameHighlight::MatchedAdtAndSegment(span), + ) + | RegionNameSource::AnonRegionFromOutput( + RegionNameHighlight::MatchedAdtAndSegment(span), + _, + ) => { + diag.span_label(*span, format!("let's call this `{self}`")); + } + RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::Occluded( + span, + type_name, + )) => { + diag.span_label( + *span, + format!("lifetime `{self}` appears in the type {type_name}"), + ); + } + RegionNameSource::AnonRegionFromOutput( + RegionNameHighlight::Occluded(span, type_name), + mir_description, + ) => { + diag.span_label( + *span, + format!( + "return type{mir_description} `{type_name}` contains a lifetime `{self}`" + ), + ); + } + RegionNameSource::AnonRegionFromUpvar(span, upvar_name) => { + diag.span_label( + *span, + format!("lifetime `{self}` appears in the type of `{upvar_name}`"), + ); + } + RegionNameSource::AnonRegionFromOutput( + RegionNameHighlight::CannotMatchHirTy(span, type_name), + mir_description, + ) => { + diag.span_label(*span, format!("return type{mir_description} is {type_name}")); + } + RegionNameSource::AnonRegionFromYieldTy(span, type_name) => { + diag.span_label(*span, format!("yield type is {type_name}")); + } + RegionNameSource::AnonRegionFromImplSignature(span, location) => { + diag.span_label( + *span, + format!("lifetime `{self}` appears in the `impl`'s {location}"), + ); + } + RegionNameSource::Static => {} + } + } +} + +impl Display for RegionName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl<'tcx> MirBorrowckCtxt<'_, 'tcx> { + pub(crate) fn mir_def_id(&self) -> hir::def_id::LocalDefId { + self.body.source.def_id().expect_local() + } + + pub(crate) fn mir_hir_id(&self) -> hir::HirId { + self.infcx.tcx.hir().local_def_id_to_hir_id(self.mir_def_id()) + } + + /// Generate a synthetic region named `'N`, where `N` is the next value of the counter. Then, + /// increment the counter. + /// + /// This is _not_ idempotent. Call `give_region_a_name` when possible. + fn synthesize_region_name(&self) -> Symbol { + let c = self.next_region_name.replace_with(|counter| *counter + 1); + Symbol::intern(&format!("'{:?}", c)) + } + + /// Maps from an internal MIR region vid to something that we can + /// report to the user. In some cases, the region vids will map + /// directly to lifetimes that the user has a name for (e.g., + /// `'static`). But frequently they will not, in which case we + /// have to find some way to identify the lifetime to the user. To + /// that end, this function takes a "diagnostic" so that it can + /// create auxiliary notes as needed. + /// + /// The names are memoized, so this is both cheap to recompute and idempotent. + /// + /// Example (function arguments): + /// + /// Suppose we are trying to give a name to the lifetime of the + /// reference `x`: + /// + /// ```ignore (pseudo-rust) + /// fn foo(x: &u32) { .. } + /// ``` + /// + /// This function would create a label like this: + /// + /// ```text + /// | fn foo(x: &u32) { .. } + /// ------- fully elaborated type of `x` is `&'1 u32` + /// ``` + /// + /// and then return the name `'1` for us to use. + pub(crate) fn give_region_a_name(&self, fr: RegionVid) -> Option<RegionName> { + debug!( + "give_region_a_name(fr={:?}, counter={:?})", + fr, + self.next_region_name.try_borrow().unwrap() + ); + + assert!(self.regioncx.universal_regions().is_universal_region(fr)); + + if let Some(value) = self.region_names.try_borrow_mut().unwrap().get(&fr) { + return Some(value.clone()); + } + + let value = self + .give_name_from_error_region(fr) + .or_else(|| self.give_name_if_anonymous_region_appears_in_arguments(fr)) + .or_else(|| self.give_name_if_anonymous_region_appears_in_upvars(fr)) + .or_else(|| self.give_name_if_anonymous_region_appears_in_output(fr)) + .or_else(|| self.give_name_if_anonymous_region_appears_in_yield_ty(fr)) + .or_else(|| self.give_name_if_anonymous_region_appears_in_impl_signature(fr)); + + if let Some(ref value) = value { + self.region_names.try_borrow_mut().unwrap().insert(fr, value.clone()); + } + + debug!("give_region_a_name: gave name {:?}", value); + value + } + + /// Checks for the case where `fr` maps to something that the + /// *user* has a name for. In that case, we'll be able to map + /// `fr` to a `Region<'tcx>`, and that region will be one of + /// named variants. + #[tracing::instrument(level = "trace", skip(self))] + fn give_name_from_error_region(&self, fr: RegionVid) -> Option<RegionName> { + let error_region = self.to_error_region(fr)?; + + let tcx = self.infcx.tcx; + + debug!("give_region_a_name: error_region = {:?}", error_region); + match *error_region { + ty::ReEarlyBound(ebr) => { + if ebr.has_name() { + let span = tcx.hir().span_if_local(ebr.def_id).unwrap_or(DUMMY_SP); + Some(RegionName { + name: ebr.name, + source: RegionNameSource::NamedEarlyBoundRegion(span), + }) + } else { + None + } + } + + ty::ReStatic => { + Some(RegionName { name: kw::StaticLifetime, source: RegionNameSource::Static }) + } + + ty::ReFree(free_region) => match free_region.bound_region { + ty::BoundRegionKind::BrNamed(region_def_id, name) => { + // Get the span to point to, even if we don't use the name. + let span = tcx.hir().span_if_local(region_def_id).unwrap_or(DUMMY_SP); + debug!( + "bound region named: {:?}, is_named: {:?}", + name, + free_region.bound_region.is_named() + ); + + if free_region.bound_region.is_named() { + // A named region that is actually named. + Some(RegionName { name, source: RegionNameSource::NamedFreeRegion(span) }) + } else if let hir::IsAsync::Async = tcx.asyncness(self.mir_hir_id().owner) { + // If we spuriously thought that the region is named, we should let the + // system generate a true name for error messages. Currently this can + // happen if we have an elided name in an async fn for example: the + // compiler will generate a region named `'_`, but reporting such a name is + // not actually useful, so we synthesize a name for it instead. + let name = self.synthesize_region_name(); + Some(RegionName { + name, + source: RegionNameSource::AnonRegionFromAsyncFn(span), + }) + } else { + None + } + } + + ty::BoundRegionKind::BrEnv => { + let def_ty = self.regioncx.universal_regions().defining_ty; + + let DefiningTy::Closure(_, substs) = def_ty else { + // Can't have BrEnv in functions, constants or generators. + bug!("BrEnv outside of closure."); + }; + let hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }) + = tcx.hir().expect_expr(self.mir_hir_id()).kind + else { + bug!("Closure is not defined by a closure expr"); + }; + let region_name = self.synthesize_region_name(); + + let closure_kind_ty = substs.as_closure().kind_ty(); + let note = match closure_kind_ty.to_opt_closure_kind() { + Some(ty::ClosureKind::Fn) => { + "closure implements `Fn`, so references to captured variables \ + can't escape the closure" + } + Some(ty::ClosureKind::FnMut) => { + "closure implements `FnMut`, so references to captured variables \ + can't escape the closure" + } + Some(ty::ClosureKind::FnOnce) => { + bug!("BrEnv in a `FnOnce` closure"); + } + None => bug!("Closure kind not inferred in borrow check"), + }; + + Some(RegionName { + name: region_name, + source: RegionNameSource::SynthesizedFreeEnvRegion(fn_decl_span, note), + }) + } + + ty::BoundRegionKind::BrAnon(_) => None, + }, + + ty::ReLateBound(..) + | ty::ReVar(..) + | ty::RePlaceholder(..) + | ty::ReEmpty(_) + | ty::ReErased => None, + } + } + + /// Finds an argument that contains `fr` and label it with a fully + /// elaborated type, returning something like `'1`. Result looks + /// like: + /// + /// ```text + /// | fn foo(x: &u32) { .. } + /// ------- fully elaborated type of `x` is `&'1 u32` + /// ``` + #[tracing::instrument(level = "trace", skip(self))] + fn give_name_if_anonymous_region_appears_in_arguments( + &self, + fr: RegionVid, + ) -> Option<RegionName> { + let implicit_inputs = self.regioncx.universal_regions().defining_ty.implicit_inputs(); + let argument_index = self.regioncx.get_argument_index_for_region(self.infcx.tcx, fr)?; + + let arg_ty = self.regioncx.universal_regions().unnormalized_input_tys + [implicit_inputs + argument_index]; + let (_, span) = self.regioncx.get_argument_name_and_span_for_region( + &self.body, + &self.local_names, + argument_index, + ); + + let highlight = self + .get_argument_hir_ty_for_highlighting(argument_index) + .and_then(|arg_hir_ty| self.highlight_if_we_can_match_hir_ty(fr, arg_ty, arg_hir_ty)) + .unwrap_or_else(|| { + // `highlight_if_we_cannot_match_hir_ty` needs to know the number we will give to + // the anonymous region. If it succeeds, the `synthesize_region_name` call below + // will increment the counter, "reserving" the number we just used. + let counter = *self.next_region_name.try_borrow().unwrap(); + self.highlight_if_we_cannot_match_hir_ty(fr, arg_ty, span, counter) + }); + + Some(RegionName { + name: self.synthesize_region_name(), + source: RegionNameSource::AnonRegionFromArgument(highlight), + }) + } + + fn get_argument_hir_ty_for_highlighting( + &self, + argument_index: usize, + ) -> Option<&hir::Ty<'tcx>> { + let fn_decl = self.infcx.tcx.hir().fn_decl_by_hir_id(self.mir_hir_id())?; + let argument_hir_ty: &hir::Ty<'_> = fn_decl.inputs.get(argument_index)?; + match argument_hir_ty.kind { + // This indicates a variable with no type annotation, like + // `|x|`... in that case, we can't highlight the type but + // must highlight the variable. + // NOTE(eddyb) this is handled in/by the sole caller + // (`give_name_if_anonymous_region_appears_in_arguments`). + hir::TyKind::Infer => None, + + _ => Some(argument_hir_ty), + } + } + + /// Attempts to highlight the specific part of a type in an argument + /// that has no type annotation. + /// For example, we might produce an annotation like this: + /// + /// ```text + /// | foo(|a, b| b) + /// | - - + /// | | | + /// | | has type `&'1 u32` + /// | has type `&'2 u32` + /// ``` + fn highlight_if_we_cannot_match_hir_ty( + &self, + needle_fr: RegionVid, + ty: Ty<'tcx>, + span: Span, + counter: usize, + ) -> RegionNameHighlight { + let mut highlight = RegionHighlightMode::new(self.infcx.tcx); + highlight.highlighting_region_vid(needle_fr, counter); + let type_name = + self.infcx.extract_inference_diagnostics_data(ty.into(), Some(highlight)).name; + + debug!( + "highlight_if_we_cannot_match_hir_ty: type_name={:?} needle_fr={:?}", + type_name, needle_fr + ); + if type_name.contains(&format!("'{counter}")) { + // Only add a label if we can confirm that a region was labelled. + RegionNameHighlight::CannotMatchHirTy(span, type_name) + } else { + RegionNameHighlight::Occluded(span, type_name) + } + } + + /// Attempts to highlight the specific part of a type annotation + /// that contains the anonymous reference we want to give a name + /// to. For example, we might produce an annotation like this: + /// + /// ```text + /// | fn a<T>(items: &[T]) -> Box<dyn Iterator<Item = &T>> { + /// | - let's call the lifetime of this reference `'1` + /// ``` + /// + /// the way this works is that we match up `ty`, which is + /// a `Ty<'tcx>` (the internal form of the type) with + /// `hir_ty`, a `hir::Ty` (the syntax of the type + /// annotation). We are descending through the types stepwise, + /// looking in to find the region `needle_fr` in the internal + /// type. Once we find that, we can use the span of the `hir::Ty` + /// to add the highlight. + /// + /// This is a somewhat imperfect process, so along the way we also + /// keep track of the **closest** type we've found. If we fail to + /// find the exact `&` or `'_` to highlight, then we may fall back + /// to highlighting that closest type instead. + fn highlight_if_we_can_match_hir_ty( + &self, + needle_fr: RegionVid, + ty: Ty<'tcx>, + hir_ty: &hir::Ty<'_>, + ) -> Option<RegionNameHighlight> { + let search_stack: &mut Vec<(Ty<'tcx>, &hir::Ty<'_>)> = &mut vec![(ty, hir_ty)]; + + while let Some((ty, hir_ty)) = search_stack.pop() { + match (ty.kind(), &hir_ty.kind) { + // Check if the `ty` is `&'X ..` where `'X` + // is the region we are looking for -- if so, and we have a `&T` + // on the RHS, then we want to highlight the `&` like so: + // + // & + // - let's call the lifetime of this reference `'1` + ( + ty::Ref(region, referent_ty, _), + hir::TyKind::Rptr(_lifetime, referent_hir_ty), + ) => { + if region.to_region_vid() == needle_fr { + // Just grab the first character, the `&`. + let source_map = self.infcx.tcx.sess.source_map(); + let ampersand_span = source_map.start_point(hir_ty.span); + + return Some(RegionNameHighlight::MatchedHirTy(ampersand_span)); + } + + // Otherwise, let's descend into the referent types. + search_stack.push((*referent_ty, &referent_hir_ty.ty)); + } + + // Match up something like `Foo<'1>` + ( + ty::Adt(_adt_def, substs), + hir::TyKind::Path(hir::QPath::Resolved(None, path)), + ) => { + match path.res { + // Type parameters of the type alias have no reason to + // be the same as those of the ADT. + // FIXME: We should be able to do something similar to + // match_adt_and_segment in this case. + Res::Def(DefKind::TyAlias, _) => (), + _ => { + if let Some(last_segment) = path.segments.last() { + if let Some(highlight) = self.match_adt_and_segment( + substs, + needle_fr, + last_segment, + search_stack, + ) { + return Some(highlight); + } + } + } + } + } + + // The following cases don't have lifetimes, so we + // just worry about trying to match up the rustc type + // with the HIR types: + (&ty::Tuple(elem_tys), hir::TyKind::Tup(elem_hir_tys)) => { + search_stack.extend(iter::zip(elem_tys, *elem_hir_tys)); + } + + (ty::Slice(elem_ty), hir::TyKind::Slice(elem_hir_ty)) + | (ty::Array(elem_ty, _), hir::TyKind::Array(elem_hir_ty, _)) => { + search_stack.push((*elem_ty, elem_hir_ty)); + } + + (ty::RawPtr(mut_ty), hir::TyKind::Ptr(mut_hir_ty)) => { + search_stack.push((mut_ty.ty, &mut_hir_ty.ty)); + } + + _ => { + // FIXME there are other cases that we could trace + } + } + } + + None + } + + /// We've found an enum/struct/union type with the substitutions + /// `substs` and -- in the HIR -- a path type with the final + /// segment `last_segment`. Try to find a `'_` to highlight in + /// the generic args (or, if not, to produce new zipped pairs of + /// types+hir to search through). + fn match_adt_and_segment<'hir>( + &self, + substs: SubstsRef<'tcx>, + needle_fr: RegionVid, + last_segment: &'hir hir::PathSegment<'hir>, + search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty<'hir>)>, + ) -> Option<RegionNameHighlight> { + // Did the user give explicit arguments? (e.g., `Foo<..>`) + let args = last_segment.args.as_ref()?; + let lifetime = + self.try_match_adt_and_generic_args(substs, needle_fr, args, search_stack)?; + match lifetime.name { + hir::LifetimeName::Param(_, hir::ParamName::Plain(_) | hir::ParamName::Error) + | hir::LifetimeName::Error + | hir::LifetimeName::Static => { + let lifetime_span = lifetime.span; + Some(RegionNameHighlight::MatchedAdtAndSegment(lifetime_span)) + } + + hir::LifetimeName::Param(_, hir::ParamName::Fresh) + | hir::LifetimeName::ImplicitObjectLifetimeDefault + | hir::LifetimeName::Infer => { + // In this case, the user left off the lifetime; so + // they wrote something like: + // + // ``` + // x: Foo<T> + // ``` + // + // where the fully elaborated form is `Foo<'_, '1, + // T>`. We don't consider this a match; instead we let + // the "fully elaborated" type fallback above handle + // it. + None + } + } + } + + /// We've found an enum/struct/union type with the substitutions + /// `substs` and -- in the HIR -- a path with the generic + /// arguments `args`. If `needle_fr` appears in the args, return + /// the `hir::Lifetime` that corresponds to it. If not, push onto + /// `search_stack` the types+hir to search through. + fn try_match_adt_and_generic_args<'hir>( + &self, + substs: SubstsRef<'tcx>, + needle_fr: RegionVid, + args: &'hir hir::GenericArgs<'hir>, + search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty<'hir>)>, + ) -> Option<&'hir hir::Lifetime> { + for (kind, hir_arg) in iter::zip(substs, args.args) { + match (kind.unpack(), hir_arg) { + (GenericArgKind::Lifetime(r), hir::GenericArg::Lifetime(lt)) => { + if r.to_region_vid() == needle_fr { + return Some(lt); + } + } + + (GenericArgKind::Type(ty), hir::GenericArg::Type(hir_ty)) => { + search_stack.push((ty, hir_ty)); + } + + (GenericArgKind::Const(_ct), hir::GenericArg::Const(_hir_ct)) => { + // Lifetimes cannot be found in consts, so we don't need + // to search anything here. + } + + ( + GenericArgKind::Lifetime(_) + | GenericArgKind::Type(_) + | GenericArgKind::Const(_), + _, + ) => { + // HIR lowering sometimes doesn't catch this in erroneous + // programs, so we need to use delay_span_bug here. See #82126. + self.infcx.tcx.sess.delay_span_bug( + hir_arg.span(), + &format!("unmatched subst and hir arg: found {:?} vs {:?}", kind, hir_arg), + ); + } + } + } + + None + } + + /// Finds a closure upvar that contains `fr` and label it with a + /// fully elaborated type, returning something like `'1`. Result + /// looks like: + /// + /// ```text + /// | let x = Some(&22); + /// - fully elaborated type of `x` is `Option<&'1 u32>` + /// ``` + #[tracing::instrument(level = "trace", skip(self))] + fn give_name_if_anonymous_region_appears_in_upvars(&self, fr: RegionVid) -> Option<RegionName> { + let upvar_index = self.regioncx.get_upvar_index_for_region(self.infcx.tcx, fr)?; + let (upvar_name, upvar_span) = self.regioncx.get_upvar_name_and_span_for_region( + self.infcx.tcx, + &self.upvars, + upvar_index, + ); + let region_name = self.synthesize_region_name(); + + Some(RegionName { + name: region_name, + source: RegionNameSource::AnonRegionFromUpvar(upvar_span, upvar_name), + }) + } + + /// Checks for arguments appearing in the (closure) return type. It + /// must be a closure since, in a free fn, such an argument would + /// have to either also appear in an argument (if using elision) + /// or be early bound (named, not in argument). + #[tracing::instrument(level = "trace", skip(self))] + fn give_name_if_anonymous_region_appears_in_output(&self, fr: RegionVid) -> Option<RegionName> { + let tcx = self.infcx.tcx; + let hir = tcx.hir(); + + let return_ty = self.regioncx.universal_regions().unnormalized_output_ty; + debug!("give_name_if_anonymous_region_appears_in_output: return_ty = {:?}", return_ty); + if !tcx.any_free_region_meets(&return_ty, |r| r.to_region_vid() == fr) { + return None; + } + + let mir_hir_id = self.mir_hir_id(); + + let (return_span, mir_description, hir_ty) = match hir.get(mir_hir_id) { + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(&hir::Closure { fn_decl, body, fn_decl_span, .. }), + .. + }) => { + let (mut span, mut hir_ty) = match fn_decl.output { + hir::FnRetTy::DefaultReturn(_) => { + (tcx.sess.source_map().end_point(fn_decl_span), None) + } + hir::FnRetTy::Return(hir_ty) => (fn_decl.output.span(), Some(hir_ty)), + }; + let mir_description = match hir.body(body).generator_kind { + Some(hir::GeneratorKind::Async(gen)) => match gen { + hir::AsyncGeneratorKind::Block => " of async block", + hir::AsyncGeneratorKind::Closure => " of async closure", + hir::AsyncGeneratorKind::Fn => { + let parent_item = hir.get_by_def_id(hir.get_parent_item(mir_hir_id)); + let output = &parent_item + .fn_decl() + .expect("generator lowered from async fn should be in fn") + .output; + span = output.span(); + if let hir::FnRetTy::Return(ret) = output { + hir_ty = Some(self.get_future_inner_return_ty(*ret)); + } + " of async function" + } + }, + Some(hir::GeneratorKind::Gen) => " of generator", + None => " of closure", + }; + (span, mir_description, hir_ty) + } + node => match node.fn_decl() { + Some(fn_decl) => { + let hir_ty = match fn_decl.output { + hir::FnRetTy::DefaultReturn(_) => None, + hir::FnRetTy::Return(ty) => Some(ty), + }; + (fn_decl.output.span(), "", hir_ty) + } + None => (self.body.span, "", None), + }, + }; + + let highlight = hir_ty + .and_then(|hir_ty| self.highlight_if_we_can_match_hir_ty(fr, return_ty, hir_ty)) + .unwrap_or_else(|| { + // `highlight_if_we_cannot_match_hir_ty` needs to know the number we will give to + // the anonymous region. If it succeeds, the `synthesize_region_name` call below + // will increment the counter, "reserving" the number we just used. + let counter = *self.next_region_name.try_borrow().unwrap(); + self.highlight_if_we_cannot_match_hir_ty(fr, return_ty, return_span, counter) + }); + + Some(RegionName { + name: self.synthesize_region_name(), + source: RegionNameSource::AnonRegionFromOutput(highlight, mir_description), + }) + } + + /// From the [`hir::Ty`] of an async function's lowered return type, + /// retrieve the `hir::Ty` representing the type the user originally wrote. + /// + /// e.g. given the function: + /// + /// ``` + /// async fn foo() -> i32 { 2 } + /// ``` + /// + /// this function, given the lowered return type of `foo`, an [`OpaqueDef`] that implements `Future<Output=i32>`, + /// returns the `i32`. + /// + /// [`OpaqueDef`]: hir::TyKind::OpaqueDef + fn get_future_inner_return_ty(&self, hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { + let hir = self.infcx.tcx.hir(); + + let hir::TyKind::OpaqueDef(id, _) = hir_ty.kind else { + span_bug!( + hir_ty.span, + "lowered return type of async fn is not OpaqueDef: {:?}", + hir_ty + ); + }; + let opaque_ty = hir.item(id); + if let hir::ItemKind::OpaqueTy(hir::OpaqueTy { + bounds: + [ + hir::GenericBound::LangItemTrait( + hir::LangItem::Future, + _, + _, + hir::GenericArgs { + bindings: + [ + hir::TypeBinding { + ident: Ident { name: sym::Output, .. }, + kind: + hir::TypeBindingKind::Equality { term: hir::Term::Ty(ty) }, + .. + }, + ], + .. + }, + ), + ], + .. + }) = opaque_ty.kind + { + ty + } else { + span_bug!( + hir_ty.span, + "bounds from lowered return type of async fn did not match expected format: {:?}", + opaque_ty + ); + } + } + + #[tracing::instrument(level = "trace", skip(self))] + fn give_name_if_anonymous_region_appears_in_yield_ty( + &self, + fr: RegionVid, + ) -> Option<RegionName> { + // Note: generators from `async fn` yield `()`, so we don't have to + // worry about them here. + let yield_ty = self.regioncx.universal_regions().yield_ty?; + debug!("give_name_if_anonymous_region_appears_in_yield_ty: yield_ty = {:?}", yield_ty); + + let tcx = self.infcx.tcx; + + if !tcx.any_free_region_meets(&yield_ty, |r| r.to_region_vid() == fr) { + return None; + } + + let mut highlight = RegionHighlightMode::new(tcx); + highlight.highlighting_region_vid(fr, *self.next_region_name.try_borrow().unwrap()); + let type_name = + self.infcx.extract_inference_diagnostics_data(yield_ty.into(), Some(highlight)).name; + + let yield_span = match tcx.hir().get(self.mir_hir_id()) { + hir::Node::Expr(hir::Expr { + kind: hir::ExprKind::Closure(&hir::Closure { fn_decl_span, .. }), + .. + }) => tcx.sess.source_map().end_point(fn_decl_span), + _ => self.body.span, + }; + + debug!( + "give_name_if_anonymous_region_appears_in_yield_ty: \ + type_name = {:?}, yield_span = {:?}", + yield_span, type_name, + ); + + Some(RegionName { + name: self.synthesize_region_name(), + source: RegionNameSource::AnonRegionFromYieldTy(yield_span, type_name), + }) + } + + fn give_name_if_anonymous_region_appears_in_impl_signature( + &self, + fr: RegionVid, + ) -> Option<RegionName> { + let ty::ReEarlyBound(region) = *self.to_error_region(fr)? else { + return None; + }; + if region.has_name() { + return None; + }; + + let tcx = self.infcx.tcx; + let body_parent_did = tcx.opt_parent(self.mir_def_id().to_def_id())?; + if tcx.parent(region.def_id) != body_parent_did + || tcx.def_kind(body_parent_did) != DefKind::Impl + { + return None; + } + + let mut found = false; + tcx.fold_regions(tcx.type_of(body_parent_did), |r: ty::Region<'tcx>, _| { + if *r == ty::ReEarlyBound(region) { + found = true; + } + r + }); + + Some(RegionName { + name: self.synthesize_region_name(), + source: RegionNameSource::AnonRegionFromImplSignature( + tcx.def_span(region.def_id), + // FIXME(compiler-errors): Does this ever actually show up + // anywhere other than the self type? I couldn't create an + // example of a `'_` in the impl's trait being referenceable. + if found { "self type" } else { "header" }, + ), + }) + } +} diff --git a/compiler/rustc_borrowck/src/diagnostics/var_name.rs b/compiler/rustc_borrowck/src/diagnostics/var_name.rs new file mode 100644 index 000000000..9ba29f04b --- /dev/null +++ b/compiler/rustc_borrowck/src/diagnostics/var_name.rs @@ -0,0 +1,133 @@ +use crate::Upvar; +use crate::{nll::ToRegionVid, region_infer::RegionInferenceContext}; +use rustc_index::vec::{Idx, IndexVec}; +use rustc_middle::mir::{Body, Local}; +use rustc_middle::ty::{RegionVid, TyCtxt}; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; + +impl<'tcx> RegionInferenceContext<'tcx> { + pub(crate) fn get_var_name_and_span_for_region( + &self, + tcx: TyCtxt<'tcx>, + body: &Body<'tcx>, + local_names: &IndexVec<Local, Option<Symbol>>, + upvars: &[Upvar<'tcx>], + fr: RegionVid, + ) -> Option<(Option<Symbol>, Span)> { + debug!("get_var_name_and_span_for_region(fr={:?})", fr); + assert!(self.universal_regions().is_universal_region(fr)); + + debug!("get_var_name_and_span_for_region: attempting upvar"); + self.get_upvar_index_for_region(tcx, fr) + .map(|index| { + // FIXME(project-rfc-2229#8): Use place span for diagnostics + let (name, span) = self.get_upvar_name_and_span_for_region(tcx, upvars, index); + (Some(name), span) + }) + .or_else(|| { + debug!("get_var_name_and_span_for_region: attempting argument"); + self.get_argument_index_for_region(tcx, fr).map(|index| { + self.get_argument_name_and_span_for_region(body, local_names, index) + }) + }) + } + + /// Search the upvars (if any) to find one that references fr. Return its index. + pub(crate) fn get_upvar_index_for_region( + &self, + tcx: TyCtxt<'tcx>, + fr: RegionVid, + ) -> Option<usize> { + let upvar_index = + self.universal_regions().defining_ty.upvar_tys().position(|upvar_ty| { + debug!("get_upvar_index_for_region: upvar_ty={:?}", upvar_ty); + tcx.any_free_region_meets(&upvar_ty, |r| { + let r = r.to_region_vid(); + debug!("get_upvar_index_for_region: r={:?} fr={:?}", r, fr); + r == fr + }) + })?; + + let upvar_ty = self.universal_regions().defining_ty.upvar_tys().nth(upvar_index); + + debug!( + "get_upvar_index_for_region: found {:?} in upvar {} which has type {:?}", + fr, upvar_index, upvar_ty, + ); + + Some(upvar_index) + } + + /// Given the index of an upvar, finds its name and the span from where it was + /// declared. + pub(crate) fn get_upvar_name_and_span_for_region( + &self, + tcx: TyCtxt<'tcx>, + upvars: &[Upvar<'tcx>], + upvar_index: usize, + ) -> (Symbol, Span) { + let upvar_hir_id = upvars[upvar_index].place.get_root_variable(); + debug!("get_upvar_name_and_span_for_region: upvar_hir_id={:?}", upvar_hir_id); + + let upvar_name = tcx.hir().name(upvar_hir_id); + let upvar_span = tcx.hir().span(upvar_hir_id); + debug!( + "get_upvar_name_and_span_for_region: upvar_name={:?} upvar_span={:?}", + upvar_name, upvar_span + ); + + (upvar_name, upvar_span) + } + + /// Search the argument types for one that references fr (which should be a free region). + /// Returns Some(_) with the index of the input if one is found. + /// + /// N.B., in the case of a closure, the index is indexing into the signature as seen by the + /// user - in particular, index 0 is not the implicit self parameter. + pub(crate) fn get_argument_index_for_region( + &self, + tcx: TyCtxt<'tcx>, + fr: RegionVid, + ) -> Option<usize> { + let implicit_inputs = self.universal_regions().defining_ty.implicit_inputs(); + let argument_index = + self.universal_regions().unnormalized_input_tys.iter().skip(implicit_inputs).position( + |arg_ty| { + debug!("get_argument_index_for_region: arg_ty = {:?}", arg_ty); + tcx.any_free_region_meets(arg_ty, |r| r.to_region_vid() == fr) + }, + )?; + + debug!( + "get_argument_index_for_region: found {:?} in argument {} which has type {:?}", + fr, + argument_index, + self.universal_regions().unnormalized_input_tys[argument_index], + ); + + Some(argument_index) + } + + /// Given the index of an argument, finds its name (if any) and the span from where it was + /// declared. + pub(crate) fn get_argument_name_and_span_for_region( + &self, + body: &Body<'tcx>, + local_names: &IndexVec<Local, Option<Symbol>>, + argument_index: usize, + ) -> (Option<Symbol>, Span) { + let implicit_inputs = self.universal_regions().defining_ty.implicit_inputs(); + let argument_local = Local::new(implicit_inputs + argument_index + 1); + debug!("get_argument_name_and_span_for_region: argument_local={:?}", argument_local); + + let argument_name = local_names[argument_local]; + let argument_span = body.local_decls[argument_local].source_info.span; + debug!( + "get_argument_name_and_span_for_region: argument_name={:?} argument_span={:?}", + argument_name, argument_span + ); + + (argument_name, argument_span) + } +} |