1
Fork 0

reduce false positives of tail-expr-drop-order from consumed values

take 2

open up coroutines

tweak the wordings

the lint works up until 2021

We were missing one case, for ADTs, which was
causing `Result` to yield incorrect results.

only include field spans with significant types

deduplicate and eliminate field spans

switch to emit spans to impl Drops

Co-authored-by: Niko Matsakis <nikomat@amazon.com>

collect drops instead of taking liveness diff

apply some suggestions and add explantory notes

small fix on the cache

let the query recurse through coroutine

new suggestion format with extracted variable name

fine-tune the drop span and messages

bugfix on runtime borrows

tweak message wording

filter out ecosystem types earlier

apply suggestions

clippy

check lint level at session level

further restrict applicability of the lint

translate bid into nop for stable mir

detect cycle in type structure
This commit is contained in:
Ding Xiang Fei 2024-09-02 01:13:07 +08:00
parent 70e814bd9e
commit 297b618944
No known key found for this signature in database
GPG key ID: 3CD748647EEF6359
58 changed files with 2015 additions and 538 deletions

View file

@ -1,6 +1,5 @@
//! See docs in build/expr/mod.rs
use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::thir::*;
use tracing::{debug, instrument};
@ -9,6 +8,12 @@ use crate::build::expr::category::Category;
use crate::build::{BlockAnd, BlockAndExtension, Builder, NeedsTemporary};
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Construct a temporary lifetime restricted to just the local scope
pub(crate) fn local_temp_lifetime(&self) -> TempLifetime {
let local_scope = self.local_scope();
TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None }
}
/// Returns an operand suitable for use until the end of the current
/// scope expression.
///
@ -21,8 +26,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block: BasicBlock,
expr_id: ExprId,
) -> BlockAnd<Operand<'tcx>> {
let local_scope = self.local_scope();
self.as_operand(block, Some(local_scope), expr_id, LocalInfo::Boring, NeedsTemporary::Maybe)
self.as_operand(
block,
self.local_temp_lifetime(),
expr_id,
LocalInfo::Boring,
NeedsTemporary::Maybe,
)
}
/// Returns an operand suitable for use until the end of the current scope expression and
@ -80,8 +90,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block: BasicBlock,
expr: ExprId,
) -> BlockAnd<Operand<'tcx>> {
let local_scope = self.local_scope();
self.as_call_operand(block, Some(local_scope), expr)
self.as_call_operand(block, self.local_temp_lifetime(), expr)
}
/// Compile `expr` into a value that can be used as an operand.
@ -102,7 +111,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn as_operand(
&mut self,
mut block: BasicBlock,
scope: Option<region::Scope>,
scope: TempLifetime,
expr_id: ExprId,
local_info: LocalInfo<'tcx>,
needs_temporary: NeedsTemporary,
@ -146,7 +155,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn as_call_operand(
&mut self,
mut block: BasicBlock,
scope: Option<region::Scope>,
scope: TempLifetime,
expr_id: ExprId,
) -> BlockAnd<Operand<'tcx>> {
let this = self;

View file

@ -6,7 +6,6 @@ use std::iter;
use rustc_abi::{FIRST_VARIANT, FieldIdx, VariantIdx};
use rustc_hir::def_id::LocalDefId;
use rustc_middle::hir::place::{Projection as HirProjection, ProjectionKind as HirProjectionKind};
use rustc_middle::middle::region;
use rustc_middle::mir::AssertKind::BoundsCheck;
use rustc_middle::mir::*;
use rustc_middle::thir::*;
@ -598,7 +597,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
index: ExprId,
mutability: Mutability,
fake_borrow_temps: Option<&mut Vec<Local>>,
temp_lifetime: Option<region::Scope>,
temp_lifetime: TempLifetime,
expr_span: Span,
source_info: SourceInfo,
) -> BlockAnd<PlaceBuilder<'tcx>> {

View file

