Skip to content

Commit 16a4bb2

Browse files
authored
Rollup merge of rust-lang#80629 - sexxi-goose:migrations_1, r=nikomatsakis
Add lint for 2229 migrations Implements the first for RFC 2229 where we make the decision to migrate a root variable based on if the type of the variable needs Drop and if the root variable would be moved into the closure when the feature isn't enabled. r? `@nikomatsakis`
2 parents 755be93 + 11abaa1 commit 16a4bb2

File tree

11 files changed

+803
-44
lines changed

11 files changed

+803
-44
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5389,7 +5389,7 @@ dependencies = [
53895389
"chrono",
53905390
"lazy_static",
53915391
"matchers",
5392-
"parking_lot 0.11.0",
5392+
"parking_lot 0.9.0",
53935393
"regex",
53945394
"serde",
53955395
"serde_json",

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2968,6 +2968,7 @@ declare_lint_pass! {
29682968
UNSUPPORTED_NAKED_FUNCTIONS,
29692969
MISSING_ABI,
29702970
SEMICOLON_IN_EXPRESSIONS_FROM_MACROS,
2971+
DISJOINT_CAPTURE_DROP_REORDER,
29712972
]
29722973
}
29732974

@@ -2994,6 +2995,51 @@ declare_lint! {
29942995
"detects doc comments that aren't used by rustdoc"
29952996
}
29962997

2998+
declare_lint! {
2999+
/// The `disjoint_capture_drop_reorder` lint detects variables that aren't completely
3000+
/// captured when the feature `capture_disjoint_fields` is enabled and it affects the Drop
3001+
/// order of at least one path starting at this variable.
3002+
///
3003+
/// ### Example
3004+
///
3005+
/// ```rust,compile_fail
3006+
/// # #![deny(disjoint_capture_drop_reorder)]
3007+
/// # #![allow(unused)]
3008+
/// struct FancyInteger(i32);
3009+
///
3010+
/// impl Drop for FancyInteger {
3011+
/// fn drop(&mut self) {
3012+
/// println!("Just dropped {}", self.0);
3013+
/// }
3014+
/// }
3015+
///
3016+
/// struct Point { x: FancyInteger, y: FancyInteger }
3017+
///
3018+
/// fn main() {
3019+
/// let p = Point { x: FancyInteger(10), y: FancyInteger(20) };
3020+
///
3021+
/// let c = || {
3022+
/// let x = p.x;
3023+
/// };
3024+
///
3025+
/// c();
3026+
///
3027+
/// // ... More code ...
3028+
/// }
3029+
/// ```
3030+
///
3031+
/// {{produces}}
3032+
///
3033+
/// ### Explanation
3034+
///
3035+
/// In the above example `p.y` will be dropped at the end of `f` instead of with `c` if
3036+
/// the feature `capture_disjoint_fields` is enabled.
3037+
pub DISJOINT_CAPTURE_DROP_REORDER,
3038+
Allow,
3039+
"Drop reorder because of `capture_disjoint_fields`"
3040+
3041+
}
3042+
29973043
declare_lint_pass!(UnusedDocComment => [UNUSED_DOC_COMMENTS]);
29983044

