Skip to content

Commit 042c837

Browse files
nikomatsakiscuviper
authored andcommitted
rework diagnostic reporting to be more structured
(cherry picked from commit 76bc027)
1 parent 5de0c84 commit 042c837

File tree

10 files changed

+164
-122
lines changed

10 files changed

+164
-122
lines changed

compiler/rustc_typeck/src/check/upvar.rs

Lines changed: 108 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,53 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8888
/// name (i.e: a.b.c)
8989
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9090
enum CapturesInfo {
91+
/// We previously captured all of `x`, but now we capture some sub-path.
9192
CapturingLess { source_expr: Option<hir::HirId>, var_name: String },
93+
//CapturingNothing {
94+
// // where the variable appears in the closure (but is not captured)
95+
// use_span: Span,
96+
//},
9297
}
9398

94-
/// Intermediate format to store information needed to generate migration lint. The tuple
95-
/// contains the hir_id pointing to the use that resulted in the
96-
/// corresponding place being captured, a String which contains the captured value's
97-
/// name (i.e: a.b.c) and a String which contains the reason why migration is needed for that
98-
/// capture
99-
type MigrationNeededForCapture = (Option<hir::HirId>, String, String);
99+
/// Reasons that we might issue a migration warning.
100+
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
101+
struct MigrationWarningReason {
102+
/// When we used to capture `x` in its entirety, we implemented the auto-trait(s)
103+
/// in this vec, but now we don't.
104+
auto_traits: Vec<&'static str>,
105+
106+
/// When we used to capture `x` in its entirety, we would execute some destructors
107+
/// at a different time.
108+
drop_order: bool,
109+
}
110+
111+
impl MigrationWarningReason {
112+
fn migration_message(&self) -> String {
113+
let base = "changes to closure capture in Rust 2021 will affect";
114+
if !self.auto_traits.is_empty() && self.drop_order {
115+
format!("{} drop order and which traits the closure implements", base)
116+
} else if self.drop_order {
117+
format!("{} drop order", base)
118+
} else {
119+
format!("{} which traits the closure implements", base)
120+
}
121+
}
122+
}
123+
124+
/// Intermediate format to store information needed to generate a note in the migration lint.
125+
struct MigrationLintNote {
126+
captures_info: CapturesInfo,
127+
128+
/// reasons why migration is needed for this capture
129+
reason: MigrationWarningReason,
130+
}
100131

101132
/// Intermediate format to store the hir id of the root variable and a HashSet containing
102133
/// information on why the root variable should be fully captured
103-
type MigrationDiagnosticInfo = (hir::HirId, Vec<MigrationNeededForCapture>);
134+
struct NeededMigration {
135+
var_hir_id: hir::HirId,
136+
diagnostics_info: Vec<MigrationLintNote>,
137+
}
104138

