Files
rust/compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

860 lines
38 KiB
Rust
Raw Normal View History

2019-11-29 19:48:29 -06:00
//! Print diagnostics to explain why values are borrowed.
#![allow(rustc::diagnostic_outside_of_impl)]
#![allow(rustc::untranslatable_diagnostic)]
2024-08-11 12:10:36 -04:00
use std::assert_matches::assert_matches;
use rustc_errors::{Applicability, Diag, EmissionGuarantee};
2023-01-15 03:06:44 +00:00
use rustc_hir as hir;
use rustc_hir::intravisit::Visitor;
use rustc_infer::infer::NllRegionVariableOrigin;
use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
2020-03-29 16:41:09 +02:00
use rustc_middle::mir::{
Body, CallSource, CastKind, ConstraintCategory, FakeReadCause, Local, LocalInfo, Location,
Operand, Place, Rvalue, Statement, StatementKind, TerminatorKind,
};
use rustc_middle::ty::adjustment::PointerCoercion;
use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt};
use rustc_middle::util::CallKind;
use rustc_span::{DesugaringKind, Span, kw, sym};
use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
use tracing::{debug, instrument};
2018-06-27 16:13:33 -03:00
2019-11-27 12:22:17 -06:00
use super::{RegionName, UseSpans, find_use};
2019-12-28 20:36:42 -06:00
use crate::borrow_set::BorrowData;
use crate::constraints::OutlivesConstraint;
2019-12-28 20:36:42 -06:00
use crate::nll::ConstraintDescription;
use crate::region_infer::{BlameConstraint, Cause};
2019-12-28 20:36:42 -06:00
use crate::{MirBorrowckCtxt, WriteKind};
#[derive(Debug)]
pub(crate) enum BorrowExplanation<'tcx> {
UsedLater(Local, 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>,
path: Vec<OutlivesConstraint<'tcx>>,
},
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 {
2021-11-06 01:31:32 +01:00
!matches!(self, BorrowExplanation::Unexplained)
}
pub(crate) fn add_explanation_to_diagnostic<G: EmissionGuarantee>(
&self,
cx: &MirBorrowckCtxt<'_, '_, 'tcx>,
err: &mut Diag<'_, G>,
borrow_desc: &str,
borrow_span: Option<Span>,
multiple_borrow_span: Option<(Span, Span)>,
) {
let tcx = cx.infcx.tcx;
let body = cx.body;
let local_names = &cx.local_names;
2023-01-15 03:06:44 +00:00
if let Some(span) = borrow_span {
let def_id = body.source.def_id();
if let Some(node) = tcx.hir().get_if_local(def_id)
&& let Some(body_id) = node.body_id()
{
let body = tcx.hir().body(body_id);
let mut expr_finder = FindExprBySpan::new(span, tcx);
2023-01-15 03:06:44 +00:00
expr_finder.visit_expr(body.value);
if let Some(mut expr) = expr_finder.result {
while let hir::ExprKind::AddrOf(_, _, inner)
| hir::ExprKind::Unary(hir::UnOp::Deref, inner)
| hir::ExprKind::Field(inner, _)
| hir::ExprKind::MethodCall(_, inner, _, _)
| hir::ExprKind::Index(inner, _, _) = &expr.kind
{
2023-01-15 03:06:44 +00:00
expr = inner;
}
if let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind
&& let [hir::PathSegment { ident, args: None, .. }] = p.segments
&& let hir::def::Res::Local(hir_id) = p.res
&& let hir::Node::Pat(pat) = tcx.hir_node(hir_id)
2023-01-15 03:06:44 +00:00
{
err.span_label(pat.span, format!("binding `{ident}` declared here"));
2023-01-15 03:06:44 +00:00
}
}
}
}
match *self {
BorrowExplanation::UsedLater(
dropped_local,
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",
};
let local_decl = &body.local_decls[dropped_local];
if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info()
&& let Some((_, hir::Node::Expr(expr))) = tcx.hir().parent_iter(if_then).next()
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
&& let hir::ExprKind::Let(&hir::LetExpr {
span: _,
pat,
init,
// FIXME(#101728): enable rewrite when type ascription is stabilized again
ty: None,
recovered: _,
}) = cond.kind
&& pat.span.can_be_used_for_suggestions()
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
{
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
} else if path_span.map_or(true, |path_span| path_span == var_or_use_span) {
// We can use `var_or_use_span` if either `path_span` is not present, or both
// spans are the same.
if borrow_span.map_or(true, |sp| !sp.overlaps(var_or_use_span)) {
err.span_label(
var_or_use_span,
format!("{borrow_desc}borrow later {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
2024-08-11 12:10:36 -04:00
assert_matches!(later_use_kind, LaterUseKind::ClosureCapture);
if !borrow_span.is_some_and(|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_desc}borrow later {capture_kind_label}"),
);
err.span_label(path_span, path_label);
}
}
2019-02-20 12:15:27 -03:00
}
BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span) => {
let message = match later_use_kind {
2019-02-20 12:15:27 -03:00
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
2024-08-11 12:10:36 -04:00
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_desc}borrow later {capture_kind_label}"),
);
err.span_label(path_span, path_label);
}
}
2019-02-20 12:15:27 -03:00
}
BorrowExplanation::UsedLaterWhenDropped {
drop_loc,
dropped_local,
should_note_order,
} => {
let local_decl = &body.local_decls[dropped_local];
2021-10-25 23:33:12 -05:00
let mut ty = local_decl.ty;
if local_decl.source_info.span.desugaring_kind() == Some(DesugaringKind::ForLoop) {
if let ty::Adt(adt, args) = local_decl.ty.kind() {
if tcx.is_diagnostic_item(sym::Option, adt.did()) {
2021-10-25 23:33:12 -05:00
// in for loop desugaring, only look at the `Some(..)` inner type
ty = args.type_at(0);
2021-10-25 23:33:12 -05:00
}
}
}
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, _args) if adt.has_dtor(tcx) && !adt.is_box() => {
("`Drop` code", format!("type `{}`", tcx.def_path_str(adt.did())))
2019-12-22 17:42:04 -05:00
}
// Otherwise, just report the whole type (and use
// the intentionally fuzzy phrase "destructor")
2019-02-20 12:15:27 -03:00
ty::Closure(..) => ("destructor", "closure".to_owned()),
2023-10-19 21:46:28 +00:00
ty::Coroutine(..) => ("destructor", "coroutine".to_owned()),
_ => ("destructor", format!("type `{}`", local_decl.ty)),
};
match local_names[dropped_local] {
Some(local_name) if !local_decl.from_compiler_desugaring() => {
2019-02-20 12:15:27 -03:00
let message = format!(
"{borrow_desc}borrow might be used here, when `{local_name}` is dropped \
and runs the {dtor_desc} for {type_desc}",
2019-02-20 12:15:27 -03:00
);
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",
);
}
}
_ => {
2019-02-20 12:15:27 -03:00
err.span_label(
local_decl.source_info.span,
format!(
"a temporary with access to the {borrow_desc}borrow \
2019-02-20 12:15:27 -03:00
is created here ...",
),
);
let message = format!(
"... and the {borrow_desc}borrow might be used here, \
2019-02-20 12:15:27 -03:00
when that temporary is dropped \
and runs the {dtor_desc} for {type_desc}",
2019-02-20 12:15:27 -03:00
);
err.span_label(body.source_info(drop_loc).span, message);
if let LocalInfo::BlockTailTemp(info) = local_decl.local_info() {
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.is_some_and(|(old, new)| {
old.to(info.span.shrink_to_hi()).contains(new)
}) {
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,
);
};
} else if let &LocalInfo::IfThenRescopeTemp { if_then } =
local_decl.local_info()
&& let hir::Node::Expr(expr) = tcx.hir_node(if_then)
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
&& let hir::ExprKind::Let(&hir::LetExpr {
span: _,
pat,
init,
// FIXME(#101728): enable rewrite when type ascription is
// stabilized again.
ty: None,
recovered: _,
}) = cond.kind
&& pat.span.can_be_used_for_suggestions()
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
{
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
}
}
}
2019-02-20 12:15:27 -03:00
}
BorrowExplanation::MustBeValidFor {
category,
span,
ref region_name,
ref opt_place_desc,
from_closure: _,
ref path,
} => {
region_name.highlight_region_name(err);
if let Some(desc) = opt_place_desc {
2019-02-20 12:15:27 -03:00
err.span_label(
span,
format!(
"{}requires that `{desc}` is borrowed for `{region_name}`",
2019-02-20 12:15:27 -03:00
category.description(),
),
);
} else {
2019-02-20 12:15:27 -03:00
err.span_label(
span,
format!(
"{}requires that {borrow_desc}borrow lasts for `{region_name}`",
2019-02-20 12:15:27 -03:00
category.description(),
),
);
};
cx.add_placeholder_from_predicate_note(err, &path);
cx.add_sized_or_copy_bound_info(err, category, &path);
2022-09-16 17:43:45 -04:00
if let ConstraintCategory::Cast {
is_implicit_coercion: true,
unsize_to: Some(unsize_ty),
} = category
{
self.add_object_lifetime_default_note(tcx, err, unsize_ty);
}
self.add_lifetime_bound_suggestion_to_diagnostic(err, &category, span, region_name);
}
_ => {}
}
}
fn add_object_lifetime_default_note<G: EmissionGuarantee>(
&self,
tcx: TyCtxt<'tcx>,
err: &mut Diag<'_, G>,
unsize_ty: Ty<'tcx>,
) {
if let ty::Adt(def, args) = unsize_ty.kind() {
// We try to elaborate the object lifetime defaults and present those to the user. This
// should make it clear where the region constraint is coming from.
let generics = tcx.generics_of(def.did());
let mut has_dyn = false;
let mut failed = false;
let elaborated_args =
std::iter::zip(*args, &generics.own_params).map(|(arg, param)| {
if let Some(ty::Dynamic(obj, _, ty::Dyn)) = arg.as_type().map(Ty::kind) {
let default = tcx.object_lifetime_default(param.def_id);
let re_static = tcx.lifetimes.re_static;
let implied_region = match default {
// This is not entirely precise.
ObjectLifetimeDefault::Empty => re_static,
ObjectLifetimeDefault::Ambiguous => {
failed = true;
re_static
}
ObjectLifetimeDefault::Param(param_def_id) => {
let index = generics.param_def_id_to_index[&param_def_id] as usize;
args.get(index).and_then(|arg| arg.as_region()).unwrap_or_else(
|| {
failed = true;
re_static
},
)
}
ObjectLifetimeDefault::Static => re_static,
};
has_dyn = true;
Ty::new_dynamic(tcx, obj, implied_region, ty::Dyn).into()
} else {
arg
}
});
let elaborated_ty = Ty::new_adt(tcx, *def, tcx.mk_args_from_iter(elaborated_args));
if has_dyn && !failed {
err.note(format!(
"due to object lifetime defaults, `{unsize_ty}` actually means `{elaborated_ty}`"
));
}
}
}
fn add_lifetime_bound_suggestion_to_diagnostic<G: EmissionGuarantee>(
&self,
err: &mut Diag<'_, G>,
category: &ConstraintCategory<'tcx>,
span: Span,
region_name: &RegionName,
) {
if !span.is_desugaring(DesugaringKind::OpaqueTy) {
return;
}
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 `{region_name}`",
category.description(),
);
err.span_suggestion_verbose(
span.shrink_to_hi(),
msg,
format!(" + {suggestable_name}"),
Applicability::Unspecified,
);
}
}
}
fn suggest_rewrite_if_let<G: EmissionGuarantee>(
tcx: TyCtxt<'_>,
expr: &hir::Expr<'_>,
pat: &str,
init: &hir::Expr<'_>,
conseq: &hir::Expr<'_>,
alt: Option<&hir::Expr<'_>>,
err: &mut Diag<'_, G>,
) {
let source_map = tcx.sess.source_map();
err.span_note(
source_map.end_point(conseq.span),
"lifetimes for temporaries generated in `if let`s have been shortened in Edition 2024 so that they are dropped here instead",
);
if expr.span.can_be_used_for_suggestions() && conseq.span.can_be_used_for_suggestions() {
let needs_block = if let Some(hir::Node::Expr(expr)) =
alt.and_then(|alt| tcx.hir().parent_iter(alt.hir_id).next()).map(|(_, node)| node)
{
matches!(expr.kind, hir::ExprKind::If(..))
} else {
false
};
let mut sugg = vec![
(
expr.span.shrink_to_lo().between(init.span),
if needs_block { "{ match ".into() } else { "match ".into() },
),
(conseq.span.shrink_to_lo(), format!(" {{ {pat} => ")),
];
let expr_end = expr.span.shrink_to_hi();
let mut expr_end_code;
if let Some(alt) = alt {
sugg.push((conseq.span.between(alt.span), " _ => ".into()));
expr_end_code = "}".to_string();
} else {
expr_end_code = " _ => {} }".into();
}
expr_end_code.push('}');
sugg.push((expr_end, expr_end_code));
err.multipart_suggestion(
"consider rewriting the `if` into `match` which preserves the extended lifetime",
sugg,
Applicability::MaybeIncorrect,
);
}
}
impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
fn free_region_constraint_info(
&self,
borrow_region: RegionVid,
outlived_region: RegionVid,
) -> (ConstraintCategory<'tcx>, bool, Span, Option<RegionName>, Vec<OutlivesConstraint<'tcx>>)
{
let (blame_constraint, path) = self.regioncx.best_blame_constraint(
borrow_region,
NllRegionVariableOrigin::FreeRegion,
|r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
);
let BlameConstraint { category, from_closure, cause, .. } = blame_constraint;
2019-12-28 20:36:42 -06:00
let outlived_fr_name = self.give_region_a_name(outlived_region);
(category, from_closure, cause.span, outlived_fr_name, path)
}
/// Returns structured explanation for *why* the borrow contains the
2019-05-02 06:03:17 +09:00
/// point from `location`. This is key for the "3-point errors"
2018-03-05 16:12:19 -03:00
/// [described in the NLL RFC][d].
///
/// # Parameters
///
/// - `borrow`: the borrow in question
2019-05-02 06:03:17 +09:00
/// - `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
///
2018-03-05 16:12:19 -03:00
/// [d]: https://rust-lang.github.io/rfcs/2094-nll.html#leveraging-intuition-framing-errors-in-terms-of-points
#[instrument(level = "debug", skip(self))]
pub(crate) fn explain_why_borrow_contains_point(
&self,
2019-05-02 06:03:17 +09:00
location: Location,
borrow: &BorrowData<'tcx>,
2020-03-30 17:49:33 -03:00
kind_place: Option<(WriteKind, Place<'tcx>)>,
) -> BorrowExplanation<'tcx> {
2019-12-28 20:02:20 -06:00
let regioncx = &self.regioncx;
let body: &Body<'_> = self.body;
let tcx = self.infcx.tcx;
let borrow_region_vid = borrow.region;
debug!(?borrow_region_vid);
let mut region_sub = self.regioncx.find_sub_region_live_at(borrow_region_vid, location);
debug!(?region_sub);
2018-06-19 21:22:52 -03:00
let mut use_location = location;
let mut use_in_later_iteration_of_loop = false;
if region_sub == borrow_region_vid {
// When `region_sub` is the same as `borrow_region_vid` (the location where the borrow
// is issued is the same location that invalidates the reference), this is likely a
// loop iteration. In this case, try using the loop terminator location in
// `find_sub_region_live_at`.
if let Some(loop_terminator_location) =
regioncx.find_loop_terminator_location(borrow.region, body)
{
region_sub = self
.regioncx
.find_sub_region_live_at(borrow_region_vid, loop_terminator_location);
debug!("explain_why_borrow_contains_point: region_sub in loop={:?}", region_sub);
use_location = loop_terminator_location;
use_in_later_iteration_of_loop = true;
}
}
match find_use::find(body, regioncx, tcx, region_sub, use_location) {
Some(Cause::LiveVar(local, location)) => {
let span = body.source_info(location).span;
2019-02-20 12:15:27 -03:00
let spans = self
.move_spans(Place::from(local).as_ref(), location)
.or_else(|| self.borrow_spans(span, location));
if use_in_later_iteration_of_loop {
let (later_use_kind, var_or_use_span, path_span) =
self.later_use_kind(borrow, spans, use_location);
BorrowExplanation::UsedLaterInLoop(later_use_kind, var_or_use_span, path_span)
} 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_kind, var_or_use_span, path_span) =
self.later_use_kind(borrow, spans, location);
BorrowExplanation::UsedLater(
borrow.borrowed_place.local,
later_use_kind,
var_or_use_span,
path_span,
)
}
2018-07-01 19:43:01 -03:00
}
2018-06-19 21:22:52 -03:00
2019-02-20 12:15:27 -03:00
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;
2019-02-20 12:15:27 -03:00
}
BorrowExplanation::UsedLaterWhenDropped {
drop_loc: location,
dropped_local: local,
should_note_order,
}
}
2019-02-20 12:15:27 -03:00
None => {
if let Some(region) = self.to_error_region_vid(borrow_region_vid) {
let (category, from_closure, span, region_name, path) =
self.free_region_constraint_info(borrow_region_vid, region);
2019-02-20 12:15:27 -03:00
if let Some(region_name) = region_name {
let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref());
2019-02-20 12:15:27 -03:00
BorrowExplanation::MustBeValidFor {
category,
from_closure,
span,
region_name,
opt_place_desc,
path,
2019-02-20 12:15:27 -03:00
}
} else {
debug!("Could not generate a region name");
2019-02-20 12:15:27 -03:00
BorrowExplanation::Unexplained
}
} else {
debug!("Could not generate an error region vid");
BorrowExplanation::Unexplained
}
}
}
}
/// 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
#[instrument(level = "debug", skip(self))]
fn later_use_kind(
&self,
borrow: &BorrowData<'tcx>,
use_spans: UseSpans<'tcx>,
2019-02-20 12:15:27 -03:00
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))
2019-02-20 12:15:27 -03:00
}
// In the case that the borrowed value (probably a temporary)
// overlaps with the method's receiver, then point at the method.
UseSpans::FnSelfUse {
var_span: span,
kind: CallKind::Normal { desugaring: None, .. },
..
} if span
.overlaps(self.body.local_decls[borrow.assigned_place.local].source_info.span) =>
{
if let TerminatorKind::Call { func, call_source: CallSource::Normal, .. } =
&self.body.basic_blocks[location.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
}
}
};
(LaterUseKind::Call, function_span, None)
} else {
(LaterUseKind::Other, span, None)
}
}
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(_), place)),
..
2019-02-20 12:15:27 -03:00
}) = block.statements.get(location.statement_index)
{
if let Some(l) = place.as_local()
&& let local_decl = &self.body.local_decls[l]
&& local_decl.ty.is_closure()
{
LaterUseKind::ClosureCapture
} else {
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 { func, call_source: CallSource::Normal, .. } =
&block.terminator().kind
2019-02-20 12:15:27 -03:00
{
// 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
}
2019-02-20 12:15:27 -03:00
}
};
return (LaterUseKind::Call, function_span, None);
} else {
LaterUseKind::Other
}
} else {
LaterUseKind::Other
};
(kind, span, None)
}
}
}
2019-02-08 14:53:55 +01:00
/// 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
2021-08-22 17:27:18 +02:00
/// 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);
2019-02-20 12:15:27 -03:00
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 (place, _)), .. }) = stmt {
if let Some(local) = place.as_local() {
local
} else {
return false;
}
} else {
return false;
};
2019-02-20 12:15:27 -03:00
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 {
2022-02-19 00:48:49 +01:00
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;
}
}
2019-02-20 12:15:27 -03:00
}
_ => {}
},
2021-08-22 17:27:18 +02:00
// 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::PointerCoercion(PointerCoercion::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.
2020-08-03 00:49:11 +02:00
return match ty.kind() {
// `&dyn Trait`
ty::Ref(_, ty, _) if ty.is_trait() => true,
// `Box<dyn Trait>`
2024-09-05 00:34:04 +03:00
_ if ty.boxed_ty().is_some_and(Ty::is_trait) => {
true
2019-12-22 17:42:04 -05:00
}
2024-09-05 00:34:04 +03:00
// `dyn Trait`
_ if ty.is_trait() => true,
// Anything else.
_ => false,
};
2019-12-22 17:42:04 -05:00
}
}
return false;
}
_ => return false,
2019-02-20 12:15:27 -03:00
}
}
2019-02-20 12:15:27 -03:00
_ => {}
}
}
// 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.node {
if let Some(potential) = place.as_local() {
potential == target
} else {
false
}
} else {
false
}
});
2018-10-17 16:58:12 +02:00
// If it is, follow this to the next block and update the target.
if found_target {
target = dest;
queue.push(block.start_location());
}
2018-10-17 16:58:12 +02:00
}
}
}
debug!("was_captured_by_trait: queue={:?}", queue);
}
// We didn't find anything and ran out of locations to check.
false
}
}