@ -33,14 +33,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
expr_id: ExprId,
) -> BlockAnd<Rvalue<'tcx>> {
let local_scope = self.local_scope();
self.as_rvalue(block, Some(local_scope), expr_id)
self.as_rvalue(
block,
TempLifetime { temp_lifetime: Some(local_scope), backwards_incompatible: None },
expr_id,
)
}
/// Compile `expr`, yielding an rvalue.
pub(crate) fn as_rvalue(
&mut self,
mut block: BasicBlock,
scope: Option<region::Scope>,
scope: TempLifetime,
expr_id: ExprId,
) -> BlockAnd<Rvalue<'tcx>> {
let this = self;
@ -171,7 +175,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
source_info,
kind: StatementKind::StorageLive(result),
});
if let Some(scope) = scope {
if let Some(scope) = scope.temp_lifetime {
// schedule a shallow free of that memory, lest we unwind:
this.schedule_drop_storage_and_value(expr_span, scope, result);
}
@ -445,7 +449,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block = this.limit_capture_mutability(
upvar_expr.span,
upvar_expr.ty,
scope,
scope.temp_lifetime,
block,
arg,
)
@ -705,7 +709,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
&mut self,
mut block: BasicBlock,
value: ExprId,
scope: Option<region::Scope>,
scope: TempLifetime,
outer_source_info: SourceInfo,
) -> BlockAnd<Rvalue<'tcx>> {
let this = self;

View file

@ -2,7 +2,6 @@
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir::HirId;
use rustc_middle::middle::region;
use rustc_middle::middle::region::{Scope, ScopeData};
use rustc_middle::mir::*;
use rustc_middle::thir::*;
@ -17,7 +16,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
pub(crate) fn as_temp(
&mut self,
block: BasicBlock,
temp_lifetime: Option<region::Scope>,
temp_lifetime: TempLifetime,
expr_id: ExprId,
mutability: Mutability,
) -> BlockAnd<Local> {
@ -31,7 +30,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
fn as_temp_inner(
&mut self,
mut block: BasicBlock,
temp_lifetime: Option<region::Scope>,
temp_lifetime: TempLifetime,
expr_id: ExprId,
mutability: Mutability,
) -> BlockAnd<Local> {
@ -47,8 +46,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
let expr_ty = expr.ty;
let deduplicate_temps =
this.fixed_temps_scope.is_some() && this.fixed_temps_scope == temp_lifetime;
let deduplicate_temps = this.fixed_temps_scope.is_some()
&& this.fixed_temps_scope == temp_lifetime.temp_lifetime;
let temp = if deduplicate_temps && let Some(temp_index) = this.fixed_temps.get(&expr_id) {
*temp_index
} else {
@ -76,7 +75,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
LocalInfo::BlockTailTemp(tail_info)
}
_ if let Some(Scope { data: ScopeData::IfThenRescope, id }) = temp_lifetime => {
_ if let Some(Scope { data: ScopeData::IfThenRescope, id }) =
temp_lifetime.temp_lifetime =>
{
LocalInfo::IfThenRescopeTemp {
if_then: HirId { owner: this.hir_id.owner, local_id: id },
}
@ -117,7 +118,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// Anything with a shorter lifetime (e.g the `&foo()` in
// `bar(&foo())` or anything within a block will keep the
// regular drops just like runtime code.
if let Some(temp_lifetime) = temp_lifetime {
if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Storage);
}
}
@ -125,10 +126,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
block = this.expr_into_dest(temp_place, block, expr_id).into_block();
if let Some(temp_lifetime) = temp_lifetime {
if let Some(temp_lifetime) = temp_lifetime.temp_lifetime {
this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Value);
}
if let Some(backwards_incompatible) = temp_lifetime.backwards_incompatible {
this.schedule_backwards_incompatible_drop(expr_span, backwards_incompatible, temp);
}
block.and(temp)
}
}

View file

