Skip to content

Commit 0b7ff96

Browse files
committed
Add note on why the variable is not fully captured
1 parent ee86f96 commit 0b7ff96

16 files changed

+227
-75
lines changed

compiler/rustc_typeck/src/check/upvar.rs

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
510510
)
511511
.as_str(),
512512
);
513+
for (var_hir_id, diagnostics_info) in need_migrations.iter() {
514+
for (captured_hir_id, captured_name) in diagnostics_info.iter() {
515+
if let Some(captured_hir_id) = captured_hir_id {
516+
let cause_span = self.tcx.hir().span(*captured_hir_id);
517+
diagnostics_builder.span_label(cause_span, format!("in Rust 2018, closure captures all of `{}`, but in Rust 2021, it only captures `{}`",
518+
self.tcx.hir().name(*var_hir_id),
519+
captured_name,
520+
));
521+
}
522+
}
523+
}
513524
diagnostics_builder.note("for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/disjoint-capture-in-closures.html>");
514525
let closure_body_span = self.tcx.hir().span(body_id.hir_id);
515526
let (sugg, app) =
@@ -579,13 +590,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
579590
var_hir_id: hir::HirId,
580591
check_trait: Option<DefId>,
581592
closure_clause: hir::CaptureBy,
582-
) -> bool {
593+
) -> Option<(Option<hir::HirId>, String)> {
583594
let root_var_min_capture_list = if let Some(root_var_min_capture_list) =
584595
min_captures.and_then(|m| m.get(&var_hir_id))
585596
{
586597
root_var_min_capture_list
587598
} else {
588-
return false;
599+
return None;
589600
};
590601

591602
let ty = self.infcx.resolve_vars_if_possible(self.node_ty(var_hir_id));
@@ -639,10 +650,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
639650
.unwrap_or(false);
640651

641652
if !obligation_holds_for_capture && obligation_should_hold {
642-
return true;
653+
return Some((capture.info.path_expr_id, capture.to_string(self.tcx)));
643654
}
644655
}
645-
false
656+
None
646657
}
647658