105139
struct InferBorrowKindVisitor<'a, 'tcx> {
106140
fcx: &'a FnCtxt<'a, 'tcx>,
@@ -710,47 +744,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
710744
closure_head_span,
711745
|lint| {
712746
let mut diagnostics_builder = lint.build(
713-
format!(
714-
"changes to closure capture in Rust 2021 will affect {}",
715-
reasons
716-
)
717-
.as_str(),
747+
&reasons.migration_message(),
718748
);
719-
for (var_hir_id, diagnostics_info) in need_migrations.iter() {
749+
for NeededMigration { var_hir_id, diagnostics_info } in &need_migrations {
720750
// Labels all the usage of the captured variable and why they are responsible
721751
// for migration being needed
722-
for (captured_hir_id, captured_name, reasons) in diagnostics_info.iter() {
723-
if let Some(captured_hir_id) = captured_hir_id {
724-
let cause_span = self.tcx.hir().span(*captured_hir_id);
725-
diagnostics_builder.span_label(cause_span, format!("in Rust 2018, this closure captures all of `{}`, but in Rust 2021, it will only capture `{}`",
726-
self.tcx.hir().name(*var_hir_id),
727-
captured_name,
728-
));
752+
for lint_note in diagnostics_info.iter() {
753+
match &lint_note.captures_info {
754+
CapturesInfo::CapturingLess { source_expr: Some(capture_expr_id), var_name: captured_name } => {
755+
let cause_span = self.tcx.hir().span(*capture_expr_id);
756+
diagnostics_builder.span_label(cause_span, format!("in Rust 2018, this closure captures all of `{}`, but in Rust 2021, it will only capture `{}`",
757+
self.tcx.hir().name(*var_hir_id),
758+
captured_name,
759+
));
760+
}
761+
_ => { }
729762
}
730763

731764
// Add a label pointing to where a captured variable affected by drop order
732765
// is dropped
733-
if reasons.contains("drop order") {
766+
if lint_note.reason.drop_order {
734767
let drop_location_span = drop_location_span(self.tcx, &closure_hir_id);
735768

736-
diagnostics_builder.span_label(drop_location_span, format!("in Rust 2018, `{}` is dropped here, but in Rust 2021, only `{}` will be dropped here as part of the closure",
737-
self.tcx.hir().name(*var_hir_id),
738-
captured_name,
739-
));
769+
match &lint_note.captures_info {
770+
CapturesInfo::CapturingLess { var_name: captured_name, .. } => {
771+
diagnostics_builder.span_label(drop_location_span, format!("in Rust 2018, `{}` is dropped here, but in Rust 2021, only `{}` will be dropped here as part of the closure",
772+
self.tcx.hir().name(*var_hir_id),
773+
captured_name,
774+
));
775+
}
776+
}
740777
}
741778

742779
// Add a label explaining why a closure no longer implements a trait
743-
if reasons.contains("trait implementation") {
744-
let missing_trait = &reasons[..reasons.find("trait implementation").unwrap() - 1];
745-
746-
diagnostics_builder.span_label(closure_head_span, format!("in Rust 2018, this closure implements {} as `{}` implements {}, but in Rust 2021, this closure will no longer implement {} as `{}` does not implement {}",
747-
missing_trait,
748-
self.tcx.hir().name(*var_hir_id),
749-
missing_trait,
750-
missing_trait,
751-
captured_name,
752-
missing_trait,
753-
));
780+
for &missing_trait in &lint_note.reason.auto_traits {
781+
// not capturing something anymore cannot cause a trait to fail to be implemented:
782+
match &lint_note.captures_info {
783+
CapturesInfo::CapturingLess { var_name: captured_name, .. } => {
784+
diagnostics_builder.span_label(closure_head_span, format!("in Rust 2018, this closure implements {missing_trait} as `{x}` implements {missing_trait}, but in Rust 2021, this closure will no longer implement {missing_trait} as `{p}` does not implement {missing_trait}",
785+
missing_trait = missing_trait,
786+
x = self.tcx.hir().name(*var_hir_id),
787+
p = captured_name,
788+
));
789+
}
790+
}
754791
}
755792
}
756793
}
@@ -843,25 +880,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
843880
/// Combines all the reasons for 2229 migrations
844881
fn compute_2229_migrations_reasons(
845882
&self,
846-
auto_trait_reasons: FxHashSet<&str>,
847-
drop_reason: bool,
848-
) -> String {
849-
let mut reasons = String::new();
850-
851-
if !auto_trait_reasons.is_empty() {
852-
reasons = format!(
853-
"{} trait implementation for closure",
854-
auto_trait_reasons.clone().into_iter().collect::<Vec<&str>>().join(", ")
855-
);
856-
}
883+
auto_trait_reasons: FxHashSet<&'static str>,
884+
drop_order: bool,
885+
) -> MigrationWarningReason {
886+
let mut reasons = MigrationWarningReason::default();
857887

858-
if !auto_trait_reasons.is_empty() && drop_reason {
859-
reasons = format!("{} and ", reasons);
888+
for auto_trait in auto_trait_reasons {
889+
reasons.auto_traits.push(auto_trait);
860890
}
861891

862-
if drop_reason {
863-
reasons = format!("{}drop order", reasons);
864-
}
892+
reasons.drop_order = drop_order;
865893

866894
reasons
867895
}
@@ -877,7 +905,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
877905
min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>,
878906
var_hir_id: hir::HirId,
879907
closure_clause: hir::CaptureBy,
880-
) -> Option<FxHashMap<CapturesInfo, FxHashSet<&str>>> {
908+
) -> Option<FxHashMap<CapturesInfo, FxHashSet<&'static str>>> {
881909
let auto_traits_def_id = vec![
882910
self.tcx.lang_items().clone_trait(),
883911
self.tcx.lang_items().sync_trait(),
@@ -1026,7 +1054,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10261054

10271055
match closure_clause {
10281056
// Only migrate if closure is a move closure
1029-
hir::CaptureBy::Value => return Some(FxHashSet::default()),
1057+
hir::CaptureBy::Value => {
1058+
let diagnostics_info = FxHashSet::default();
1059+
//diagnostics_info.insert(CapturesInfo::CapturingNothing);
1060+
//let upvars = self.tcx.upvars_mentioned(closure_def_id).expect("must be an upvar");
1061+
//let _span = upvars[&var_hir_id];
1062+
return Some(diagnostics_info);
1063+
}
10301064
hir::CaptureBy::Ref => {}
10311065
}
10321066

@@ -1099,11 +1133,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10991133
closure_span: Span,
11001134
closure_clause: hir::CaptureBy,
11011135
min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>,
1102-
) -> (Vec<MigrationDiagnosticInfo>, String) {
1136+
) -> (Vec<NeededMigration>, MigrationWarningReason) {
11031137
let upvars = if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) {
11041138
upvars
11051139
} else {
1106-
return (Vec::new(), format!(""));
1140+
return (Vec::new(), MigrationWarningReason::default());
11071141
};
11081142

11091143
let mut need_migrations = Vec::new();
@@ -1112,7 +1146,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11121146

11131147
// Perform auto-trait analysis
11141148
for (&var_hir_id, _) in upvars.iter() {
1115-
let mut responsible_captured_hir_ids = Vec::new();
1149+
let mut diagnostics_info = Vec::new();
11161150

11171151
let auto_trait_diagnostic = if let Some(diagnostics_info) =
11181152
self.compute_2229_migrations_for_trait(min_captures, var_hir_id, closure_clause)
@@ -1144,38 +1178,33 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11441178

11451179
let mut capture_diagnostic = capture_diagnostic.into_iter().collect::<Vec<_>>();
11461180
capture_diagnostic.sort();
1147-
for captured_info in capture_diagnostic.iter() {
1181+
for captures_info in capture_diagnostic {
11481182
// Get the auto trait reasons of why migration is needed because of that capture, if there are any
11491183
let capture_trait_reasons =
1150-
if let Some(reasons) = auto_trait_diagnostic.get(captured_info) {
1184+
if let Some(reasons) = auto_trait_diagnostic.get(&captures_info) {
11511185
reasons.clone()
11521186
} else {
11531187
FxHashSet::default()
11541188
};
11551189

11561190
// Check if migration is needed because of drop reorder as a result of that capture
1157-
let capture_drop_reorder_reason = drop_reorder_diagnostic.contains(captured_info);
1191+
let capture_drop_reorder_reason = drop_reorder_diagnostic.contains(&captures_info);
11581192

11591193
// Combine all the reasons of why the root variable should be captured as a result of
11601194
// auto trait implementation issues
11611195
auto_trait_migration_reasons.extend(capture_trait_reasons.clone());
11621196

1163-
match captured_info {
1164-
CapturesInfo::CapturingLess { source_expr, var_name } => {
1165-
responsible_captured_hir_ids.push((
1166-
*source_expr,
1167-
var_name.clone(),
1168-
self.compute_2229_migrations_reasons(
1169-
capture_trait_reasons,
1170-
capture_drop_reorder_reason,
1171-
),
1172-
));
1173-
}
1174-
}
1197+
diagnostics_info.push(MigrationLintNote {
1198+
captures_info,
1199+
reason: self.compute_2229_migrations_reasons(
1200+
capture_trait_reasons,
1201+
capture_drop_reorder_reason,
1202+
),
1203+
});
11751204
}
11761205

1177-
if !capture_diagnostic.is_empty() {
1178-
need_migrations.push((var_hir_id, responsible_captured_hir_ids));
1206+
if !diagnostics_info.is_empty() {
1207+
need_migrations.push(NeededMigration { var_hir_id, diagnostics_info });
11791208
}
11801209
}
11811210
(
@@ -2138,10 +2167,12 @@ fn should_do_rust_2021_incompatible_closure_captures_analysis(
21382167
/// - s2: Comma separated names of the variables being migrated.
21392168
fn migration_suggestion_for_2229(
21402169
tcx: TyCtxt<'_>,
2141-
need_migrations: &Vec<MigrationDiagnosticInfo>,
2170+
need_migrations: &Vec<NeededMigration>,
21422171
) -> (String, String) {
2143-
let need_migrations_variables =
2144-
need_migrations.iter().map(|(v, _)| var_name(tcx, *v)).collect::<Vec<_>>();
2172+
let need_migrations_variables = need_migrations
2173+
.iter()
2174+
.map(|NeededMigration { var_hir_id: v, .. }| var_name(tcx, *v))
2175+
.collect::<Vec<_>>();
21452176

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

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn test_send_trait() {
2020
let mut f = 10;
2121
let fptr = SendPointer(&mut f as *mut i32);
2222
thread::spawn(move || { let _ = &fptr; unsafe {
23-
//~^ ERROR: `Send` trait implementation for closure
23+
//~^ ERROR: changes to closure capture
2424
//~| NOTE: in Rust 2018, this closure implements `Send` as `fptr` implements `Send`, but in Rust 2021, this closure will no longer implement `Send` as `fptr.0` does not implement `Send`
2525
//~| NOTE: for more information, see
2626
//~| HELP: add a dummy let to cause `fptr` to be fully captured
@@ -40,8 +40,9 @@ fn test_sync_trait() {
4040
let f = CustomInt(&mut f as *mut i32);
4141
let fptr = SyncPointer(f);
4242
thread::spawn(move || { let _ = &fptr; unsafe {
43-
//~^ ERROR: `Sync`, `Send` trait implementation for closure
44-
//~| NOTE: in Rust 2018, this closure implements `Sync`, `Send` as `fptr` implements `Sync`, `Send`, but in Rust 2021, this closure will no longer implement `Sync`, `Send` as `fptr.0.0` does not implement `Sync`, `Send`
43+
//~^ ERROR: changes to closure capture
44+
//~| NOTE: in Rust 2018, this closure implements `Sync` as `fptr` implements `Sync`, but in Rust 2021, this closure will no longer implement `Sync` as `fptr.0.0` does not implement `Sync`
45+
//~| NOTE: in Rust 2018, this closure implements `Send` as `fptr` implements `Send`, but in Rust 2021, this closure will no longer implement `Send` as `fptr.0.0` does not implement `Send`
4546
//~| NOTE: for more information, see
4647
//~| HELP: add a dummy let to cause `fptr` to be fully captured
4748
*fptr.0.0 = 20;
@@ -65,7 +66,7 @@ fn test_clone_trait() {
6566
let f = U(S(Foo(0)), T(0));
6667
let c = || {
6768
let _ = &f;
68-
//~^ ERROR: `Clone` trait implementation for closure and drop order
69+
//~^ ERROR: changes to closure capture in Rust 2021 will affect drop order and which traits the closure implements
6970
//~| NOTE: in Rust 2018, this closure implements `Clone` as `f` implements `Clone`, but in Rust 2021, this closure will no longer implement `Clone` as `f.1` does not implement `Clone`
7071
//~| NOTE: for more information, see
7172
//~| HELP: add a dummy let to cause `f` to be fully captured

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn test_send_trait() {
2020
let mut f = 10;
2121
let fptr = SendPointer(&mut f as *mut i32);
2222
thread::spawn(move || unsafe {
23-
//~^ ERROR: `Send` trait implementation for closure
23+
//~^ ERROR: changes to closure capture
2424
//~| NOTE: in Rust 2018, this closure implements `Send` as `fptr` implements `Send`, but in Rust 2021, this closure will no longer implement `Send` as `fptr.0` does not implement `Send`
2525
//~| NOTE: for more information, see
2626
//~| HELP: add a dummy let to cause `fptr` to be fully captured
@@ -40,8 +40,9 @@ fn test_sync_trait() {
4040
let f = CustomInt(&mut f as *mut i32);
4141
let fptr = SyncPointer(f);
4242
thread::spawn(move || unsafe {
43-
//~^ ERROR: `Sync`, `Send` trait implementation for closure
44-
//~| NOTE: in Rust 2018, this closure implements `Sync`, `Send` as `fptr` implements `Sync`, `Send`, but in Rust 2021, this closure will no longer implement `Sync`, `Send` as `fptr.0.0` does not implement `Sync`, `Send`
43+
//~^ ERROR: changes to closure capture
44+
//~| NOTE: in Rust 2018, this closure implements `Sync` as `fptr` implements `Sync`, but in Rust 2021, this closure will no longer implement `Sync` as `fptr.0.0` does not implement `Sync`
45+
//~| NOTE: in Rust 2018, this closure implements `Send` as `fptr` implements `Send`, but in Rust 2021, this closure will no longer implement `Send` as `fptr.0.0` does not implement `Send`
4546
//~| NOTE: for more information, see
4647
//~| HELP: add a dummy let to cause `fptr` to be fully captured
4748
*fptr.0.0 = 20;
@@ -64,7 +65,7 @@ impl Clone for U {
6465
fn test_clone_trait() {
6566
let f = U(S(Foo(0)), T(0));
6667
let c = || {
67-
//~^ ERROR: `Clone` trait implementation for closure and drop order
68+
//~^ ERROR: changes to closure capture in Rust 2021 will affect drop order and which traits the closure implements
6869
//~| NOTE: in Rust 2018, this closure implements `Clone` as `f` implements `Clone`, but in Rust 2021, this closure will no longer implement `Clone` as `f.1` does not implement `Clone`
6970
//~| NOTE: for more information, see
7071
//~| HELP: add a dummy let to cause `f` to be fully captured

0 commit comments

Comments
 (0)