@ -139,7 +139,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// (#66975) Source could be a const of type `!`, so has to
// exist in the generated MIR.
unpack!(
block = this.as_temp(block, Some(this.local_scope()), source, Mutability::Mut)
block =
this.as_temp(block, this.local_temp_lifetime(), source, Mutability::Mut)
);
// This is an optimization. If the expression was a call then we already have an
@ -321,7 +322,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let is_union = adt_def.is_union();
let active_field_index = is_union.then(|| fields[0].name);
let scope = this.local_scope();
let scope = this.local_temp_lifetime();
// first process the set of fields that were provided
// (evaluating them in order given by user)
@ -333,7 +334,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
unpack!(
block = this.as_operand(
block,
Some(scope),
scope,
f.expr,
LocalInfo::AggregateTemp,
NeedsTemporary::Maybe,
@ -548,15 +549,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}
ExprKind::Yield { value } => {
let scope = this.local_scope();
let scope = this.local_temp_lifetime();
let value = unpack!(
block = this.as_operand(
block,
Some(scope),
value,
LocalInfo::Boring,
NeedsTemporary::No
)
block =
this.as_operand(block, scope, value, LocalInfo::Boring, NeedsTemporary::No)
);
let resume = this.cfg.start_new_block();
this.cfg.terminate(block, source_info, TerminatorKind::Yield {

View file

@ -172,8 +172,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
None
};
let temp =
unpack!(block = this.as_temp(block, statement_scope, expr_id, Mutability::Not));
let temp = unpack!(
block = this.as_temp(
block,
TempLifetime {
temp_lifetime: statement_scope,
backwards_incompatible: None
},
expr_id,
Mutability::Not
)
);
if let Some(span) = adjusted_span {
this.local_decls[temp].source_info.span = span;

View file

@ -202,8 +202,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
// Increment the decision depth, in case we encounter boolean expressions
// further down.
this.mcdc_increment_depth_if_enabled();
let place =
unpack!(block = this.as_temp(block, Some(temp_scope), expr_id, mutability));
let place = unpack!(
block = this.as_temp(
block,
TempLifetime {
temp_lifetime: Some(temp_scope),
backwards_incompatible: None
},
expr_id,
mutability
)
);
this.mcdc_decrement_depth_if_enabled();
let operand = Operand::Move(Place::from(place));

View file

@ -89,7 +89,7 @@ use rustc_index::{IndexSlice, IndexVec};
use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::thir::{ExprId, LintLevel};
use rustc_middle::{bug, span_bug};
use rustc_middle::{bug, span_bug, ty};
use rustc_session::lint::Level;
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Span};
@ -151,6 +151,9 @@ struct DropData {
/// Whether this is a value Drop or a StorageDead.
kind: DropKind,
/// Whether this is a backwards-incompatible drop lint
backwards_incompatible_lint: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -274,8 +277,12 @@ impl DropTree {
// represents the block in the tree that should be jumped to once all
// of the required drops have been performed.
let fake_source_info = SourceInfo::outermost(DUMMY_SP);
let fake_data =
DropData { source_info: fake_source_info, local: Local::MAX, kind: DropKind::Storage };
let fake_data = DropData {
source_info: fake_source_info,
local: Local::MAX,
kind: DropKind::Storage,
backwards_incompatible_lint: false,
};
let drops = IndexVec::from_raw(vec![DropNode { data: fake_data, next: DropIdx::MAX }]);
Self { drops, entry_points: Vec::new(), existing_drops_map: FxHashMap::default() }
}
@ -763,7 +770,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
let local =
place.as_local().unwrap_or_else(|| bug!("projection in tail call args"));
Some(DropData { source_info, local, kind: DropKind::Value })
Some(DropData {
source_info,
local,
kind: DropKind::Value,
backwards_incompatible_lint: false,
})
}
Operand::Constant(_) => None,
})
@ -1019,9 +1031,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
if local.index() <= self.arg_count {
span_bug!(
span,
"`schedule_drop` called with local {:?} and arg_count {}",
"`schedule_drop` called with body argument {:?} \
but its storage does not require a drop",
local,
self.arg_count,
)
}
false
@ -1089,6 +1101,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
source_info: SourceInfo { span: scope_end, scope: scope.source_scope },
local,
kind: drop_kind,
backwards_incompatible_lint: false,
});
return;
@ -1098,6 +1111,45 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
span_bug!(span, "region scope {:?} not in scope to drop {:?}", region_scope, local);
}
/// Schedule emission of a backwards incompatible drop lint hint.
/// Applicable only to temporary values for now.
pub(crate) fn schedule_backwards_incompatible_drop(
&mut self,
span: Span,
region_scope: region::Scope,
local: Local,
) {
if !self.local_decls[local].ty.has_significant_drop(self.tcx, ty::TypingEnv {
typing_mode: ty::TypingMode::non_body_analysis(),
param_env: self.param_env,
}) {
return;
}
for scope in self.scopes.scopes.iter_mut().rev() {
// Since we are inserting linting MIR statement, we have to invalidate the caches
scope.invalidate_cache();
if scope.region_scope == region_scope {
let region_scope_span = region_scope.span(self.tcx, self.region_scope_tree);
let scope_end = self.tcx.sess.source_map().end_point(region_scope_span);
scope.drops.push(DropData {
source_info: SourceInfo { span: scope_end, scope: scope.source_scope },
local,
kind: DropKind::Value,
backwards_incompatible_lint: true,
});
return;
}
}
span_bug!(
span,
"region scope {:?} not in scope to drop {:?} for linting",
region_scope,
local
);
}
/// Indicates that the "local operand" stored in `local` is
/// *moved* at some point during execution (see `local_scope` for
/// more information about what a "local operand" is -- in short,
@ -1378,16 +1430,25 @@ fn build_scope_drops<'tcx>(
continue;
}
unwind_drops.add_entry_point(block, unwind_to);
let next = cfg.start_new_block();
cfg.terminate(block, source_info, TerminatorKind::Drop {
place: local.into(),
target: next,
unwind: UnwindAction::Continue,
replace: false,
});
block = next;
if drop_data.backwards_incompatible_lint {
cfg.push(block, Statement {
source_info,
kind: StatementKind::BackwardIncompatibleDropHint {
place: Box::new(local.into()),
reason: BackwardIncompatibleDropReason::Edition2024,
},
});
} else {
unwind_drops.add_entry_point(block, unwind_to);
let next = cfg.start_new_block();
cfg.terminate(block, source_info, TerminatorKind::Drop {
place: local.into(),
target: next,
unwind: UnwindAction::Continue,
replace: false,
});
block = next;
}
}
DropKind::Storage => {
if storage_dead_on_unwind {

View file

@ -23,7 +23,6 @@ use tracing::{debug, info, instrument, trace};
use crate::errors;
use crate::thir::cx::Cx;
use crate::thir::cx::region::Scope;
use crate::thir::util::UserAnnotatedTyHelpers;
impl<'tcx> Cx<'tcx> {
@ -240,7 +239,7 @@ impl<'tcx> Cx<'tcx> {
fn mirror_expr_cast(
&mut self,
source: &'tcx hir::Expr<'tcx>,
temp_lifetime: Option<Scope>,
temp_lifetime: TempLifetime,
span: Span,
) -> ExprKind<'tcx> {
let tcx = self.tcx;
@ -325,7 +324,7 @@ impl<'tcx> Cx<'tcx> {
fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> {
let tcx = self.tcx;
let expr_ty = self.typeck_results().expr_ty(expr);
let temp_lifetime =
let (temp_lifetime, backwards_incompatible) =
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
let kind = match expr.kind {
@ -361,7 +360,7 @@ impl<'tcx> Cx<'tcx> {
let arg_tys = args.iter().map(|e| self.typeck_results().expr_ty_adjusted(e));
let tupled_args = Expr {
ty: Ty::new_tup_from_iter(tcx, arg_tys),
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
span: expr.span,
kind: ExprKind::Tuple { fields: self.mirror_exprs(args) },
};
@ -391,7 +390,10 @@ impl<'tcx> Cx<'tcx> {
&& let [value] = args
{
return Expr {
temp_lifetime,
temp_lifetime: TempLifetime {
temp_lifetime,
backwards_incompatible,
},
ty: expr_ty,
span: expr.span,
kind: ExprKind::Box { value: self.mirror_expr(value) },
@ -811,13 +813,13 @@ impl<'tcx> Cx<'tcx> {
},
hir::ExprKind::Loop(body, ..) => {
let block_ty = self.typeck_results().node_type(body.hir_id);
let temp_lifetime = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, body.hir_id.local_id);
let block = self.mirror_block(body);
let body = self.thir.exprs.push(Expr {
ty: block_ty,
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
span: self.thir[block].span,
kind: ExprKind::Block { block },
});
@ -838,13 +840,17 @@ impl<'tcx> Cx<'tcx> {
expr, cast_ty.hir_id, user_ty,
);
let cast = self.mirror_expr_cast(source, temp_lifetime, expr.span);
let cast = self.mirror_expr_cast(
source,
TempLifetime { temp_lifetime, backwards_incompatible },
expr.span,
);
if let Some(user_ty) = user_ty {
// NOTE: Creating a new Expr and wrapping a Cast inside of it may be
// inefficient, revisit this when performance becomes an issue.
let cast_expr = self.thir.exprs.push(Expr {
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: expr_ty,
span: expr.span,
kind: cast,
@ -887,7 +893,12 @@ impl<'tcx> Cx<'tcx> {
hir::ExprKind::Err(_) => unreachable!("cannot lower a `hir::ExprKind::Err` to THIR"),
};
Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind }
Expr {
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: expr_ty,
span: expr.span,
kind,
}
}
fn user_args_applied_to_res(
@ -931,7 +942,7 @@ impl<'tcx> Cx<'tcx> {
span: Span,
overloaded_callee: Option<Ty<'tcx>>,
) -> Expr<'tcx> {
let temp_lifetime =
let (temp_lifetime, backwards_incompatible) =
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
let (ty, user_ty) = match overloaded_callee {
Some(fn_def) => (fn_def, None),
@ -952,7 +963,12 @@ impl<'tcx> Cx<'tcx> {
)
}
};
Expr { temp_lifetime, ty, span, kind: ExprKind::ZstLiteral { user_ty } }
Expr {
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty,
span,
kind: ExprKind::ZstLiteral { user_ty },
}
}
fn convert_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) -> ArmId {
@ -1025,7 +1041,7 @@ impl<'tcx> Cx<'tcx> {
Res::Def(DefKind::Static { .. }, id) => {
// this is &raw for extern static or static mut, and & for other statics
let ty = self.tcx.static_ptr_ty(id, self.typing_env());
let temp_lifetime = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
let kind = if self.tcx.is_thread_local_static(id) {
@ -1035,7 +1051,12 @@ impl<'tcx> Cx<'tcx> {
ExprKind::StaticRef { alloc_id, ty, def_id: id }
};
ExprKind::Deref {
arg: self.thir.exprs.push(Expr { ty, temp_lifetime, span: expr.span, kind }),
arg: self.thir.exprs.push(Expr {
ty,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
span: expr.span,
kind,
}),
}
}
@ -1106,13 +1127,13 @@ impl<'tcx> Cx<'tcx> {
// construct the complete expression `foo()` for the overloaded call,
// which will yield the &T type
let temp_lifetime =
let (temp_lifetime, backwards_incompatible) =
self.rvalue_scopes.temporary_scope(self.region_scope_tree, expr.hir_id.local_id);
let fun = self.method_callee(expr, span, overloaded_callee);
let fun = self.thir.exprs.push(fun);
let fun_ty = self.thir[fun].ty;
let ref_expr = self.thir.exprs.push(Expr {
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: ref_ty,
span,
kind: ExprKind::Call { ty: fun_ty, fun, args, from_hir_call: false, fn_span: span },
@ -1127,7 +1148,7 @@ impl<'tcx> Cx<'tcx> {
closure_expr: &'tcx hir::Expr<'tcx>,
place: HirPlace<'tcx>,
) -> Expr<'tcx> {
let temp_lifetime = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
let var_ty = place.base_ty;
@ -1143,7 +1164,7 @@ impl<'tcx> Cx<'tcx> {
};
let mut captured_place_expr = Expr {
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: var_ty,
span: closure_expr.span,
kind: self.convert_var(var_hir_id),
@ -1168,8 +1189,12 @@ impl<'tcx> Cx<'tcx> {
}
};
captured_place_expr =
Expr { temp_lifetime, ty: proj.ty, span: closure_expr.span, kind };
captured_place_expr = Expr {
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: proj.ty,
span: closure_expr.span,
kind,
};
}
captured_place_expr
@ -1184,7 +1209,7 @@ impl<'tcx> Cx<'tcx> {
let upvar_capture = captured_place.info.capture_kind;
let captured_place_expr =
self.convert_captured_hir_place(closure_expr, captured_place.place.clone());
let temp_lifetime = self
let (temp_lifetime, backwards_incompatible) = self
.rvalue_scopes
.temporary_scope(self.region_scope_tree, closure_expr.hir_id.local_id);
@ -1201,7 +1226,7 @@ impl<'tcx> Cx<'tcx> {
}
};
Expr {
temp_lifetime,
temp_lifetime: TempLifetime { temp_lifetime, backwards_incompatible },
ty: upvar_ty,
span: closure_expr.span,
kind: ExprKind::Borrow {