648659
/// Figures out the list of root variables (and their types) that aren't completely
@@ -660,68 +671,75 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
660671
min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>,
661672
var_hir_id: hir::HirId,
662673
closure_clause: hir::CaptureBy,
663-
) -> Option<FxHashSet<&str>> {
674+
) -> Option<(FxHashSet<&str>, FxHashSet<(Option<hir::HirId>, String)>)> {
664675
let tcx = self.infcx.tcx;
665676

666677
// Check whether catpured fields also implement the trait
667678
let mut auto_trait_reasons = FxHashSet::default();
679+
let mut diagnostics_info = FxHashSet::default();
668680

669-
if self.need_2229_migrations_for_trait(
681+
if let Some(info) = self.need_2229_migrations_for_trait(
670682
min_captures,
671683
var_hir_id,
672684
tcx.lang_items().clone_trait(),
673685
closure_clause,
674686
) {
675687
auto_trait_reasons.insert("`Clone`");
688+
diagnostics_info.insert(info);
676689
}
677690

678-
if self.need_2229_migrations_for_trait(
691+
if let Some(info) = self.need_2229_migrations_for_trait(
679692
min_captures,
680693
var_hir_id,
681694
tcx.lang_items().sync_trait(),
682695
closure_clause,
683696
) {
684697
auto_trait_reasons.insert("`Sync`");
698+
diagnostics_info.insert(info);
685699
}
686700

687-
if self.need_2229_migrations_for_trait(
701+
if let Some(info) = self.need_2229_migrations_for_trait(
688702
min_captures,
689703
var_hir_id,
690704
tcx.get_diagnostic_item(sym::send_trait),
691705
closure_clause,
692706
) {
693707
auto_trait_reasons.insert("`Send`");
708+
diagnostics_info.insert(info);
694709
}
695710

696-
if self.need_2229_migrations_for_trait(
711+
if let Some(info) = self.need_2229_migrations_for_trait(
697712
min_captures,
698713
var_hir_id,
699714
tcx.lang_items().unpin_trait(),
700715
closure_clause,
701716
) {
702717
auto_trait_reasons.insert("`Unpin`");
718+
diagnostics_info.insert(info);
703719
}
704720

705-
if self.need_2229_migrations_for_trait(
721+
if let Some(info) = self.need_2229_migrations_for_trait(
706722
min_captures,
707723
var_hir_id,
708724
tcx.get_diagnostic_item(sym::unwind_safe_trait),
709725
closure_clause,
710726
) {
711727
auto_trait_reasons.insert("`UnwindSafe`");
728+
diagnostics_info.insert(info);
712729
}
713730

714-
if self.need_2229_migrations_for_trait(
731+
if let Some(info) = self.need_2229_migrations_for_trait(
715732
min_captures,
716733
var_hir_id,
717734
tcx.get_diagnostic_item(sym::ref_unwind_safe_trait),
718735
closure_clause,
719736
) {
720737
auto_trait_reasons.insert("`RefUnwindSafe`");
738+
diagnostics_info.insert(info);
721739
}
722740

723741
if auto_trait_reasons.len() > 0 {
724-
return Some(auto_trait_reasons);
742+
return Some((auto_trait_reasons, diagnostics_info));
725743
}
726744

727745
return None;
@@ -746,11 +764,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
746764
min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>,
747765
closure_clause: hir::CaptureBy,
748766
var_hir_id: hir::HirId,
749-
) -> bool {
767+
) -> Option<FxHashSet<(Option<hir::HirId>, String)>> {
750768
let ty = self.infcx.resolve_vars_if_possible(self.node_ty(var_hir_id));
751769

752770
if !ty.has_significant_drop(self.tcx, self.tcx.param_env(closure_def_id.expect_local())) {
753-
return false;
771+
return None;
754772
}
755773

756774
let root_var_min_capture_list = if let Some(root_var_min_capture_list) =
@@ -763,21 +781,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
763781

764782
match closure_clause {
765783
// Only migrate if closure is a move closure
766-
hir::CaptureBy::Value => return true,
784+
hir::CaptureBy::Value => return Some(FxHashSet::default()),
767785
hir::CaptureBy::Ref => {}
768786
}
769787

770-
return false;
788+
return None;
771789
};
772790

773-
let projections_list = root_var_min_capture_list
774-
.iter()
775-
.filter_map(|captured_place| match captured_place.info.capture_kind {
791+
let mut projections_list = Vec::new();
792+
let mut diagnostics_info = FxHashSet::default();
793+
794+
for captured_place in root_var_min_capture_list.iter() {
795+
match captured_place.info.capture_kind {
776796
// Only care about captures that are moved into the closure
777-
ty::UpvarCapture::ByValue(..) => Some(captured_place.place.projections.as_slice()),
778-
ty::UpvarCapture::ByRef(..) => None,
779-
})
780-
.collect::<Vec<_>>();
797+
ty::UpvarCapture::ByValue(..) => {
798+
projections_list.push(captured_place.place.projections.as_slice());
799+
diagnostics_info.insert((
800+
captured_place.info.path_expr_id,
801+
captured_place.to_string(self.tcx),
802+
));
803+
}
804+
ty::UpvarCapture::ByRef(..) => {}
805+
}
806+
}
781807

782808
let is_moved = !projections_list.is_empty();
783809

@@ -793,10 +819,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
793819
projections_list,
794820
)
795821
{
796-
return true;
822+
return Some(diagnostics_info);
797823
}
798824

799-
return false;
825+
return None;
800826
}
801827

802828
/// Figures out the list of root variables (and their types) that aren't completely
@@ -820,7 +846,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
820846
closure_span: Span,
821847
closure_clause: hir::CaptureBy,
822848
min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>,
823-
) -> (Vec<hir::HirId>, String) {
849+
) -> (Vec<(hir::HirId, FxHashSet<(Option<hir::HirId>, String)>)>, String) {
824850
let upvars = if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
825851
upvars
826852
} else {
@@ -834,14 +860,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
834860
// Perform auto-trait analysis
835861
for (&var_hir_id, _) in upvars.iter() {
836862
let mut need_migration = false;
837-
if let Some(trait_migration_cause) =
863+
let mut responsible_captured_hir_ids = FxHashSet::default();
864+
865+
if let Some((trait_migration_cause, diagnostics_info)) =
838866
self.compute_2229_migrations_for_trait(min_captures, var_hir_id, closure_clause)
839867
{
840868
need_migration = true;
841869
auto_trait_reasons.extend(trait_migration_cause);
870+
responsible_captured_hir_ids.extend(diagnostics_info);
842871
}
843872

844-
if self.compute_2229_migrations_for_drop(
873+
if let Some(diagnostics_info) = self.compute_2229_migrations_for_drop(
845874
closure_def_id,
846875
closure_span,
847876
min_captures,
@@ -850,10 +879,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
850879
) {
851880
need_migration = true;
852881
drop_reorder_reason = true;
882+
responsible_captured_hir_ids.extend(diagnostics_info);
853883
}
854884

855885
if need_migration {
856-
need_migrations.push(var_hir_id);
886+
need_migrations.push((var_hir_id, responsible_captured_hir_ids));
857887
}
858888
}
859889

@@ -1877,10 +1907,10 @@ fn should_do_rust_2021_incompatible_closure_captures_analysis(
18771907
/// - s2: Comma separated names of the variables being migrated.
18781908
fn migration_suggestion_for_2229(
18791909
tcx: TyCtxt<'_>,
1880-
need_migrations: &Vec<hir::HirId>,
1910+
need_migrations: &Vec<(hir::HirId, FxHashSet<(Option<hir::HirId>, String)>)>,
18811911
) -> (String, String) {
18821912
let need_migrations_variables =
1883-
need_migrations.iter().map(|v| var_name(tcx, *v)).collect::<Vec<_>>();
1913+
need_migrations.iter().map(|(v, _)| var_name(tcx, *v)).collect::<Vec<_>>();
18841914

18851915
let migration_ref_concat =
18861916
need_migrations_variables.iter().map(|v| format!("&{}", v)).collect::<Vec<_>>().join(", ");

src/test/ui/closures/2229_closure_analysis/migrations/auto_traits.stderr

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ LL | thread::spawn(move || unsafe {
66
LL | |
77
LL | |
88
LL | | *fptr.0 = 20;
9+
| | ------- in Rust 2018, closure captures all of `fptr`, but in Rust 2021, it only captures `fptr.0`
910
LL | | });
1011
| |_____^
1112
|
@@ -32,6 +33,7 @@ LL | thread::spawn(move || unsafe {
3233
LL | |
3334
LL | |
3435
LL | | *fptr.0.0 = 20;
36+
| | --------- in Rust 2018, closure captures all of `fptr`, but in Rust 2021, it only captures `fptr.0.0`
3537
LL | | });
3638
| |_____^
3739
|
@@ -53,6 +55,7 @@ LL | let c = || {
5355
LL | |
5456
LL | |
5557
LL | | let f_1 = f.1;
58+
| | --- in Rust 2018, closure captures all of `f`, but in Rust 2021, it only captures `f.1`
5659
LL | | println!("{:?}", f_1.0);
5760
LL | | };
5861
| |_____^

src/test/ui/closures/2229_closure_analysis/migrations/insignificant_drop.fixed

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ fn test1_all_need_migration() {
1818
//~| HELP: add a dummy let to cause `t`, `t1`, `t2` to be fully captured
1919

2020
let _t = t.0;
21+
//~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0`
2122
let _t1 = t1.0;
23+
//~^ NOTE: in Rust 2018, closure captures all of `t1`, but in Rust 2021, it only captures `t1.0`
2224
let _t2 = t2.0;
25+
//~^ NOTE: in Rust 2018, closure captures all of `t2`, but in Rust 2021, it only captures `t2.0`
2326
};
2427

2528
c();
@@ -37,7 +40,9 @@ fn test2_only_precise_paths_need_migration() {
3740
//~| NOTE: for more information, see
3841
//~| HELP: add a dummy let to cause `t`, `t1` to be fully captured
3942
let _t = t.0;
43+
//~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0`
4044
let _t1 = t1.0;
45+
//~^ NOTE: in Rust 2018, closure captures all of `t1`, but in Rust 2021, it only captures `t1.0`
4146
let _t2 = t2;
4247
};
4348

@@ -54,6 +59,7 @@ fn test3_only_by_value_need_migration() {
5459
//~| NOTE: for more information, see
5560
//~| HELP: add a dummy let to cause `t` to be fully captured
5661
let _t = t.0;
62+
//~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0`
5763
println!("{}", t1.1);
5864
};
5965

@@ -73,6 +79,7 @@ fn test4_only_non_copy_types_need_migration() {
7379
//~| NOTE: for more information, see
7480
//~| HELP: add a dummy let to cause `t` to be fully captured
7581
let _t = t.0;
82+
//~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0`
7683
let _t1 = t1.0;
7784
};
7885

@@ -92,6 +99,7 @@ fn test5_only_drop_types_need_migration() {
9299
//~| NOTE: for more information, see
93100
//~| HELP: add a dummy let to cause `t` to be fully captured
94101
let _t = t.0;
102+
//~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0`
95103
let _s = s.0;
96104
};
97105

@@ -108,6 +116,8 @@ fn test6_move_closures_non_copy_types_might_need_migration() {
108116
//~| NOTE: for more information, see
109117
//~| HELP: add a dummy let to cause `t1`, `t` to be fully captured
110118
println!("{} {}", t1.1, t.1);
119+
//~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.1`
120+
//~| NOTE: in Rust 2018, closure captures all of `t1`, but in Rust 2021, it only captures `t1.1`
111121
};
112122

113123
c();
@@ -124,6 +134,7 @@ fn test7_drop_non_drop_aggregate_need_migration() {
124134
//~| NOTE: for more information, see
125135
//~| HELP: add a dummy let to cause `t` to be fully captured
126136
let _t = t.0;
137+
//~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0`
127138
};
128139

129140
c();

0 commit comments

Comments
 (0)