From ca443729575da9c81e47da80adcbf1b14fa1e09b Mon Sep 17 00:00:00 2001 From: Roxane Date: Thu, 8 Jul 2021 17:04:58 -0400 Subject: [PATCH] Handle multi diagnostics --- compiler/rustc_typeck/src/check/upvar.rs | 354 +++++++++--------- .../migrations/multi_diagnostics.fixed | 138 +++++++ .../migrations/multi_diagnostics.rs | 138 +++++++ .../migrations/multi_diagnostics.stderr | 134 +++++++ .../migrations/precise.fixed | 12 + .../migrations/precise.rs | 12 + .../migrations/precise.stderr | 22 +- 7 files changed, 625 insertions(+), 185 deletions(-) create mode 100644 src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.fixed create mode 100644 src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.rs create mode 100644 src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.stderr diff --git a/compiler/rustc_typeck/src/check/upvar.rs b/compiler/rustc_typeck/src/check/upvar.rs index 11638720e8d..2b82b4dff2e 100644 --- a/compiler/rustc_typeck/src/check/upvar.rs +++ b/compiler/rustc_typeck/src/check/upvar.rs @@ -50,6 +50,7 @@ use rustc_span::sym; use rustc_span::{MultiSpan, Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt; +use rustc_data_structures::stable_map::FxHashMap; use rustc_data_structures::stable_set::FxHashSet; use rustc_index::vec::Idx; use rustc_target::abi::VariantIdx; @@ -81,6 +82,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } +/// Intermediate format to store the hir_id pointing to the use that resulted in the +/// corresponding place being captured and a String which contains the captured value's +/// name (i.e: a.b.c) +type CapturesInfo = (Option, String); + +/// Intermediate format to store information needed to generate migration lint. The tuple +/// contains the hir_id pointing to the use that resulted in the +/// corresponding place being captured, a String which contains the captured value's +/// name (i.e: a.b.c) and a String which contains the reason why migration is needed for that +/// capture +type MigrationNeededForCapture = (Option, String, String); + +/// Intermediate format to store the hir id of the root variable and a HashSet containing +/// information on why the root variable should be fully captured +type MigrationDiagnosticInfo = (hir::HirId, FxHashSet); + struct InferBorrowKindVisitor<'a, 'tcx> { fcx: &'a FnCtxt<'a, 'tcx>, } @@ -513,45 +530,41 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .as_str(), ); for (var_hir_id, diagnostics_info) in need_migrations.iter() { - let mut captured_names = format!(""); - // Label every Span which are responsible for the captured values - for (captured_hir_id, captured_name) in diagnostics_info.iter() { + // Labels all the usage of the captured variable and why they are responsible + // for migration being needed + for (captured_hir_id, captured_name, reasons) in diagnostics_info.iter() { if let Some(captured_hir_id) = captured_hir_id { let cause_span = self.tcx.hir().span(*captured_hir_id); diagnostics_builder.span_label(cause_span, format!("in Rust 2018, closure captures all of `{}`, but in Rust 2021, it only captures `{}`", self.tcx.hir().name(*var_hir_id), captured_name, )); - if captured_names == "" { - captured_names = format!("`{}`", captured_name); - } else { - captured_names = format!("{}, `{}`", captured_names, captured_name); - } } - } - // Add a label pointing to where a closure and it's captured variables affected by drop order are dropped - if reasons.contains("drop order") { - let drop_location_span = drop_location_span(self.tcx, &closure_hir_id); + // Add a label pointing to where a captured variable affected by drop order + // is dropped + if reasons.contains("drop order") { + let drop_location_span = drop_location_span(self.tcx, &closure_hir_id); - diagnostics_builder.span_label(drop_location_span, format!("in Rust 2018, `{}` would be dropped here, but in Rust 2021, only {} would be dropped here alongside the closure", - self.tcx.hir().name(*var_hir_id), - captured_names, - )); - } + diagnostics_builder.span_label(drop_location_span, format!("in Rust 2018, `{}` would be dropped here, but in Rust 2021, only `{}` would be dropped here alongside the closure", + self.tcx.hir().name(*var_hir_id), + captured_name, + )); + } - // Add a label explaining why a closure no longer implements a trait - if reasons.contains("trait implementation") { - let missing_trait = &reasons[..reasons.find("trait implementation").unwrap() - 1]; + // Add a label explaining why a closure no longer implements a trait + if reasons.contains("trait implementation") { + let missing_trait = &reasons[..reasons.find("trait implementation").unwrap() - 1]; - diagnostics_builder.span_label(closure_head_span, format!("in Rust 2018, this closure would implement {} as `{}` implements {}, but in Rust 2021, this closure would no longer implement {} as {} does not implement {}", - missing_trait, - self.tcx.hir().name(*var_hir_id), - missing_trait, - missing_trait, - captured_names, - missing_trait, - )); + diagnostics_builder.span_label(closure_head_span, format!("in Rust 2018, this closure would implement {} as `{}` implements {}, but in Rust 2021, this closure would no longer implement {} as `{}` does not implement {}", + missing_trait, + self.tcx.hir().name(*var_hir_id), + missing_trait, + missing_trait, + captured_name, + missing_trait, + )); + } } } diagnostics_builder.note("for more information, see "); @@ -616,16 +629,29 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { reasons } - /// Returns a tuple that contains the hir_id pointing to the use that resulted in the - /// corresponding place being captured and a String which contains the captured value's name - /// (i.e: a.b.c) if migration is needed for trait for the provided var_hir_id, otherwise returns None - fn need_2229_migrations_for_trait( + /// Figures out the list of root variables (and their types) that aren't completely + /// captured by the closure when `capture_disjoint_fields` is enabled and auto-traits + /// differ between the root variable and the captured paths. + /// + /// Returns a tuple containing a HashMap of CapturesInfo that maps to a HashSet of trait names + /// if migration is needed for traits for the provided var_hir_id, otherwise returns None + fn compute_2229_migrations_for_trait( &self, min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>, var_hir_id: hir::HirId, - check_trait: Option, closure_clause: hir::CaptureBy, - ) -> Option<(Option, String)> { + ) -> Option>> { + let auto_traits_def_id = vec![ + self.tcx.lang_items().clone_trait(), + self.tcx.lang_items().sync_trait(), + self.tcx.get_diagnostic_item(sym::send_trait), + self.tcx.lang_items().unpin_trait(), + self.tcx.get_diagnostic_item(sym::unwind_safe_trait), + self.tcx.get_diagnostic_item(sym::ref_unwind_safe_trait), + ]; + let auto_traits = + vec!["`Clone`", "`Sync`", "`Send`", "`Unpin`", "`UnwindSafe`", "`RefUnwindSafe`"]; + let root_var_min_capture_list = if let Some(root_var_min_capture_list) = min_captures.and_then(|m| m.get(&var_hir_id)) { @@ -650,19 +676,26 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; - let obligation_should_hold = check_trait - .map(|check_trait| { - self.infcx - .type_implements_trait( - check_trait, - ty, - self.tcx.mk_substs_trait(ty, &[]), - self.param_env, - ) - .must_apply_modulo_regions() - }) - .unwrap_or(false); + let mut obligations_should_hold = Vec::new(); + // Checks if a root variable implements any of the auto traits + for check_trait in auto_traits_def_id.iter() { + obligations_should_hold.push( + check_trait + .map(|check_trait| { + self.infcx + .type_implements_trait( + check_trait, + ty, + self.tcx.mk_substs_trait(ty, &[]), + self.param_env, + ) + .must_apply_modulo_regions() + }) + .unwrap_or(false), + ); + } + let mut problematic_captures = FxHashMap::default(); // Check whether captured fields also implement the trait for capture in root_var_min_capture_list.iter() { let ty = apply_capture_kind_on_capture_ty( @@ -671,114 +704,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { capture.info.capture_kind, ); - let obligation_holds_for_capture = check_trait - .map(|check_trait| { - self.infcx - .type_implements_trait( - check_trait, - ty, - self.tcx.mk_substs_trait(ty, &[]), - self.param_env, - ) - .must_apply_modulo_regions() - }) - .unwrap_or(false); + // Checks if a capture implements any of the auto traits + let mut obligations_holds_for_capture = Vec::new(); + for check_trait in auto_traits_def_id.iter() { + obligations_holds_for_capture.push( + check_trait + .map(|check_trait| { + self.infcx + .type_implements_trait( + check_trait, + ty, + self.tcx.mk_substs_trait(ty, &[]), + self.param_env, + ) + .must_apply_modulo_regions() + }) + .unwrap_or(false), + ); + } - if !obligation_holds_for_capture && obligation_should_hold { - return Some((capture.info.path_expr_id, capture.to_string(self.tcx))); + let mut capture_problems = FxHashSet::default(); + + // Checks if for any of the auto traits, one or more trait is implemented + // by the root variable but not by the capture + for (idx, _) in obligations_should_hold.iter().enumerate() { + if !obligations_holds_for_capture[idx] && obligations_should_hold[idx] { + capture_problems.insert(auto_traits[idx]); + } + } + + if capture_problems.len() > 0 { + problematic_captures.insert( + (capture.info.path_expr_id, capture.to_string(self.tcx)), + capture_problems, + ); } } + if problematic_captures.len() > 0 { + return Some(problematic_captures); + } None } - /// Figures out the list of root variables (and their types) that aren't completely - /// captured by the closure when `capture_disjoint_fields` is enabled and auto-traits - /// differ between the root variable and the captured paths. - /// - /// Returns a tuple containing a HashSet of traits that not implemented by the captured fields - /// of a root variables that has the provided var_hir_id and a HashSet of tuples that contains - /// the hir_id pointing to the use that resulted in the corresponding place being captured and - /// a String which contains the captured value's name (i.e: a.b.c) if migration is needed for - /// trait for the provided var_hir_id, otherwise returns None - fn compute_2229_migrations_for_trait( - &self, - min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>, - var_hir_id: hir::HirId, - closure_clause: hir::CaptureBy, - ) -> Option<(FxHashSet<&str>, FxHashSet<(Option, String)>)> { - let tcx = self.infcx.tcx; - - // Check whether catpured fields also implement the trait - let mut auto_trait_reasons = FxHashSet::default(); - let mut diagnostics_info = FxHashSet::default(); - - if let Some(info) = self.need_2229_migrations_for_trait( - min_captures, - var_hir_id, - tcx.lang_items().clone_trait(), - closure_clause, - ) { - auto_trait_reasons.insert("`Clone`"); - diagnostics_info.insert(info); - } - - if let Some(info) = self.need_2229_migrations_for_trait( - min_captures, - var_hir_id, - tcx.lang_items().sync_trait(), - closure_clause, - ) { - auto_trait_reasons.insert("`Sync`"); - diagnostics_info.insert(info); - } - - if let Some(info) = self.need_2229_migrations_for_trait( - min_captures, - var_hir_id, - tcx.get_diagnostic_item(sym::send_trait), - closure_clause, - ) { - auto_trait_reasons.insert("`Send`"); - diagnostics_info.insert(info); - } - - if let Some(info) = self.need_2229_migrations_for_trait( - min_captures, - var_hir_id, - tcx.lang_items().unpin_trait(), - closure_clause, - ) { - auto_trait_reasons.insert("`Unpin`"); - diagnostics_info.insert(info); - } - - if let Some(info) = self.need_2229_migrations_for_trait( - min_captures, - var_hir_id, - tcx.get_diagnostic_item(sym::unwind_safe_trait), - closure_clause, - ) { - auto_trait_reasons.insert("`UnwindSafe`"); - diagnostics_info.insert(info); - } - - if let Some(info) = self.need_2229_migrations_for_trait( - min_captures, - var_hir_id, - tcx.get_diagnostic_item(sym::ref_unwind_safe_trait), - closure_clause, - ) { - auto_trait_reasons.insert("`RefUnwindSafe`"); - diagnostics_info.insert(info); - } - - if auto_trait_reasons.len() > 0 { - return Some((auto_trait_reasons, diagnostics_info)); - } - - return None; - } - /// Figures out the list of root variables (and their types) that aren't completely /// captured by the closure when `capture_disjoint_fields` is enabled and drop order of /// some path starting at that root variable **might** be affected. @@ -789,9 +756,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// - It wasn't completely captured by the closure, **and** /// - One of the paths starting at this root variable, that is not captured needs Drop. /// - /// This function only returns a HashSet of tuples for significant drops. The returned HashSet - /// of tuples contains the hir_id pointing to the use that resulted in the corresponding place - /// being captured anda String which contains the captured value's name (i.e: a.b.c) + /// This function only returns a HashSet of CapturesInfo for significant drops. If there + /// are no significant drops than None is returned fn compute_2229_migrations_for_drop( &self, closure_def_id: DefId, @@ -799,7 +765,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>, closure_clause: hir::CaptureBy, var_hir_id: hir::HirId, - ) -> Option, String)>> { + ) -> Option> { let ty = self.infcx.resolve_vars_if_possible(self.node_ty(var_hir_id)); if !ty.has_significant_drop(self.tcx, self.tcx.param_env(closure_def_id.expect_local())) { @@ -873,17 +839,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { /// - One of the paths captured does not implement all the auto-traits its root variable /// implements. /// - /// Returns a tuple containing a vector of tuples of HirIds and a HashSet of tuples that contains - /// the hir_id pointing to the use that resulted in the corresponding place being captured and - /// a String which contains the captured value's name (i.e: a.b.c), as well as a String + /// Returns a tuple containing a vector of MigrationDiagnosticInfo, as well as a String /// containing the reason why root variables whose HirId is contained in the vector should + /// be captured fn compute_2229_migrations( &self, closure_def_id: DefId, closure_span: Span, closure_clause: hir::CaptureBy, min_captures: Option<&ty::RootVariableMinCaptureList<'tcx>>, - ) -> (Vec<(hir::HirId, FxHashSet<(Option, String)>)>, String) { + ) -> (Vec, String) { let upvars = if let Some(upvars) = self.tcx.upvars_mentioned(closure_def_id) { upvars } else { @@ -891,42 +856,77 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }; let mut need_migrations = Vec::new(); - let mut auto_trait_reasons = FxHashSet::default(); - let mut drop_reorder_reason = false; + let mut auto_trait_migration_reasons = FxHashSet::default(); + let mut drop_migration_needed = false; // Perform auto-trait analysis for (&var_hir_id, _) in upvars.iter() { - let mut need_migration = false; let mut responsible_captured_hir_ids = FxHashSet::default(); - if let Some((trait_migration_cause, diagnostics_info)) = + let auto_trait_diagnostic = if let Some(diagnostics_info) = self.compute_2229_migrations_for_trait(min_captures, var_hir_id, closure_clause) { - need_migration = true; - auto_trait_reasons.extend(trait_migration_cause); - responsible_captured_hir_ids.extend(diagnostics_info); + diagnostics_info + } else { + FxHashMap::default() + }; + + let drop_reorder_diagnostic = if let Some(diagnostics_info) = self + .compute_2229_migrations_for_drop( + closure_def_id, + closure_span, + min_captures, + closure_clause, + var_hir_id, + ) { + drop_migration_needed = true; + diagnostics_info + } else { + FxHashSet::default() + }; + + // Combine all the captures responsible for needing migrations into one HashSet + let mut capture_disagnostic = drop_reorder_diagnostic.clone(); + for key in auto_trait_diagnostic.keys() { + capture_disagnostic.insert(key.clone()); } - if let Some(diagnostics_info) = self.compute_2229_migrations_for_drop( - closure_def_id, - closure_span, - min_captures, - closure_clause, - var_hir_id, - ) { - need_migration = true; - drop_reorder_reason = true; - responsible_captured_hir_ids.extend(diagnostics_info); + for captured_info in capture_disagnostic.iter() { + // Get the auto trait reasons of why migration is needed because of that capture, if there are any + let capture_trait_reasons = + if let Some(reasons) = auto_trait_diagnostic.get(captured_info) { + reasons.clone() + } else { + FxHashSet::default() + }; + + // Check if migration is needed because of drop reorder as a result of that capture + let capture_drop_reorder_reason = drop_reorder_diagnostic.contains(captured_info); + + // Combine all the reasons of why the root variable should be captured as a result of + // auto trait implementation issues + auto_trait_migration_reasons.extend(capture_trait_reasons.clone()); + + responsible_captured_hir_ids.insert(( + captured_info.0, + captured_info.1.clone(), + self.compute_2229_migrations_reasons( + capture_trait_reasons, + capture_drop_reorder_reason, + ), + )); } - if need_migration { + if capture_disagnostic.len() > 0 { need_migrations.push((var_hir_id, responsible_captured_hir_ids)); } } - ( need_migrations, - self.compute_2229_migrations_reasons(auto_trait_reasons, drop_reorder_reason), + self.compute_2229_migrations_reasons( + auto_trait_migration_reasons, + drop_migration_needed, + ), ) } @@ -1964,7 +1964,7 @@ fn should_do_rust_2021_incompatible_closure_captures_analysis( /// - s2: Comma separated names of the variables being migrated. fn migration_suggestion_for_2229( tcx: TyCtxt<'_>, - need_migrations: &Vec<(hir::HirId, FxHashSet<(Option, String)>)>, + need_migrations: &Vec, ) -> (String, String) { let need_migrations_variables = need_migrations.iter().map(|(v, _)| var_name(tcx, *v)).collect::>(); diff --git a/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.fixed b/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.fixed new file mode 100644 index 00000000000..e5102fee588 --- /dev/null +++ b/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.fixed @@ -0,0 +1,138 @@ +// run-rustfix +#![deny(rust_2021_incompatible_closure_captures)] +//~^ NOTE: the lint level is defined here + +use std::thread; + +struct S(String); + +#[derive(Clone)] +struct T(i32); + +struct U(S, T); + +impl Clone for U { + fn clone(&self) -> Self { + U(S(String::from("Hello World")), T(0)) + } +} + +fn test_multi_issues() { + let f1 = U(S(String::from("foo")), T(0)); + let f2 = U(S(String::from("bar")), T(0)); + let c = || { let _ = (&f1, &f2); + //~^ ERROR: `Clone` trait implementation for closure, and drop order + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1`, `f2` to be fully captured + let _f_1 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_2 = f2.1; + //~^ NOTE: in Rust 2018, closure captures all of `f2`, but in Rust 2021, it only captures `f2.1` + }; + + let c_clone = c.clone(); + + c_clone(); +} +//~^ NOTE: in Rust 2018, `f2` would be dropped here, but in Rust 2021, only `f2.1` would be dropped here alongside the closure + +fn test_capturing_all_disjoint_fields_individually() { + let f1 = U(S(String::from("foo")), T(0)); + let c = || { let _ = &f1; + //~^ ERROR: `Clone` trait implementation for closure + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1` to be fully captured + let _f_1 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_2 = f1.1; + }; + + let c_clone = c.clone(); + + c_clone(); +} + +struct U1(S, T, S); + +impl Clone for U1 { + fn clone(&self) -> Self { + U1(S(String::from("foo")), T(0), S(String::from("bar"))) + } +} + +fn test_capturing_several_disjoint_fields_individually_1() { + let f1 = U1(S(String::from("foo")), T(0), S(String::from("bar"))); + let c = || { let _ = &f1; + //~^ ERROR: `Clone` trait implementation for closure + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.2` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1` to be fully captured + let _f_0 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_2 = f1.2; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.2` + }; + + let c_clone = c.clone(); + + c_clone(); +} + +fn test_capturing_several_disjoint_fields_individually_2() { + let f1 = U1(S(String::from("foo")), T(0), S(String::from("bar"))); + let c = || { let _ = &f1; + //~^ ERROR: `Clone` trait implementation for closure, and drop order + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1` to be fully captured + let _f_0 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_1 = f1.1; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.1` + }; + + let c_clone = c.clone(); + + c_clone(); +} +//~^ NOTE: in Rust 2018, `f1` would be dropped here, but in Rust 2021, only `f1.1` would be dropped here alongside the closure +//~| NOTE: in Rust 2018, `f1` would be dropped here, but in Rust 2021, only `f1.0` would be dropped here alongside the closure + +struct SendPointer(*mut i32); +unsafe impl Send for SendPointer {} + +struct CustomInt(*mut i32); +struct SyncPointer(CustomInt); +unsafe impl Sync for SyncPointer {} +unsafe impl Send for CustomInt {} + +fn test_multi_traits_issues() { + let mut f1 = 10; + let f1 = CustomInt(&mut f1 as *mut i32); + let fptr1 = SyncPointer(f1); + + let mut f2 = 10; + let fptr2 = SendPointer(&mut f2 as *mut i32); + thread::spawn(move || { let _ = (&fptr1, &fptr2); unsafe { + //~^ ERROR: `Sync`, `Send` trait implementation for closure + //~| NOTE: in Rust 2018, this closure would implement `Sync`, `Send` as `fptr1` implements `Sync`, `Send`, but in Rust 2021, this closure would no longer implement `Sync`, `Send` as `fptr1.0.0` does not implement `Sync`, `Send` + //~| NOTE: in Rust 2018, this closure would implement `Send` as `fptr2` implements `Send`, but in Rust 2021, this closure would no longer implement `Send` as `fptr2.0` does not implement `Send` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `fptr1`, `fptr2` to be fully captured + *fptr1.0.0 = 20; + //~^ NOTE: in Rust 2018, closure captures all of `fptr1`, but in Rust 2021, it only captures `fptr1.0.0` + *fptr2.0 = 20; + //~^ NOTE: in Rust 2018, closure captures all of `fptr2`, but in Rust 2021, it only captures `fptr2.0` + } }); +} + +fn main() { + test_multi_issues(); + test_capturing_all_disjoint_fields_individually(); + test_capturing_several_disjoint_fields_individually_1(); + test_capturing_several_disjoint_fields_individually_2(); + test_multi_traits_issues(); +} diff --git a/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.rs b/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.rs new file mode 100644 index 00000000000..d05c0bc1bbc --- /dev/null +++ b/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.rs @@ -0,0 +1,138 @@ +// run-rustfix +#![deny(rust_2021_incompatible_closure_captures)] +//~^ NOTE: the lint level is defined here + +use std::thread; + +struct S(String); + +#[derive(Clone)] +struct T(i32); + +struct U(S, T); + +impl Clone for U { + fn clone(&self) -> Self { + U(S(String::from("Hello World")), T(0)) + } +} + +fn test_multi_issues() { + let f1 = U(S(String::from("foo")), T(0)); + let f2 = U(S(String::from("bar")), T(0)); + let c = || { + //~^ ERROR: `Clone` trait implementation for closure, and drop order + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1`, `f2` to be fully captured + let _f_1 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_2 = f2.1; + //~^ NOTE: in Rust 2018, closure captures all of `f2`, but in Rust 2021, it only captures `f2.1` + }; + + let c_clone = c.clone(); + + c_clone(); +} +//~^ NOTE: in Rust 2018, `f2` would be dropped here, but in Rust 2021, only `f2.1` would be dropped here alongside the closure + +fn test_capturing_all_disjoint_fields_individually() { + let f1 = U(S(String::from("foo")), T(0)); + let c = || { + //~^ ERROR: `Clone` trait implementation for closure + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1` to be fully captured + let _f_1 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_2 = f1.1; + }; + + let c_clone = c.clone(); + + c_clone(); +} + +struct U1(S, T, S); + +impl Clone for U1 { + fn clone(&self) -> Self { + U1(S(String::from("foo")), T(0), S(String::from("bar"))) + } +} + +fn test_capturing_several_disjoint_fields_individually_1() { + let f1 = U1(S(String::from("foo")), T(0), S(String::from("bar"))); + let c = || { + //~^ ERROR: `Clone` trait implementation for closure + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.2` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1` to be fully captured + let _f_0 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_2 = f1.2; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.2` + }; + + let c_clone = c.clone(); + + c_clone(); +} + +fn test_capturing_several_disjoint_fields_individually_2() { + let f1 = U1(S(String::from("foo")), T(0), S(String::from("bar"))); + let c = || { + //~^ ERROR: `Clone` trait implementation for closure, and drop order + //~| NOTE: in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `f1` to be fully captured + let _f_0 = f1.0; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + let _f_1 = f1.1; + //~^ NOTE: in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.1` + }; + + let c_clone = c.clone(); + + c_clone(); +} +//~^ NOTE: in Rust 2018, `f1` would be dropped here, but in Rust 2021, only `f1.1` would be dropped here alongside the closure +//~| NOTE: in Rust 2018, `f1` would be dropped here, but in Rust 2021, only `f1.0` would be dropped here alongside the closure + +struct SendPointer(*mut i32); +unsafe impl Send for SendPointer {} + +struct CustomInt(*mut i32); +struct SyncPointer(CustomInt); +unsafe impl Sync for SyncPointer {} +unsafe impl Send for CustomInt {} + +fn test_multi_traits_issues() { + let mut f1 = 10; + let f1 = CustomInt(&mut f1 as *mut i32); + let fptr1 = SyncPointer(f1); + + let mut f2 = 10; + let fptr2 = SendPointer(&mut f2 as *mut i32); + thread::spawn(move || unsafe { + //~^ ERROR: `Sync`, `Send` trait implementation for closure + //~| NOTE: in Rust 2018, this closure would implement `Sync`, `Send` as `fptr1` implements `Sync`, `Send`, but in Rust 2021, this closure would no longer implement `Sync`, `Send` as `fptr1.0.0` does not implement `Sync`, `Send` + //~| NOTE: in Rust 2018, this closure would implement `Send` as `fptr2` implements `Send`, but in Rust 2021, this closure would no longer implement `Send` as `fptr2.0` does not implement `Send` + //~| NOTE: for more information, see + //~| HELP: add a dummy let to cause `fptr1`, `fptr2` to be fully captured + *fptr1.0.0 = 20; + //~^ NOTE: in Rust 2018, closure captures all of `fptr1`, but in Rust 2021, it only captures `fptr1.0.0` + *fptr2.0 = 20; + //~^ NOTE: in Rust 2018, closure captures all of `fptr2`, but in Rust 2021, it only captures `fptr2.0` + }); +} + +fn main() { + test_multi_issues(); + test_capturing_all_disjoint_fields_individually(); + test_capturing_several_disjoint_fields_individually_1(); + test_capturing_several_disjoint_fields_individually_2(); + test_multi_traits_issues(); +} diff --git a/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.stderr b/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.stderr new file mode 100644 index 00000000000..5f09230d45c --- /dev/null +++ b/src/test/ui/closures/2229_closure_analysis/migrations/multi_diagnostics.stderr @@ -0,0 +1,134 @@ +error: changes to closure capture in Rust 2021 will affect `Clone` trait implementation for closure, and drop order + --> $DIR/multi_diagnostics.rs:23:13 + | +LL | let c = || { + | ^^ in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` +... +LL | let _f_1 = f1.0; + | ---- in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` +LL | +LL | let _f_2 = f2.1; + | ---- in Rust 2018, closure captures all of `f2`, but in Rust 2021, it only captures `f2.1` +... +LL | } + | - in Rust 2018, `f2` would be dropped here, but in Rust 2021, only `f2.1` would be dropped here alongside the closure + | +note: the lint level is defined here + --> $DIR/multi_diagnostics.rs:2:9 + | +LL | #![deny(rust_2021_incompatible_closure_captures)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: for more information, see +help: add a dummy let to cause `f1`, `f2` to be fully captured + | +LL | let c = || { let _ = (&f1, &f2); +LL | +LL | +LL | +LL | +LL | let _f_1 = f1.0; + ... + +error: changes to closure capture in Rust 2021 will affect `Clone` trait implementation for closure + --> $DIR/multi_diagnostics.rs:42:13 + | +LL | let c = || { + | ^^ in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` +... +LL | let _f_1 = f1.0; + | ---- in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` + | + = note: for more information, see +help: add a dummy let to cause `f1` to be fully captured + | +LL | let c = || { let _ = &f1; +LL | +LL | +LL | +LL | +LL | let _f_1 = f1.0; + ... + +error: changes to closure capture in Rust 2021 will affect `Clone` trait implementation for closure + --> $DIR/multi_diagnostics.rs:67:13 + | +LL | let c = || { + | ^^ + | | + | in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` + | in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.2` does not implement `Clone` +... +LL | let _f_0 = f1.0; + | ---- in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` +LL | +LL | let _f_2 = f1.2; + | ---- in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.2` + | + = note: for more information, see +help: add a dummy let to cause `f1` to be fully captured + | +LL | let c = || { let _ = &f1; +LL | +LL | +LL | +LL | +LL | + ... + +error: changes to closure capture in Rust 2021 will affect `Clone` trait implementation for closure, and drop order + --> $DIR/multi_diagnostics.rs:86:13 + | +LL | let c = || { + | ^^ in Rust 2018, this closure would implement `Clone` as `f1` implements `Clone`, but in Rust 2021, this closure would no longer implement `Clone` as `f1.0` does not implement `Clone` +... +LL | let _f_0 = f1.0; + | ---- in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.0` +LL | +LL | let _f_1 = f1.1; + | ---- in Rust 2018, closure captures all of `f1`, but in Rust 2021, it only captures `f1.1` +... +LL | } + | - + | | + | in Rust 2018, `f1` would be dropped here, but in Rust 2021, only `f1.0` would be dropped here alongside the closure + | in Rust 2018, `f1` would be dropped here, but in Rust 2021, only `f1.1` would be dropped here alongside the closure + | + = note: for more information, see +help: add a dummy let to cause `f1` to be fully captured + | +LL | let c = || { let _ = &f1; +LL | +LL | +LL | +LL | +LL | let _f_0 = f1.0; + ... + +error: changes to closure capture in Rust 2021 will affect `Sync`, `Send` trait implementation for closure + --> $DIR/multi_diagnostics.rs:119:19 + | +LL | thread::spawn(move || unsafe { + | ^^^^^^^^^^^^^^ + | | + | in Rust 2018, this closure would implement `Sync`, `Send` as `fptr1` implements `Sync`, `Send`, but in Rust 2021, this closure would no longer implement `Sync`, `Send` as `fptr1.0.0` does not implement `Sync`, `Send` + | in Rust 2018, this closure would implement `Send` as `fptr2` implements `Send`, but in Rust 2021, this closure would no longer implement `Send` as `fptr2.0` does not implement `Send` +... +LL | *fptr1.0.0 = 20; + | ---------- in Rust 2018, closure captures all of `fptr1`, but in Rust 2021, it only captures `fptr1.0.0` +LL | +LL | *fptr2.0 = 20; + | -------- in Rust 2018, closure captures all of `fptr2`, but in Rust 2021, it only captures `fptr2.0` + | + = note: for more information, see +help: add a dummy let to cause `fptr1`, `fptr2` to be fully captured + | +LL | thread::spawn(move || { let _ = (&fptr1, &fptr2); unsafe { +LL | +LL | +LL | +LL | +LL | + ... + +error: aborting due to 5 previous errors + diff --git a/src/test/ui/closures/2229_closure_analysis/migrations/precise.fixed b/src/test/ui/closures/2229_closure_analysis/migrations/precise.fixed index ba5e5b573f1..226172fb93e 100644 --- a/src/test/ui/closures/2229_closure_analysis/migrations/precise.fixed +++ b/src/test/ui/closures/2229_closure_analysis/migrations/precise.fixed @@ -1,6 +1,7 @@ // run-rustfix #![deny(rust_2021_incompatible_closure_captures)] +//~^ NOTE: the lint level is defined here #[derive(Debug)] struct Foo(i32); @@ -18,13 +19,16 @@ fn test_precise_analysis_drop_paths_not_captured_by_move() { let c = || { let _ = &t; //~^ ERROR: drop order + //~| NOTE: for more information, see //~| HELP: add a dummy let to cause `t` to be fully captured let _t = t.0; + //~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0` let _t = &t.1; }; c(); } +//~^ NOTE: in Rust 2018, `t` would be dropped here, but in Rust 2021, only `t.0` would be dropped here alongside the closure struct S; impl Drop for S { @@ -40,14 +44,22 @@ fn test_precise_analysis_long_path_missing() { let c = || { let _ = &u; //~^ ERROR: drop order + //~| NOTE: for more information, see //~| HELP: add a dummy let to cause `u` to be fully captured let _x = u.0.0; + //~^ NOTE: in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.0.0` let _x = u.0.1; + //~^ NOTE: in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.0.1` let _x = u.1.0; + //~^ NOTE: in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.1.0` }; c(); } +//~^ NOTE: in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.0.0` would be dropped here alongside the closure +//~| NOTE: in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.0.1` would be dropped here alongside the closure +//~| NOTE: in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.1.0` would be dropped here alongside the closure + fn main() { test_precise_analysis_drop_paths_not_captured_by_move(); diff --git a/src/test/ui/closures/2229_closure_analysis/migrations/precise.rs b/src/test/ui/closures/2229_closure_analysis/migrations/precise.rs index 92b6f25c80d..7035abe6de0 100644 --- a/src/test/ui/closures/2229_closure_analysis/migrations/precise.rs +++ b/src/test/ui/closures/2229_closure_analysis/migrations/precise.rs @@ -1,6 +1,7 @@ // run-rustfix #![deny(rust_2021_incompatible_closure_captures)] +//~^ NOTE: the lint level is defined here #[derive(Debug)] struct Foo(i32); @@ -18,13 +19,16 @@ fn test_precise_analysis_drop_paths_not_captured_by_move() { let c = || { //~^ ERROR: drop order + //~| NOTE: for more information, see //~| HELP: add a dummy let to cause `t` to be fully captured let _t = t.0; + //~^ NOTE: in Rust 2018, closure captures all of `t`, but in Rust 2021, it only captures `t.0` let _t = &t.1; }; c(); } +//~^ NOTE: in Rust 2018, `t` would be dropped here, but in Rust 2021, only `t.0` would be dropped here alongside the closure struct S; impl Drop for S { @@ -40,14 +44,22 @@ fn test_precise_analysis_long_path_missing() { let c = || { //~^ ERROR: drop order + //~| NOTE: for more information, see //~| HELP: add a dummy let to cause `u` to be fully captured let _x = u.0.0; + //~^ NOTE: in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.0.0` let _x = u.0.1; + //~^ NOTE: in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.0.1` let _x = u.1.0; + //~^ NOTE: in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.1.0` }; c(); } +//~^ NOTE: in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.0.0` would be dropped here alongside the closure +//~| NOTE: in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.0.1` would be dropped here alongside the closure +//~| NOTE: in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.1.0` would be dropped here alongside the closure + fn main() { test_precise_analysis_drop_paths_not_captured_by_move(); diff --git a/src/test/ui/closures/2229_closure_analysis/migrations/precise.stderr b/src/test/ui/closures/2229_closure_analysis/migrations/precise.stderr index 153c0d6b686..ee2cae0ecb2 100644 --- a/src/test/ui/closures/2229_closure_analysis/migrations/precise.stderr +++ b/src/test/ui/closures/2229_closure_analysis/migrations/precise.stderr @@ -1,5 +1,5 @@ error: changes to closure capture in Rust 2021 will affect drop order - --> $DIR/precise.rs:19:13 + --> $DIR/precise.rs:20:13 | LL | let c = || { | ^^ @@ -21,26 +21,32 @@ help: add a dummy let to cause `t` to be fully captured LL | let c = || { let _ = &t; LL | LL | +LL | LL | let _t = t.0; -LL | let _t = &t.1; -LL | }; - | +LL | + ... error: changes to closure capture in Rust 2021 will affect drop order - --> $DIR/precise.rs:41:13 + --> $DIR/precise.rs:45:13 | LL | let c = || { | ^^ ... LL | let _x = u.0.0; | ----- in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.0.0` +LL | LL | let _x = u.0.1; | ----- in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.0.1` +LL | LL | let _x = u.1.0; | ----- in Rust 2018, closure captures all of `u`, but in Rust 2021, it only captures `u.1.0` ... LL | } - | - in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.0.1`, `u.0.0`, `u.1.0` would be dropped here alongside the closure + | - + | | + | in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.0.1` would be dropped here alongside the closure + | in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.0.0` would be dropped here alongside the closure + | in Rust 2018, `u` would be dropped here, but in Rust 2021, only `u.1.0` would be dropped here alongside the closure | = note: for more information, see help: add a dummy let to cause `u` to be fully captured @@ -48,9 +54,9 @@ help: add a dummy let to cause `u` to be fully captured LL | let c = || { let _ = &u; LL | LL | +LL | LL | let _x = u.0.0; -LL | let _x = u.0.1; -LL | let _x = u.1.0; +LL | ... error: aborting due to 2 previous errors