where
T: Generator<ResumeTy, Yield = ()>,
{
+ #[rustc_diagnostic_item = "gen_future"]
struct GenFuture<T: Generator<ResumeTy, Yield = ()>>(T);
// We rely on the fact that async/await futures are immovable in order to create
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[derive(RustcEncodable, RustcDecodable, HashStable)]
pub enum ConstraintCategory {
- Return,
+ Return(ReturnConstraint),
Yield,
UseAsConst,
UseAsStatic,
SizedBound,
Assignment,
OpaqueType,
+ ClosureUpvar(hir::HirId),
/// A "boring" constraint (caused by the given location) is one that
/// the user probably doesn't want to see described in diagnostics,
Internal,
}
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+#[derive(RustcEncodable, RustcDecodable, HashStable)]
+pub enum ReturnConstraint {
+ Normal,
+ ClosureUpvar(hir::HirId),
+}
+
/// The subject of a `ClosureOutlivesRequirement` -- that is, the thing
/// that must outlive some region.
#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable, HashStable)]
category:
category
@
- (ConstraintCategory::Return
+ (ConstraintCategory::Return(_)
| ConstraintCategory::CallArgument
| ConstraintCategory::OpaqueType),
from_closure: false,
opt_place_desc: Option<&String>,
) -> Option<DiagnosticBuilder<'cx>> {
let return_kind = match category {
- ConstraintCategory::Return => "return",
+ ConstraintCategory::Return(_) => "return",
ConstraintCategory::Yield => "yield",
_ => return None,
};
);
let msg = match category {
- ConstraintCategory::Return | ConstraintCategory::OpaqueType => {
+ ConstraintCategory::Return(_) | ConstraintCategory::OpaqueType => {
format!("{} is returned here", kind)
}
ConstraintCategory::CallArgument => {
error_reporting::nice_region_error::NiceRegionError,
error_reporting::unexpected_hidden_region_diagnostic, NLLRegionVariableOrigin,
};
-use rustc_middle::mir::ConstraintCategory;
+use rustc_middle::mir::{ConstraintCategory, ReturnConstraint};
use rustc_middle::ty::{self, RegionVid, Ty};
-use rustc_span::symbol::kw;
+use rustc_span::symbol::{kw, sym};
use rustc_span::Span;
use crate::util::borrowck_errors;
// Must end with a space. Allows for empty names to be provided.
match self {
ConstraintCategory::Assignment => "assignment ",
- ConstraintCategory::Return => "returning this value ",
+ 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::SizedBound => "proving this value is `Sized` ",
ConstraintCategory::CopyBound => "copying this value ",
ConstraintCategory::OpaqueType => "opaque type ",
+ ConstraintCategory::ClosureUpvar(_) => "closure capture ",
ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => "",
};
let diag = match (category, fr_is_local, outlived_fr_is_local) {
- (ConstraintCategory::Return, true, false) if self.is_closure_fn_mut(fr) => {
- self.report_fnmut_error(&errci)
+ (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) => {
/// executing...
/// = note: ...therefore, returned references to captured variables will escape the closure
/// ```
- fn report_fnmut_error(&self, errci: &ErrorConstraintInfo) -> DiagnosticBuilder<'tcx> {
+ fn report_fnmut_error(
+ &self,
+ errci: &ErrorConstraintInfo,
+ kind: ReturnConstraint,
+ ) -> DiagnosticBuilder<'tcx> {
let ErrorConstraintInfo { outlived_fr, span, .. } = errci;
let mut diag = self
.sess
.struct_span_err(*span, "captured variable cannot escape `FnMut` closure body");
- // We should check if the return type of this closure is in fact a closure - in that
- // case, we can special case the error further.
- let return_type_is_closure =
- self.regioncx.universal_regions().unnormalized_output_ty.is_closure();
- let message = if return_type_is_closure {
- "returns a closure that contains a reference to a captured variable, which then \
- escapes the closure body"
- } else {
- "returns a reference to a captured variable which escapes the 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) = kind {
+ let def_id = match self.regioncx.universal_regions().defining_ty {
+ DefiningTy::Closure(def_id, _) => def_id,
+ ty @ _ => bug!("unexpected DefiningTy {:?}", ty),
+ };
+
+ let upvar_def_span = self.infcx.tcx.hir().span(upvar);
+ let upvar_span = self.infcx.tcx.upvars_mentioned(def_id).unwrap()[&upvar].span;
+ diag.span_label(upvar_def_span, "variable defined here");
+ diag.span_label(upvar_span, "variable captured here");
+ }
+
match self.give_region_a_name(*outlived_fr).unwrap().source {
RegionNameSource::NamedEarlyBoundRegion(fr_span)
| RegionNameSource::NamedFreeRegion(fr_span)
outlived_fr_name.highlight_region_name(&mut diag);
match (category, outlived_fr_is_local, fr_is_local) {
- (ConstraintCategory::Return, true, _) => {
+ (ConstraintCategory::Return(_), true, _) => {
diag.span_label(
*span,
format!(
&mut flow_inits,
&mdpe.move_data,
&borrow_set,
+ &upvars,
);
// Dump MIR results into a file, if that is enabled. This let us
/// be `self` in the current MIR, because that is the only time we directly access the fields
/// of a closure type.
pub fn is_upvar_field_projection(&self, place_ref: PlaceRef<'tcx>) -> Option<Field> {
- let mut place_projection = place_ref.projection;
- let mut by_ref = false;
-
- if let [proj_base @ .., ProjectionElem::Deref] = place_projection {
- place_projection = proj_base;
- by_ref = true;
- }
-
- match place_projection {
- [base @ .., ProjectionElem::Field(field, _ty)] => {
- let tcx = self.infcx.tcx;
- let base_ty = Place::ty_from(place_ref.local, base, self.body(), tcx).ty;
-
- if (base_ty.is_closure() || base_ty.is_generator())
- && (!by_ref || self.upvars[field.index()].by_ref)
- {
- Some(*field)
- } else {
- None
- }
- }
-
- _ => None,
- }
+ path_utils::is_upvar_field_projection(self.infcx.tcx, &self.upvars, place_ref, self.body())
}
}
renumber,
type_check::{self, MirTypeckRegionConstraints, MirTypeckResults},
universal_regions::UniversalRegions,
+ Upvar,
};
crate type PoloniusOutput = Output<RustcFacts>;
flow_inits: &mut ResultsCursor<'cx, 'tcx, MaybeInitializedPlaces<'cx, 'tcx>>,
move_data: &MoveData<'tcx>,
borrow_set: &BorrowSet<'tcx>,
+ upvars: &[Upvar],
) -> NllOutput<'tcx> {
let mut all_facts = AllFacts::enabled(infcx.tcx).then_some(AllFacts::default());
flow_inits,
move_data,
elements,
+ upvars,
);
if let Some(all_facts) = &mut all_facts {
use crate::borrow_check::borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation};
use crate::borrow_check::places_conflict;
use crate::borrow_check::AccessDepth;
+use crate::borrow_check::Upvar;
use crate::dataflow::indexes::BorrowIndex;
use rustc_data_structures::graph::dominators::Dominators;
use rustc_middle::mir::BorrowKind;
-use rustc_middle::mir::{BasicBlock, Body, Location, Place};
+use rustc_middle::mir::{BasicBlock, Body, Field, Location, Place, PlaceRef, ProjectionElem};
use rustc_middle::ty::TyCtxt;
/// Returns `true` if the borrow represented by `kind` is
// Any errors will be caught on the initial borrow
!place.is_indirect()
}
+
+/// If `place` is a field projection, and the field is being projected from a closure type,
+/// then returns the index of the field being projected. Note that this closure will always
+/// be `self` in the current MIR, because that is the only time we directly access the fields
+/// of a closure type.
+pub(crate) fn is_upvar_field_projection(
+ tcx: TyCtxt<'tcx>,
+ upvars: &[Upvar],
+ place_ref: PlaceRef<'tcx>,
+ body: &Body<'tcx>,
+) -> Option<Field> {
+ let mut place_projection = place_ref.projection;
+ let mut by_ref = false;
+
+ if let [proj_base @ .., ProjectionElem::Deref] = place_projection {
+ place_projection = proj_base;
+ by_ref = true;
+ }
+
+ match place_projection {
+ [base @ .., ProjectionElem::Field(field, _ty)] => {
+ let base_ty = Place::ty_from(place_ref.local, base, body, tcx).ty;
+
+ if (base_ty.is_closure() || base_ty.is_generator())
+ && (!by_ref || upvars[field.index()].by_ref)
+ {
+ Some(*field)
+ } else {
+ None
+ }
+ }
+
+ _ => None,
+ }
+}
use rustc_infer::infer::{InferCtxt, NLLRegionVariableOrigin, RegionVariableOrigin};
use rustc_middle::mir::{
Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegionRequirements,
- ConstraintCategory, Local, Location,
+ ConstraintCategory, Local, Location, ReturnConstraint,
};
use rustc_middle::ty::{self, subst::SubstsRef, RegionVid, Ty, TyCtxt, TypeFoldable};
use rustc_span::Span;
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => false,
ConstraintCategory::TypeAnnotation
- | ConstraintCategory::Return
+ | ConstraintCategory::Return(_)
| ConstraintCategory::Yield => true,
_ => constraint_sup_scc != target_scc,
}
if let Some(i) = best_choice {
if let Some(next) = categorized_path.get(i + 1) {
- if categorized_path[i].0 == ConstraintCategory::Return
+ if matches!(categorized_path[i].0, ConstraintCategory::Return(_))
&& next.0 == ConstraintCategory::OpaqueType
{
// The return expression is being influenced by the return type being
return *next;
}
}
+
+ if categorized_path[i].0 == ConstraintCategory::Return(ReturnConstraint::Normal) {
+ let field = categorized_path.iter().find_map(|p| {
+ if let ConstraintCategory::ClosureUpvar(f) = p.0 { Some(f) } else { None }
+ });
+
+ if let Some(field) = field {
+ categorized_path[i].0 =
+ ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field));
+ }
+ }
+
return categorized_path[i];
}
location::LocationTable,
member_constraints::MemberConstraintSet,
nll::ToRegionVid,
+ path_utils,
region_infer::values::{
LivenessValues, PlaceholderIndex, PlaceholderIndices, RegionValueElements,
},
renumber,
type_check::free_region_relations::{CreateResult, UniversalRegionRelations},
universal_regions::{DefiningTy, UniversalRegions},
+ Upvar,
};
macro_rules! span_mirbug {
flow_inits: &mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
move_data: &MoveData<'tcx>,
elements: &Rc<RegionValueElements>,
+ upvars: &[Upvar],
) -> MirTypeckResults<'tcx> {
let implicit_region_bound = infcx.tcx.mk_region(ty::ReVar(universal_regions.fr_fn_body));
let mut constraints = MirTypeckRegionConstraints {
borrow_set,
all_facts,
constraints: &mut constraints,
+ upvars,
};
let opaque_type_values = type_check_internal(
for constraint in constraints.outlives().iter() {
let mut constraint = *constraint;
constraint.locations = locations;
- if let ConstraintCategory::Return
+ if let ConstraintCategory::Return(_)
| ConstraintCategory::UseAsConst
| ConstraintCategory::UseAsStatic = constraint.category
{
all_facts: &'a mut Option<AllFacts>,
borrow_set: &'a BorrowSet<'tcx>,
constraints: &'a mut MirTypeckRegionConstraints<'tcx>,
+ upvars: &'a [Upvar],
}
crate struct MirTypeckResults<'tcx> {
ConstraintCategory::UseAsConst
}
} else {
- ConstraintCategory::Return
+ ConstraintCategory::Return(ReturnConstraint::Normal)
}
}
Some(l) if !body.local_decls[l].is_user_variable() => {
ConstraintCategory::UseAsConst
}
} else {
- ConstraintCategory::Return
+ ConstraintCategory::Return(ReturnConstraint::Normal)
}
}
Some(l) if !body.local_decls[l].is_user_variable() => {
);
let mut cursor = borrowed_place.projection.as_ref();
+ let tcx = self.infcx.tcx;
+ let field = path_utils::is_upvar_field_projection(
+ tcx,
+ &self.borrowck_context.upvars,
+ borrowed_place.as_ref(),
+ body,
+ );
+ let category = if let Some(field) = field {
+ ConstraintCategory::ClosureUpvar(self.borrowck_context.upvars[field.index()].var_hir_id)
+ } else {
+ ConstraintCategory::Boring
+ };
+
while let [proj_base @ .., elem] = cursor {
cursor = proj_base;
match elem {
ProjectionElem::Deref => {
- let tcx = self.infcx.tcx;
let base_ty = Place::ty_from(borrowed_place.local, proj_base, body, tcx).ty;
debug!("add_reborrow_constraint - base_ty = {:?}", base_ty);
sup: ref_region.to_region_vid(),
sub: borrow_region.to_region_vid(),
locations: location.to_locations(),
- category: ConstraintCategory::Boring,
+ category,
});
match mutbl {
--- /dev/null
+// Regression test for issue #69446 - we should display
+// which variable is captured
+// edition:2018
+
+use core::future::Future;
+
+struct Foo;
+impl Foo {
+ fn foo(&mut self) {}
+}
+
+async fn bar<T>(_: impl FnMut() -> T)
+where
+ T: Future<Output = ()>,
+{}
+
+fn main() {
+ let mut x = Foo;
+ bar(move || async { //~ ERROR captured
+ x.foo();
+ });
+}
--- /dev/null
+error: captured variable cannot escape `FnMut` closure body
+ --> $DIR/issue-69446-fnmut-capture.rs:19:17
+ |
+LL | let mut x = Foo;
+ | ----- variable defined here
+LL | bar(move || async {
+ | _______________-_^
+ | | |
+ | | inferred to be a `FnMut` closure
+LL | | x.foo();
+ | | - variable captured here
+LL | | });
+ | |_____^ returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
+ |
+ = note: `FnMut` closures only have access to their captured variables while they are executing...
+ = note: ...therefore, they cannot allow references to captured variables to escape
+
+error: aborting due to previous error
+
error: captured variable cannot escape `FnMut` closure body
--> $DIR/borrowck-describe-lvalue.rs:264:16
|
+LL | let mut x = 0;
+ | ----- variable defined here
LL | || {
| - inferred to be a `FnMut` closure
LL | / || {
LL | | let y = &mut x;
+ | | - variable captured here
LL | | &mut x;
LL | | *y = 1;
LL | | drop(y);
error: captured variable cannot escape `FnMut` closure body
--> $DIR/issue-40510-1.rs:7:9
|
+LL | let mut x: Box<()> = Box::new(());
+ | ----- variable defined here
+LL |
LL | || {
| - inferred to be a `FnMut` closure
LL | &mut x
- | ^^^^^^ returns a reference to a captured variable which escapes the closure body
+ | ^^^^^-
+ | | |
+ | | variable captured here
+ | returns a reference to a captured variable which escapes the closure body
|
= note: `FnMut` closures only have access to their captured variables while they are executing...
= note: ...therefore, they cannot allow references to captured variables to escape
error: captured variable cannot escape `FnMut` closure body
--> $DIR/issue-40510-3.rs:7:9
|
+LL | let mut x: Vec<()> = Vec::new();
+ | ----- variable defined here
+LL |
LL | || {
| - inferred to be a `FnMut` closure
LL | / || {
LL | | x.push(())
+ | | - variable captured here
LL | | }
| |_________^ returns a closure that contains a reference to a captured variable, which then escapes the closure body
|
error: captured variable cannot escape `FnMut` closure body
--> $DIR/issue-49824.rs:4:9
|
+LL | let mut x = 0;
+ | ----- variable defined here
LL | || {
| - inferred to be a `FnMut` closure
LL | / || {
LL | |
LL | | let _y = &mut x;
+ | | - variable captured here
LL | | }
| |_________^ returns a closure that contains a reference to a captured variable, which then escapes the closure body
|
error: captured variable cannot escape `FnMut` closure body
--> $DIR/issue-53040.rs:3:8
|
+LL | let mut v: Vec<()> = Vec::new();
+ | ----- variable defined here
LL | || &mut v;
- | - ^^^^^^ returns a reference to a captured variable which escapes the closure body
- | |
+ | - ^^^^^-
+ | | | |
+ | | | variable captured here
+ | | returns 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...
error: captured variable cannot escape `FnMut` closure body
--> $DIR/regions-return-ref-to-upvar-issue-17403.rs:7:24
|
+LL | let mut x = 0;
+ | ----- variable defined here
LL | let mut f = || &mut x;
- | - ^^^^^^ returns a reference to a captured variable which escapes the closure body
- | |
+ | - ^^^^^-
+ | | | |
+ | | | variable captured here
+ | | returns 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...