Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2229: Support migration via rustfix #83757

Merged
merged 6 commits into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 81 additions & 28 deletions compiler/rustc_typeck/src/check/upvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use super::FnCtxt;

use crate::expr_use_visitor as euv;
use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::def_id::LocalDefId;
Expand Down Expand Up @@ -91,7 +92,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InferBorrowKindVisitor<'a, 'tcx> {
if let hir::ExprKind::Closure(cc, _, body_id, _, _) = expr.kind {
let body = self.fcx.tcx.hir().body(body_id);
self.visit_body(body);
self.fcx.analyze_closure(expr.hir_id, expr.span, body, cc);
self.fcx.analyze_closure(expr.hir_id, expr.span, body_id, body, cc);
}

intravisit::walk_expr(self, expr);
Expand All @@ -104,6 +105,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
&self,
closure_hir_id: hir::HirId,
span: Span,
body_id: hir::BodyId,
body: &'tcx hir::Body<'tcx>,
capture_clause: hir::CaptureBy,
) {
Expand Down Expand Up @@ -167,7 +169,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

let closure_hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id);
if should_do_migration_analysis(self.tcx, closure_hir_id) {
self.perform_2229_migration_anaysis(closure_def_id, capture_clause, span);
self.perform_2229_migration_anaysis(closure_def_id, body_id, capture_clause, span);
}

// We now fake capture information for all variables that are mentioned within the closure
Expand Down Expand Up @@ -465,6 +467,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn perform_2229_migration_anaysis(
&self,
closure_def_id: DefId,
body_id: hir::BodyId,
capture_clause: hir::CaptureBy,
span: Span,
) {
Expand All @@ -476,7 +479,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
);