29993045
declare_lint! {

compiler/rustc_typeck/src/check/upvar.rs

Lines changed: 188 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
//! then mean that all later passes would have to check for these figments
3131
//! and report an error, and it just seems like more mess in the end.)
3232
33+
use super::writeback::Resolver;
3334
use super::FnCtxt;
3435

3536
use crate::expr_use_visitor as euv;
@@ -40,7 +41,9 @@ use rustc_hir::def_id::LocalDefId;
4041
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
4142
use rustc_infer::infer::UpvarRegion;
4243
use rustc_middle::hir::place::{Place, PlaceBase, PlaceWithHirId, ProjectionKind};
44+
use rustc_middle::ty::fold::TypeFoldable;
4345
use rustc_middle::ty::{self, Ty, TyCtxt, UpvarSubsts};
46+
use rustc_session::lint;
4447
use rustc_span::sym;
4548
use rustc_span::{MultiSpan, Span, Symbol};
4649

@@ -55,6 +58,11 @@ enum PlaceAncestryRelation {
5558
Divergent,
5659
}
5760

61+
/// Intermediate format to store a captured `Place` and associated `ty::CaptureInfo`
62+
/// during capture analysis. Information in this map feeds into the minimum capture
63+
/// analysis pass.
64+
type InferredCaptureInformation<'tcx> = FxIndexMap<Place<'tcx>, ty::CaptureInfo<'tcx>>;
65+
5866
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5967
pub fn closure_analyze(&self, body: &'tcx hir::Body<'tcx>) {
6068
InferBorrowKindVisitor { fcx: self }.visit_body(body);
@@ -92,7 +100,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
92100
&self,
93101
closure_hir_id: hir::HirId,
94102
span: Span,
95-
body: &hir::Body<'_>,
103+
body: &'tcx hir::Body<'tcx>,
96104
capture_clause: hir::CaptureBy,
97105
) {
98106
debug!("analyze_closure(id={:?}, body.id={:?})", closure_hir_id, body.id());
@@ -124,28 +132,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
124132

125133
let local_def_id = closure_def_id.expect_local();
126134

127-
let mut capture_information: FxIndexMap<Place<'tcx>, ty::CaptureInfo<'tcx>> =
128-
Default::default();
129-
if !self.tcx.features().capture_disjoint_fields {
130-
if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
131-
for (&var_hir_id, _) in upvars.iter() {
132-
let place = self.place_for_root_variable(local_def_id, var_hir_id);
133-
134-
debug!("seed place {:?}", place);
135-
136-
let upvar_id = ty::UpvarId::new(var_hir_id, local_def_id);
137-
let capture_kind = self.init_capture_kind(capture_clause, upvar_id, span);
138-
let info = ty::CaptureInfo {
139-
capture_kind_expr_id: None,
140-
path_expr_id: None,
141-
capture_kind,
142-
};
143-
144-
capture_information.insert(place, info);
145-
}
146-
}
147-
}
148-
149135
let body_owner_def_id = self.tcx.hir().body_owner_def_id(body.id());
150136
assert_eq!(body_owner_def_id.to_def_id(), closure_def_id);
151137
let mut delegate = InferBorrowKind {
@@ -155,7 +141,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
155141
capture_clause,
156142
current_closure_kind: ty::ClosureKind::LATTICE_BOTTOM,
157143
current_origin: None,
158-
capture_information,
144+
capture_information: Default::default(),
159145
};
160146
euv::ExprUseVisitor::new(
161147
&mut delegate,
@@ -172,6 +158,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
172158
);
173159
self.log_capture_analysis_first_pass(closure_def_id, &delegate.capture_information, span);
174160

161+
self.compute_min_captures(closure_def_id, delegate.capture_information);
162+
163+
let closure_hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id);
164+
if should_do_migration_analysis(self.tcx, closure_hir_id) {
165+
self.perform_2229_migration_anaysis(closure_def_id, capture_clause, span, body);
166+
}
167+
168+
// We now fake capture information for all variables that are mentioned within the closure
169+
// We do this after handling migrations so that min_captures computes before
170+
if !self.tcx.features().capture_disjoint_fields {
171+
let mut capture_information: InferredCaptureInformation<'tcx> = Default::default();
172+
173+
if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
174+
for var_hir_id in upvars.keys() {
175+
let place = self.place_for_root_variable(local_def_id, *var_hir_id);
176+
177+
debug!("seed place {:?}", place);
178+
179+
let upvar_id = ty::UpvarId::new(*var_hir_id, local_def_id);
180+
let capture_kind = self.init_capture_kind(capture_clause, upvar_id, span);
181+
let fake_info = ty::CaptureInfo {
182+
capture_kind_expr_id: None,
183+
path_expr_id: None,
184+
capture_kind,
185+
};
186+
187+
capture_information.insert(place, fake_info);
188+
}
189+
}
190+
191+
// This will update the min captures based on this new fake information.
192+
self.compute_min_captures(closure_def_id, capture_information);
193+
}
194+
175195
if let Some(closure_substs) = infer_kind {
176196
// Unify the (as yet unbound) type variable in the closure
177197
// substs with the kind we inferred.
@@ -197,7 +217,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
197217
}
198218
}
199219

200-
self.compute_min_captures(closure_def_id, delegate);
201220
self.log_closure_min_capture_info(closure_def_id, span);
202221

