summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_borrowck/src/diagnostics
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /compiler/rustc_borrowck/src/diagnostics
parentInitial commit. (diff)
downloadrustc-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.rs494
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs2773
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs744
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/find_all_local_uses.rs26
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/find_use.rs128
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/mod.rs1127
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/move_errors.rs529
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs1115
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/outlives_suggestion.rs261
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_errors.rs904
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/region_name.rs896
-rw-r--r--compiler/rustc_borrowck/src/diagnostics/var_name.rs133
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,
+ &region_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, &note_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, &note_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(&note);
+ 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)
+ }
+}