if !need_migrations.is_empty() {
let migrations_text = migration_suggestion_for_2229(self.tcx, &need_migrations);
let (migration_string, migrated_variables_concat) =
migration_suggestion_for_2229(self.tcx, &need_migrations);

let local_def_id = closure_def_id.expect_local();
let closure_hir_id = self.tcx.hir().local_def_id_to_hir_id(local_def_id);
Expand All @@ -488,7 +492,35 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let mut diagnostics_builder = lint.build(
"drop order affected for closure because of `capture_disjoint_fields`",
);
diagnostics_builder.note(&migrations_text);
let closure_body_span = self.tcx.hir().span(body_id.hir_id);
let (sugg, app) =
match self.tcx.sess.source_map().span_to_snippet(closure_body_span) {
Ok(s) => {
let trimmed = s.trim_start();

// If the closure contains a block then replace the opening brace
// with "{ let _ = (..); "
let sugg = if let Some('{') = trimmed.chars().next() {
format!("{{ {}; {}", migration_string, &trimmed[1..])
} else {
format!("{{ {}; {} }}", migration_string, s)
};
(sugg, Applicability::MachineApplicable)
}
Err(_) => (migration_string.clone(), Applicability::HasPlaceholders),
};

let diagnostic_msg = format!(
"add a dummy let to cause {} to be fully captured",
migrated_variables_concat
);

diagnostics_builder.span_suggestion(
closure_body_span,
&diagnostic_msg,
sugg,
app,
);
diagnostics_builder.emit();
},
);
Expand Down Expand Up @@ -621,7 +653,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
/// `w[c]`.
/// Notation:
/// - Ty(place): Type of place
/// - `(a, b)`: Represents the function parameters `base_path_ty` and `captured_projs`
/// - `(a, b)`: Represents the function parameters `base_path_ty` and `captured_by_move_projs`
/// respectively.
/// ```
/// (Ty(w), [ &[p, x], &[c] ])
Expand Down Expand Up @@ -682,7 +714,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
closure_def_id: DefId,
closure_span: Span,
base_path_ty: Ty<'tcx>,
captured_projs: Vec<&[Projection<'tcx>]>,
captured_by_move_projs: Vec<&[Projection<'tcx>]>,
) -> bool {
let needs_drop = |ty: Ty<'tcx>| {
ty.needs_drop(self.tcx, self.tcx.param_env(closure_def_id.expect_local()))
Expand All @@ -707,33 +739,37 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
//
// eg. If `a.b` is captured and we are processing `a.b`, then we can't have the closure also
// capture `a.b.c`, because that voilates min capture.
let is_completely_captured = captured_projs.iter().any(|projs| projs.is_empty());
let is_completely_captured = captured_by_move_projs.iter().any(|projs| projs.is_empty());

assert!(!is_completely_captured || (captured_projs.len() == 1));
assert!(!is_completely_captured || (captured_by_move_projs.len() == 1));

if is_completely_captured {
// The place is captured entirely, so doesn't matter if needs dtor, it will be drop
// when the closure is dropped.
return false;
}

if captured_by_move_projs.is_empty() {
return needs_drop(base_path_ty);
}

if is_drop_defined_for_ty {
// If drop is implemented for this type then we need it to be fully captured,
// which we know it is not because of the previous check. Therefore we need to
// do migrate.
return true;
}
// and we know it is not completely captured because of the previous checks.

if captured_projs.is_empty() {
return needs_drop(base_path_ty);
// Note that this is a bug in the user code that will be reported by the
// borrow checker, since we can't move out of drop types.

// The bug exists in the user's code pre-migration, and we don't migrate here.
return false;
}

match base_path_ty.kind() {
// Observations:
// - `captured_projs` is not empty. Therefore we can call
// `captured_projs.first().unwrap()` safely.
// - All entries in `captured_projs` have atleast one projection.
// Therefore we can call `captured_projs.first().unwrap().first().unwrap()` safely.
// - `captured_by_move_projs` is not empty. Therefore we can call
// `captured_by_move_projs.first().unwrap()` safely.
// - All entries in `captured_by_move_projs` have atleast one projection.
// Therefore we can call `captured_by_move_projs.first().unwrap().first().unwrap()` safely.

// We don't capture derefs in case of move captures, which would have be applied to
// access any further paths.
Expand All @@ -743,19 +779,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

ty::Adt(def, substs) => {
// Multi-varaint enums are captured in entirety,
// which would've been handled in the case of single empty slice in `captured_projs`.
// which would've been handled in the case of single empty slice in `captured_by_move_projs`.
assert_eq!(def.variants.len(), 1);

// Only Field projections can be applied to a non-box Adt.
assert!(
captured_projs.iter().all(|projs| matches!(
captured_by_move_projs.iter().all(|projs| matches!(
projs.first().unwrap().kind,
ProjectionKind::Field(..)
))
);
def.variants.get(VariantIdx::new(0)).unwrap().fields.iter().enumerate().any(
|(i, field)| {
let paths_using_field = captured_projs
let paths_using_field = captured_by_move_projs
.iter()
.filter_map(|projs| {
if let ProjectionKind::Field(field_idx, _) =
Expand All @@ -782,14 +818,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::Tuple(..) => {
// Only Field projections can be applied to a tuple.
assert!(
captured_projs.iter().all(|projs| matches!(
captured_by_move_projs.iter().all(|projs| matches!(
projs.first().unwrap().kind,
ProjectionKind::Field(..)
))
);

base_path_ty.tuple_fields().enumerate().any(|(i, element_ty)| {
let paths_using_field = captured_projs
let paths_using_field = captured_by_move_projs
.iter()
.filter_map(|projs| {
if let ProjectionKind::Field(field_idx, _) = projs.first().unwrap().kind
Expand Down Expand Up @@ -1515,12 +1551,29 @@ fn should_do_migration_analysis(tcx: TyCtxt<'_>, closure_id: hir::HirId) -> bool
!matches!(level, lint::Level::Allow)
}

fn migration_suggestion_for_2229(tcx: TyCtxt<'_>, need_migrations: &Vec<hir::HirId>) -> String {
let need_migrations_strings =
need_migrations.iter().map(|v| format!("{}", var_name(tcx, *v))).collect::<Vec<_>>();
let migrations_list_concat = need_migrations_strings.join(", ");
/// Return a two string tuple (s1, s2)
/// - s1: Line of code that is needed for the migration: eg: `let _ = (&x, ...)`.
/// - s2: Comma separated names of the variables being migrated.
fn migration_suggestion_for_2229(
tcx: TyCtxt<'_>,
need_migrations: &Vec<hir::HirId>,
) -> (String, String) {
let need_migrations_variables =
need_migrations.iter().map(|v| var_name(tcx, *v)).collect::<Vec<_>>();

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

let migration_string = if 1 == need_migrations.len() {
format!("let _ = {}", migration_ref_concat)
} else {
format!("let _ = ({})", migration_ref_concat)
};

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

format!("drop(&({}));", migrations_list_concat)
(migration_string, migrated_variables_concat)
}

/// Helper function to determine if we need to escalate CaptureKind from
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// run-rustfix

#![deny(disjoint_capture_drop_reorder)]
//~^ NOTE: the lint level is defined here

// Test cases for types that implement a insignificant drop (stlib defined)

// `t` needs Drop because one of its elements needs drop,
// therefore precise capture might affect drop ordering
fn test1_all_need_migration() {
let t = (String::new(), String::new());
let t1 = (String::new(), String::new());
let t2 = (String::new(), String::new());

let c = || { let _ = (&t, &t1, &t2);
//~^ ERROR: drop order affected for closure because of `capture_disjoint_fields`
//~| HELP: add a dummy let to cause `t`, `t1`, `t2` to be fully captured

let _t = t.0;
let _t1 = t1.0;
let _t2 = t2.0;
};

c();
}

// String implements drop and therefore should be migrated.
// But in this test cases, `t2` is completely captured and when it is dropped won't be affected
fn test2_only_precise_paths_need_migration() {
let t = (String::new(), String::new());
let t1 = (String::new(), String::new());
let t2 = (String::new(), String::new());

let c = || { let _ = (&t, &t1);
//~^ ERROR: drop order affected for closure because of `capture_disjoint_fields`
//~| HELP: add a dummy let to cause `t`, `t1` to be fully captured
let _t = t.0;
let _t1 = t1.0;
let _t2 = t2;
};

c();
}

// If a variable would've not been captured by value then it would've not been
// dropped with the closure and therefore doesn't need migration.
fn test3_only_by_value_need_migration() {
let t = (String::new(), String::new());
let t1 = (String::new(), String::new());
let c = || { let _ = &t;
//~^ ERROR: drop order affected for closure because of `capture_disjoint_fields`
//~| HELP: add a dummy let to cause `t` to be fully captured
let _t = t.0;
println!("{}", t1.1);
};

c();
}

// Copy types get copied into the closure instead of move. Therefore we don't need to
// migrate then as their drop order isn't tied to the closure.
fn test4_only_non_copy_types_need_migration() {
let t = (String::new(), String::new());

// `t1` is Copy because all of its elements are Copy
let t1 = (0i32, 0i32);

let c = || { let _ = &t;
//~^ ERROR: drop order affected for closure because of `capture_disjoint_fields`
//~| HELP: add a dummy let to cause `t` to be fully captured
let _t = t.0;
let _t1 = t1.0;
};

c();
}

fn test5_only_drop_types_need_migration() {
struct S(i32, i32);

let t = (String::new(), String::new());

// `s` doesn't implement Drop or any elements within it, and doesn't need migration
let s = S(0i32, 0i32);

let c = || { let _ = &t;
//~^ ERROR: drop order affected for closure because of `capture_disjoint_fields`
//~| HELP: add a dummy let to cause `t` to be fully captured
let _t = t.0;
let _s = s.0;
};

c();
}

// Since we are using a move closure here, both `t` and `t1` get moved
// even though they are being used by ref inside the closure.
fn test6_move_closures_non_copy_types_might_need_migration() {
let t = (String::new(), String::new());
let t1 = (String::new(), String::new());
let c = move || { let _ = (&t1, &t);
//~^ ERROR: drop order affected for closure because of `capture_disjoint_fields`
//~| HELP: add a dummy let to cause `t1`, `t` to be fully captured
println!("{} {}", t1.1, t.1);
};

c();
}

// Test migration analysis in case of Drop + Non Drop aggregates.
// Note we need migration here only because the non-copy (because Drop type) is captured,
// otherwise we won't need to, since we can get away with just by ref capture in that case.
fn test7_drop_non_drop_aggregate_need_migration() {
let t = (String::new(), String::new(), 0i32);

let c = || { let _ = &t;
//~^ ERROR: drop order affected for closure because of `capture_disjoint_fields`
//~| HELP: add a dummy let to cause `t` to be fully captured
let _t = t.0;
};

c();
}

fn main() {
test1_all_need_migration();
test2_only_precise_paths_need_migration();
test3_only_by_value_need_migration();
test4_only_non_copy_types_need_migration();
test5_only_drop_types_need_migration();
test6_move_closures_non_copy_types_might_need_migration();
test7_drop_non_drop_aggregate_need_migration();
}
Loading