203222
self.min_captures_to_closure_captures_bridge(closure_def_id);
@@ -344,6 +363,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
344363
/// Places (and corresponding capture kind) that we need to keep track of to support all
345364
/// the required captured paths.
346365
///
366+
///
367+
/// Note: If this function is called multiple times for the same closure, it will update
368+
/// the existing min_capture map that is stored in TypeckResults.
369+
///
347370
/// Eg:
348371
/// ```rust,no_run
349372
/// struct Point { x: i32, y: i32 }
@@ -408,11 +431,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
408431
fn compute_min_captures(
409432
&self,
410433
closure_def_id: DefId,
411-
inferred_info: InferBorrowKind<'_, 'tcx>,
434+
capture_information: InferredCaptureInformation<'tcx>,
412435
) {
413-
let mut root_var_min_capture_list: ty::RootVariableMinCaptureList<'_> = Default::default();
436+
if capture_information.is_empty() {
437+
return;
438+
}
414439

415-
for (place, capture_info) in inferred_info.capture_information.into_iter() {
440+
let mut typeck_results = self.typeck_results.borrow_mut();
441+
442+
let root_var_min_capture_list =
443+
typeck_results.closure_min_captures.entry(closure_def_id).or_insert(Default::default());
444+
445+
for (place, capture_info) in capture_information.into_iter() {
416446
let var_hir_id = match place.base {
417447
PlaceBase::Upvar(upvar_id) => upvar_id.var_path.hir_id,
418448
base => bug!("Expected upvar, found={:?}", base),
@@ -495,15 +525,122 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
495525
}
496526

497527
debug!("For closure={:?}, min_captures={:#?}", closure_def_id, root_var_min_capture_list);
528+
}
498529

499-
if !root_var_min_capture_list.is_empty() {
500-
self.typeck_results
501-
.borrow_mut()
502-
.closure_min_captures
503-
.insert(closure_def_id, root_var_min_capture_list);
530+
/// Perform the migration analysis for RFC 2229, and emit lint
531+
/// `disjoint_capture_drop_reorder` if needed.
532+
fn perform_2229_migration_anaysis(
533+
&self,
534+
closure_def_id: DefId,
535+
capture_clause: hir::CaptureBy,
536+
span: Span,
537+
body: &'tcx hir::Body<'tcx>,
538+
) {
539+
let need_migrations = self.compute_2229_migrations_first_pass(
540+
closure_def_id,
541+
span,
542+
capture_clause,
543+
body,
544+
self.typeck_results.borrow().closure_min_captures.get(&closure_def_id),
545+
);
546+
547+
if !need_migrations.is_empty() {
548+
let need_migrations_hir_id = need_migrations.iter().map(|m| m.0).collect::<Vec<_>>();
549+
550+
let migrations_text = migration_suggestion_for_2229(self.tcx, &need_migrations_hir_id);
551+
552+
let local_def_id = closure_def_id.expect_local();
553+
let closure_hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id);
554+
self.tcx.struct_span_lint_hir(
555+
lint::builtin::DISJOINT_CAPTURE_DROP_REORDER,
556+
closure_hir_id,
557+
span,
558+
|lint| {
559+
let mut diagnostics_builder = lint.build(
560+
"Drop order affected for closure because of `capture_disjoint_fields`",
561+
);
562+
diagnostics_builder.note(&migrations_text);
563+
diagnostics_builder.emit();
564+
},
565+
);
504566
}
505567
}
506568

569+
/// Figures out the list of root variables (and their types) that aren't completely
570+
/// captured by the closure when `capture_disjoint_fields` is enabled and drop order of
571+
/// some path starting at that root variable **might** be affected.
572+
///
573+
/// The output list would include a root variable if:
574+
/// - It would have been moved into the closure when `capture_disjoint_fields` wasn't
575+
/// enabled, **and**
576+
/// - It wasn't completely captured by the closure, **and**
577+
/// - The type of the root variable needs Drop.
578+
fn compute_2229_migrations_first_pass(
579+
&self,
580+
closure_def_id: DefId,
581+
closure_span: Span,
582+
closure_clause: hir::CaptureBy,
583+
body: &'tcx hir::Body<'tcx>,
584+
min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>,
585+
) -> Vec<(hir::HirId, Ty<'tcx>)> {
586+
fn resolve_ty<T: TypeFoldable<'tcx>>(
587+
fcx: &FnCtxt<'_, 'tcx>,
588+
span: Span,
589+
body: &'tcx hir::Body<'tcx>,
590+
ty: T,
591+
) -> T {
592+
let mut resolver = Resolver::new(fcx, &span, body);
593+
ty.fold_with(&mut resolver)
594+
}
595+
596+
let upvars = if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
597+
upvars
598+
} else {
599+
return vec![];
600+
};
601+
602+
let mut need_migrations = Vec::new();
603+
604+
for (&var_hir_id, _) in upvars.iter() {
605+
let ty = resolve_ty(self, closure_span, body, self.node_ty(var_hir_id));
606+
607+
if !ty.needs_drop(self.tcx, self.tcx.param_env(closure_def_id.expect_local())) {
608+
continue;
609+
}
610+
611+
let root_var_min_capture_list = if let Some(root_var_min_capture_list) =
612+
min_captures.and_then(|m| m.get(&var_hir_id))
613+
{
614+
root_var_min_capture_list
615+
} else {
616+
// The upvar is mentioned within the closure but no path starting from it is
617+
// used.
618+
619+
match closure_clause {
620+
// Only migrate if closure is a move closure
621+
hir::CaptureBy::Value => need_migrations.push((var_hir_id, ty)),
622+
623+
hir::CaptureBy::Ref => {}
624+
}
625+
626+
continue;
627+
};
628+
629+
let is_moved = root_var_min_capture_list
630+
.iter()
631+
.any(|capture| matches!(capture.info.capture_kind, ty::UpvarCapture::ByValue(_)));
632+
633+
let is_not_completely_captured =
634+
root_var_min_capture_list.iter().any(|capture| capture.place.projections.len() > 0);
635+
636+
if is_moved && is_not_completely_captured {
637+
need_migrations.push((var_hir_id, ty));
638+
}
639+
}
640+
641+
need_migrations
642+
}
643+
507644
fn init_capture_kind(
508645
&self,
509646
capture_clause: hir::CaptureBy,
@@ -698,9 +835,11 @@ struct InferBorrowKind<'a, 'tcx> {
698835
///
699836
/// For closure `fix_s`, (at a high level) the map contains
700837
///
838+
/// ```
701839
/// Place { V1, [ProjectionKind::Field(Index=0, Variant=0)] } : CaptureKind { E1, ImmutableBorrow }
702840
/// Place { V1, [ProjectionKind::Field(Index=1, Variant=0)] } : CaptureKind { E2, MutableBorrow }
703-
capture_information: FxIndexMap<Place<'tcx>, ty::CaptureInfo<'tcx>>,
841+
/// ```
842+
capture_information: InferredCaptureInformation<'tcx>,
704843
}
705844

706845
impl<'a, 'tcx> InferBorrowKind<'a, 'tcx> {
@@ -1119,6 +1258,21 @@ fn var_name(tcx: TyCtxt<'_>, var_hir_id: hir::HirId) -> Symbol {
11191258
tcx.hir().name(var_hir_id)
11201259
}
11211260

1261+
fn should_do_migration_analysis(tcx: TyCtxt<'_>, closure_id: hir::HirId) -> bool {
1262+
let (level, _) =
1263+
tcx.lint_level_at_node(lint::builtin::DISJOINT_CAPTURE_DROP_REORDER, closure_id);
1264+
1265+
!matches!(level, lint::Level::Allow)
1266+
}
1267+
1268+
fn migration_suggestion_for_2229(tcx: TyCtxt<'_>, need_migrations: &Vec<hir::HirId>) -> String {
1269+
let need_migrations_strings =
1270+
need_migrations.iter().map(|v| format!("{}", var_name(tcx, *v))).collect::<Vec<_>>();
1271+
let migrations_list_concat = need_migrations_strings.join(", ");
1272+
1273+
format!("drop(&({}));", migrations_list_concat)
1274+
}
1275+
11221276
/// Helper function to determine if we need to escalate CaptureKind from
11231277
/// CaptureInfo A to B and returns the escalated CaptureInfo.
11241278
/// (Note: CaptureInfo contains CaptureKind and an expression that led to capture it in that way)

0 commit comments

Comments
 (0)