Skip to content

Commit 9cee22c

Browse files
committed
Display information about captured variable in FnMut error
Fixes #69446 When we encounter a region error involving an `FnMut` closure, we display a specialized error message. However, we currently do not tell the user which upvar was captured. This makes it difficult to determine the cause of the error, especially when the closure is large. This commit records marks constraints involving closure upvars with `ConstraintCategory::ClosureUpvar`. When we decide to 'blame' a `ConstraintCategory::Return`, we additionall store the captured upvar if we found a `ConstraintCategory::ClosureUpvar` in the path. When generating an error message, we point to relevant spans if we have closure upvar information available. We further customize the message if an `async` closure is being returned, to make it clear that the captured variable is being returned indirectly.
1 parent f93bb2a commit 9cee22c

17 files changed

+203
-58
lines changed

src/libcore/future/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return>
5656
where
5757
T: Generator<ResumeTy, Yield = ()>,
5858
{
59+
#[rustc_diagnostic_item = "gen_future"]
5960
struct GenFuture<T: Generator<ResumeTy, Yield = ()>>(T);
6061

6162
// We rely on the fact that async/await futures are immovable in order to create

src/librustc_middle/mir/query.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ pub struct ClosureOutlivesRequirement<'tcx> {
171171
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
172172
#[derive(RustcEncodable, RustcDecodable, HashStable)]
173173
pub enum ConstraintCategory {
174-
Return,
174+
Return(ReturnConstraint),
175175
Yield,
176176
UseAsConst,
177177
UseAsStatic,
@@ -187,6 +187,7 @@ pub enum ConstraintCategory {
187187
SizedBound,
188188
Assignment,
189189
OpaqueType,
190+
ClosureUpvar(hir::HirId),
190191

191192
/// A "boring" constraint (caused by the given location) is one that
192193
/// the user probably doesn't want to see described in diagnostics,
@@ -204,6 +205,13 @@ pub enum ConstraintCategory {
204205
Internal,
205206
}
206207

208+
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
209+
#[derive(RustcEncodable, RustcDecodable, HashStable)]
210+
pub enum ReturnConstraint {
211+
Normal,
212+
ClosureUpvar(hir::HirId),
213+
}
214+
207215
/// The subject of a `ClosureOutlivesRequirement` -- that is, the thing
208216
/// that must outlive some region.
209217
#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable, HashStable)]

src/librustc_mir/borrow_check/diagnostics/conflict_errors.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
766766
category:
767767
category
768768
@
769-
(ConstraintCategory::Return
769+
(ConstraintCategory::Return(_)
770770
| ConstraintCategory::CallArgument
771771
| ConstraintCategory::OpaqueType),
772772
from_closure: false,
@@ -1096,7 +1096,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
10961096
opt_place_desc: Option<&String>,
10971097
) -> Option<DiagnosticBuilder<'cx>> {
10981098
let return_kind = match category {
1099-
ConstraintCategory::Return => "return",
1099+
ConstraintCategory::Return(_) => "return",
11001100
ConstraintCategory::Yield => "yield",
11011101
_ => return None,
11021102
};
@@ -1210,7 +1210,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
12101210
);
12111211

12121212
let msg = match category {
1213-
ConstraintCategory::Return | ConstraintCategory::OpaqueType => {
1213+
ConstraintCategory::Return(_) | ConstraintCategory::OpaqueType => {
12141214
format!("{} is returned here", kind)
12151215
}
12161216
ConstraintCategory::CallArgument => {

src/librustc_mir/borrow_check/diagnostics/region_errors.rs

+41-16
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use rustc_infer::infer::{
55
error_reporting::nice_region_error::NiceRegionError,
66
error_reporting::unexpected_hidden_region_diagnostic, NLLRegionVariableOrigin,
77
};
8-
use rustc_middle::mir::ConstraintCategory;
8+
use rustc_middle::mir::{ConstraintCategory, ReturnConstraint};
99
use rustc_middle::ty::{self, RegionVid, Ty};
10-
use rustc_span::symbol::kw;
10+
use rustc_span::symbol::{kw, sym};
1111
use rustc_span::Span;
1212

1313
use crate::util::borrowck_errors;
@@ -26,7 +26,7 @@ impl ConstraintDescription for ConstraintCategory {
2626
// Must end with a space. Allows for empty names to be provided.
2727
match self {
2828
ConstraintCategory::Assignment => "assignment ",
29-
ConstraintCategory::Return => "returning this value ",
29+
ConstraintCategory::Return(_) => "returning this value ",
3030
ConstraintCategory::Yield => "yielding this value ",
3131
ConstraintCategory::UseAsConst => "using this value as a constant ",
3232
ConstraintCategory::UseAsStatic => "using this value as a static ",
@@ -37,6 +37,7 @@ impl ConstraintDescription for ConstraintCategory {
3737
ConstraintCategory::SizedBound => "proving this value is `Sized` ",
3838
ConstraintCategory::CopyBound => "copying this value ",
3939
ConstraintCategory::OpaqueType => "opaque type ",
40+
ConstraintCategory::ClosureUpvar(_) => "closure capture ",
4041
ConstraintCategory::Boring
4142
| ConstraintCategory::BoringNoLocation
4243
| ConstraintCategory::Internal => "",
@@ -306,8 +307,8 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
306307
};
307308

308309
let diag = match (category, fr_is_local, outlived_fr_is_local) {
309-
(ConstraintCategory::Return, true, false) if self.is_closure_fn_mut(fr) => {
310-
self.report_fnmut_error(&errci)
310+
(ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => {
311+
self.report_fnmut_error(&errci, kind)
311312
}
312313
(ConstraintCategory::Assignment, true, false)
313314
| (ConstraintCategory::CallArgument, true, false) => {
@@ -347,7 +348,11 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
347348
/// executing...
348349
/// = note: ...therefore, returned references to captured variables will escape the closure
349350
/// ```
350-
fn report_fnmut_error(&self, errci: &ErrorConstraintInfo) -> DiagnosticBuilder<'tcx> {
351+
fn report_fnmut_error(
352+
&self,
353+
errci: &ErrorConstraintInfo,
354+
kind: ReturnConstraint,
355+
) -> DiagnosticBuilder<'tcx> {
351356
let ErrorConstraintInfo { outlived_fr, span, .. } = errci;
352357

353358
let mut diag = self
@@ -356,19 +361,39 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
356361
.sess
357362
.struct_span_err(*span, "captured variable cannot escape `FnMut` closure body");
358363

359-
// We should check if the return type of this closure is in fact a closure - in that
360-
// case, we can special case the error further.
361-
let return_type_is_closure =
362-
self.regioncx.universal_regions().unnormalized_output_ty.is_closure();
363-
let message = if return_type_is_closure {
364-
"returns a closure that contains a reference to a captured variable, which then \
365-
escapes the closure body"
366-
} else {
367-
"returns a reference to a captured variable which escapes the closure body"
364+
let mut output_ty = self.regioncx.universal_regions().unnormalized_output_ty;
365+
if let ty::Opaque(def_id, _) = output_ty.kind {
366+
output_ty = self.infcx.tcx.type_of(def_id)
367+
};
368+
369+
debug!("report_fnmut_error: output_ty={:?}", output_ty);
370+
371+
let message = match output_ty.kind {
372+
ty::Closure(_, _) => {
373+
"returns a closure that contains a reference to a captured variable, which then \
374+
escapes the closure body"
375+
}
376+
ty::Adt(def, _) if self.infcx.tcx.is_diagnostic_item(sym::gen_future, def.did) => {
377+
"returns an `async` block that contains a reference to a captured variable, which then \
378+
escapes the closure body"
379+
}
380+
_ => "returns a reference to a captured variable which escapes the closure body",
368381
};
369382

370383
diag.span_label(*span, message);
371384

385+
if let ReturnConstraint::ClosureUpvar(upvar) = kind {
386+
let def_id = match self.regioncx.universal_regions().defining_ty {
387+
DefiningTy::Closure(def_id, _) => def_id,
388+
ty @ _ => bug!("unexpected DefiningTy {:?}", ty),
389+
};
390+
391+
let upvar_def_span = self.infcx.tcx.hir().span(upvar);
392+
let upvar_span = self.infcx.tcx.upvars_mentioned(def_id).unwrap()[&upvar].span;
393+
diag.span_label(upvar_def_span, "variable defined here");
394+
diag.span_label(upvar_span, "variable captured here");
395+
}
396+
372397
match self.give_region_a_name(*outlived_fr).unwrap().source {
373398
RegionNameSource::NamedEarlyBoundRegion(fr_span)
374399
| RegionNameSource::NamedFreeRegion(fr_span)
@@ -506,7 +531,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
506531
outlived_fr_name.highlight_region_name(&mut diag);
507532

508533
match (category, outlived_fr_is_local, fr_is_local) {
509-
(ConstraintCategory::Return, true, _) => {
534+
(ConstraintCategory::Return(_), true, _) => {
510535
diag.span_label(
511536
*span,
512537
format!(

src/librustc_mir/borrow_check/mod.rs

+2-24
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ fn do_mir_borrowck<'a, 'tcx>(
218218
&mut flow_inits,
219219
&mdpe.move_data,
220220
&borrow_set,
221+
&upvars,
221222
);
222223

223224
// Dump MIR results into a file, if that is enabled. This let us
@@ -2301,30 +2302,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
23012302
/// be `self` in the current MIR, because that is the only time we directly access the fields
23022303
/// of a closure type.
23032304
pub fn is_upvar_field_projection(&self, place_ref: PlaceRef<'tcx>) -> Option<Field> {
2304-
let mut place_projection = place_ref.projection;
2305-
let mut by_ref = false;
2306-
2307-
if let [proj_base @ .., ProjectionElem::Deref] = place_projection {
2308-
place_projection = proj_base;
2309-
by_ref = true;
2310-
}
2311-
2312-
match place_projection {
2313-
[base @ .., ProjectionElem::Field(field, _ty)] => {
2314-
let tcx = self.infcx.tcx;
2315-
let base_ty = Place::ty_from(place_ref.local, base, self.body(), tcx).ty;
2316-
2317-
if (base_ty.is_closure() || base_ty.is_generator())
2318-
&& (!by_ref || self.upvars[field.index()].by_ref)
2319-
{
2320-
Some(*field)
2321-
} else {
2322-
None
2323-
}
2324-
}
2325-
2326-
_ => None,
2327-
}
2305+
path_utils::is_upvar_field_projection(self.infcx.tcx, &self.upvars, place_ref, self.body())
23282306
}
23292307
}
23302308

src/librustc_mir/borrow_check/nll.rs

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use crate::borrow_check::{
3939
renumber,
4040
type_check::{self, MirTypeckRegionConstraints, MirTypeckResults},
4141
universal_regions::UniversalRegions,
42+
Upvar,
4243
};
4344

4445
crate type PoloniusOutput = Output<RustcFacts>;
@@ -166,6 +167,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
166167
flow_inits: &mut ResultsCursor<'cx, 'tcx, MaybeInitializedPlaces<'cx, 'tcx>>,
167168
move_data: &MoveData<'tcx>,
168169
borrow_set: &BorrowSet<'tcx>,
170+
upvars: &[Upvar],
169171
) -> NllOutput<'tcx> {
170172
let mut all_facts = AllFacts::enabled(infcx.tcx).then_some(AllFacts::default());
171173

@@ -188,6 +190,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
188190
flow_inits,
189191
move_data,
190192
elements,
193+
upvars,
191194
);
192195

193196
if let Some(all_facts) = &mut all_facts {

src/librustc_mir/borrow_check/path_utils.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use crate::borrow_check::borrow_set::{BorrowData, BorrowSet, TwoPhaseActivation};
22
use crate::borrow_check::places_conflict;
33
use crate::borrow_check::AccessDepth;
4+
use crate::borrow_check::Upvar;
45
use crate::dataflow::indexes::BorrowIndex;
56
use rustc_data_structures::graph::dominators::Dominators;
67
use rustc_middle::mir::BorrowKind;
7-
use rustc_middle::mir::{BasicBlock, Body, Location, Place};
8+
use rustc_middle::mir::{BasicBlock, Body, Field, Location, Place, PlaceRef, ProjectionElem};
89
use rustc_middle::ty::TyCtxt;
910

1011
/// Returns `true` if the borrow represented by `kind` is
@@ -135,3 +136,38 @@ pub(super) fn borrow_of_local_data(place: Place<'_>) -> bool {
135136
// Any errors will be caught on the initial borrow
136137
!place.is_indirect()
137138
}
139+
140+
/// If `place` is a field projection, and the field is being projected from a closure type,
141+
/// then returns the index of the field being projected. Note that this closure will always
142+
/// be `self` in the current MIR, because that is the only time we directly access the fields
143+
/// of a closure type.
144+
pub(crate) fn is_upvar_field_projection(
145+
tcx: TyCtxt<'tcx>,
146+
upvars: &[Upvar],
147+
place_ref: PlaceRef<'tcx>,
148+
body: &Body<'tcx>,
149+
) -> Option<Field> {
150+
let mut place_projection = place_ref.projection;
151+
let mut by_ref = false;
152+
153+
if let [proj_base @ .., ProjectionElem::Deref] = place_projection {
154+
place_projection = proj_base;
155+
by_ref = true;
156+
}
157+
158+
match place_projection {
159+
[base @ .., ProjectionElem::Field(field, _ty)] => {
160+
let base_ty = Place::ty_from(place_ref.local, base, body, tcx).ty;
161+
162+
if (base_ty.is_closure() || base_ty.is_generator())
163+
&& (!by_ref || upvars[field.index()].by_ref)
164+
{
165+
Some(*field)
166+
} else {
167+
None
168+
}
169+
}
170+
171+
_ => None,
172+
}
173+
}

src/librustc_mir/borrow_check/region_infer/mod.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound}
1212
use rustc_infer::infer::{InferCtxt, NLLRegionVariableOrigin, RegionVariableOrigin};
1313
use rustc_middle::mir::{
1414
Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegionRequirements,
15-
ConstraintCategory, Local, Location,
15+
ConstraintCategory, Local, Location, ReturnConstraint,
1616
};
1717
use rustc_middle::ty::{self, subst::SubstsRef, RegionVid, Ty, TyCtxt, TypeFoldable};
1818
use rustc_span::Span;
@@ -2017,7 +2017,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
20172017
| ConstraintCategory::BoringNoLocation
20182018
| ConstraintCategory::Internal => false,
20192019
ConstraintCategory::TypeAnnotation
2020-
| ConstraintCategory::Return
2020+
| ConstraintCategory::Return(_)
20212021
| ConstraintCategory::Yield => true,
20222022
_ => constraint_sup_scc != target_scc,
20232023
}
@@ -2042,14 +2042,26 @@ impl<'tcx> RegionInferenceContext<'tcx> {
20422042

20432043
if let Some(i) = best_choice {
20442044
if let Some(next) = categorized_path.get(i + 1) {
2045-
if categorized_path[i].0 == ConstraintCategory::Return
2045+
if matches!(categorized_path[i].0, ConstraintCategory::Return(_))
20462046
&& next.0 == ConstraintCategory::OpaqueType
20472047
{
20482048
// The return expression is being influenced by the return type being
20492049
// impl Trait, point at the return type and not the return expr.
20502050
return *next;
20512051
}
20522052
}
2053+
2054+
if categorized_path[i].0 == ConstraintCategory::Return(ReturnConstraint::Normal) {
2055+
let field = categorized_path.iter().find_map(|p| {
2056+
if let ConstraintCategory::ClosureUpvar(f) = p.0 { Some(f) } else { None }
2057+
});
2058+
2059+
if let Some(field) = field {
2060+
categorized_path[i].0 =
2061+
ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field));
2062+
}
2063+
}
2064+
20532065
return categorized_path[i];
20542066
}
20552067

0 commit comments

Comments
 (0)